首先在我们讲我们的主角之前,先来看一张图。
你的第一反应是什么?哇,好大一张蜘蛛网!
而事实上它连蜘蛛网都不是。
在制作游戏的过程中,动画是必不可少的东西,Unity的新版动画系统已经很好的帮我们解决了动画管理难的问题。
但是它虽然方便了我们,但同样不可避免的出现了某种意外,也就是上面所示的“蜘蛛网”的状态。
当然,虽然出现了这种情况,也并不是没有解决方案的,也就是我们接下来所说的FSM有限状态机。
FSM有限状态机(Finite-state machine)
它是专门用于解决这种多状态切换导致的问题的。
使用动画状态机中的 Any State(任意状态),可以使得蜘蛛网瞬间变得清晰:
看,多么清晰明了。
那么接下来,我们需要做的就非常简单了,写一个状态机的管理!
首先来分析,我们什么时候需要状态,或者说,我们的有限状态机能做什么?
接下来给大家简单的分析一下代码实现。
首先创建一个脚本StateBase,是所有状态的父类。
这个脚本中一共有3个方法:进入、持续、退出;和一个管理者一个调用者。
/// <summary>
/// 转换的条件
/// </summary>
public enum Transition
{
//空状态
NullTrans = 0,
//按下空格
ClickSp,
//血量清空
HpClear
}
/// <summary>
/// 状态ID
/// </summary>
public enum State
{
NullState = 0,
//idle
Idle,
//跳跃中
Jump,
//死亡
Death
}
public abstract class StateBase {
/// <summary>
/// 状态字典
/// </summary>
protected Dictionary<Transition, State> mStateDic = new Dictionary<Transition, State>(); //定义一个字典,键为转换条件,值为状态ID
/// <summary>
/// 状态控制器 他们要知道是谁在控制它
/// </summary>
public FSMStateCtrl StateCtrl;
/// <summary>
/// 当前类对应的状态
/// </summary>
public State CurrentState;
public Rigidbody rig;
/// <summary>
/// 初始化
/// </summary>
public StateBase(State state ,FSMStateCtrl StateCtrl,Rigidbody rig)
{
CurrentState = state;
this.StateCtrl = StateCtrl;
this.rig = rig;
}
/// <summary>
/// 添加状态
/// </summary>
public void AddState(Transition trans,State state) //添加一个状态进入字典中
{
//这里一系列防误操作判断
if (mStateDic.ContainsKey(trans))
{
Debug.LogError("FSM状态机错误:【"+trans+"】状态已添加,不可重复添加!");return;
}
mStateDic[trans] = state;
}
/// <summary>
/// 删除
/// </summary>
public void DeleteState(State state) //移除一个状态
{
foreach (Transition tran in mStateDic.Keys)
{
if (mStateDic[tran].Equals(state))
{
mStateDic.Remove(tran); return;
}
}
}
/// <summary>
/// 从他身上拿到要转换的状态
/// </summary>
public State GetState(Transition trans) //由于分解耦合性的思想,这里将转换条件的获取由条件自己而来,就是当前状态满足了这个条件后,它自身有一个对应的跳转状态。
{ //这样可以将每个状态都区分开。
if (mStateDic.ContainsKey(trans))
{
return mStateDic[trans];
}
return State.NullState;
}
//定义虚方法
//每帧调用 用于检测是否需要转换
public abstract void Act(Transform player);
//每帧调用 用于执行事件
public abstract void Continue(Transform player);
//进入时调用
public virtual void OnEnter() { }
//退出时调用
public virtual void OnExit() { }
}
然后在创建一个脚本StateManager,状态机的管理类。
它持有一个状态的集合,和一个当前状态。其中有几个方法,分别是往集合中添加状态、移除状态的方法,还有更换当前状态的方法。
//用于控制状态机
public class FSMStateManager : MonoBehaviour
{
/// <summary>
/// 状态的集合,所有状态在里面存着
List<StateBase> mStateList;
/// <summary>
/// 当前状态
/// </summary>
private StateBase CuttouenState;
private void Awake()
{
//初始化
mStateList = new List<StateBase>();
}
private void Update()
{
//如果当前状态不为空
if (CuttouenState != null)
{
CuttouenState.Act(transform); //执行每帧调用的方法
CuttouenState.Continue(transform);
}
}
/// <summary>
/// 添加一个状态
/// </summary>
/// <param name="stateb"></param>
public void AddState(StateBase stateb)
{
//这里不能重复添加,所以一系列防误触
if (mStateList.Contains(stateb))
{
Debug.LogError("FSM错误:【"+stateb+"】已存在,添加失败!");
}
//将第一个进入的状态设置为初始状态
if (mStateList.Count == 0)
{
CuttouenState = stateb;
}
mStateList.Add(stateb);
}
/// <summary>
/// 切换状态,通过条件,状态身上拿到它要转换到的状态ID
/// </summary>
public void CutState(Transition trans)
{
State s = CuttouenState.GetState(trans);
if (s == State.NullState)
{
Debug.Log("转到状态为空!"); return;
}
for (int i = 0; i < mStateList.Count; i++)
{
if (s == mStateList[i].CurrentState)
{
CuttouenState.OnExit();
CuttouenState = mStateList[i];
CuttouenState.OnEnter();
}
}
}
}
之后,当我们需要添加一个状态的时候,就可以继承StateBase状态基类,实现它身上的方法:
public class IdleState : StateBase
{
//实现父类构造方法
public IdleState(State CurrentState, FSMStateCtrl StateCtrl,Rigidbody rig) :base(CurrentState, StateCtrl, rig)
{
}
//重写判断条件(Update中每帧调用的)
public override void Act(Transform player)
{
if (Input.GetKeyDown(KeyCode.Space))
{
StateCtrl.CutState(Transition.ClickSp); //这里枚举是转换条件:当按下空格
//对应的事件是:进入跳跃状态
}
}
public override void Continue(Transform player) //每帧调用:执行事件
{
Debug.Log("我是Idle");
}
}
接下来只需要一个启动状态机的类,就可以完成啦:
/// <summary>
/// 用于启动状态机
/// </summary>
public class StateCtrl: MonoBehaviour {
FSMStateCtrl mFsmStateCtrl;
void Start () {
//初始化 得到他身上的FSM控制器
mFsmStateCtrl = GetComponent<FSMStateCtrl>();
//创建几个状态出来,并且给他们添加上可以转换到的状态和转换条件。
IdleState idle = new IdleState(State.Idle, mFsmStateCtrl,transform.GetComponent<Rigidbody>());
idle.AddState(Transition.DoubleClickSp, State.Death);
idle.AddState(Transition.HpClear, State.Death);
DeathState death = new DeathState(State.Death, mFsmStateCtrl, transform.GetComponent<Rigidbody>());
death.AddState(Transition.IsDown, State.Idle);
//把这几个状态添加进状态机中
mFsmStateCtrl.AddState(idle);
mFsmStateCtrl.AddState(death);
}
}
这样,我们只需要定义自己的状态,然后通过转换,就可以实现所需要的需求啦,是不是很方便。
下面是笔者简单的总结出来这套状态框架的使用方法:
1、给需要使用状态的物体添加上 StateManager(状态管理器)
2、创建出自己的StateCtrl(状态控制器)并挂在在游戏物体上 - 命名格式:物体名+StateCtrl;
3、在状态枚举类中(EnumBase)按分类添加对应的转换条件和状态ID,命名格式要加上物体名。
4、创建出实际状态类,继承StateBase,并且实现方法与构造方法(这里说一下,如果你的执行事件需要其他的某些条件,直接就在这个状态类中获取,它身上有进入方法,可以进行初始化赋值)
5、在StateCtrl中获取到StateManager(GetComponent方法获取)
6、在StateCtrl中创建状态对象(在start中用new)
7、使用状态的Add方法添加转换条件和对应状态
8、将这些状态用创建好的StateManager对象.Add方法添加入管理器中
9、至此完成步骤。
好了,今天的分享就到这里结束啦,感谢大家的观看。