使用场景:满足一定的条件就从某一个状态切换到指定状态
主要思想:一个脚本,两个普通类。脚本负责状态的切换和执行,一个类为状态,一个类为条件。
1、可以先做两个全局变量,用于给各类脚本/条件命名,方便查看
public enum FSMStateID
{
None,//无状态
Idle,//待机
Dead,//死亡
Pursuit,//追逐
Attacking,//攻击
Default,//默认
Patrolling//巡逻
}
public enum FSMTriggerID
{
NoHealth,//生命为0
SawTarget,//发现目标
ReachTarget,//目标进入攻击范围
LoseTarget,//丢失目标
CompletePatrol,//完成巡逻
KilledTarget,//击杀目标
WithoutAttackRange//目标离开/不在攻击范围
}
2、条件类,负责存放状态转换的条件,只需要提供一个输出为bool类型的函数
///<summary>
///条件类
///<summary>
public abstract class FSMTrigger
{
//ID用于命名该类
public FSMTriggerID TriggerID { get; set; }
/// <summary>
/// 必须做初始化,写Id
/// </summary>
public FSMTrigger()
{
Init();
}
public abstract void Init();
//必须实现的抽象类方法,传入脚本是为了方便数据的调用
public abstract bool HandleTrigger(FSMBase fsm);
}
举一个例子:血量没有的条件
public class NoHealthTrigger : FSMTrigger
{
//利用传入的脚本获取数据
public override bool HandleTrigger(FSMBase fsm)
{
return fsm.characterBasic.blood <= 0;
}
public override void Init()
{
TriggerID = FSMTriggerID.NoHealth;
}
}
3、状态类,需要对外提供进入状态、状态内的执行、退出状态、判断条件的方法。
有一个字典用于存放,条件和满足该条件会进入的状态(这个字典需要在脚本中配置),所以还需要对外提供一个增加字典词条的函数
再有一个列表用于存放所有条件类,用于状态内的判断。
public abstract class FSMState
{
public FSMStateID StateID;
//存放条件以及对应的状态
private Dictionary<FSMTriggerID, FSMStateID> map;
//用于存放相关条件
private List<FSMTrigger> Triggers;
public abstract void Init();
//初始化
public FSMState()
{
Init();
map = new Dictionary<FSMTriggerID, FSMStateID> ();
Triggers = new List<FSMTrigger> ();
}
//对外提供的增加字典词条的方法
public void AddMap (FSMTriggerID triggerID, FSMStateID stateID)
{
map.Add (triggerID, stateID);
//词条没增加一条就用反射将条件类放入列表
Type type = Type.GetType ("FSM."+triggerID.ToString()+"Trigger");
FSMTrigger trigger =Activator.CreateInstance(type) as FSMTrigger;
Triggers.Add(trigger);
}
//进入、离开、执行的方法
public virtual void EnterState(FSMBase fsm) { }
public virtual void ExitState(FSMBase fsm) { }
public virtual void ActionState(FSMBase fsm) { }
//用于判断是否满足条件
public void Reason(FSMBase fsm)
{
for (int i = 0; i < Triggers.Count; i++)
{
if (Triggers[i].HandleTrigger(fsm))
{
FSMStateID stateID = map[Triggers[i].TriggerID];
fsm.ChangeActiveState(stateID);
//切换状态
return;
}
}
}
}
举两个例子死亡和待机
//死亡状态
public class DeadState : FSMState
{
public override void Init()
{
StateID = FSMStateID.Dead;
}
public override void EnterState(FSMBase fsm)
{
base.EnterState(fsm);
fsm.enabled = false;
}
}
//待机状态
public class IdleState:FSMState
{
public override void Init()
{
StateID = FSMStateID.Idle;
}
public override void EnterState(FSMBase fsm)
{
base.EnterState(fsm);
//可以放一个播动画的
}
public override void ExitState(FSMBase fsm)
{
base.ExitState(fsm);
}
}
4、控制脚本:用于调用状态的方法和提供状态切换的方法。我这里将一些数据的导入也做在这个脚本中了也可以分开写。
public class FSMBase : MonoBehaviour
{
//存放所有状态需要手动创建
private List<FSMState> states;
[Tooltip("默认状态ID")]
public FSMStateID defaultStateID;
//用于存放当前状态
private FSMState currentState;
//保存默认的状态
private FSMState defaultState;
#region 查找数据
[HideInInspector]
public CharacterBasic characterBasic;
private void InitComponent()
{
characterBasic = GetComponent<CharacterBasic>();
}
#endregion
private void Start()
{
//找数据
InitComponent();
手动初始化
ConfigFSM();
//执行默认的状态
InitDefualtState();
}
private void ConfigFSM()
{
//将现有的状态类加入list,并把对应的条件和状态存入对应状态类中
states = new List<FSMState>();
IdleState idle = new IdleState();
idle.AddMap(FSMTriggerID.NoHealth, FSMStateID.Dead);
states.Add(idle);
DeadState dead = new DeadState();
states.Add(dead);
}
private void InitDefualtState()
{
执行默认状态
defaultState = states.Find(s=> s.StateID == defaultStateID);
currentState = defaultState;
currentState.EnterState(this);
}
public void Update()
{
currentState.Reason(this);//检测
currentState.ActionState(this);
}
//对外提供改变当前状态的方法
public void ChangeActiveState(FSMStateID stateID)
{
currentState.ExitState(this);
currentState = stateID== FSMStateID.Default ? defaultState : states.Find(s => s.StateID == stateID);
currentState.EnterState(this);
}
}