本文章参考自Unity3D人工智能编程精粹,转载请注明出处。
有限状态机的FSM图
有限状态机(FSM)由一组状态(包括一个初始状态)、输入和根据输入及现有状态转换为下一个状态的转换函数组成。
什么是状态?
飞翔,行走,攻击,跑步,游泳,这些动词是状态。高兴,伤心,生气这些形容词也是状态,一些名词也能表示状态。状态的关键意义是:不同的状态对应不同的行为。
关于有限状态机(FSM),需要了解以下几点:
1. 有限状态机是AI系统中最简单的,同时也是最为有效和最常用的方法。对于游戏中的每个对象,都可以在其生命周期内区分出一些状态。
2. 当某些条件发生时,状态机从当前状态转换为其他状态。在不同的状态下,游戏对象对外部激励做出不同的反应或执行不同的动作。有限状态机方法让我们可以很容易地把游戏对象的行为划分为小块,这样更容易调试和扩展。
3. 用户编写的每个程序都是状态机。当没写下一个if语句的时候,就创造出了一段至少拥有两个状态的代码——写的代码越多,程序就可能具有越多的状态。Switch和if语句数量的爆发会让事情很快失去控制,程序会出现奇怪的BUG。
4. 有限状态机是AI中最容易的部分,但也很容易出错。在设计有限状态机的时候,一定要认真地考虑清楚其中的每个状态和转换部分。
简易的FSM图
有限状态机的状态转移矩阵
状态转移矩阵是简介表明FSM图的转移矩阵,分别有当前状态,输入,输出状态。如上图的状态转移矩阵可以为
用FSM框架实现通用的有限状态机
介绍一个通用的FSM框架,可以在unitycommunity.com上找到,地址是http://wiki.unity3d.com/index/php?title=Finite_State_Machine。
其实仔细想想,这个FSM框架是不是跟Animator动画状态机非常像,对!动画状态机、有限状态机,你没看错。但是我们还是得会写才是王道!
上面的状态UML图为
FSM: AdvancedFSM的父类,继承MonoBehaviour,封装了其中的Start,Update,FixedUpdate方法。定义了巡逻数组等。
AdvanceFSM: 管理所有的状态类,FSM的派生类,负责管理FSMState的派生类,并且随着当前状态和输入,进行状态更新。
FSMState: 所有状态的基类,状态类中具有添加转换,删除转换的方法,用于管理记录这些转换。在FSMState中有一个字典对象,用来存储 “转换—状态” 对,表明在当前状态(即这个类所代表的状态)下,如果发生某个 “转换” ,FSM将会转移到何种状态。可以通过AddTransition方法和DeleteTransition方法(自己写)添加或删除 “转换—状态” 对。另外这个类中还包括Reason方法和Act方法,Reason用来确定是否需要转换状态。Act方法定义了在本状态角色行为,例如移动,动画等。
AICotroller: AdvanceFSM的派生类,负责创建有限状态机,通过它来控制角色。
代码如下:
FSM.cs
public class FSM : MonoBehaviour { /// <summary> /// 玩家Transfrom组件 /// </summary> protected Transform playerTransform; /// <summary> /// 下一个目标 /// </summary> protected Vector3 destPos; /// <summary> /// 攻击速率 /// </summary> protected float attackRate; /// <summary> /// 攻击间隔 /// </summary> protected float elapsedTime; /// <summary> /// 初始化 /// </summary> protected virtual void Init() { } /// <summary> /// Update /// </summary> protected virtual void FSMUpdate() { } /// <summary> /// FixedUpdate /// </summary> protected virtual void FSMFixedUpdate() { } void Start() { Init(); } void Update() { FSMUpdate(); } void FixedUpdate() { FSMFixedUpdate(); } }
AdvanceFSM.cs
public class AdvanceFSM : FSM { private List<FSMState> fsmStates; private FSMStateID currentStateID; public FSMStateID CurrentStateID { get { return currentStateID; } } private FSMState currentState; public FSMState CurrentState { get { return currentState; } } public AdvanceFSM() { fsmStates = new List<FSMState>(); } /// <summary> /// 添加状态 /// </summary> /// <param name="fsmState"></param> public void AddFSMState(FSMState fsmState) { if (fsmState == null) { return; } if (fsmStates.Count == 0) { fsmStates.Add(fsmState); currentState = fsmState; currentStateID = fsmState.ID; return; } foreach (FSMState state in fsmStates) { if (state.ID == fsmState.ID) { return; } } fsmStates.Add(fsmState); } /// <summary> /// 删除状态 /// </summary> /// <param name="fsmState"></param> public void DelFSMState(FSMStateID fsmState) { foreach (FSMState state in fsmStates) { if (state.ID == fsmState) { fsmStates.Remove(state); return; } } } /// <summary> /// 转移新状态 /// </summary> /// <param name="transition"></param> public void PerformTransition(Transition transition) { FSMStateID id = currentState.GetOutputState(transition); currentStateID = id; foreach (FSMState state in fsmStates) { if (state.ID == currentStateID) { currentState = state; return; } } } } public enum Transition { /// <summary> /// 看到玩家 /// </summary> SawPlayer = 0, /// <summary> /// 接近玩家 /// </summary> ReachPlayer, /// <summary> /// 玩家离开视线 /// </summary> LostPlayer, /// <summary> /// 被伤害 /// </summary> Injured, /// <summary> /// 死亡 /// </summary> NoHealth, } public enum FSMStateID { /// <summary> /// 巡逻状态 /// </summary> Idling = 0, /// <summary> /// 追逐状态 /// </summary> Chasing, /// <summary> /// 攻击状态 /// </summary> Attacking, /// <summary> /// 受伤状态 /// </summary> Injuring, /// <summary> /// 死亡状态 /// </summary> Dead, }
FSMState.cs
public abstract class FSMState { /// <summary> /// 转换-状态,信息字典 /// </summary> protected Dictionary<Transition, FSMStateID> map = new Dictionary<Transition, FSMStateID>(); /// <summary> /// 状态编号ID /// </summary> protected FSMStateID stateID; /// <summary> /// ID公开 /// </summary> public FSMStateID ID { get { return stateID; } } /// <summary> /// 目标位置 /// </summary> protected Vector3 destPos; /// <summary> /// 转向速度 /// </summary> protected float rotSpeed; /// <summary> /// 移动速度 /// </summary> protected float speed; /// <summary> /// 追逐距离 /// </summary> protected float chaseDistance = 7.0f; /// <summary> /// 攻击距离 /// </summary> protected float attackDistance = 1.4f; /// <summary> /// 到达距离 /// </summary> protected float arriveDistance = 1.4f; /// <summary> /// 添加“转换-状态” /// </summary> /// <param name="transition"></param> /// <param name="id"></param> public void AddTransition(Transition transition, FSMStateID id) { if (map.ContainsKey(transition)) { Debug.LogWarning("GG"); } map.Add(transition, id); } /// <summary> /// 删除“转换-状态” /// </summary> /// <param name="transition"></param> public void DelTransition(Transition transition) { if (map.ContainsKey(transition)) { map.Remove(transition); } } /// <summary> /// 获取转换后新状态编号 /// </summary> /// <param name="transition"></param> /// <returns></returns> public FSMStateID GetOutputState(Transition transition) { Debug.Log(transition); return map[transition]; } /// <summary> /// 是否需要转换,如何转换 /// </summary> /// <param name="player"></param> /// <param name="monster"></param> public abstract void Reason(Transform player, Transform monster); /// <summary> /// 角色行为 /// </summary> /// <param name="player"></param> /// <param name="monster"></param> public abstract void Act(Transform player, Transform monster); }
AIController.cs
public class AIController : AdvanceFSM { /// <summary> /// 角色生命值 /// </summary> private int health; /// <summary> /// 初始化AI角色的FSM,在FSM基类的Start函数中调用。 /// </summary> protected override void Init() { health = 100; Blood.Instance.Change(health); elapsedTime = 0.0f; attackRate = 2.0f; GameObject objPlayer = GameObject.FindGameObjectWithTag("Player"); playerTransform = objPlayer.transform; if (!playerTransform) print("GG"); ConstructFSM(); } /// <summary> /// 这个函数在初始化Init方法中调用,为AI角色构造FSM。 /// </summary> private void ConstructFSM() { IdleState idle = new IdleState(); idle.AddTransition(Transition.SawPlayer, FSMStateID.Chasing); idle.AddTransition(Transition.Injured, FSMStateID.Injuring); idle.AddTransition(Transition.NoHealth, FSMStateID.Dead); ChaseState chase = new ChaseState(); chase.AddTransition(Transition.LostPlayer, FSMStateID.Idling); chase.AddTransition(Transition.ReachPlayer, FSMStateID.Attacking); chase.AddTransition(Transition.Injured, FSMStateID.Injuring); chase.AddTransition(Transition.NoHealth, FSMStateID.Dead); AttackState attack = new AttackState(); attack.AddTransition(Transition.LostPlayer, FSMStateID.Idling); attack.AddTransition(Transition.SawPlayer, FSMStateID.Chasing); attack.AddTransition(Transition.Injured, FSMStateID.Injuring); attack.AddTransition(Transition.NoHealth, FSMStateID.Dead); InjuryState injury = new InjuryState(); injury.AddTransition(Transition.LostPlayer, FSMStateID.Idling); injury.AddTransition(Transition.SawPlayer, FSMStateID.Chasing); injury.AddTransition(Transition.NoHealth, FSMStateID.Dead); DeadState dead = new DeadState(); dead.AddTransition(Transition.NoHealth, FSMStateID.Dead); AddFSMState(idle); AddFSMState(chase); AddFSMState(attack); AddFSMState(injury); AddFSMState(dead); } protected override void FSMUpdate() { elapsedTime += Time.deltaTime; } protected override void FSMFixedUpdate() { CurrentState.Act(playerTransform, transform); CurrentState.Reason(playerTransform, transform); } /// <summary> /// 这个方法在每个状态类的Reason中被调用。 /// </summary> /// <param name="t"></param> public void SetTransition(Transition t) { PerformTransition(t); } /// <summary> /// AI角色与其他物体碰撞时,调用这个函数。 /// </summary> /// <param name="collider"></param> void OnTriggerEnter(Collider collider) { if (collider.gameObject.tag.Equals("Player")) { health -= 40; Blood.Instance.Change(health); if (health <= 0) { SetTransition(Transition.NoHealth); } else { SetTransition(Transition.Injured); } } } public void Attack(Animator anim) { if (elapsedTime >= attackRate) { anim.SetTrigger("Surround Attack"); elapsedTime = 0f; } } public void Injury(Animator anim) { anim.SetTrigger("Take Damage"); } }
IdleState.cs(没有做随机移动,用站立表示默认状态)
public class IdleState : FSMState { /// <summary> /// 初始化状态 /// </summary> public IdleState() { stateID = FSMStateID.Idling; rotSpeed = 6.0f; speed = 10.0f; } /// <summary> /// 播放动画 /// </summary> /// <param name="player"></param> /// <param name="monster"></param> public override void Act(Transform player, Transform monster) { Animator anim = monster.GetComponent<Animator>(); } /// <summary> /// 是否进行状态转移 /// </summary> /// <param name="player"></param> /// <param name="monster"></param> public override void Reason(Transform player, Transform monster) { //如果玩家与怪物距离小于等于追逐距离,那么转移状态为追逐 if (Vector3.Distance(player.position,monster.position) <= chaseDistance) { monster.GetComponent<AIController>().SetTransition(Transition.SawPlayer); } } }