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();}
}

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

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

相关文章

前端工程化工具系列

所有和前端工程化工具的系列合集&#xff0c;快速提升开发效率。文档持续更新中&#xff0c;敬请期待&#xff5e;感兴趣的可收藏 前端工程化 这个专栏 已完成 前端工程化工具系列&#xff08;一&#xff09;—— ESLint(v9.4.0)&#xff1a;代码质量守护者 基础篇 前端工程化…

AI技术从起源到革命性的未来

在科技日新月异的今天&#xff0c;huizerc.com人工智能&#xff08;AI&#xff09;技术已成为推动社会进步的重要力量。从最初的概念提出&#xff0c;到如今的广泛应用&#xff0c;AI技术经历了漫长而曲折的发展历程。本文将深入探讨AI技术的起源、发展历程、当前应用以及未来展…

Vue——模板引用(不建议使用,了解)

文章目录 前言测试案例 前言 模板引用&#xff0c;在官方文档中也有很详细的描述。 虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作&#xff0c;但在某些情况下&#xff0c;我们仍然需要直接访问底层 DOM 元素。 个人理解为&#xff1a; 在vue中&#xff0c;依据…

【数据库系统概论】程序题

“学生管理数据库”包含以下三个表,即学生表Student、课程表Course和选课表SC,结构如下: Student(Sno,Sname,Ssex,Sage,Sdept)Course (Cno,Cname,Cpno,Ccredit)SC(Sno,Cno,Grade)其中,Sno为学号,Sname为学生姓名,Ssex为性别,Sage为年龄 系别:Cno为课程号…

Milvus 基本操作

1、maven 依赖 <dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.3.3</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>sl…

STL容器--list

1. list的介绍及使用 1.1 list的介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指其前…

面试官:对于MQ中的消息丢失你是如何理解的?

相信很多的小伙伴在面试的时候&#xff0c;涉及到MQ的面试题&#xff0c;消息丢失是必问面试题之一。那么对于消息丢失你又是如何理解的呢&#xff1f; 下面我们一起来看一下。 本文以 Kafka 举例说明 一、什么是消息丢失&#xff1f; 消息丢失的定义是&#xff1a;在消息传递…

ActivityRecord、TaskRecord、ActivityStack以及Activity详解

adb shell dumpsys activity activities 输出涉及到了解 Android 活动管理器&#xff08;Activity Manager&#xff09;的当前状态&#xff0c;以及系统中运行的活动和任务的详细信息。这是系统中活动&#xff08;Activities&#xff09;、任务&#xff08;Tasks&#xff09;、…

【动手学深度学习】softmax回归从零开始实现的研究详情

目录 &#x1f30a;1. 研究目的 &#x1f30a;2. 研究准备 &#x1f30a;3. 研究内容 &#x1f30d;3.1 softmax回归的从零开始实现 &#x1f30d;3.2 基础练习 &#x1f30a;4. 研究体会 &#x1f30a;1. 研究目的 理解softmax回归的原理和基本实现方式&#xff1b;学习…

Python打印当前目录下,所有文件名的首字母

代码如下&#xff1a; #!/usr/bin/env python3 """ 按顺序打印当前目录下&#xff0c;所有文件名的首字母&#xff08;忽略大小写&#xff09; """ import sys from pathlib import Pathdef main() -> None:ps Path(__file__).parent.glob(…

代码随想录算法训练营第26天(py)| 回溯 | 39. 组合总和、40.组合总和II、131.分割回文串

39. 组合总和 力扣链接 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明&#xff1a; 所有数字&#xff08;包括 target&#xff09;都是正整数…

上传RKP 证书签名请求息上传到 Google 的后端服务器

上传证书签名请求 1.准备环境&#xff1a;OK pip3 install google-auth2.13.0 requests2.28下载 device_info_uploader.py 。 没找到先跳过 选项 1&#xff1a;通过 GCP 帐户使用 device_info_uploader.py 运行脚本。 ./device_info_uploader.py --credentials /secure/s…

深入理解Python的包管理器:pip

深入理解Python的包管理器&#xff1a;pip 引言 Python作为一门流行的编程语言&#xff0c;拥有强大的生态系统&#xff0c;其中pip扮演着至关重要的角色。pip是Python的包管理工具&#xff0c;它允许用户安装、升级和管理Python包。本专栏旨在帮助读者深入了解pip的各个方面…

NFS服务p.2 用户的上传与下载,以及用户映射

如何进行上传和下载呢&#xff1f; 目录 如何进行上传和下载呢&#xff1f; 上传 访问时的账户映射对于上传文件和下载文件的影响&#xff1f; 在服务器里进行修改用户的权限 如和修改使用用户上传时的名字&#xff1f; 上传 上传的话&#xff0c;因为我们现在所在的nfs1…

端午佳节到,礼轻情意重,闪侠惠递帮你高效便宜寄快递

马上就是端午佳节了&#xff0c;我们通常会吃粽子&#xff0c;赛龙舟&#xff0c;但是这些礼物我们该怎么快速的送到我们亲朋好友的手中呢&#xff1f;小编这里非常推荐大家使用闪侠惠递来寄快递。不仅能高效便捷的把礼物送到你的手中&#xff0c;而且还能以非常便宜的价格呢&a…

03_初识Spring Cloud Gateway

文章目录 一、网关简介1.1 网关提出的背景1.2 网关在微服务中的位置1.3 网关的技术选型1.4 补充 二、Spring Cloud Gateway的简介2.1 核心概念&#xff1a;路由&#xff08;Route&#xff09;2.2 核心概念&#xff1a;断言&#xff08;Predicate&#xff09;2.3 核心概念&#…

聊聊Java中的动态代理机制

引言 动态代理是Java中一个非常强大的特性&#xff0c;它允许我们在运行时动态地创建代理对象。本文将深入探讨动态代理的工作原理、实现步骤以及在实际项目中的应用。 第一部分&#xff1a;代理模式基础 代理模式是一种结构型设计模式&#xff0c;它为其他对象提供一个代替…

力扣524. 通过删除字母匹配到字典里最长单词

给你一个字符串 s 和一个字符串数组 dictionary &#xff0c;找出并返回 dictionary 中最长的字符串&#xff0c;该字符串可以通过删除 s 中的某些字符得到。 如果答案不止一个&#xff0c;返回长度最长且字母序最小的字符串。如果答案不存在&#xff0c;则返回空字符串。 示…

为什么人工智能用 Python?

为什么人工智能用 Python&#xff1f; 人工智能&#xff08;AI&#xff09;技术取得了飞速发展&#xff0c;从语音识别、图像处理到自然语言处理&#xff0c;而在众多编程语言中&#xff0c;Python 因其简洁易用、丰富的库和社区支持&#xff0c;成为了 AI 开发的首选语言。本…

linux系统——性能检测工具glances

在linux系统中&#xff0c;由python开发的glances工具是一个功能强大的性能检测工具 可以通过yum进行安装 安装glances后&#xff0c;进入命令界面 glance支持网站模式&#xff0c;将监控到的数据以网站形式显示出来 这里需要用python包管理命令 使用glances -w开放…