前言
原文链接 https://www.yuque.com/finctive/game-dev/fsm-ai
本文参考自:
- Unity3d College - Unity3D AI with State Machine (FSM), Drones, and Lasers!(视频教程)
- 游戏设计模式-状态模式 (译文)
- Game Programming Patterns - State (英语原文)
在阅读完本文后,请查看上述链接内容。
本文使用的示例工程:FINCTIVE/lost-in-the-wilderness-nightmare(荒野迷踪:噩梦)欢迎Star!
由于水平有限,文章内容可能有误,欢迎探讨、交流、或直接批评。
┗|`O′|┛
本文作者: FINCTIVE
联系邮箱: finctive@qq.com
转载请标明原文链接以及作者名字,谢谢。
应用场景
在荒野迷踪:噩梦中,敌人的行为如下图(在线运行demo):
文字描述再详细也不如你亲自打开网页玩一下 :D
看起来可以用大大的 if{...}else if{...}else{...} 语句实现,但实际上手开发之后我发现……
切入正题:一个状态指AI的一系列行为,例如本项目中的静止、追逐、自爆状态,可以使用一个类描述。对于一个状态,应该把处理代码写在一个类中。比如,“追逐玩家”状态相关的代码,尽量不要写到其他状态的类里面。
解决方案
敌人AI游戏对象截图
以下是状态的基类
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<EnemyController>();
}
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(finctive@qq.com)
欢迎转载,请附上原文链接以及作者名字,谢谢!