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游戏开发3 天前
Unity3D事件驱动架构设计指南
前端框架·unity3d·游戏开发
古力德6 天前
Unity中造轮子:定时器
c#·unity3d
Mapmost8 天前
【数据融合实战手册·进阶篇】模型融合总出错?先看看这些“对齐”了没!
unity3d
北桥苏10 天前
如何在 Unity3D 导入 Spine 动画
unity3d
Thomas游戏开发11 天前
Unity3D状态管理器实现指南
前端框架·unity3d·游戏开发
土豆宝16 天前
Unity Visual Scripting(可视化脚本) 自定义节点 踩坑教程
unity3d
Thomas游戏开发17 天前
Unity3D光照层级与动态切换指南
前端框架·unity3d·游戏开发
Thomas游戏开发1 个月前
Unity3D 崩溃分析工具的集成与优化
前端框架·unity3d·游戏开发
Thomas游戏开发1 个月前
Unity3D网格简化与LOD技术详解
前端框架·unity3d·游戏开发
Thomas_YXQ1 个月前
Unity3D 图形渲染(Graphics & Rendering)详解
开发语言·unity·图形渲染·unity3d·shader