在游戏开发中,一般角色都会有很多的状态,我们需要对这些状态进行处理以及状态之间的转换。
如下图是一个简单的角色拥有的的状态,箭头标识的是状态之间的转换关系,简单的方式是我们在一个Switch中进行判断,在每一个case分支中进行相应的状态处理和转换条件判断。使用这种方式的好处是简单方便,但是缺点也非常的明显,就是不利于扩展,修改起来很麻烦。基于这个我们就可以想到使用有限状态机(FSM)来进行状态的管理。
有限状态机定义
有限状态机(英语:finite-state machine,缩写:FSM)
定义:总的来说,有限状态机系统,是指在不同阶段会呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。这样的系统在某一时刻一定会处于其所有状态中的一个状态,此时它接收一部分允许的输入,产生一部分可能的响应,并且迁移到一部分可能的状态。
有限状态机的实现
- 状态基类:抽象出一个状态基类,让每个状态都继承该基类
/// <summary>
/// 状态抽象类
/// </summary>
public abstract class FSMState
{
// 所属的状态机
protected FSMSystem m_FSM = null;
protected FSMState(FSMSystem fsm)
{
m_FSM = fsm;
}
/// <summary>
/// 进入状态
/// </summary>
public virtual void OnEnter() { }
/// <summary>
/// 状态中进行的动作
/// </summary>
public abstract void Action();
/// <summary>
/// 检测状态转换
/// </summary>
public abstract void Check();
/// <summary>
/// 退出状态
/// </summary>
public virtual void OnExit() { }
}
- 状态的ID枚举,用来标识每个状态
/// <summary>
/// 状态ID
/// </summary>
public enum StateID
{
Move, // 移动
Rotate // 旋转
}
- FSM类:用来进行状态之间的管理和切换
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 有限状态机系统
/// </summary>
public class FSMSystem
{
/// <summary>
/// 状态机所属游戏物体
/// </summary>
public GameObject OwnerGo { get; private set; }
// 用字典存储每个状态ID对应的状态
private Dictionary<StateID, FSMState> m_StateMap = new Dictionary<StateID, FSMState>();
public FSMSystem(GameObject ownerGo)
{
OwnerGo = ownerGo;
}
/// <summary>
/// 当前状态ID
/// </summary>
public StateID CurrentStateID { get; private set; }
/// <summary>
/// 当前状态
/// </summary>
public FSMState CurrentState { get; private set; }
/// <summary>
/// 添加状态
/// </summary>
/// <param name="id">添加的状态ID</param>
/// <param name="state">对应的状态对象</param>
public void AddState(StateID id,FSMState state)
{
// 如果当前状态为空,就设置为默认状态
if (CurrentState == null)
{
CurrentStateID = id;
CurrentState = state;
}
if (m_StateMap.ContainsKey(id))
{
Debug.LogErrorFormat("状态ID:{0}已经存在,不能重复添加!", id);
return;
}
m_StateMap.Add(id, state);
}
/// <summary>
/// 移除状态
/// </summary>
/// <param name="id">要移除的状态ID</param>
public void RemoveState(StateID id)
{
if (!m_StateMap.ContainsKey(id))
{
Debug.LogWarningFormat("状态ID:{0}不存在,不需要移除",id);
return;
}
m_StateMap.Remove(id);
}
/// <summary>
/// 改变状态
/// </summary>
/// <param name="id">需要转换到的目标状态ID</param>
public void ChangeState(StateID id)
{
if (id == CurrentStateID) return;
if (!m_StateMap.ContainsKey(id))
{
Debug.LogErrorFormat("状态ID:{0}不存在!", id);
return;
}
if (CurrentState != null)
CurrentState.OnExit();
CurrentStateID = id;
CurrentState = m_StateMap[id];
CurrentState.OnEnter();
}
/// <summary>
/// 更新,在状态机持有者物体的Update中调用
/// </summary>
public void Update()
{
CurrentState.Action();
CurrentState.Check();
}
}
以上就是有限状态机的核心部分,下面我们进行测试,看一下效果
测试
我们假设角色有两个状态,分别是【移动】和【旋转】,我们让角色移动2秒钟后转换到旋转状态,在旋转之后转换到移动状态
- 实现移动状态类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 移动状态
/// </summary>
public class MoveState : FSMState
{
// 计时
private float m_Timer = 0;
public MoveState(FSMSystem fsm) : base(fsm)
{
}
public override void Action()
{
// 向前移动
m_FSM.OwnerGo.transform.Translate(m_FSM.OwnerGo.transform.forward * 3f * Time.deltaTime,Space.World);
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果计时达到2秒
if(m_Timer > 2f)
{
// 转变为旋转状态
m_FSM.ChangeState(StateID.Rotate);
}
}
public override void OnExit()
{
// 退出状态时将计时器置零
m_Timer = 0f;
}
}
- 实现旋转状态类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateState : FSMState
{
// 计时器
private float m_Timer = 0f;
public RotateState(FSMSystem fsm) : base(fsm)
{
}
public override void Action()
{
// 旋转
m_FSM.OwnerGo.transform.localEulerAngles = new Vector3(0, m_FSM.OwnerGo.transform.localEulerAngles.y + 3f, 0);
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋转达到1秒
if (m_Timer > 1f)
{
// 转换到移动状态
m_FSM.ChangeState(StateID.Move);
}
}
public override void OnExit()
{
// 置零计时器
m_Timer = 0f;
}
}
- 实现角色类,这个脚本是挂载到角色游戏物体上的
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character : MonoBehaviour
{
// 有限状态机
private FSMSystem m_FSM = null;
private void Start()
{
// 实例化有限状态机
m_FSM = new FSMSystem(this.gameObject);
// 添加状态到有限状态机中
m_FSM.AddState(StateID.Move, new MoveState(m_FSM));
m_FSM.AddState(StateID.Rotate, new RotateState(m_FSM));
}
private void Update()
{
// 调用有限状态机中的Update方法
if (m_FSM != null)
m_FSM.Update();
}
}