分层有限状态机
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状态等复杂的状态转换场景。