Unity有限状态机——利用状态模式编写灵活和模块化的代码

什么是状态和状态机

想象一下你正在编写一个玩家控制脚本:人物在默认状态下是站立在地面上的,当你按下方向键时,人物会移动,按下空格键时,人物会先跳起进入半空中,之后受重力影响又回到地面。

可以用类似流程图的方式表示上述过程,图中的方框表示人物拥有的各种状态,箭头表示状态之间的切换以及对应的条件。在任何时候,人物只会处于一种状态中,当满足预定的条件时会触发状态的切换。

在游戏开发中,可以利用有限状态机来管理游戏中的物体或角色的内部状态,Unity中的Animator其实就是有限状态机的体现。

经典的FSM通常使用switch语句实现,而状态设计模式定义了一个状态接口和一个实现该接口的状态基类,更加面向对象。

经典的状态机实现

要实现一个基本的有限状态机,可以使用一种简单的方式,就是利用枚举和switch语句。

首先需要定义一个枚举类,它包含角色的各个状态。

CSharp 复制代码
public enum PlayerControllerState {  
    Idle, Move, Jump  
}

然后在MonoBehaviourUpdate函数中使用switch语句,执行不同状态下对应的逻辑。

CSharp 复制代码
public class PlayerController : MonoBehaviour
{
    private PlayerControllerState currentState;
    
    private void Update()
    {
        CheckInput();
       
        switch (currentState)
        {
            case PlayerControllerState.Idle:
                Idle();
                break;
            case PlayerControllerState.Move:
                Walk();
                break;
            case PlayerControllerState.Jump:
                Jump();
                break;
        }
    }
    
    public void CheckInput() { ... }
    
    public void Idle() { ... }
    
    public void Walk() { ... }
    
    public void Jump() { ... }
}

虽然这种方式的实现可以正常工作,但随着角色状态越来越多,PlayerController这个脚本很快就会变得混乱不堪,代码难以理解和维护。所以对于比较复杂的项目来说,使用switch语句并不是一种最佳做法。

这时候状态模式就派上用场了,它可以让你的代码更加模块化,易扩展,使添加新状态和管理状态切换更加简单。

使用状态模式优化

状态模式主要解决两个问题:

  1. 一个对象应该在它的内部状态改变时,改变它的行为。
  2. 特定状态的行为应该被独立地定义,添加新的状态不应该影响现有状态的行为。

在前面的实现中,PlayerController可以切换自己的状态来改变行为,但还不太满足第二点需求。

为了尽可能减小添加新状态对已有状态的影响,可以把状态封装成一个个对象。

IState接口

首先需要创建一个接口IState,每一个具体的状态都要实现这个接口。接口中包含三个方法:EnterUpdateExit,分别对应进入状态时、处于状态中、退出状态时需要执行的方法。

CSharp 复制代码
public interface IState  
{  
    // 进入状态时执行的逻辑  
    public void Enter();  
    
    // 处于状态时每帧执行的逻辑,包括处理状态的切换  
    public void Update();  
    
    // 退出状态时执行的逻辑  
    public void Exit();  
}

StateMachine类

StateMachine类为了管理各个状态以及处理状态之间的切换,会持有各个状态的引用。另外,每个状态都需要用到PlayerController脚本,所以要将PlayerController对象传入构造函数。

CSharp 复制代码
[Serializable]
public class StateMachine
{
    public IState CurrentState { get; private set; }
    
    // 状态对象的引用
    public WalkState walkState;
    public JumpState jumpState;
    public IdleState idleState;
    
    // 构造函数
    public StateMachine(PlayerController player)
    {
        // 实例化各个状态
        this.walkState = new WalkState(player);
        this.jumpState = new JumpState(player);
        this.idleState = new IdleState(player);
    }
    
    // 初始化状态机,设置初始状态
    public void Initialize(IState state)
    {
        CurrentState = state;
        state.Enter();
    }
    
    // 切换状态
    public void TransitionTo(IState nextState)
    {
        CurrentState.Exit();
        CurrentState = nextState;
        nextState.Enter();
    }
    
    // 状态更新
    public void Update()
    {
        if (CurrentState != null)
        {
            CurrentState.Update();
        }
    }
}

状态类

IdleState为例,在状态更新函数中,检查是否满足切换到移动状态或跳跃状态的条件,通过PlayerController引用获取到状态机进行状态切换。

CSharp 复制代码
public class IdleState : IState {
    private PlayerController player;
    
    public IdleState(PlayerController player)
    {
        this.player = player;
    }
    
    public void Enter()
    {
        //Debug.Log("Entering Idle State");
    }
    
    public void Update()
    {
        if (!player.IsGrounded)
        {
            player.PlayerStateMachine.
                TransitionTo(player.PlayerStateMachine.jumpState);
        }
       
        if (Mathf.Abs(player.CharController.velocity.x) > 0.1f 
            || Mathf.Abs(player.CharController.velocity.z) > 0.1f)
        {
            player.PlayerStateMachine
                .TransitionTo(player.PlayerStateMachine.walkState);
        }
    }
    
    public void Exit()
    {
        //Debug.Log("Exiting Idle State");
    }
}

参考文章:How to Develop a Modular & Flexible Codebase With the State Programming Pattern | Unity

相关推荐
Thomas_YXQ12 天前
Unity3D项目为什么要使用FairyGUI
开发语言·unity·游戏引擎·unity3d·游戏开发
nicepainkiller12 天前
Flutter 内嵌 unity3d for android
flutter·unity3d
Thomas_YXQ1 个月前
Unity3D ngui和ugui区别与优缺点详解
服务器·游戏·unity·unity3d·游戏开发
Thomas_YXQ1 个月前
Unity3D Lua如何支持面向对象详解
开发语言·游戏·junit·性能优化·lua·unity3d
Thomas游戏开发1 个月前
Unity3D 逻辑服的Entity, ComponentData与System划分详解
前端框架·unity3d·游戏开发
大眼睛姑娘1 个月前
unity运行状态下移动、旋转、缩放控制模型
unity3d
lin zaixi()1 个月前
手把手教你写Unity3D飞机大战(2)天空盒布置
unity3d
Thomas_YXQ2 个月前
Unity3D中管理Shader效果详解
开发语言·游戏·unity·unity3d·游戏开发
羊羊20352 个月前
线性代数:Matrix2x2和Matrix3x3
线性代数·数学建模·unity3d
天人合一peng2 个月前
Unity hub登录时一直无法进入license
unity3d