Unity有限状态机实现怪物AI(代码框架思路)

2024-06-04 05:20

本文主要是介绍Unity有限状态机实现怪物AI(代码框架思路),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

状态的枚举

状态基类

接口(规范不同对象的同一行为)

 状态机类(作为媒介用于管理各个状态之间的转换)

附带一个攻击状态的子类脚本作为示例:


状态的枚举

首先最容易想到的是状态的枚举,比如说攻击状态、巡逻状态、追击状态等等,用枚举进行表示

public enum E_AI_State 
{/// <summary>/// 睡眠状态/// </summary>Sleep,/// <summary>/// 巡逻状态/// </summary>Patrol,/// <summary>/// 聊天状态/// </summary>Chat,/// <summary>/// 逃跑状态/// </summary>Run,/// <summary>/// 追逐玩家状态/// </summary>Chase,/// <summary>/// 攻击玩家的状态/// </summary>Atk,/// <summary>/// 警觉状态/// </summary>Alertness,
}

状态基类

再就是所有怪物 对应的状态都会有一个 脚本去控制该状态下该执行什么,而这些状态肯定会有一部分的逻辑是相同的,所以可以提取出一个抽象类,即状态基类

public abstract class BaseState
{//有限状态机实现的AI中的 这些 状态类 它的本质 对于我们来说 是在做什么?//逻辑处理(不仅仅是做AI,不管你用代码做什么样的事情 都是在进行逻辑处理)//AI状态的切换 //切换这个词 就意味着// 状态1 ——> 状态2//在这个基类中 可以去实现所有状态共有的 进入、离开、处于状态的行为(函数、方法)//但是这些方法中 由于是基类,没有明确是哪种状态,也就意味着方法中不会写内容//那么 不能写内容的函数 你能联想到什么?//1.如果是接口 ,那么直接声明//2.如果是类,那么可以考虑抽象方法——一定是抽象类//管理自己的有限状态机对象protected StateMachine stateMachine;/// <summary>/// 初始化状态类时  将管理者传入 进行记录/// </summary>/// <param name="machine"></param>public BaseState(StateMachine machine){stateMachine = machine;}//当前状态的类型public virtual E_AI_State AIState{get;}// 1.离开状态时 做什么public abstract void QuitState();// 2.进入状态时 做什么public abstract void EnterState();// 3.处于状态时 做什么(核心逻辑处理)public abstract void UpdateState();
}

接口(规范不同对象的同一行为)

public interface IAIObj 
{//所有AI对象都应该可以获取到它的Transform信息public Transform objTransform{get;}//所有AI对象都应该有一个当前的位置public Vector3 nowPos{get;}//AI对象的目标对象所在的位置public Vector3 targetObjPos{get;}//所有AI对象都应该有一个攻击范围的概念//好用于判断 什么时候开始攻击玩家public float atkRange{get;}//出生位置 需要继承它的AI对象提供public Vector3 bornPos {get;set;}//AI对象中 应该有 移动相关的方法public void Move(Vector3 dirOrPos);//AI对象中 应该有 停止移动相关的方法public void StopMove();//AI对象中 应该有 攻击相关的方法public void Atk();//AI对象中 可能想要单独 切换指定动作//切换动作 应该传递一些相关参数 才能够指定切换哪个动作吧public void ChangeAction(E_Action action);//我们应该根据AI不同的状态 去提取出他们的行为合集
}

 状态机类(作为媒介用于管理各个状态之间的转换)

public class StateMachine
{//他要管理AI的所有状态//所以我们通过一个容器去存储这些状态//这些状态会随时的取出来进行切换 因此我们要选用一个方便查找获取的容器存储//key —— 状态类型(是有限的状态类型,那么就可以是一开始定死的,//                 即使以后策划天马行空 有了新状态需求 ,我们改代码即可,因为我们有了热更新技术 所以也没有太大的影响)//value —— 代表的是处理状态的逻辑对象private Dictionary<E_AI_State, BaseState> stateDic = new Dictionary<E_AI_State, BaseState>();//表示当前有限状态 处于的状态(也就是对应的怪物或玩家当前处于的AI状态)private BaseState nowState;//这个就是ai有限状态机 管理的 ai对象 会去通过ai状态命令该对象 执行对应的行为public IAIObj aiObj;//回归的判断临界距离 现在我们写死 以后可能是从AI表中读取public float backDis = 15;//我们的有限状态机制作的AI 里面有很多的AI状态//那么这些AI状态逻辑当中,最终要去针对什么处理对应的状态逻辑//处理的其实是 游戏当中需要AI的对象 比如 怪物、玩家、宠物、NPC等等//虽然这些对象都是不一样的对象 但是 他们理论上来说需要具备共同的行为//这样在处理AI逻辑时 才更方便进行一些行为的调用//我们其实可以尝试 在AI模块把这些内容提取出来 作为接口 让这些需要AI的对象 必须要实现这个接口 才行/// <summary>/// 初始化有限状态机类 /// </summary>/// <param name="aiObj">传入 ai对象 用于之后的行为控制</param>public void Init(IAIObj aiObj){this.aiObj = aiObj;}/// <summary>/// 添加AI状态/// </summary>public void AddState(E_AI_State state){switch (state){case E_AI_State.Patrol:stateDic.Add(state, new PatrolState(this));break;case E_AI_State.Run:stateDic.Add(state, new RunState(this));break;case E_AI_State.Chase:stateDic.Add(state, new ChaseState(this));break;case E_AI_State.Atk:stateDic.Add(state, new AtkState(this));break;}}/// <summary>/// 改变状态/// </summary>public void ChangeState(E_AI_State state){//如果当前处于另一个状态 就退出该状态if (nowState != null)nowState.QuitState();//如果存在该状态的逻辑出来对象 那么就进入该状态if(stateDic.ContainsKey(state)){nowState = stateDic[state];nowState.EnterState();}}/// <summary>/// 更新当前状态逻辑处理/// </summary>public void UpdateState(){if (nowState != null)nowState.UpdateState();}/// <summary>/// 检测是否切换到回归状态/// </summary>public void CheckChangeRun(){//在追逐过程中 发现超出了 我们的最大距离 就应该切换到回归的状态//目前我们处理的是利用ai对象和自己的出生点距离 进行最大距离判断//达到的效果是 ai对象一定要跑到边界 才甘心//其实还可以利用 目标对象和自己的出生点距离 + 自己攻击距离 来进行距离判断//达到的效果就是 目标达不到了 就没有必要追了if (Vector3.Distance(this.aiObj.nowPos, this.aiObj.bornPos) >= this.backDis){ChangeState(E_AI_State.Run);}}
}

在状态机类中的AddState方法中,向字典中添加了对应的状态(把自己这个状态机类传进去供初始化),也就是说,当我在另一个脚本中调用状态机类的中的AddState,他就会在BaseState中自动关联上了状态机类 这个脚本,参考代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;public class Monster : MonoBehaviour, IAIObj
{public GameObject bullet;//网格寻路组件private NavMeshAgent navMeshAgent;//在需要使用AI模块的对象当中 声明一个 AI状态机对象 用于开启AI功能private StateMachine aiStateMachine;private Vector3 nowObjPos;//对象当前的位置public Vector3 nowPos {get{nowObjPos = this.transform.position;//为了和我们AI模块的定位规则相同 没有考虑 Y上的位置 主要是在xz平面进行位移nowObjPos.y = 0;return nowObjPos;}}//出生位置public Vector3 bornPos{get;set;}//AI对象必须能够被AI模块获取到Transform 方便我们进行相关处理public Transform objTransform => this.transform;//自己的攻击范围(目前我们可以写死,以后 一般是通过配置表进行数据初始化//如果还有其他规则,自己实现对应的攻击范围规则即可)public float atkRange => 2;//用于测试用的目标对象//正常情况下,应该通过代码动态的再场景中寻找满足条件的目标 我们这里仅仅是测试//所以直接通过拖拽进行关联public Transform targetTransform;//由于我们现在还不用去考虑 目标 所以随便给一个目标位置public Vector3 targetObjPos{get{//注意:这里减去y方向的0.5 是因为我们用立方体举例子,它的y往上升了0.5//为了贴合地面 所以我们减去0.5return targetTransform.position - Vector3.up * 0.5f;}}private void Start(){navMeshAgent = this.GetComponent<NavMeshAgent>();//之所以把AI的重要初始化 放到对象类当中 主要原因//是因为不同对象 可能会存在不同的AI状态,不同的起始状态//这些往往在游戏中 都是配置表当中配置的 所以一般写在怪物创建处//注意://大多数情况下 会放在 怪物管理器中的创建怪物的方法中,但是我们目前没有设计怪物管理器//因此,我们把这一块代码 放在了 怪物出生的生命周期函数中 也就是Start中(也可以放在Awake)//初始化AI模块的有限状态机对象aiStateMachine = new StateMachine();//把ai对象自己 传入其中进行初始化aiStateMachine.Init(this);//你需要什么AI状态 就动态添加(以后一般情况下 是通过配置表的配置去添加)//为AI添加巡逻状态aiStateMachine.AddState(E_AI_State.Patrol);aiStateMachine.AddState(E_AI_State.Chase);aiStateMachine.AddState(E_AI_State.Atk);aiStateMachine.AddState(E_AI_State.Run);//初始化完所有AI状态后 那就需要一个当前的AI状态//目前一开始就让对象时一个巡逻状态aiStateMachine.ChangeState(E_AI_State.Patrol);//出生位置 就是对象一开始所在的位置bornPos = this.transform.position;}private void Update(){//ai相关的更新 是由 ai对象的 帧更新函数 发起的 aiStateMachine.UpdateState();}public void Atk(){//暂时不写 之后写到攻击AI时 再去写它print("攻击");//动态创建自动 发射即可GameObject obj = Instantiate(bullet, this.transform.position + this.transform.forward + Vector3.up * 0.5f, this.transform.rotation);Destroy(obj, 5f);}public void ChangeAction(E_Action action){print(action);}public void Move(Vector3 dirOrPos){//结束停止移动navMeshAgent.isStopped = false;navMeshAgent.SetDestination(dirOrPos);}public void StopMove(){//该方法过时了//navMeshAgent.Stop();//停止移动navMeshAgent.isStopped = true;}
}

附带一个攻击状态的子类脚本作为示例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class AtkState : BaseState
{public override E_AI_State AIState => E_AI_State.Atk;//下一次攻击的时间private float nextAtkTime;//下次攻击等待的时间private float waitTime = 2f;public AtkState(StateMachine machine):base(machine){}public override void EnterState(){Debug.Log("进入攻击状态了");//进入攻击状态时 认为此时此刻就要攻击nextAtkTime = Time.time;}public override void QuitState(){}public override void UpdateState(){//进入AI状态后 不停的让ai对象去攻击即可if (Time.time >= nextAtkTime){stateMachine.aiObj.Atk();nextAtkTime = Time.time + waitTime;}//如果目标和我的距离过远了,我们应该去切换到追逐状态 ,追到了再继续打它if (Vector3.Distance(stateMachine.aiObj.nowPos, stateMachine.aiObj.targetObjPos) > stateMachine.aiObj.atkRange){stateMachine.ChangeState(E_AI_State.Chase);}//我们可以利用向量和四元数相关知识 让ai对象看向目标对象 也可以简单粗暴的用LookAt//我们在这里只是举例子 就使用LookAt来节约一些事件 之后 大家可以根据自己的需求去进行制作stateMachine.aiObj.objTransform.LookAt(stateMachine.aiObj.targetObjPos + Vector3.up * 0.5f);//在追逐过程中 发现超出了 我们的最大距离 就应该切换到回归的状态stateMachine.CheckChangeRun();}
}

这篇关于Unity有限状态机实现怪物AI(代码框架思路)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1029176

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

AI绘图怎么变现?想做点副业的小白必看!

在科技飞速发展的今天,AI绘图作为一种新兴技术,不仅改变了艺术创作的方式,也为创作者提供了多种变现途径。本文将详细探讨几种常见的AI绘图变现方式,帮助创作者更好地利用这一技术实现经济收益。 更多实操教程和AI绘画工具,可以扫描下方,免费获取 定制服务:个性化的创意商机 个性化定制 AI绘图技术能够根据用户需求生成个性化的头像、壁纸、插画等作品。例如,姓氏头像在电商平台上非常受欢迎,

从去中心化到智能化:Web3如何与AI共同塑造数字生态

在数字时代的演进中,Web3和人工智能(AI)正成为塑造未来互联网的两大核心力量。Web3的去中心化理念与AI的智能化技术,正相互交织,共同推动数字生态的变革。本文将探讨Web3与AI的融合如何改变数字世界,并展望这一新兴组合如何重塑我们的在线体验。 Web3的去中心化愿景 Web3代表了互联网的第三代发展,它基于去中心化的区块链技术,旨在创建一个开放、透明且用户主导的数字生态。不同于传统

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

AI一键生成 PPT

AI一键生成 PPT 操作步骤 作为一名打工人,是不是经常需要制作各种PPT来分享我的生活和想法。但是,你们知道,有时候灵感来了,时间却不够用了!😩直到我发现了Kimi AI——一个能够自动生成PPT的神奇助手!🌟 什么是Kimi? 一款月之暗面科技有限公司开发的AI办公工具,帮助用户快速生成高质量的演示文稿。 无论你是职场人士、学生还是教师,Kimi都能够为你的办公文

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time