yorickyao
yorickyao
发布于 2025-10-15 / 10 阅读
0
0

🧩基于Unity的简单框架工具类(5)

分层有限状态机

1、概述

分层有限状态机(Hierarchical Finite State Machine)是一种在游戏开发中广泛使用的设计模式,它通过状态嵌套和层级管理来简化复杂行为逻辑的实现。本框架提供了轻量级、可扩展的状态机实现。

2、设计架构

状态机核心接口

IState 状态接口

public interface IState
{
    void Enter();           // 状态进入时调用
    void Exit();            // 状态退出时调用  
    void Update();          // 每帧更新时调用
    void OnMessageReceived(string message, object data); // 消息处理
}

ITransition<TKey> 状态过渡接口

public interface ITransition<TKey>
{
    bool CanTransition();   // 判断是否满足转换条件
    TKey TargetStateKey { get; } // 目标状态标识
}

IStateMachine<TKey> 状态机接口

public interface IStateMachine<TKey> : IState
{
    void ChangeState(TKey key, bool allowRepeat = false); // 状态切换
    IState CurrentState { get; }     // 当前状态实例
    TKey CurrentStateKey { get; }    // 当前状态标识
}

基础实现类

BaseState 状态基类

/// <summary>
/// 状态抽象基类,实现 IState 接口,作为叶子状态使用。
/// 可作为所有具体叶子状态的父类,便于扩展与复用。
/// </summary>
public abstract class BaseState : IState
{
    // 状态进入时调用。用于执行初始化操作及消息注册。
    public virtual void Enter() { }

    // 状态退出时调用。用于执行资源清理及消息注销。
    public virtual void Exit() { }

    // 状态每帧更新时调用。用于处理持续性业务逻辑。
    public virtual void Update() { }

    // 消息总线事件回调方法。状态可根据消息类型及携带数据进行相应处理。
    public virtual void OnMessageReceived(string message, object data) { }
}

Transition<TKey> 过渡条件基类

/// <summary>
/// 状态过渡基类,实现 ITransition 接口。
/// </summary>
public abstract class Transition<TKey> : ITransition<TKey>
{
    // 目标状态标识。
    public TKey TargetStateKey { get; protected set; }

    // 初始化目标状态标识。
    protected Transition(TKey targetStateKey)
    {
        TargetStateKey = targetStateKey;
    }

    // 判断是否满足状态转换条件。
    public abstract bool CanTransition();
}

StateMachine<TKey> 状态机实现

using System.Collections.Generic;

/// <summary>
/// 状态机抽象基类,支持状态添加、过渡条件添加、全局过渡及自动切换。
/// </summary>
public abstract class StateMachine<TKey> : IStateMachine<TKey>
{
    private IState currentState;
    private TKey currentStateKey;

    // 已添加的状态集合,按标识存储。
    private readonly Dictionary<TKey, IState> stateMap = new();

    // 每个状态对应的过渡条件集合。
    private readonly Dictionary<TKey, List<ITransition<TKey>>> transitions = new();

    // 全局过渡条件集合,任意状态均可触发。
    private readonly List<ITransition<TKey>> globalTransitions = new();

    // 当前激活的状态实例。
    public IState CurrentState => currentState;

    // 当前激活状态的标识。
    public TKey CurrentStateKey => currentStateKey;

    // 添加状态。
    public void AddState(TKey key, IState state)
    {
        stateMap[key] = state;
    }

    // 添加状态过渡条件。
    public void AddTransition(TKey from, ITransition<TKey> transition)
    {
        if (!transitions.ContainsKey(from))
            transitions[from] = new List<ITransition<TKey>>();
        transitions[from].Add(transition);
    }

    // 添加全局过渡条件,任意状态均可触发。
    public void AddGlobalTransition(ITransition<TKey> transition)
    {
        globalTransitions.Add(transition);
    }    

    // 获取指定标识的状态实例。
    public IState GetState(TKey key)
    {
        stateMap.TryGetValue(key, out var state);
        return state;
    }

    // 切换至指定状态。
    public void ChangeState(TKey key, bool allowRepeat = false)
    {
        if (!allowRepeat && EqualityComparer<TKey>.Default.Equals(currentStateKey, key))
            return;

        if (stateMap.TryGetValue(key, out var newState))
        {
            currentState?.Exit();
            currentState = newState;
            currentStateKey = key;
            currentState?.Enter();
        }
    }

    // 设置初始状态。
    public void SetInitialState(TKey key)
    {
        foreach (var state in stateMap.Values)
        {
            state.Exit();
        }
        ChangeState(key, true);
    }

    // 执行当前状态的进入逻辑。
    public void Enter() => currentState?.Enter();

    // 执行当前状态的退出逻辑。
    public void Exit() => currentState?.Exit();

    // 每帧检测全局和局部过渡条件并自动切换状态。
    public void Update()
    {
        // 检查全局过渡条件
        foreach (var t in globalTransitions)
        {
            if (t.CanTransition())
            {
                ChangeState(t.TargetStateKey);
                return;
            }
        }
        // 检查当前状态的局部过渡条件
        if (transitions.TryGetValue(currentStateKey, out var list))
        {
            foreach (var t in list)
            {
                if (t.CanTransition())
                {
                    ChangeState(t.TargetStateKey);
                    return;
                }
            }
        }
        currentState?.Update();
    }

    // 分发消息至当前状态实例。
    public void OnMessageReceived(string message, object data)
    {
        currentState?.OnMessageReceived(message, data);
    }
}

3、使用示例

定义状态枚举

public enum GameState
{
    Menu,
    Playing,
    Paused,
    GameOver
}

public enum PlayerState
{
    Idle,
    Walking,
    Running,
    Jumping,
    Attacking
}

实现具体状态

public class MenuState : BaseState
{
    public override void Enter()
    {
        Debug.Log("进入菜单状态");
        // 显示菜单UI
        UIManager.ShowMenu();
    }
    
    public override void Exit()
    {
        Debug.Log("退出菜单状态");
        // 隐藏菜单UI
        UIManager.HideMenu();
    }
}

public class PlayingState : BaseState
{
    private StateMachine<PlayerState> playerStateMachine;
    
    public override void Enter()
    {
        Debug.Log("开始游戏");
        InitializePlayerStateMachine();
    }
    
    public override void Update()
    {
        playerStateMachine?.Update();
    }
    
    public override void Exit()
    {
        playerStateMachine?.Exit();
    }
    
    private void InitializePlayerStateMachine()
    {
        playerStateMachine = new StateMachine<PlayerState>();
        
        // 添加玩家状态
        playerStateMachine.AddState(PlayerState.Idle, new IdleState());
        playerStateMachine.AddState(PlayerState.Walking, new WalkingState());
        playerStateMachine.AddState(PlayerState.Jumping, new JumpingState());
        
        // 添加状态过渡
        playerStateMachine.AddTransition(PlayerState.Idle, new StartWalkingTransition());
        playerStateMachine.AddTransition(PlayerState.Walking, new StopWalkingTransition());
        playerStateMachine.AddTransition(PlayerState.Walking, new JumpTransition());
        
        playerStateMachine.SetInitialState(PlayerState.Idle);
    }
}

实现过渡条件

public class StartGameTransition : Transition<GameState>
{
    public StartGameTransition() : base(GameState.Playing) { }
    
    public override bool CanTransition()
    {
        // 检测开始游戏条件(如点击开始按钮)
        return Input.GetKeyDown(KeyCode.Space) || 
               UIManager.IsStartButtonClicked();
    }
}

public class JumpTransition : Transition<PlayerState>
{
    public JumpTransition() : base(PlayerState.Jumping) { }
    
    public override bool CanTransition()
    {
        return Input.GetKeyDown(KeyCode.Space) && 
               PlayerController.IsGrounded;
    }
}

public class TimeOutTransition : Transition<GameState>
{
    private float timer = 60f;
    
    public TimeOutTransition() : base(GameState.GameOver) { }
    
    public override bool CanTransition()
    {
        timer -= Time.deltaTime;
        return timer <= 0f;
    }
}

完整使用示例

public class GameController : MonoBehaviour
{
    private StateMachine<GameState> gameStateMachine;
    
    private void Start()
    {
        InitializeGameStateMachine();
    }
    
    private void InitializeGameStateMachine()
    {
        gameStateMachine = new StateMachine<GameState>();
        
        // 注册状态
        gameStateMachine.AddState(GameState.Menu, new MenuState());
        gameStateMachine.AddState(GameState.Playing, new PlayingState());
        gameStateMachine.AddState(GameState.Paused, new PausedState());
        gameStateMachine.AddState(GameState.GameOver, new GameOverState());
        
        // 注册过渡条件
        gameStateMachine.AddTransition(GameState.Menu, new StartGameTransition());
        gameStateMachine.AddTransition(GameState.Playing, new PauseTransition());
        gameStateMachine.AddTransition(GameState.Playing, new TimeOutTransition());
        gameStateMachine.AddTransition(GameState.Paused, new ResumeTransition());
        
        // 全局过渡条件(任意状态都可触发)
        gameStateMachine.AddGlobalTransition(new ExitGameTransition());
        
        // 设置初始状态
        gameStateMachine.SetInitialState(GameState.Menu);
    }
    
    private void Update()
    {
        gameStateMachine.Update();
    }
    
    // 外部触发状态切换
    public void ForceGameOver()
    {
        gameStateMachine.ChangeState(GameState.GameOver);
    }
    
    // 接收消息并传递给状态机
    public void OnMessageReceived(string message, object data)
    {
        gameStateMachine.OnMessageReceived(message, data);
    }
}

复杂状态示例(包含子状态机)

public class BossBattleState : BaseState
{
    private StateMachine<BossPhase> phaseMachine;
    private StateMachine<BossAttackPattern> attackMachine;
    
    public override void Enter()
    {
        InitializePhaseMachine();
        InitializeAttackMachine();
    }
    
    public override void Update()
    {
        phaseMachine.Update();
        attackMachine.Update();
    }
    
    public override void Exit()
    {
        phaseMachine.Exit();
        attackMachine.Exit();
    }
    
    private void InitializePhaseMachine()
    {
        phaseMachine = new StateMachine<BossPhase>();
        
        // 添加BOSS阶段状态
        phaseMachine.AddState(BossPhase.Phase1, new BossPhase1State());
        phaseMachine.AddState(BossPhase.Phase2, new BossPhase2State());
        phaseMachine.AddState(BossPhase.FinalPhase, new BossFinalPhaseState());
        
        // 阶段过渡条件
        phaseMachine.AddTransition(BossPhase.Phase1, new Phase1ToPhase2Transition());
        phaseMachine.AddTransition(BossPhase.Phase2, new Phase2ToFinalTransition());
        
        phaseMachine.SetInitialState(BossPhase.Phase1);
    }
    
    private void InitializeAttackMachine()
    {
        attackMachine = new StateMachine<BossAttackPattern>();
        
        // 添加攻击模式状态
        attackMachine.AddState(BossAttackPattern.Idle, new BossIdleState());
        attackMachine.AddState(BossAttackPattern.Melee, new BossMeleeAttackState());
        attackMachine.AddState(BossAttackPattern.Ranged, new BossRangedAttackState());
        attackMachine.AddState(BossAttackPattern.Special, new BossSpecialAttackState());
        
        // 攻击模式过渡条件
        attackMachine.AddTransition(BossAttackPattern.Idle, new StartMeleeAttackTransition());
        attackMachine.AddTransition(BossAttackPattern.Melee, new ToRangedAttackTransition());
        
        attackMachine.SetInitialState(BossAttackPattern.Idle);
    }
}

4、特性说明

分层状态管理

通过状态嵌套实现复杂的行为逻辑:

// 主状态机管理游戏流程
StateMachine<GameState> mainStateMachine;

// 子状态机专门管理特定模块
StateMachine<PlayerState> playerStateMachine;
StateMachine<UIState> uiStateMachine;
StateMachine<AudioState> audioStateMachine;

全局过渡条件

任意状态下均可触发的过渡条件:

// 全局退出游戏过渡
public class GlobalExitTransition : Transition<GameState>
{
    public GlobalExitTransition() : base(GameState.Menu) { }
    
    public override bool CanTransition()
    {
        return Input.GetKeyDown(KeyCode.Escape);
    }
}

// 添加到状态机
gameStateMachine.AddGlobalTransition(new GlobalExitTransition());

消息传递机制

通过消息系统实现状态间通信:

public class BattleState : BaseState
{
    public override void OnMessageReceived(string message, object data)
    {
        switch (message)
        {
            case "PlayerDamaged":
                HandleDamage((int)data);
                break;
            case "EnemyDefeated":
                HandleEnemyDefeat();
                break;
            case "LevelCompleted":
                RequestStateChange(GameState.LevelComplete);
                break;
        }
    }
}

条件重复进入控制

// 允许重复进入相同状态(如重新开始游戏)
gameStateMachine.ChangeState(GameState.Playing, true);

// 默认不允许重复进入(避免不必要的状态切换)
gameStateMachine.ChangeState(GameState.Paused);

5、最佳实践

状态设计原则

  • 单一职责​:每个状态只负责特定的行为逻辑

  • 明确边界​:状态间的转换条件要清晰明确

  • 可复用性​:设计通用的基础状态,便于扩展

性能优化建议

  • 避免在Update方法中执行复杂计算

  • 使用对象池管理需要频繁创建的状态实例

  • 合理设置状态切换频率,避免过度切换

调试和维护

  • 为关键状态转换添加日志记录

  • 使用状态可视化工具监控状态机运行

  • 编写单元测试验证状态转换逻辑

这种分层有限状态机框架为Unity游戏开发提供了灵活、可维护的状态管理解决方案,特别适合管理游戏流程、角色行为、UI状态等复杂的状态转换场景。


评论