有限状态机英文全称Finite State Machine,它的应用及其广泛,虽然主要是在Unity中的应用,但是核心内容无论你是用C#,java,Object—C都是一样的,这也是我这笔记的目的。
对对象状态间的行为和转化进行管理,方便扩展,维护。
表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
创建的数学模型状态机可分为四要素:
现态(State)条件(Transition)动作(Action)次态(State)
1.现态:是指当前所处的状态。
2.条件:又称为事件。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
3.动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必须的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
4.次态:条件满足后要迁往的新状态。‘次态’是相对于‘现态’而言的,‘次态’一旦被激活,就转变成新的‘现态’了。
要注意的两个问题:
1.避免把某个‘程序动作’当作是一种状态来处理,那么如何区分动作和状态?动作一旦执行完毕就结束了,而状态是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
2.状态划分时会漏掉一些状态,导致跳转逻辑不完整。所以维护一张状态表就非常必要了。从表中可以直观看出哪些状态直接存在
跳转路径,哪些状态直接不存在。如果不存在,就把相应的单元格置灰。每次写代码之前先把表格填写好,看着是否有‘漏太’,然后才是写代码。QA拿到这张表格之后,写测试用例也是手到擒来。
首先是两个枚举,一个是动作,或者说事件。一个是状态
public enum Transition
{
NullTransition = 0,
}
public enum StateID
{
NullStateID = 0,
}
状态父类
public abstract class FSMState
{
protected Dictionary map = new Dictionary();
protected StateID stateID;
public StateID ID { get { return stateID; } }
public void AddTransition(Transition trans, StateID id)
{
if (trans == Transition.NullTransition)
{
//trans不能为空
Debug.LogError("FSMState ERROR:NullTransition is not allowed for a real transition");
return;
}
if (id == StateID.NullStateID)
{
//id状态不能为空
Debug.LogError("FSMState ERROR:NullStateID is not allowed for a real ID");
return;
}
if (map.ContainsKey(trans))
{
//trans已经添加上了
Debug.LogError("FSMState ERROR:State " + stateID.ToString() + " already has transition+" + trans.ToString() + " Impossible to assign to another state");
return;
}
map.Add(trans, id);
}
public void DeleteTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
//不允许删除空值
Debug.LogError("FSMState ERROR:NullTransition is not allowed");
return;
}
if (map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
//删除转换条件的时候,转换条件trans不存在
Debug.LogError("FSMState ERROR:Transition " + trans.ToString() + "passed to+" + stateID.ToString() + " was not on the state's transition list");
}
public StateID GetOutputState(Transition trans)
{
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;
}
public virtual void DoBeforeEntering() { }//进入状态时执行的方法
public virtual void DoBeforeLeaving() { }//离开状态时执行的方法
public abstract void Reason(GameObject player, GameObject npc);//是否需要转换到别的状态
public abstract void Action(GameObject player, GameObject npc);//当前状态所要处理的逻辑
}
状态管理类
public class FSMSystem
{
private List states;
private StateID currentStateID;
public StateID CurrentStateID { get { return currentStateID; } }
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }
public FSMSystem()
{
states = new List();
}
public void AddState(FSMState s)
{
if (s == null)
{
Debug.LogError("FSM ERROR:Null reference is not allowed");//警告:要添加的状态为空
}
if (states.Count == 0)
{
states.Add(s);
currentState = s;
currentStateID = s.ID;
return;
}
foreach (FSMState state in states)
{
if (state.ID == s.ID)
{
//警告:要添加的转台ID已经添加
Debug.LogError("FSM ERROR:Impossible to add state " + s.ID.ToString() + "because state has already been added");
return;
}
}
states.Add(s);
}
public void DeleteState(StateID id)
{
if (id == StateID.NullStateID)
{
//要删除的状态ID为空
Debug.LogError("FSM ERROR:NullStateID is not allowed for a real state");
return;
}
foreach (FSMState state in states)
{
if (state.ID == id)
{
states.Remove(state);
return;
}
}
//要删除的StateID不存在集合中
Debug.LogError("FSM ERROR:Impossible to delete state" + id.ToString() + " It was not no the list of states");
}
public void PerformTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
//要执行的转换条件为空
Debug.LogError("FSM ERROR:NullTransition is not allowed for a real transition");
return;
}
StateID id = currentState.GetOutputState(trans);
if (id == StateID.NullStateID)
{
//在转换条件id情况下,没有相应的转换状态。
Debug.LogError("FSM ERROR:State" + currentStateID.ToString() + "+does not have a target state" + "for transition" + trans.ToString());
return;
}
currentStateID = id;
foreach (FSMState state in states)
{
if (state.ID == currentStateID)
{
currentState.DoBeforeLeaving();
currentState = state;
currentState.DoBeforeEntering();
break;
}
}
}
}
我们来分析一下战士的有限状态机。首先战士Soldier的状态有Idie(等待),Chase(追逐),Attack(攻击)三种,它的转化条件有看见敌人SeeEnemy(看见敌人),NoEnemy(没有敌人),CanAttack(可以攻击)三种。
代码:
战士的状态父类:
public enum SoldierTransition
{
NullTransition=0,
///
/// 看见敌人
///
SeeEnemy,
///
/// 没有看见敌人
///
NoEnmey,
///
/// 可以攻击
///
CanAttack
}
public enum SoldierStateID
{
NullState,
///
/// 停止
///
Idie,
///
/// 追逐
///
Chase,
///
/// 攻击
///
Attack
}
public abstract class ISoldierState
{
protected Dictionary mMap = new Dictionary();
protected SoldierStateID mStateID;
protected ICharacter mCharacter;
protected SoldierFSMSytem mFSM;
public ISoldierState(SoldierFSMSytem fsm,ICharacter character)
{
mFSM = fsm;
mCharacter = character;
}
//
public SoldierStateID stateID { get { return mStateID; } }
public void AddTransition(SoldierTransition trans,SoldierStateID id)
{
if (trans==SoldierTransition.NullTransition)
{
Debug.LogError("SoldierState Error:trans不能为空");
return;
}
if (id==SoldierStateID.NullState)
{
Debug.LogError("SoldierState Error:id状态ID不能为空");
return;
}
if (mMap.ContainsKey(trans))
{
Debug.LogError("SoldierState Error:"+trans+"已经添加上了");
return;
}
mMap.Add(trans,id);
}
public void DeleteTransition(SoldierTransition trans)
{
if(mMap.ContainsKey(trans)==false)
{
Debug.LogError("删除转换条件的时候,转换条件:["+trans+"]不存在");
return;
}
mMap.Remove(trans);
}
public SoldierStateID GetOutPutState(SoldierTransition trans)
{
if (mMap.ContainsKey(trans) == false)
{
return SoldierStateID.NullState;
}
else
{
return mMap[trans];
}
}
public virtual void DoBeforeEntering() { }//进入状态时执行的方法
public virtual void DoBeforeLeaving() { }//离开状态时执行的方法
public abstract void Reason(List targets);//是否需要转换到别的状态
public abstract void Act(List targets);//当前状态所要处理的逻辑
}
Idle
public class SoldierIdleState : ISoldierState
{
public SoldierIdleState(SoldierFSMSytem fsm, ICharacter character) : base(fsm, character)
{
mStateID = SoldierStateID.Idie;
}
public override void Act(List targets)
{
mCharacter.PlayAnimation("stand");
}
public override void Reason(List targets)
{
if (targets!=null&&targets.Count>0)
{
mFSM.PerformTransition(SoldierTransition.SeeEnemy);
}
}
}
Chase
public class SoldierChaseState : ISoldierState
{
public SoldierChaseState(SoldierFSMSytem fsm,ICharacter character) : base(fsm, character)
{
mStateID = SoldierStateID.Chase;
}
public override void Act(List targets)
{
if (targets!=null&&targets.Count>0)
{
mCharacter.MoveTo(targets[0].Position);
}
}
public override void Reason(List targets)
{
if (targets == null || targets.Count == 0)
{
mFSM.PerformTransition(SoldierTransition.NoEnmey);
return;
}
float distance = Vector3.Distance(targets[0].Position,mCharacter.Position);
if (distance<=mCharacter.atkRange)
{
mFSM.PerformTransition(SoldierTransition.CanAttack);
}
}
}
攻击状态
public class SoldierAttackState : ISoldierState
{
private float mAttackTime=1;
private float mAttackTimer=1;
public SoldierAttackState(SoldierFSMSytem fsm, ICharacter character) : base(fsm, character)
{
mStateID = SoldierStateID.Attack;
mAttackTimer = mAttackTime;
}
public override void Act(List targets)
{
if (targets==null||targets.Count==0)
{
return;
}
mAttackTimer += Time.deltaTime;
if (mAttackTimer>=mAttackTime)
{
mCharacter.Attack(targets[0]);
mAttackTimer = 0;
}
}
public override void Reason(List targets)
{
if(targets==null||targets.Count==0)
{
mFSM.PerformTransition(SoldierTransition.NoEnmey);
return;
}
float distance = Vector3.Distance(mCharacter.Position,targets[0].Position);
if (distance>mCharacter.atkRange)
{
mFSM.PerformTransition(SoldierTransition.SeeEnemy);
return;
}
}
}
注册和更新状态机
public class ISoldier:ICharacter
{
protected SoldierFSMSytem mFSMSystem;
public ISoldier():base()
{
MakeFSM();
}
public override void UpdateFSMAI(List targets)
{
mFSMSystem.currentState.Reason(targets);
mFSMSystem.currentState.Act(targets);
}
private void MakeFSM()
{
mFSMSystem = new SoldierFSMSytem();
SoldierIdleState idleState = new SoldierIdleState(mFSMSystem,this);
idleState.AddTransition(SoldierTransition.SeeEnemy,SoldierStateID.Chase);
//
SoldierChaseState chaseState = new SoldierChaseState(mFSMSystem,this);
chaseState.AddTransition(SoldierTransition.NoEnmey,SoldierStateID.Idie);
chaseState.AddTransition(SoldierTransition.CanAttack,SoldierStateID.Attack);
//
SoldierAttackState attackState = new SoldierAttackState(mFSMSystem,this);
attackState.AddTransition(SoldierTransition.NoEnmey,SoldierStateID.Idie);
attackState.AddTransition(SoldierTransition.SeeEnemy,SoldierStateID.Chase);
//mFSMSystem.AddState(idleState);
//mFSMSystem.AddState(chaseState);
//mFSMSystem.AddState(attackState);
mFSMSystem.AddState(idleState,chaseState,attackState);
}
https://www.jianshu.com/p/5eb45c64f3e3