Blink

纸上得来终觉浅,绝知此事要躬行

Unity:有限状态机实现

在游戏开发中,一般角色都会有很多的状态,我们需要对这些状态进行处理以及状态之间的转换。
如下图是一个简单的角色拥有的的状态,箭头标识的是状态之间的转换关系,简单的方式是我们在一个Switch中进行判断,在每一个case分支中进行相应的状态处理和转换条件判断。使用这种方式的好处是简单方便,但是缺点也非常的明显,就是不利于扩展,修改起来很麻烦。基于这个我们就可以想到使用有限状态机(FSM)来进行状态的管理。

《Unity:有限状态机实现》

有限状态机定义

有限状态机(英语: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秒钟后转换到旋转状态,在旋转之后转换到移动状态

《Unity:有限状态机实现》

  • 实现移动状态类
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();
    }
}

将Character脚本挂载到角色游戏物体上,点击运行就可以看到效果
《Unity:有限状态机实现》

点赞

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注