本文分享Unity中的AI算法和实现3-有限状态机FSM(下)

news2024/11/28 14:46:36

本文分享Unity中的AI算法和实现3-有限状态机FSM(下)

回家生孩子, 暂停了一个多月的更新, 今天重新续上, ^_^.

在上一篇文章中, 我们分享了状态机的基本理论, 然后结合Unity的Animator来熟悉了这些理论, 最后设计了我们自己的状态机并实现了框架部分.

这一篇文章, 我们将继续完成业务部分, 也就是怎么使用框架来实现我们的目标:

怪物在巡逻过程中发现玩家并追逐玩家, 并且在离玩家一定距离之后脱离追逐然后继续进行巡逻.

好了, 废话不多说, 马上开始今天的内容.

效果预览

在这里插入图片描述

怎么样, 是不是逐渐开始有点意思了?

设计我们自己的FSM示例

我们将在第一篇文章的基础之上, 扩充我们的示例.

需要保留和修改的是:

  • 怪物对象: Monster
  • Waypoints列表: Waypoints
  • 地面: Plane

需要新增:

  • 玩家: Gamer
  • 数据管理器: DataMgr_FSM
  • 怪物状态类: FSMState_Monster
  • 怪物状态机类型: FSMStateType_Monster
  • 怪物状态机控制器: MonsterContoller_FSM

场景搭建

创建相关类:

  • DataMgr_FSM
  • FSMState_Monster
  • MonsterContoller_FSM

然后直接复制上一个场景, 创建GameObject代表Gamer, 为了简化, 这里使用简单的对象即可, 我们后续通过直接在Scene视图下拖动这个对象来代表玩家在场景中的移动.

创建GameObject代表DataMgr_FSM, 并将DataMgr_FSM附加上.

删除Monster身上的MonsterContoller, 然后附加MonsterContoller_FSM.

在这里插入图片描述

对应类的实现

代码实现部分的主体内容其实在上一篇文章里, 这个部分只是业务的实现, 内容不是很多. 我们一个个介绍.

DataMgr_FSM

DataMgr_FSM就是一个简单的数据管理器, 用于在多个类之间共享数据, 当然在实际的项目里没有这么简单, 我们在示例中一切以简化为主.

public class DataMgr_FSM : MonoBehaviour
{
    /// <summary>
    /// 单例
    /// </summary>
    public static DataMgr_FSM instance;

    /// <summary>
    /// 序列化的npc巡逻点
    /// </summary>
    public Transform[] monsterWaypoints;

    /// <summary>
    /// npc需要追踪的目标, 也就是Gamer
    /// </summary>
    public Transform target;

    /// <summary>
    /// npc移动速度
    /// </summary>
    public float monsterMoveSpeed = 10f;

    /// <summary>
    /// npc旋转速度
    /// </summary>
    public float monsterRotateSpeed = 10f;

    /// <summary>
    /// npc距离目标(跟踪目标和巡逻目标点)的最小距离, 达到后就不再接近以免乱跳
    /// </summary>
    public float minTargetDistance = 0.5f;

    /// <summary>
    /// 最大观测距离
    /// </summary>
    public float maxSeeGamerDistance = 10f;

    /// <summary>
    /// 最小观测距离
    /// </summary>
    public float minSeeGamerDistance = 5f;

    /// <summary>
    /// 玩家是否处于友好状态, 友好状态下的玩家不会被追击
    /// </summary>
    public bool IsGamerFriendly = false;

    private void Awake()
    {
        instance = this;
    }
}

FSMStateType_Monster

FSMStateType_Monster是怪物状态机类型, 定义了怪物的几种行为.

/// <summary>
/// 状态机类型 怪物
/// </summary>
public enum FSMStateType_Monster
{
    None,
    Patrol, // 巡逻
    SeeGamer, // 看到玩家
    LostGamer, // 丢失玩家
    CatchGamer, // 抓到玩家
    AttackGamer, // 攻击玩家
}

FSMState_Monster

FSMState_Monster是怪物状态类, 是怪物行为的核心实现, 这里只实现了PatrolSeeGamer, 其它的状态基本上被揉在了两个状态中.

比如LostGamer就是SeeGamer超过可视距离之后需要切换的状态, 但是本身没有什么逻辑, 所以直接揉在了Patrol中.

public class FSMState_Monster_Patrol : FSMStateBase
{
    private Transform m_SelfTrans;
    private Transform m_GamerTrans;

    /// <summary>
    /// 当前wp索引
    /// </summary>
    private int m_CurrentWpIndex = 0;

    public FSMState_Monster_Patrol(object owner) : base((int)FSMStateType_Monster.Patrol, owner)
    {
        m_SelfTrans = owner as Transform;
    }

    public override void OnEnter(FSMMgr mgr)
    {
        base.OnEnter(mgr);

        if (DataMgr_FSM.instance.monsterWaypoints.Length <= 0)
            Debug.LogError("Waypoint列表为空!");
    }

    public override string ToString()
    {
        return $"{(FSMStateType_Monster)StateType}状态";
    }

    private void RunningPatrol()
    {
        var targetPatrolTrans = DataMgr_FSM.instance.monsterWaypoints[m_CurrentWpIndex];
        if (Vector3.Distance(targetPatrolTrans.position, m_SelfTrans.position) < DataMgr_FSM.instance.minTargetDistance)
        { // 已经靠近, 切换到下一个点
            m_CurrentWpIndex++;
            m_CurrentWpIndex %= DataMgr_FSM.instance.monsterWaypoints.Length; // 越界后从头开始

            targetPatrolTrans = DataMgr_FSM.instance.monsterWaypoints[m_CurrentWpIndex];
        }

        var targetDir = (targetPatrolTrans.position - m_SelfTrans.position).normalized;
        targetDir.y = 0;

        // 移动和转向
        m_SelfTrans.Translate(targetDir * Time.deltaTime * DataMgr_FSM.instance.monsterMoveSpeed, Space.World);
        m_SelfTrans.rotation = Quaternion.Lerp(m_SelfTrans.rotation, Quaternion.LookRotation(targetDir), Time.deltaTime * DataMgr_FSM.instance.monsterRotateSpeed);
    }

    private void CheckSeeGamer()
    {
        m_GamerTrans = DataMgr_FSM.instance.target;
        if (m_GamerTrans == null) return;

        if (Vector3.Distance(m_GamerTrans.position, m_SelfTrans.position) <= DataMgr_FSM.instance.minSeeGamerDistance)
        {
            StateMgr.SetBool("SeeGamer", true);
        }
    }

    public override void OnRunning()
    {
        RunningPatrol();
        CheckSeeGamer();
    }
}

public class FSMState_Monster_SeeGamer : FSMStateBase
{
    private Transform m_SelfTrans;
    private Transform m_GamerTrans;

    public FSMState_Monster_SeeGamer(object owner) : base((int)FSMStateType_Monster.SeeGamer, owner)
    {
        m_SelfTrans = owner as Transform;
    }

    public override void OnEnter(FSMMgr mgr)
    {
        base.OnEnter(mgr);
    }

    public override string ToString()
    {
        return $"{(FSMStateType_Monster)StateType}状态";
    }

    public override void OnRunning()
    {
        m_GamerTrans = DataMgr_FSM.instance.target;

        var targetPos = m_GamerTrans.position - m_SelfTrans.position;
        var distance = Vector3.Distance(m_GamerTrans.position, m_SelfTrans.position);
        if (distance > DataMgr_FSM.instance.maxSeeGamerDistance)
        { // 达到最大距离, 脱离目标
            StateMgr.SetBool("SeeGamer", false);
            return;
        }

        var targetDir = targetPos.normalized;
        targetDir.y = 0;

        if (targetDir.x != 0 && targetDir.z != 0)
            m_SelfTrans.rotation = Quaternion.Lerp(m_SelfTrans.rotation, Quaternion.LookRotation(targetDir), Time.deltaTime * DataMgr_FSM.instance.monsterRotateSpeed);

        if (distance > DataMgr_FSM.instance.minTargetDistance)
        { // 达到最近距离, 需要停止位移
            m_SelfTrans.Translate(targetDir * Time.deltaTime * DataMgr_FSM.instance.monsterMoveSpeed, Space.World);
        }
    }
}

代码不是很复杂, 需要关注的是状态机的关系和切换.

MonsterContoller_FSM

MonsterContoller_FSM是怪物状态机控制器.

public class MonsterContoller_FSM : MonoBehaviour
{
    [SerializeField] private Transform m_Target;

    /// <summary>
    /// 缓存Transform, 避免每帧使用属性获取
    /// </summary>
    private Transform m_SelfTrans;

    private FSMMgr m_FsmMgr = new FSMMgr();

    private bool m_IsGamerFriendly = false;

    private void Awake()
    {
        m_SelfTrans = transform;
        Application.targetFrameRate = 60;

        m_FsmMgr.SetBool("SeeGamer", false);
        m_FsmMgr.SetBool("IsGamerFriendly", false);
    }

    private void Start()
    {
        var patrolState = new FSMState_Monster_Patrol(m_SelfTrans);
        var seeGamerState = new FSMState_Monster_SeeGamer(m_SelfTrans);

        m_FsmMgr.RegisterState(patrolState);
        m_FsmMgr.RegisterState(seeGamerState);

        var patrolToSeeGamerTransition = new FSMTransitionBase(patrolState, seeGamerState);
        var seeGamerToPatrolTransition = new FSMTransitionBase(seeGamerState, patrolState);

        patrolToSeeGamerTransition.AddCondition("SeeGamer", FSMTransitionConditionMode.IfTrue);
        patrolToSeeGamerTransition.AddCondition("IsGamerFriendly", FSMTransitionConditionMode.IfFalse);

        seeGamerToPatrolTransition.AddCondition("SeeGamer", FSMTransitionConditionMode.IfFalse);

        patrolState.RegisterTransition(patrolToSeeGamerTransition);
        seeGamerState.RegisterTransition(seeGamerToPatrolTransition);

        m_FsmMgr.ChangeState(patrolState);
    }

    private void Update()
    {
        if (m_IsGamerFriendly != DataMgr_FSM.instance.IsGamerFriendly)
        {
            m_IsGamerFriendly = DataMgr_FSM.instance.IsGamerFriendly;
            m_FsmMgr.SetBool("IsGamerFriendly", m_IsGamerFriendly);
        }

        m_FsmMgr.OnRunning();
    }
}

基本上是仿照Unity的Animator逻辑做的, 用过的应该很熟悉.

总结

今天我们使用上一篇文章设计的框架来实现了简单的怪物巡逻和追击玩家的行为, 目前市场上大部分游戏的类似逻辑实现基本上差不太多, 只是可能具体技术不一样, 比如我们这里使用的是状态机, 有些还可以使用行为树(后面会介绍), 目标导向的行为规划(GOAP, 听过还没研究过), 机器学习(简单研究过, 后面会介绍)等.

我们这里提供的专栏, 只是让大家对AI有一个基本的认识, 而不用说觉得AI有多么高大上, 其实日常游戏开发中使用的AI并不算很复杂(至少我目前遇到的不算), 复杂的是在量上而不是质上.

下篇文章可能是有限状态机的最后一篇, 可能会分享一下怪物如何避过路上遇到的障碍来追击玩家, 其实和有限状态机关系不大, 只是保持一个完整性.

但是也不太确定, 就想到哪里写哪里, AI相关的文章还有很多想要分享的, 比如之前提到的行为树, 机器学习, 还有比较常用的寻路算法之类的, 就是不知道有没有时间和心情了, 前几天遭了新冠, 这两天才稍微有点好转, 总之, 这世道, 麻烦多呢…

好啦, 不吐槽了, 今天就是这么多, 希望对大家有所帮助.

PS: 顺便对我家二少爷的到来表示热烈欢迎, 他老父亲还得继续加油呢~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/88193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20221212 SpringCloud Alibaba

Spring Cloud Alibaba介绍主要功能组件注册中心脚手架创建实例使用RestTemplate实现微服务调用使用openfeign实现微服务调用负载均衡的使用创建多实例修改负载均衡Spring Cloud Alibaba 介绍 官方文档&#xff1a; https://spring.io/projects/spring-cloud-alibaba https://…

git stash 命令详解

1. 应用场景 2. 添加储藏 3. 查看储藏 4. 删除储藏 5. 使用储藏 6. 常见用法 1. 应用场景 git stash 命令用于将工作区中的更改和暂存区中的内容储存起来 日常开发中&#xff0c;会经常遇到这种场景 我们正在 dev 分支开发新功能&#xff0c;做到一半时&#xff0c;产品经理…

模块化、组件化和插件化

模块化&#xff1a;业务解耦、代码重用 组件化&#xff1a;模块化为基础、开发阶段每个moudle都是一个app &#xff0c;可以单独编译,并行开发 互不干扰&#xff0c;不用编译整个工程&#xff0c;打包的时候每个moudle又是moudle 不是app 只有一个app 插件化&#xff1a;也是…

【愚公系列】2022年12月 Elasticsearch数据库-ELK环境的搭建(一)

文章目录前言一、ELK环境的搭建1.前提条件2.启动Elasticsearch3.配置可视化工具 head-master3.配置kibana前言 ELK是三个开源软件的缩写&#xff0c;分别表示&#xff1a;Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat&#xff0c;它是一个轻量级…

大学生可以在校搞搞副业吗?尝试做外卖跑腿项目有没有市场?

随着大学寒假的即将到来&#xff0c;40多天的假期&#xff0c;为什么大学生不利用这个机会去想明年的校园生活该如何度过&#xff0c;想要自己的校园生活过得精彩&#xff0c;就给自己找一个副业吧&#xff01; 副业&#xff01;这两个词应该是针对工作的&#xff0c;而不是针…

MapReduce 编程实例:词频统计

文章目录MapReduce 编程实例&#xff1a;词频统计一&#xff0c;准备数据文件&#xff08;1&#xff09;在虚拟机上创建文本文件&#xff08;2&#xff09;上传文件到HDFS指定目录二&#xff0c;使用IDEA创建Maven项目三&#xff0c;添加相关依赖四&#xff0c;创建日志属性文件…

【AI with ML】第 3 章 :超越基础知识:检测图像中的特征

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

树上操作【点分治】 - 原理 中心分解 【POJ No. 1741】 树上两点之间的路径数 Tree

树上操作【点分治】 - 原理 中心分解 分治法指将规模较大的问题分解为规模较小的子问题&#xff0c;解决各个子问题后合并得到原问题的答案。树上的分治算法分为点分治和边分治。 点分治经常用于带权树上的路径统计&#xff0c;本质上是一种带优化的暴力算法&#xff0c;并融…

【内网安全-基础】基础知识、信息收集、工具

目录 一、基础知识 1、内网&#xff1a; 2、工作组&#xff1a; 3、域(Domain)&#xff1a; 二、基础信息收集 1、判断是否在域内 2、机器角色判断 3、出网协议判断 4、端口判断 三、常规信息收集 1、常用命令 2、常用命令 3、工具&插件 LadonGO CS插件 Adfi…

基于Java(Spring+Struts+Hibernate 框架)实现(Web)学生课程管理系统【100010038】

课程管理系统设计文档 二、引言 2.1 目的 ​ 本文档详细描述了课程管理系统的设计&#xff0c;达到引导开发的作用&#xff0c;同时实现测试人员以及用户的沟通。 ​ 本文档面向开发人员&#xff0c;测试人员以及最终用户编写&#xff0c;是了解系统的导航。 2.2 范围 ​…

Win10系统电脑连接打印机的操作方法教学

Win10系统电脑连接打印机的操作方法教学分享&#xff0c;很多用户在办公的时候都会需要使用到打印机。用用户自己购买了打印机之后&#xff0c;不懂怎么去连接自己的电脑来进行使用的方法&#xff0c;接下来我们一起来看看Win10系统电脑连接打印机的操作方法分享吧。 Win10连接…

2022职场人状态和顺风出行感受调研报告

2022年即将过去&#xff0c;作为职场人的你会如何总结&#xff1f;职场同路人又有哪些想对彼此说的话&#xff1f;近日&#xff0c;嘀嗒出行发布《2022职场人状态和顺风出行感受调研报告》&#xff0c;基于近8000名嘀嗒顺风车车主和乘客分享各自职场经历和顺风出行感受&#xf…

运维人必须掌握的 5 种常用运维监控工具

运维监控工具千千万&#xff0c;仅开源的解决方案就有流量监控&#xff08;MRTG、Cacti、SmokePing、Graphite 等&#xff09;和性能告警&#xff08;Nagios、Zabbix、Zenoss Core、Ganglia、OpenTSDB等&#xff09;可供选择。 并且每种软件都有自己的特点和功能&#xff0c;各…

WiFi热点加装短信认证怎么操作?

公共场所提供无线wifi上网服务&#xff0c;需对用户进行实名认证。手机短信实名认证以其用户体验、综合成本等优势&#xff0c;成为公共wifi上网认证的首选方案。 无线wifi上网实现信认证功能&#xff0c;需要借助上网行为管理设备搭配验证短信平台使用&#xff1b;根据无线wi…

基于Java(Jsp+Sevlet)+MySql 实现的(Web)成绩管理系统【100010041】

1 概述 1.1 开发背景 随着学生数量的日渐增多&#xff0c;学生教务系统的数据量也不断增加&#xff0c;这无疑大大增加了教务系统的负担。如果能把负责学生成绩管理的模块独立出来形成一个独立的系统&#xff0c;便可以有效降低教务系统的数据量&#xff0c;不仅可以方便管理…

阿里巴巴Java开发手册(黄山版)

阿里巴巴 Java 开发手册&#xff08;黄山版&#xff09; 链接&#xff1a;https://pan.baidu.com/s/1iKsXlq1DSbePLvuysYbA4A 提取码&#xff1a;yyds 阿里巴巴将 Java 开发手册 从 1.7.0 的嵩山版更新至 1.7.1 的黄山版&#xff0c;新增 11 条新规约&#xff0c;具体变动如下…

[ Linux ] 线程控制(线程创建,等待,终止)

在上一篇我们了解了Linux下线程的相关概念。而本篇的主要内容是线程控制。线程控制包括线程的创建&#xff0c;线程的终止&#xff0c;线程等待等问题&#xff0c;以及线程分离和Linux常见线程安全问题。 目录 1.线程控制 1.1POSIX线程库 1.2 创建线程 1.2.1 创建线程编码…

新库上线 | CnOpenData劳务外包企业工商注册基本信息数据

劳务外包企业工商注册基本信息数据 一、数据简介 随着我国社会主义市场经济的发展&#xff0c;劳务市场中的用工方式也朝着多样化方向演变&#xff0c;劳务外包正是现代化人力资源管理和企业生产实际结合的一种独特的新模式。 在劳务外包过程中&#xff0c;企业将人事管理的部…

Node 文件查找优先级及 Require 方法文件查找策略

Node 文件查找优先级及 Require 方法文件查找策略 一、模块规范 NodeJS对CommonJS进行了支持和实现&#xff0c;让我们在开发node的过程中可以方便的进行模块化开发&#xff1a; 在Node中每一个js文件都是一个单独的模块模块中包括CommonJS规范的核心变量&#xff1a;export…

图数据库知识点2:图思维方式

在上一个知识点中&#xff0c;我们剖析了关系型数据库、数仓湖与图数据库的差异。在本文&#xff0c;我们会着重介绍一个重要的概念——图思维方式&#xff08;Graph-thinking&#xff09;。 关于思维方式 每个人应该如何思考&#xff0c;他/她是如何思考的&#xff0c;这是一…