前言
本文参考自:
Unity3d College - Unity3D AI with State Machine (FSM), Drones, and Lasers!(视频教程)
https://www.youtube.com/watch?v=YdERlPfwUb0
游戏设计模式-状态模式 (译文)
https://gpp.tkchu.me/state.html
Game Programming Patterns - State (英语原文)
http://gameprogrammingpatterns.com/state.html
在阅读完本文后,请查看上述链接内容。
本文使用的示例工程:FINCTIVE/lost-in-the-wilderness-nightmare(荒野迷踪:噩梦)欢迎Star!(https://github.com/FINCTIVE/lost-in-the-wilderness-nightmare)
由于水平有限,文章内容可能有误,欢迎探讨、交流、或直接批评。
┗|`O′|┛
本文作者: FINCTIVE
联系邮箱: finctive@qq.com
应用场景
在荒野迷踪:噩梦中,敌人的行为如下图(在线运行demo):
![7651e13014c3e28e4a8bdc7b4c7decc1.png](https://i-blog.csdnimg.cn/blog_migrate/d9c2050bb108333eb87384ff45d7798a.jpeg)
文字描述再详细也不如你亲自打开网页玩一下 :D
看起来可以用大大的 if{...}else if{...}else{...} 语句实现,但实际上手开发之后我发现……
切入正题:一个状态指AI的一系列行为,例如本项目中的静止、追逐、自爆状态,可以使用一个类描述。对于一个状态,应该把处理代码写在一个类中。比如,“追逐玩家”状态相关的代码,尽量不要写到其他状态的类里面。
解决方案
敌人AI游戏对象截图
![c4677a1f6df9773ee7758c5b6064bd46.png](https://i-blog.csdnimg.cn/blog_migrate/8f9ab9ad2a2bec6d41fe0d94430a4458.jpeg)
以下是状态的基类
public abstract class BaseState : MonoBehaviour{ // 执行本状态的相关操作,返回值是下一次游戏循环的状态 public abstract BaseState Tick(); // 与本状态有关的初始化代码 public virtual void OnStateStart(){} // 与本状态有关的退出代码 public virtual void OnStateExit(){}}
状态机
public class StateMachine : MonoBehaviour{ public BaseState defaultState; [HideInInspector]public BaseState currentState; private void Awake() { currentState = defaultState; } void FixedUpdate() { BaseState nextStateType = currentState.Tick(); if (nextStateType != currentState) { nextStateType.OnStateStart(); currentState.OnStateExit(); } currentState = nextStateType; }}
我把与所有状态相关的控制脚本写在了EnemyController组件中,暴露出公共方法让状态机脚本调用。这样可以复用代码,并且让状态机的逻辑代码只负责更高一层的控制,而不管细节如何。
以下是追逐状态的代码,其他状态同理。
public class EnemyChasingState : BaseState{ public EnemyAttackingState enemyAttackingState; public EnemyIdlingState enemyIdlingState; private EnemyController _enemyController; private void Awake() { _enemyController = GetComponent(); } private static readonly int AnimMove = Animator.StringToHash("move"); public override BaseState Tick() { Vector3 targetPos = PlayerController.playerTransform.position; _enemyController.MoveToTarget(targetPos); float distanceSqrMag = (targetPos - transform.position).sqrMagnitude; // 距离足够近,开始攻击(自爆) if(distanceSqrMag < _enemyController.enemyInfo.startAttackingDistance*_enemyController.enemyInfo.startAttackingDistance) { return enemyAttackingState; } // 距离太远,放弃追逐 if(distanceSqrMag > _enemyController.enemyInfo.stopChasingDistance*_enemyController.enemyInfo.stopChasingDistance) { return enemyIdlingState; } return this; } public override void OnStateExit() { _enemyController.modelAnimator.SetFloat(AnimMove, 0f); }}
声明:发布此文是出于传递更多知识以供交流学习之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与我们联系,我们将及时更正、删除,谢谢。
作者:FINCTIVE
来源:https://www.yuque.com/finctive/game-dev/fsm-ai
More:【微信公众号】 u3dnotes