第5章 状态管理
状态管理是游戏开发中的核心问题。角色需要在空闲、奔跑、跳跃、攻击等状态间切换;AI需要在巡逻、追击、攻击等状态间转换;UI需要在不同界面状态间导航。本章将介绍三种状态管理模式:有限状态机(FSM)、分层状态机(Hierarchical FSM)和下推自动机(Pushdown Automata)。
1. 设计原理与思路
1.1 状态机的数学模型
状态机的理论基础来自计算机科学中的有限自动机理论。一个有限状态机可以形式化地定义为五元组:
M = (S, Σ, δ, s₀, F)
其中:
- S:有限状态集合 {Idle, Run, Jump, Attack, ...}
- Σ:输入字母表(触发条件){InputMove, InputJump, InputAttack, ...}
- δ:状态转移函数 S × Σ → S
- s₀:初始状态
- F:接受状态集合(在游戏开发中较少使用)
状态机示意图:
┌─────────────────────────────────────────────────────────────────┐
│ 玩家状态机示例 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ InputJump ┌──────────┐ │
│ │ │──────────────────────────▶│ │ │
│ │ Idle │ │ Jump │ │
│ │ │◀──────────────────────────│ │ │
│ └────┬─────┘ OnGround └──────────┘ │
│ │ │
│ InputMove│ OnGround │
│ │ ┌──────────┐ ┌──────────┐ │
│ └──────────▶│ │─────────▶│ │ │
│ │ Run │ │ Attack │ │
│ ◀───────────│ │◀─────────│ │ │
│ NoInput └──────────┘ └──────────┘ │
│ InputAttack │
│ │
└─────────────────────────────────────────────────────────────────┘
1.2 为什么要使用状态机
分离状态逻辑:每个状态封装自己的行为,避免复杂的if-else嵌套
// 不使用状态机:混乱的条件判断
public override void _Process(double delta)
{
if (_isStunned)
{
// 眩晕逻辑
}
else if (_isAttacking)
{
// 攻击逻辑
}
else if (!_isGrounded)
{
// 空中逻辑
}
else if (_horizontalInput != 0)
{
// 移动逻辑
}
else
{
// 站立逻辑
}
}
// 使用状态机:清晰的状态切换
public override void _Process(double delta)
{
_stateMachine.Update((float)delta);
}
明确的转换规则:状态之间的转换条件清晰定义 易于扩展:添加新状态不会影响现有状态 可调试性:可以追踪当前状态和状态历史
1.3 FSM vs 分层状态机 vs 下推自动机的选择
有限状态机(FSM):
- 适合状态数量较少(<10个)的简单场景
- 状态转换关系清晰的场景
- 玩家角色基础移动、简单AI
分层状态机(HFSM):
- 解决状态爆炸问题(状态数量>20时)
- 状态有明确的父子关系
- 复杂角色AI、RPG角色的多种行为模式
分层状态机示例:
┌──────────────┐
│ Grounded │ ← 父状态(共享地面行为)
└──────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Idle │ │ Run │ │ Crouch │ ← 子状态
└───────────┘ └───────────┘ └───────────┘
┌──────────────┐
│ InAir │ ← 父状态(共享空中行为)
└──────┬───────┘
│
┌──────▼──────┐
│ Jump │ ← 子状态
└─────────────┘
下推自动机(PDA):
- 需要状态历史和返回功能的场景
- UI导航、菜单系统
- 游戏暂停机制
- 支持任意层级的状态嵌套
| 特性 | FSM | HFSM | PDA |
|---|---|---|---|
| 状态数量 | 少(<10) | 多(分层管理) | 中等 |
| 转换复杂度 | 简单 | 中等 | 复杂 |
| 状态历史 | 无 | 有(父状态) | 有(完整栈) |
| 使用场景 | 简单角色 | 复杂AI | UI导航 |
| 实现复杂度 | 低 | 中等 | 高 |
2. 使用场景分析
2.1 适用场景
AI行为:敌人AI需要在巡逻、追击、攻击、逃跑等状态间切换
// 敌人AI状态转换示例
_stateMachine.AddTransition("PatrolState", "ChaseState",
ctx => ctx.CanSeePlayer());
_stateMachine.AddTransition("ChaseState", "AttackState",
ctx => ctx.IsPlayerInAttackRange());
_stateMachine.AddTransition("AttackState", "FleeState",
ctx => ctx.HealthPercent < 0.2f);
玩家状态:角色在移动、攻击、受伤、死亡等状态间转换 UI流程:菜单导航、对话框系统、商店界面
2.2 不适用场景
简单开关逻辑:只有一个布尔值的简单状态不需要状态机
// 不需要状态机
private bool _isVisible;
public void ToggleVisibility()
{
_isVisible = !_isVisible;
Visible = _isVisible;
}
// 对比:需要状态机的情况
private StateMachine _uiStateMachine; // 处理复杂的UI状态
连续数值状态:血量、能量条等连续变化的数值 纯物理模拟:由物理引擎完全控制的运动
2.3 典型案例
敌人AI:巡逻时发现玩家追击,进入攻击范围后攻击,血量低时逃跑 玩家战斗状态:普通攻击可以连招,技能释放期间不能移动,受伤时进入硬直
敌人AI状态转换图:
┌──────────┐ 发现玩家 ┌──────────┐
│ Patrol │─────────────────▶│ Chase │
│ (巡逻) │◀─────────────────│ (追击) │
└──────────┘ 丢失玩家 └────┬─────┘
│ 进入范围
▼
┌──────────┐
│ Attack │
│ (攻击) │
└────┬─────┘
│ 血量<20%
▼
┌──────────┐
│ Flee │
│ (逃跑) │
└──────────┘
3. 实现细节讲解
3.1 状态切换的执行顺序
状态切换时,执行顺序至关重要:
public void ChangeState(string stateId)
{
// 1. 保存当前状态ID
string previousStateId = _currentState?.StateName ?? "None";
// 2. 退出当前状态(执行清理逻辑)
_currentState?.Exit();
// 3. 切换状态引用
_currentState = _states[stateId];
// 4. 进入新状态(执行初始化逻辑)
_currentState.Enter();
// 5. 触发状态改变事件
OnStateChanged?.Invoke(previousStateId, stateId);
}
为什么这样设计?
- 确保当前状态的清理逻辑(如停止动画、重置变量)先执行
- 避免两个状态同时活跃造成的冲突
- 事件在状态切换完成后触发,订阅者可以获取正确的当前状态
3.2 如何处理状态间的共享数据
共享数据存储在上下文中:
public class PlayerStateContext
{
// 只读引用(状态不能修改)
public PlayerController Player { get; }
public AnimationPlayer Animator { get; }
// 可修改状态数据
public float HorizontalInput { get; set; }
public bool IsGrounded { get; set; }
public float StateTime { get; set; }
}
// 状态可以读取和修改上下文
public class RunState : StateBase<PlayerStateContext>
{
public override void Update(float deltaTime)
{
// 读取输入
float input = Context.HorizontalInput;
// 修改上下文中的计时器
Context.StateTime += deltaTime;
}
}
3.3 分层状态机的父状态与子状态关系
分层状态机的核心概念是状态继承:
public override void Enter()
{
// 1. 先调用父状态的Enter(如果存在)
Parent?.OnChildEnter(this);
// 2. 执行自身Enter
base.Enter();
// 3. 初始化子状态机(如果存在)
SubStateMachine?.Initialize();
}
public override void Update(float deltaTime)
{
// 1. 先执行自身Update
base.Update(deltaTime);
// 2. 再执行子状态机Update
SubStateMachine?.Update(deltaTime);
}
父状态负责:
- 处理所有子状态共享的行为(如地面检测)
- 决定是否允许子状态转换
- 在子状态进入/退出时执行通用逻辑
分层状态机的执行流程:
Frame N:
┌─────────────────────────────────────┐
│ Grounded.Update() │ ← 父状态先执行
│ - 检查是否还在地面 │
│ - 应用重力 │
└────────┬────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Run.Update() │ ← 子状态后执行
│ - 处理输入 │
│ - 更新动画 │
└─────────────────────────────────────┘
注意:子状态可以覆盖父状态的行为
4. 性能与权衡
4.1 状态查找的时间复杂度
状态机操作复杂度分析:
1. 状态注册:O(1)
_states[stateId] = state;
2. 状态查找:O(1)
_states.TryGetValue(stateId, out var state)
3. 状态切换:O(1) + Enter/Exit执行时间
_currentState?.Exit();
_currentState = _states[stateId];
_currentState.Enter();
4. 转换条件检查:O(n),n为当前状态的转换数
foreach (var transition in _transitions[currentStateId])
{
if (transition.Condition(context))
ChangeState(transition.ToStateId);
}
4.2 状态对象的内存开销
状态对象的生命周期:
- 单例模式:所有状态对象预先创建,长期驻留内存
- 临时模式:状态切换时创建新实例,立即销毁旧实例
// 单例模式(推荐):每个状态只创建一个实例
private void SetupStates()
{
_stateMachine.RegisterState(new IdleState()); // 创建一次
_stateMachine.RegisterState(new RunState());
_stateMachine.RegisterState(new JumpState());
}
// 内存占用估算
// 假设每个状态对象约100字节
// 10个状态 = 1KB内存(可以忽略)
4.3 与行为树的对比
| 特性 | 状态机 | 行为树 |
|---|---|---|
| 可视化 | 一般 | 优秀(节点图) |
| AI复杂度 | 中等 | 高 |
| 并行行为 | 困难 | 容易(Parallel节点) |
| 中断处理 | 需要AnyState | 自然支持(优先级) |
| 学习曲线 | 平缓 | 陡峭 |
| 运行时调试 | 状态名 | 整棵树 |
| 性能 | 高(O(1)切换) | 中等(每帧遍历) |
何时选择行为树:
- AI需要复杂的决策逻辑
- 需要可视化编辑器
- 行为需要频繁中断和恢复
何时选择状态机:
- 状态数量有限且转换清晰
- 需要高性能
- 团队成员熟悉状态机概念
5. 实战指南
5.1 设计状态图的步骤
步骤1:识别所有状态
// 列出玩家所有可能的状态
public enum PlayerState
{
Idle, // 站立
Run, // 奔跑
Jump, // 跳跃中
Fall, // 下落中
Attack, // 攻击中
Hurt, // 受伤
Die, // 死亡
Block // 格挡
}
步骤2:绘制状态转换图
使用纸笔或工具(如draw.io)绘制状态转换图:
- 用圆角矩形表示状态
- 用箭头表示转换
- 在箭头上标注触发条件
示例:
┌─────┐ 按下跳跃键 ┌─────┐
│Idle │ ─────────────▶ │Jump │
└─────┘ └─────┘
▲ │
│ │着地
└──────────────────────┘
步骤3:确定转换条件
// 为每条转换线编写条件
_stateMachine.AddTransition("Idle", "Jump",
ctx => ctx.InputJump && ctx.IsGrounded);
_stateMachine.AddTransition("Jump", "Idle",
ctx => ctx.IsGrounded);
_stateMachine.AddAnyStateTransition("Hurt",
ctx => ctx.IsHit, priority: -10); // 高优先级
步骤4:实现状态类
public class JumpState : StateBase<PlayerStateContext>
{
public override void Enter()
{
// 播放跳跃动画
Context.Animator.Play("jump");
// 应用跳跃力
Context.Player.Velocity = new Vector2(
Context.Player.Velocity.X,
-Context.JumpForce
);
}
public override void PhysicsUpdate(float deltaTime)
{
// 空中控制
float airControl = 0.3f;
float targetX = Context.HorizontalInput * Context.MoveSpeed * airControl;
Context.Player.Velocity = new Vector2(targetX, Context.Player.Velocity.Y);
}
public override void Exit()
{
// 着陆时可能播放着陆动画
}
}
5.2 常见错误:状态循环依赖
问题描述:两个状态可以互相直接转换,导致无限循环
// 错误:Run和Attack可以互相直接转换
_stateMachine.AddTransition("Run", "Attack", ctx => ctx.InputAttack);
_stateMachine.AddTransition("Attack", "Run", ctx => !ctx.InputAttack);
// 问题:如果玩家按住攻击键,会每帧在Run和Attack间切换
解决方案:
1. 添加冷却时间
_stateMachine.AddTransition("Run", "Attack",
ctx => ctx.InputAttack && ctx.CanAttack);
// CanAttack在AttackState.Enter时设为false,0.5秒后设为true
2. 添加状态持续时间检查
_stateMachine.AddTransition("Attack", "Run",
ctx => ctx.StateTime >= 0.3f && !ctx.InputAttack);
// StateTime在每次状态切换时重置为0
3. 使用中间状态
// Attack -> AttackRecovery -> Run
_stateMachine.AddTransition("Attack", "AttackRecovery",
ctx => ctx.AnimationFinished);
_stateMachine.AddTransition("AttackRecovery", "Run",
ctx => ctx.AnimationFinished);
5.3 调试技巧:状态可视化
实时状态显示:
public partial class StateMachineDebugger : Label
{
[Export] private StateMachine _targetStateMachine;
public override void _Process(double delta)
{
if (_targetStateMachine?.CurrentState != null)
{
Text = $"State: {_targetStateMachine.CurrentStateId}\n" +
$"Time: {_targetStateMachine.Context.StateTime:F2}s";
}
}
}
状态历史记录:
public class StateHistory
{
private readonly Queue<string> _history = new();
private const int MaxHistory = 10;
public void RecordStateChange(string from, string to)
{
_history.Enqueue($"[{Time.GetTimeDict()["tick"]}] {from} -> {to}");
if (_history.Count > MaxHistory)
_history.Dequeue();
}
public void PrintHistory()
{
GD.Print("=== 状态历史 ===");
foreach (var record in _history)
{
GD.Print(record);
}
}
}
可视化调试工具:
public partial class StateMachineVisualizer : Control
{
private Dictionary<string, Vector2> _statePositions;
private StateMachine _stateMachine;
public override void _Draw()
{
// 绘制状态节点
foreach (var state in _stateMachine.GetAllStates())
{
var pos = _statePositions[state.StateName];
DrawCircle(pos, 30, state == _stateMachine.CurrentState ? Colors.Green : Colors.Gray);
DrawString(GetThemeFont("font"), pos - new Vector2(20, 0), state.StateName);
}
// 绘制转换线
foreach (var transition in _stateMachine.GetAllTransitions())
{
var from = _statePositions[transition.FromState];
var to = _statePositions[transition.ToState];
DrawLine(from, to, Colors.White, 2);
}
}
}
5.1 有限状态机(FSM)基础实现
有限状态机是最基础也是最常用的状态管理模式。它包含有限个状态,在任意时刻只能处于其中一个状态,通过触发条件在不同状态间转换。
5.1.1 IState 接口设计
首先定义状态接口,规定所有状态必须实现的方法。
// 文件路径: Scripts/Framework/StateMachine/IState.cs
using System;
namespace GameFramework.StateMachine
{
/// <summary>
/// 状态接口 - 定义状态的基本生命周期方法
/// </summary>
public interface IState
{
/// <summary>
/// 进入状态时调用
/// </summary>
void Enter();
/// <summary>
/// 退出状态时调用
/// </summary>
void Exit();
/// <summary>
/// 每帧更新时调用
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
void Update(float deltaTime);
/// <summary>
/// 物理更新时调用
/// </summary>
/// <param name="deltaTime">物理帧间隔时间</param>
void PhysicsUpdate(float deltaTime);
/// <summary>
/// 获取状态名称
/// </summary>
string StateName { get; }
}
/// <summary>
/// 泛型状态接口 - 允许状态访问上下文对象
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public interface IState<T> : IState
{
/// <summary>
/// 设置上下文对象
/// </summary>
/// <param name="context">上下文对象</param>
void SetContext(T context);
/// <summary>
/// 获取上下文对象
/// </summary>
T Context { get; }
}
}
5.1.2 StateMachine 类实现
接下来实现状态机核心类,支持状态注册、切换和条件触发器。
// 文件路径: Scripts/Framework/StateMachine/StateMachine.cs
using System;
using System.Collections.Generic;
using Godot;
namespace GameFramework.StateMachine
{
/// <summary>
/// 状态转换条件委托
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
/// <returns>是否满足转换条件</returns>
public delegate bool StateCondition<T>(T context);
/// <summary>
/// 状态转换信息
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public class StateTransition<T>
{
/// <summary>
/// 目标状态ID
/// </summary>
public string ToStateId { get; }
/// <summary>
/// 转换条件
/// </summary>
public StateCondition<T> Condition { get; }
/// <summary>
/// 优先级(数字越小优先级越高)
/// </summary>
public int Priority { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="toStateId">目标状态ID</param>
/// <param name="condition">转换条件</param>
/// <param name="priority">优先级</param>
public StateTransition(string toStateId, StateCondition<T> condition, int priority = 0)
{
ToStateId = toStateId;
Condition = condition;
Priority = priority;
}
}
/// <summary>
/// 状态机类 - 管理状态的生命周期和转换
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public class StateMachine<T>
{
#region 字段
// 存储所有注册的状态
private readonly Dictionary<string, IState<T>> _states = new();
// 存储每个状态的转换规则
private readonly Dictionary<string, List<StateTransition<T>>> _transitions = new();
// 当前激活的状态
private IState<T> _currentState;
// 默认状态ID
private string _defaultStateId;
// 是否已初始化
private bool _isInitialized;
#endregion
#region 属性
/// <summary>
/// 当前状态
/// </summary>
public IState<T> CurrentState => _currentState;
/// <summary>
/// 当前状态ID
/// </summary>
public string CurrentStateId => _currentState?.StateName ?? "None";
/// <summary>
/// 上下文对象
/// </summary>
public T Context { get; private set; }
/// <summary>
/// 状态机是否正在运行
/// </summary>
public bool IsRunning => _currentState != null;
/// <summary>
/// 状态切换事件
/// </summary>
public event Action<string, string> OnStateChanged;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="context">上下文对象</param>
public StateMachine(T context)
{
Context = context;
}
#endregion
#region 状态注册
/// <summary>
/// 注册状态
/// </summary>
/// <param name="stateId">状态ID</param>
/// <param name="state">状态实例</param>
public void RegisterState(string stateId, IState<T> state)
{
if (_states.ContainsKey(stateId))
{
GD.PushWarning($"状态 '{stateId}' 已存在,将被覆盖");
_states.Remove(stateId);
}
// 设置状态的上下文
state.SetContext(Context);
// 注册状态
_states[stateId] = state;
// 为该状态初始化转换列表
if (!_transitions.ContainsKey(stateId))
{
_transitions[stateId] = new List<StateTransition<T>>();
}
}
/// <summary>
/// 注册状态(使用状态名称作为ID)
/// </summary>
/// <param name="state">状态实例</param>
public void RegisterState(IState<T> state)
{
RegisterState(state.StateName, state);
}
/// <summary>
/// 设置默认状态
/// </summary>
/// <param name="stateId">默认状态ID</param>
public void SetDefaultState(string stateId)
{
_defaultStateId = stateId;
}
#endregion
#region 转换规则配置
/// <summary>
/// 添加状态转换
/// </summary>
/// <param name="fromStateId">起始状态ID</param>
/// <param name="toStateId">目标状态ID</param>
/// <param name="condition">转换条件</param>
/// <param name="priority">优先级</param>
public void AddTransition(string fromStateId, string toStateId, StateCondition<T> condition, int priority = 0)
{
if (!_states.ContainsKey(fromStateId))
{
GD.PushError($"无法添加转换:起始状态 '{fromStateId}' 未注册");
return;
}
if (!_states.ContainsKey(toStateId))
{
GD.PushError($"无法添加转换:目标状态 '{toStateId}' 未注册");
return;
}
var transition = new StateTransition<T>(toStateId, condition, priority);
_transitions[fromStateId].Add(transition);
// 按优先级排序
_transitions[fromStateId].Sort((a, b) => a.Priority.CompareTo(b.Priority));
}
/// <summary>
/// 添加任意状态到目标状态的转换
/// </summary>
/// <param name="toStateId">目标状态ID</param>
/// <param name="condition">转换条件</param>
/// <param name="priority">优先级</param>
public void AddAnyStateTransition(string toStateId, StateCondition<T> condition, int priority = -1)
{
if (!_states.ContainsKey(toStateId))
{
GD.PushError($"无法添加转换:目标状态 '{toStateId}' 未注册");
return;
}
// 使用特殊键 "*" 表示任意状态
if (!_transitions.ContainsKey("*"))
{
_transitions["*"] = new List<StateTransition<T>>();
}
var transition = new StateTransition<T>(toStateId, condition, priority);
_transitions["*"].Add(transition);
_transitions["*"].Sort((a, b) => a.Priority.CompareTo(b.Priority));
}
/// <summary>
/// 移除转换
/// </summary>
/// <param name="fromStateId">起始状态ID</param>
/// <param name="toStateId">目标状态ID</param>
public void RemoveTransition(string fromStateId, string toStateId)
{
if (_transitions.ContainsKey(fromStateId))
{
_transitions[fromStateId].RemoveAll(t => t.ToStateId == toStateId);
}
}
#endregion
#region 状态控制
/// <summary>
/// 初始化状态机
/// </summary>
public void Initialize()
{
if (_isInitialized)
{
return;
}
if (string.IsNullOrEmpty(_defaultStateId))
{
GD.PushError("状态机初始化失败:未设置默认状态");
return;
}
if (!_states.ContainsKey(_defaultStateId))
{
GD.PushError($"状态机初始化失败:默认状态 '{_defaultStateId}' 未注册");
return;
}
// 进入默认状态
ChangeState(_defaultStateId);
_isInitialized = true;
}
/// <summary>
/// 强制切换到指定状态
/// </summary>
/// <param name="stateId">目标状态ID</param>
public void ChangeState(string stateId)
{
if (!_states.ContainsKey(stateId))
{
GD.PushError($"无法切换到状态 '{stateId}':状态未注册");
return;
}
// 保存上一个状态ID
string previousStateId = _currentState?.StateName ?? "None";
// 退出当前状态
_currentState?.Exit();
// 切换状态
_currentState = _states[stateId];
// 进入新状态
_currentState.Enter();
// 触发状态改变事件
OnStateChanged?.Invoke(previousStateId, stateId);
GD.Print($"[{GetType().Name}] 状态切换: {previousStateId} -> {stateId}");
}
/// <summary>
/// 触发器 - 检查并执行状态转换
/// </summary>
/// <param name="triggerName">触发器名称</param>
/// <returns>是否成功转换</returns>
public bool Trigger(string triggerName)
{
if (_currentState == null)
{
return false;
}
string currentStateId = _currentState.StateName;
// 检查当前状态的转换
if (_transitions.ContainsKey(currentStateId))
{
foreach (var transition in _transitions[currentStateId])
{
// 检查触发器名称匹配(这里简化处理,实际可以扩展)
if (transition.Condition?.Invoke(Context) == true)
{
ChangeState(transition.ToStateId);
return true;
}
}
}
// 检查任意状态的转换
if (_transitions.ContainsKey("*"))
{
foreach (var transition in _transitions["*"])
{
if (transition.Condition?.Invoke(Context) == true)
{
ChangeState(transition.ToStateId);
return true;
}
}
}
return false;
}
/// <summary>
/// 更新状态机
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
public void Update(float deltaTime)
{
if (!_isInitialized)
{
return;
}
// 检查自动转换
CheckTransitions();
// 更新当前状态
_currentState?.Update(deltaTime);
}
/// <summary>
/// 物理更新
/// </summary>
/// <param name="deltaTime">物理帧间隔时间</param>
public void PhysicsUpdate(float deltaTime)
{
if (!_isInitialized)
{
return;
}
_currentState?.PhysicsUpdate(deltaTime);
}
/// <summary>
/// 检查转换条件
/// </summary>
private void CheckTransitions()
{
if (_currentState == null)
{
return;
}
string currentStateId = _currentState.StateName;
// 检查任意状态的转换(优先级更高)
if (_transitions.ContainsKey("*"))
{
foreach (var transition in _transitions["*"])
{
if (transition.Condition?.Invoke(Context) == true)
{
ChangeState(transition.ToStateId);
return;
}
}
}
// 检查当前状态的转换
if (_transitions.ContainsKey(currentStateId))
{
foreach (var transition in _transitions[currentStateId])
{
if (transition.Condition?.Invoke(Context) == true)
{
ChangeState(transition.ToStateId);
return;
}
}
}
}
/// <summary>
/// 停止状态机
/// </summary>
public void Stop()
{
_currentState?.Exit();
_currentState = null;
_isInitialized = false;
}
#endregion
#region 工具方法
/// <summary>
/// 获取状态实例
/// </summary>
/// <param name="stateId">状态ID</param>
/// <returns>状态实例</returns>
public IState<T> GetState(string stateId)
{
return _states.TryGetValue(stateId, out var state) ? state : null;
}
/// <summary>
/// 检查是否处于指定状态
/// </summary>
/// <param name="stateId">状态ID</param>
/// <returns>是否处于该状态</returns>
public bool IsInState(string stateId)
{
return _currentState?.StateName == stateId;
}
/// <summary>
/// 检查是否存在指定状态
/// </summary>
/// <param name="stateId">状态ID</param>
/// <returns>是否存在</returns>
public bool HasState(string stateId)
{
return _states.ContainsKey(stateId);
}
#endregion
}
}
5.1.3 基础状态抽象类
为了简化状态实现,提供抽象基类。
// 文件路径: Scripts/Framework/StateMachine/StateBase.cs
using System;
namespace GameFramework.StateMachine
{
/// <summary>
/// 状态抽象基类
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public abstract class StateBase<T> : IState<T>
{
/// <summary>
/// 上下文对象
/// </summary>
public T Context { get; private set; }
/// <summary>
/// 状态名称(默认使用类名)
/// </summary>
public virtual string StateName => GetType().Name;
/// <summary>
/// 设置上下文
/// </summary>
/// <param name="context">上下文对象</param>
public void SetContext(T context)
{
Context = context;
}
/// <summary>
/// 进入状态
/// </summary>
public virtual void Enter()
{
// 子类可重写
}
/// <summary>
/// 退出状态
/// </summary>
public virtual void Exit()
{
// 子类可重写
}
/// <summary>
/// 更新
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
public virtual void Update(float deltaTime)
{
// 子类可重写
}
/// <summary>
/// 物理更新
/// </summary>
/// <param name="deltaTime">物理帧间隔时间</param>
public virtual void PhysicsUpdate(float deltaTime)
{
// 子类可重写
}
/// <summary>
/// 切换到指定状态
/// </summary>
/// <param name="stateId">目标状态ID</param>
protected void ChangeState(string stateId)
{
// 通过上下文获取状态机并切换
// 具体实现取决于上下文的设计
}
}
}
5.2 分层状态机(Hierarchical FSM)
当状态数量增多时,普通FSM会出现转换爆炸问题。分层状态机通过状态嵌套解决这个问题,子状态可以继承父状态的行为。
5.2.1 分层状态机实现
// 文件路径: Scripts/Framework/StateMachine/HierarchicalStateMachine.cs
using System;
using System.Collections.Generic;
using Godot;
namespace GameFramework.StateMachine
{
/// <summary>
/// 分层状态接口
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public interface IHierarchicalState<T> : IState<T>
{
/// <summary>
/// 父状态
/// </summary>
IHierarchicalState<T> Parent { get; set; }
/// <summary>
/// 子状态机(如果有)
/// </summary>
HierarchicalStateMachine<T> SubStateMachine { get; }
/// <summary>
/// 是否有子状态机
/// </summary>
bool HasSubStateMachine { get; }
/// <summary>
/// 初始化子状态机
/// </summary>
/// <param name="subStateMachine">子状态机</param>
void SetSubStateMachine(HierarchicalStateMachine<T> subStateMachine);
}
/// <summary>
/// 分层状态抽象基类
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public abstract class HierarchicalStateBase<T> : StateBase<T>, IHierarchicalState<T>
{
/// <summary>
/// 父状态
/// </summary>
public IHierarchicalState<T> Parent { get; set; }
/// <summary>
/// 子状态机
/// </summary>
public HierarchicalStateMachine<T> SubStateMachine { get; private set; }
/// <summary>
/// 是否有子状态机
/// </summary>
public bool HasSubStateMachine => SubStateMachine != null;
/// <summary>
/// 设置子状态机
/// </summary>
/// <param name="subStateMachine">子状态机</param>
public void SetSubStateMachine(HierarchicalStateMachine<T> subStateMachine)
{
SubStateMachine = subStateMachine;
if (SubStateMachine != null)
{
SubStateMachine.ParentState = this;
}
}
/// <summary>
/// 进入状态(调用父状态后再调用子状态)
/// </summary>
public override void Enter()
{
// 先调用父状态的Enter
Parent?.OnChildEnter(this);
base.Enter();
// 如果有子状态机,初始化它
SubStateMachine?.Initialize();
}
/// <summary>
/// 退出状态(先退出子状态)
/// </summary>
public override void Exit()
{
// 先停止子状态机
SubStateMachine?.Stop();
base.Exit();
// 通知父状态
Parent?.OnChildExit(this);
}
/// <summary>
/// 更新(先更新自身,再更新子状态机)
/// </summary>
public override void Update(float deltaTime)
{
base.Update(deltaTime);
SubStateMachine?.Update(deltaTime);
}
/// <summary>
/// 物理更新
/// </summary>
public override void PhysicsUpdate(float deltaTime)
{
base.PhysicsUpdate(deltaTime);
SubStateMachine?.PhysicsUpdate(deltaTime);
}
/// <summary>
/// 子状态进入时调用
/// </summary>
/// <param name="childState">子状态</param>
protected virtual void OnChildEnter(IHierarchicalState<T> childState)
{
}
/// <summary>
/// 子状态退出时调用
/// </summary>
/// <param name="childState">子状态</param>
protected virtual void OnChildExit(IHierarchicalState<T> childState)
{
}
}
/// <summary>
/// 分层状态机类
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public class HierarchicalStateMachine<T> : StateMachine<T>
{
/// <summary>
/// 父状态
/// </summary>
public IHierarchicalState<T> ParentState { get; set; }
/// <summary>
/// 根状态机
/// </summary>
public HierarchicalStateMachine<T> RootStateMachine { get; set; }
/// <summary>
/// 完整路径
/// </summary>
public string FullPath
{
get
{
if (ParentState == null)
{
return CurrentStateId;
}
return $"{ParentState.StateName}/{CurrentStateId}";
}
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="context">上下文对象</param>
public HierarchicalStateMachine(T context) : base(context)
{
}
/// <summary>
/// 重写状态切换,通知父状态
/// </summary>
public new void ChangeState(string stateId)
{
base.ChangeState(stateId);
}
/// <summary>
/// 向上层状态机请求切换
/// </summary>
/// <param name="targetStateId">目标状态ID</param>
public void RequestTransition(string targetStateId)
{
// 如果父状态存在,通知父状态处理
if (ParentState != null)
{
// 父状态决定是否处理这个转换
// 如果不处理,继续向上传递
}
}
}
}
5.3 下推自动机(Pushdown Automata)实现
下推自动机使用栈结构管理状态,支持状态的历史记录和返回上一状态,非常适合UI导航、游戏暂停等场景。
5.3.1 下推状态机实现
// 文件路径: Scripts/Framework/StateMachine/PushdownStateMachine.cs
using System;
using System.Collections.Generic;
using Godot;
namespace GameFramework.StateMachine
{
/// <summary>
/// 下推自动机状态机 - 使用栈管理状态
/// </summary>
/// <typeparam name="T">上下文类型</typeparam>
public class PushdownStateMachine<T>
{
#region 字段
// 状态栈
private readonly Stack<IState<T>> _stateStack = new();
// 所有注册的状态
private readonly Dictionary<string, IState<T>> _states = new();
// 状态历史记录(用于调试)
private readonly List<string> _stateHistory = new();
// 最大历史记录数
private const int MaxHistoryCount = 50;
#endregion
#region 属性
/// <summary>
/// 当前状态(栈顶)
/// </summary>
public IState<T> CurrentState => _stateStack.Count > 0 ? _stateStack.Peek() : null;
/// <summary>
/// 当前状态ID
/// </summary>
public string CurrentStateId => CurrentState?.StateName ?? "None";
/// <summary>
/// 栈中状态数量
/// </summary>
public int StackCount => _stateStack.Count;
/// <summary>
/// 上下文对象
/// </summary>
public T Context { get; private set; }
/// <summary>
/// 状态改变事件
/// </summary>
public event Action<string, string> OnStateChanged;
/// <summary>
/// 状态弹出事件
/// </summary>
public event Action<string> OnStatePopped;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="context">上下文对象</param>
public PushdownStateMachine(T context)
{
Context = context;
}
#endregion
#region 状态注册
/// <summary>
/// 注册状态
/// </summary>
/// <param name="stateId">状态ID</param>
/// <param name="state">状态实例</param>
public void RegisterState(string stateId, IState<T> state)
{
if (_states.ContainsKey(stateId))
{
GD.PushWarning($"状态 '{stateId}' 已存在,将被覆盖");
}
state.SetContext(Context);
_states[stateId] = state;
}
/// <summary>
/// 注册状态(使用类名作为ID)
/// </summary>
/// <param name="state">状态实例</param>
public void RegisterState(IState<T> state)
{
RegisterState(state.StateName, state);
}
#endregion
#region 栈操作
/// <summary>
/// 推入新状态(暂停当前状态,进入新状态)
/// </summary>
/// <param name="stateId">状态ID</param>
public void PushState(string stateId)
{
if (!_states.ContainsKey(stateId))
{
GD.PushError($"无法推入状态 '{stateId}':状态未注册");
return;
}
string previousStateId = CurrentStateId;
// 暂停当前状态
if (_stateStack.Count > 0)
{
var current = _stateStack.Peek();
OnStatePaused(current);
}
// 推入新状态
var newState = _states[stateId];
_stateStack.Push(newState);
// 进入新状态
newState.Enter();
// 记录历史
AddToHistory($"Push: {stateId}");
// 触发事件
OnStateChanged?.Invoke(previousStateId, stateId);
GD.Print($"[PushdownStateMachine] Push: {previousStateId} -> {stateId} (Stack: {_stateStack.Count})");
}
/// <summary>
/// 弹出当前状态(返回上一状态)
/// </summary>
/// <returns>是否成功弹出</returns>
public bool PopState()
{
if (_stateStack.Count == 0)
{
GD.PushWarning("状态栈为空,无法弹出");
return false;
}
string poppedStateId = CurrentStateId;
// 退出当前状态
var current = _stateStack.Pop();
current.Exit();
// 触发弹出事件
OnStatePopped?.Invoke(poppedStateId);
string newStateId = CurrentStateId;
// 恢复上一状态
if (_stateStack.Count > 0)
{
var resumed = _stateStack.Peek();
OnStateResumed(resumed);
}
// 记录历史
AddToHistory($"Pop: {poppedStateId} -> {newStateId}");
// 触发事件
OnStateChanged?.Invoke(poppedStateId, newStateId);
GD.Print($"[PushdownStateMachine] Pop: {poppedStateId} -> {newStateId} (Stack: {_stateStack.Count})");
return true;
}
/// <summary>
/// 替换当前状态(不保存历史)
/// </summary>
/// <param name="stateId">新状态ID</param>
public void ReplaceState(string stateId)
{
if (!_states.ContainsKey(stateId))
{
GD.PushError($"无法替换状态 '{stateId}':状态未注册");
return;
}
if (_stateStack.Count == 0)
{
PushState(stateId);
return;
}
string previousStateId = CurrentStateId;
// 退出并移除当前状态
var current = _stateStack.Pop();
current.Exit();
// 推入新状态
var newState = _states[stateId];
_stateStack.Push(newState);
newState.Enter();
// 记录历史
AddToHistory($"Replace: {previousStateId} -> {stateId}");
// 触发事件
OnStateChanged?.Invoke(previousStateId, stateId);
GD.Print($"[PushdownStateMachine] Replace: {previousStateId} -> {stateId}");
}
/// <summary>
/// 清空栈并设置新状态
/// </summary>
/// <param name="stateId">状态ID</param>
public void ClearAndPush(string stateId)
{
// 清空所有状态
while (_stateStack.Count > 0)
{
var state = _stateStack.Pop();
state.Exit();
}
// 推入新状态
PushState(stateId);
AddToHistory($"ClearAndPush: {stateId}");
}
/// <summary>
/// 返回到指定状态(弹出栈直到该状态)
/// </summary>
/// <param name="stateId">目标状态ID</param>
/// <returns>是否找到该状态</returns>
public bool PopToState(string stateId)
{
// 检查状态是否在栈中
var tempStack = new Stack<IState<T>>();
bool found = false;
while (_stateStack.Count > 0)
{
var state = _stateStack.Pop();
if (state.StateName == stateId)
{
found = true;
_stateStack.Push(state); // 放回去
break;
}
else
{
state.Exit();
tempStack.Push(state);
}
}
if (found)
{
// 恢复找到的状态
if (_stateStack.Count > 0)
{
var resumed = _stateStack.Peek();
OnStateResumed(resumed);
}
AddToHistory($"PopTo: {stateId}");
}
else
{
// 恢复栈
while (tempStack.Count > 0)
{
_stateStack.Push(tempStack.Pop());
}
GD.PushWarning($"状态 '{stateId}' 不在栈中");
}
return found;
}
/// <summary>
/// 状态暂停时调用(可被重写)
/// </summary>
/// <param name="state">被暂停的状态</param>
protected virtual void OnStatePaused(IState<T> state)
{
// 默认实现:状态保持,但停止更新
// 子类可以重写以实现特定行为(如暂停动画)
}
/// <summary>
/// 状态恢复时调用(可被重写)
/// </summary>
/// <param name="state">被恢复的状态</param>
protected virtual void OnStateResumed(IState<T> state)
{
// 默认实现
}
#endregion
#region 更新
/// <summary>
/// 更新当前状态
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
public void Update(float deltaTime)
{
CurrentState?.Update(deltaTime);
}
/// <summary>
/// 物理更新
/// </summary>
/// <param name="deltaTime">物理帧间隔时间</param>
public void PhysicsUpdate(float deltaTime)
{
CurrentState?.PhysicsUpdate(deltaTime);
}
#endregion
#region 工具方法
/// <summary>
/// 获取栈中所有状态(从底到顶)
/// </summary>
/// <returns>状态列表</returns>
public List<IState<T>> GetStackStates()
{
return new List<IState<T>>(_stateStack);
}
/// <summary>
/// 获取栈中所有状态ID
/// </summary>
/// <returns>状态ID列表</returns>
public List<string> GetStackStateIds()
{
var ids = new List<string>();
foreach (var state in _stateStack)
{
ids.Add(state.StateName);
}
return ids;
}
/// <summary>
/// 检查栈中是否包含指定状态
/// </summary>
/// <param name="stateId">状态ID</param>
/// <returns>是否包含</returns>
public bool ContainsState(string stateId)
{
foreach (var state in _stateStack)
{
if (state.StateName == stateId)
return true;
}
return false;
}
/// <summary>
/// 获取状态历史
/// </summary>
/// <returns>历史记录</returns>
public IReadOnlyList<string> GetStateHistory()
{
return _stateHistory.AsReadOnly();
}
/// <summary>
/// 清空历史
/// </summary>
public void ClearHistory()
{
_stateHistory.Clear();
}
/// <summary>
/// 添加到历史
/// </summary>
private void AddToHistory(string record)
{
_stateHistory.Add($"[{DateTime.Now:HH:mm:ss.fff}] {record}");
if (_stateHistory.Count > MaxHistoryCount)
{
_stateHistory.RemoveAt(0);
}
}
/// <summary>
/// 清空状态机
/// </summary>
public void Clear()
{
while (_stateStack.Count > 0)
{
var state = _stateStack.Pop();
state.Exit();
}
_stateHistory.Clear();
}
#endregion
}
}
5.4 完整示例:PlayerStateMachine
下面展示一个完整的玩家状态机实现,包含Idle、Run、Jump、Attack四个状态。
5.4.1 玩家状态上下文
// 文件路径: Scripts/Player/PlayerStateContext.cs
using Godot;
namespace GameFramework.Player
{
/// <summary>
/// 玩家状态上下文 - 保存玩家状态机所需的数据
/// </summary>
public class PlayerStateContext
{
#region 引用
/// <summary>
/// 玩家节点
/// </summary>
public PlayerController Player { get; }
/// <summary>
/// 动画播放器
/// </summary>
public AnimationPlayer Animator { get; }
#endregion
#region 输入状态
/// <summary>
/// 水平输入
/// </summary>
public float HorizontalInput { get; set; }
/// <summary>
/// 是否按下跳跃键
/// </summary>
public bool JumpPressed { get; set; }
/// <summary>
/// 是否按下攻击键
/// </summary>
public bool AttackPressed { get; set; }
#endregion
#region 物理状态
/// <summary>
/// 是否着地
/// </summary>
public bool IsGrounded { get; set; }
/// <summary>
/// 垂直速度
/// </summary>
public float VerticalVelocity { get; set; }
#endregion
#region 配置参数
/// <summary>
/// 移动速度
/// </summary>
public float MoveSpeed { get; }
/// <summary>
/// 跳跃力
/// </summary>
public float JumpForce { get; }
/// <summary>
/// 重力
/// </summary>
public float Gravity { get; }
/// <summary>
/// 攻击冷却时间
/// </summary>
public float AttackCooldown { get; }
#endregion
#region 运行时数据
/// <summary>
/// 当前状态持续时间
/// </summary>
public float StateTime { get; set; }
/// <summary>
/// 最后攻击时间
/// </summary>
public float LastAttackTime { get; set; } = float.MinValue;
/// <summary>
/// 连击计数
/// </summary>
public int ComboCount { get; set; }
#endregion
/// <summary>
/// 构造函数
/// </summary>
/// <param name="player">玩家控制器</param>
public PlayerStateContext(PlayerController player)
{
Player = player;
Animator = player.GetNode<AnimationPlayer>("AnimationPlayer");
// 从配置读取参数
MoveSpeed = player.MoveSpeed;
JumpForce = player.JumpForce;
Gravity = player.Gravity;
AttackCooldown = player.AttackCooldown;
}
/// <summary>
/// 更新输入状态
/// </summary>
public void UpdateInput()
{
HorizontalInput = Input.GetActionStrength("move_right") - Input.GetActionStrength("move_left");
JumpPressed = Input.IsActionJustPressed("jump");
AttackPressed = Input.IsActionJustPressed("attack");
}
/// <summary>
/// 是否可以攻击
/// </summary>
public bool CanAttack => Time.GetTimeDict()["tick"] / 1000f - LastAttackTime >= AttackCooldown;
}
}
5.4.2 玩家控制器
// 文件路径: Scripts/Player/PlayerController.cs
using Godot;
using GameFramework.StateMachine;
namespace GameFramework.Player
{
/// <summary>
/// 玩家控制器 - 主控制器类
/// </summary>
[GlobalClass]
public partial class PlayerController : CharacterBody2D
{
#region 导出属性
[Export] public float MoveSpeed { get; set; } = 200f;
[Export] public float JumpForce { get; set; } = 400f;
[Export] public float Gravity { get; set; } = 1000f;
[Export] public float AttackCooldown { get; set; } = 0.3f;
#endregion
#region 组件
// 状态机
private StateMachine<PlayerStateContext> _stateMachine;
// 状态上下文
private PlayerStateContext _context;
// 地面检测
private RayCast2D _groundCheck;
#endregion
#region 生命周期
public override void _Ready()
{
// 初始化组件
_groundCheck = GetNode<RayCast2D>("GroundCheck");
// 创建状态上下文
_context = new PlayerStateContext(this);
// 创建状态机
_stateMachine = new StateMachine<PlayerStateContext>(_context);
// 注册状态
SetupStates();
// 配置转换
SetupTransitions();
// 初始化状态机
_stateMachine.SetDefaultState("IdleState");
_stateMachine.Initialize();
// 订阅状态改变事件
_stateMachine.OnStateChanged += (from, to) =>
{
_context.StateTime = 0f;
};
}
public override void _Process(double delta)
{
// 更新输入
_context.UpdateInput();
// 更新状态机
_stateMachine.Update((float)delta);
}
public override void _PhysicsProcess(double delta)
{
// 更新物理状态
_context.IsGrounded = _groundCheck.IsColliding();
_context.VerticalVelocity = Velocity.Y;
// 物理更新状态机
_stateMachine.PhysicsUpdate((float)delta);
// 应用重力
if (!_context.IsGrounded)
{
Velocity = new Vector2(Velocity.X, Velocity.Y + Gravity * (float)delta);
}
// 更新状态时间
_context.StateTime += (float)delta;
MoveAndSlide();
}
#endregion
#region 状态机配置
/// <summary>
/// 注册所有状态
/// </summary>
private void SetupStates()
{
_stateMachine.RegisterState(new IdleState());
_stateMachine.RegisterState(new RunState());
_stateMachine.RegisterState(new JumpState());
_stateMachine.RegisterState(new AttackState());
}
/// <summary>
/// 配置状态转换
/// </summary>
private void SetupTransitions()
{
// Idle -> Run: 有水平输入
_stateMachine.AddTransition("IdleState", "RunState",
ctx => Mathf.Abs(ctx.HorizontalInput) > 0.1f);
// Idle -> Jump: 按下跳跃键且着地
_stateMachine.AddTransition("IdleState", "JumpState",
ctx => ctx.JumpPressed && ctx.IsGrounded);
// Idle -> Attack: 按下攻击键
_stateMachine.AddTransition("IdleState", "AttackState",
ctx => ctx.AttackPressed && ctx.CanAttack);
// Run -> Idle: 无水平输入
_stateMachine.AddTransition("RunState", "IdleState",
ctx => Mathf.Abs(ctx.HorizontalInput) < 0.1f);
// Run -> Jump: 按下跳跃键且着地
_stateMachine.AddTransition("RunState", "JumpState",
ctx => ctx.JumpPressed && ctx.IsGrounded);
// Run -> Attack: 按下攻击键
_stateMachine.AddTransition("RunState", "AttackState",
ctx => ctx.AttackPressed && ctx.CanAttack);
// Jump -> Idle: 着地且速度很小
_stateMachine.AddTransition("JumpState", "IdleState",
ctx => ctx.IsGrounded && Mathf.Abs(ctx.VerticalVelocity) < 10f);
// Jump -> Run: 着地且有水平输入
_stateMachine.AddTransition("JumpState", "RunState",
ctx => ctx.IsGrounded && Mathf.Abs(ctx.HorizontalInput) > 0.1f);
// Attack -> Idle: 攻击结束(在AttackState内部控制)
_stateMachine.AddTransition("AttackState", "IdleState",
ctx => ctx.StateTime >= 0.3f && !ctx.AttackPressed);
}
#endregion
#region 公共方法
/// <summary>
/// 播放动画
/// </summary>
public void PlayAnimation(string animName)
{
if (_context.Animator.HasAnimation(animName))
{
_context.Animator.Play(animName);
}
}
/// <summary>
/// 获取当前状态
/// </summary>
public string GetCurrentState() => _stateMachine.CurrentStateId;
#endregion
}
}
5.4.3 IdleState 空闲状态
// 文件路径: Scripts/Player/States/IdleState.cs
using GameFramework.StateMachine;
namespace GameFramework.Player
{
/// <summary>
/// 空闲状态 - 玩家站立不动
/// </summary>
public class IdleState : StateBase<PlayerStateContext>
{
public override void Enter()
{
// 步骤1: 播放空闲动画
Context.Player.PlayAnimation("idle");
// 步骤2: 停止水平移动
Context.Player.Velocity = new Godot.Vector2(0, Context.Player.Velocity.Y);
// 步骤3: 重置连击
Context.ComboCount = 0;
}
public override void Update(float deltaTime)
{
// 步骤1: 检查是否需要转向(根据输入方向)
if (Context.HorizontalInput > 0)
{
Context.Player.Scale = new Godot.Vector2(1, 1);
}
else if (Context.HorizontalInput < 0)
{
Context.Player.Scale = new Godot.Vector2(-1, 1);
}
// 步骤2: 空闲动画可能有随机变化,可以在这里处理
}
public override void PhysicsUpdate(float deltaTime)
{
// 步骤1: 保持垂直速度(受重力影响)
// 水平速度已在Enter中归零
}
public override void Exit()
{
// 步骤1: 清理(如需要)
}
}
}
5.4.4 RunState 奔跑状态
// 文件路径: Scripts/Player/States/RunState.cs
using Godot;
using GameFramework.StateMachine;
namespace GameFramework.Player
{
/// <summary>
/// 奔跑状态 - 玩家左右移动
/// </summary>
public class RunState : StateBase<PlayerStateContext>
{
public override void Enter()
{
// 步骤1: 播放奔跑动画
Context.Player.PlayAnimation("run");
}
public override void Update(float deltaTime)
{
// 步骤1: 根据输入方向翻转角色
if (Context.HorizontalInput > 0)
{
Context.Player.Scale = new Vector2(1, 1);
}
else if (Context.HorizontalInput < 0)
{
Context.Player.Scale = new Vector2(-1, 1);
}
// 步骤2: 调整动画速度以匹配移动速度
float speedRatio = Mathf.Abs(Context.HorizontalInput);
// Context.Animator.SpeedScale = speedRatio; // 可选
}
public override void PhysicsUpdate(float deltaTime)
{
// 步骤1: 计算目标速度
float targetVelocityX = Context.HorizontalInput * Context.MoveSpeed;
// 步骤2: 应用速度
Vector2 velocity = Context.Player.Velocity;
velocity.X = targetVelocityX;
Context.Player.Velocity = velocity;
}
public override void Exit()
{
// 步骤1: 恢复动画速度
// Context.Animator.SpeedScale = 1f; // 可选
}
}
}
5.4.5 JumpState 跳跃状态
// 文件路径: Scripts/Player/States/JumpState.cs
using Godot;
using GameFramework.StateMachine;
namespace GameFramework.Player
{
/// <summary>
/// 跳跃状态 - 玩家跳跃和在空中
/// </summary>
public class JumpState : StateBase<PlayerStateContext>
{
private bool _hasJumped;
public override void Enter()
{
// 步骤1: 播放跳跃动画
Context.Player.PlayAnimation("jump");
// 步骤2: 应用跳跃力
Context.Player.Velocity = new Vector2(Context.Player.Velocity.X, -Context.JumpForce);
// 步骤3: 标记已跳跃
_hasJumped = true;
}
public override void Update(float deltaTime)
{
// 步骤1: 处理空中转向
if (Context.HorizontalInput > 0)
{
Context.Player.Scale = new Vector2(1, 1);
}
else if (Context.HorizontalInput < 0)
{
Context.Player.Scale = new Vector2(-1, 1);
}
// 步骤2: 检测下落切换动画
if (Context.Player.Velocity.Y > 0)
{
Context.Player.PlayAnimation("fall");
}
// 步骤3: 可扩展:短按跳跃和长按跳跃的高度控制
if (!Input.IsActionPressed("jump") && Context.Player.Velocity.Y < 0)
{
// 提前释放跳跃键,减少跳跃高度
Vector2 velocity = Context.Player.Velocity;
velocity.Y *= 0.5f;
Context.Player.Velocity = velocity;
}
}
public override void PhysicsUpdate(float deltaTime)
{
// 步骤1: 空中可以稍微控制水平移动
float airControl = 0.3f; // 空中控制力
float targetVelocityX = Context.HorizontalInput * Context.MoveSpeed * airControl;
Vector2 velocity = Context.Player.Velocity;
velocity.X = Mathf.Lerp(velocity.X, targetVelocityX, 0.1f);
Context.Player.Velocity = velocity;
}
public override void Exit()
{
// 步骤1: 重置标记
_hasJumped = false;
// 步骤2: 着陆时添加一点缓冲
if (Context.IsGrounded)
{
Context.Player.PlayAnimation("land");
}
}
}
}
5.4.6 AttackState 攻击状态
// 文件路径: Scripts/Player/States/AttackState.cs
using Godot;
using GameFramework.StateMachine;
namespace GameFramework.Player
{
/// <summary>
/// 攻击状态 - 玩家执行攻击
/// </summary>
public class AttackState : StateBase<PlayerStateContext>
{
private const float AttackDuration = 0.3f;
private const float ComboWindow = 0.2f;
public override void Enter()
{
// 步骤1: 更新最后攻击时间
Context.LastAttackTime = Time.GetTimeDict()["tick"] / 1000f;
// 步骤2: 根据连击数播放不同动画
string animName = Context.ComboCount switch
{
0 => "attack_1",
1 => "attack_2",
_ => "attack_3"
};
Context.Player.PlayAnimation(animName);
// 步骤3: 执行攻击判定
PerformAttack();
// 步骤4: 增加连击数
Context.ComboCount++;
if (Context.ComboCount > 2)
{
Context.ComboCount = 0;
}
// 步骤5: 停止移动
Vector2 velocity = Context.Player.Velocity;
velocity.X *= 0.3f; // 攻击时减速
Context.Player.Velocity = velocity;
}
public override void Update(float deltaTime)
{
// 步骤1: 检查连击输入
// 在攻击时间窗口内按下攻击,可以继续连击
if (Context.StateTime >= AttackDuration - ComboWindow && Context.AttackPressed)
{
// 重置状态时间以延长攻击
// 注意:实际实现可能需要更复杂的连击系统
}
// 步骤2: 攻击过程中可以稍微移动
}
public override void PhysicsUpdate(float deltaTime)
{
// 步骤1: 攻击时水平速度衰减
Vector2 velocity = Context.Player.Velocity;
velocity.X = Mathf.Lerp(velocity.X, 0, deltaTime * 5f);
Context.Player.Velocity = velocity;
}
public override void Exit()
{
// 步骤1: 如果在连击窗口内退出,保留连击数
if (Context.StateTime >= AttackDuration)
{
Context.ComboCount = 0;
}
}
/// <summary>
/// 执行攻击判定
/// </summary>
private void PerformAttack()
{
// 步骤1: 获取攻击区域
var attackArea = Context.Player.GetNode<Area2D>("AttackArea");
if (attackArea == null) return;
// 步骤2: 检测碰撞
var bodies = attackArea.GetOverlappingBodies();
foreach (var body in bodies)
{
if (body is IDamageable damageable && body != Context.Player)
{
// 计算伤害
int damage = 10 * (Context.ComboCount + 1);
damageable.TakeDamage(damage, Context.Player);
}
}
// 步骤3: 播放攻击特效
// Context.Player.PlayAttackEffect();
}
}
/// <summary>
/// 可受伤接口
/// </summary>
public interface IDamageable
{
void TakeDamage(int damage, Node2D attacker);
}
}
5.5 敌人AI状态机示例
敌人AI通常需要更复杂的状态管理,包括巡逻、追击、攻击、逃跑等。
5.5.1 敌人AI状态机
// 文件路径: Scripts/Enemy/EnemyAIStateMachine.cs
using Godot;
using GameFramework.StateMachine;
using System;
namespace GameFramework.Enemy
{
/// <summary>
/// 敌人AI上下文
/// </summary>
public class EnemyAIContext
{
public EnemyController Enemy { get; }
public PlayerController Player { get; set; }
// 感知范围
public float DetectionRange { get; }
public float AttackRange { get; }
public float LoseInterestRange { get; }
// 导航
public NavigationAgent2D Navigator { get; }
// 状态数据
public Vector2 PatrolStart { get; set; }
public Vector2[] PatrolPoints { get; set; }
public int CurrentPatrolIndex { get; set; }
public float StateTime { get; set; }
public float HealthPercent => Enemy.Health / Enemy.MaxHealth;
public EnemyAIContext(EnemyController enemy)
{
Enemy = enemy;
Navigator = enemy.GetNode<NavigationAgent2D>("NavigationAgent2D");
DetectionRange = enemy.DetectionRange;
AttackRange = enemy.AttackRange;
LoseInterestRange = enemy.LoseInterestRange;
PatrolStart = enemy.GlobalPosition;
}
/// <summary>
/// 获取到玩家的距离
/// </summary>
public float GetDistanceToPlayer()
{
if (Player == null) return float.MaxValue;
return Enemy.GlobalPosition.DistanceTo(Player.GlobalPosition);
}
/// <summary>
/// 是否能看到玩家
/// </summary>
public bool CanSeePlayer()
{
if (Player == null) return false;
float dist = GetDistanceToPlayer();
return dist <= DetectionRange;
}
/// <summary>
/// 玩家是否在攻击范围内
/// </summary>
public bool IsPlayerInAttackRange()
{
if (Player == null) return false;
return GetDistanceToPlayer() <= AttackRange;
}
}
/// <summary>
/// 敌人AI状态机
/// </summary>
public class EnemyAIStateMachine : StateMachine<EnemyAIContext>
{
public EnemyAIStateMachine(EnemyAIContext context) : base(context)
{
}
/// <summary>
/// 配置敌人AI状态
/// </summary>
public void SetupEnemyStates()
{
// 注册状态
RegisterState(new PatrolState());
RegisterState(new ChaseState());
RegisterState(new AttackState());
RegisterState(new FleeState());
RegisterState(new StunnedState());
// Patrol -> Chase: 发现玩家
AddTransition("PatrolState", "ChaseState",
ctx => ctx.CanSeePlayer());
// Chase -> Attack: 进入攻击范围
AddTransition("ChaseState", "AttackState",
ctx => ctx.IsPlayerInAttackRange());
// Chase -> Patrol: 丢失玩家
AddTransition("ChaseState", "PatrolState",
ctx => ctx.GetDistanceToPlayer() > ctx.LoseInterestRange);
// Attack -> Chase: 玩家离开攻击范围
AddTransition("AttackState", "ChaseState",
ctx => !ctx.IsPlayerInAttackRange());
// Attack -> Flee: 血量过低
AddTransition("AttackState", "FleeState",
ctx => ctx.HealthPercent < 0.2f);
// Flee -> Chase: 恢复血量或玩家远离
AddTransition("FleeState", "ChaseState",
ctx => ctx.HealthPercent > 0.5f || ctx.GetDistanceToPlayer() > ctx.DetectionRange * 1.5f);
// 任意状态 -> Stunned: 受击
AddAnyStateTransition("StunnedState",
ctx => ctx.Enemy.IsStunned, priority: -10);
// Stunned -> Patrol: 眩晕结束
AddTransition("StunnedState", "PatrolState",
ctx => !ctx.Enemy.IsStunned && !ctx.CanSeePlayer());
// Stunned -> Chase: 眩晕结束且能看到玩家
AddTransition("StunnedState", "ChaseState",
ctx => !ctx.Enemy.IsStunned && ctx.CanSeePlayer());
SetDefaultState("PatrolState");
}
}
#region 敌人状态实现
/// <summary>
/// 巡逻状态
/// </summary>
public class PatrolState : StateBase<EnemyAIContext>
{
private float _waitTime;
private const float MoveSpeed = 50f;
public override void Enter()
{
Context.Enemy.PlayAnimation("walk");
SetNextPatrolPoint();
}
public override void Update(float deltaTime)
{
// 检查是否到达目标
if (Context.Navigator.IsNavigationFinished())
{
_waitTime -= deltaTime;
if (_waitTime <= 0)
{
SetNextPatrolPoint();
}
}
}
public override void PhysicsUpdate(float deltaTime)
{
if (!Context.Navigator.IsNavigationFinished())
{
Vector2 nextPos = Context.Navigator.GetNextPathPosition();
Vector2 direction = (nextPos - Context.Enemy.GlobalPosition).Normalized();
Context.Enemy.Velocity = direction * MoveSpeed;
Context.Enemy.MoveAndSlide();
// 转向
Context.Enemy.Scale = new Vector2(Mathf.Sign(direction.X), 1);
}
else
{
Context.Enemy.Velocity = Vector2.Zero;
}
}
private void SetNextPatrolPoint()
{
// 简单的巡逻:在起始点附近随机移动
Vector2 randomOffset = new Vector2(
GD.RandRange(-100, 100),
GD.RandRange(-50, 50)
);
Context.Navigator.TargetPosition = Context.PatrolStart + randomOffset;
_waitTime = GD.RandRange(1f, 3f);
}
}
/// <summary>
/// 追击状态
/// </summary>
public class ChaseState : StateBase<EnemyAIContext>
{
private const float MoveSpeed = 120f;
public override void Enter()
{
Context.Enemy.PlayAnimation("run");
}
public override void Update(float deltaTime)
{
// 更新目标位置
if (Context.Player != null)
{
Context.Navigator.TargetPosition = Context.Player.GlobalPosition;
}
}
public override void PhysicsUpdate(float deltaTime)
{
if (Context.Player == null) return;
Vector2 nextPos = Context.Navigator.GetNextPathPosition();
Vector2 direction = (nextPos - Context.Enemy.GlobalPosition).Normalized();
Context.Enemy.Velocity = direction * MoveSpeed;
Context.Enemy.MoveAndSlide();
Context.Enemy.Scale = new Vector2(Mathf.Sign(direction.X), 1);
}
}
/// <summary>
/// 攻击状态
/// </summary>
public class AttackState : StateBase<EnemyAIContext>
{
private const float AttackInterval = 1.0f;
private float _attackTimer;
public override void Enter()
{
Context.Enemy.PlayAnimation("attack");
_attackTimer = 0;
}
public override void Update(float deltaTime)
{
_attackTimer += deltaTime;
// 面向玩家
if (Context.Player != null)
{
float dir = Mathf.Sign(Context.Player.GlobalPosition.X - Context.Enemy.GlobalPosition.X);
Context.Enemy.Scale = new Vector2(dir, 1);
// 执行攻击
if (_attackTimer >= AttackInterval)
{
PerformAttack();
_attackTimer = 0;
}
}
}
public override void PhysicsUpdate(float deltaTime)
{
// 攻击时停止移动
Context.Enemy.Velocity = Context.Enemy.Velocity.MoveToward(Vector2.Zero, deltaTime * 500f);
Context.Enemy.MoveAndSlide();
}
private void PerformAttack()
{
// 播放攻击动画
Context.Enemy.PlayAnimation("attack");
// 伤害判定
var bodies = Context.Enemy.GetNode<Area2D>("AttackArea").GetOverlappingBodies();
foreach (var body in bodies)
{
if (body is PlayerController player)
{
// player.TakeDamage(Context.Enemy.AttackDamage);
}
}
}
}
/// <summary>
/// 逃跑状态
/// </summary>
public class FleeState : StateBase<EnemyAIContext>
{
private const float MoveSpeed = 150f;
public override void Enter()
{
Context.Enemy.PlayAnimation("run");
}
public override void Update(float deltaTime)
{
if (Context.Player == null) return;
// 远离玩家
Vector2 fleeDirection = (Context.Enemy.GlobalPosition - Context.Player.GlobalPosition).Normalized();
Context.Navigator.TargetPosition = Context.Enemy.GlobalPosition + fleeDirection * 200f;
}
public override void PhysicsUpdate(float deltaTime)
{
Vector2 nextPos = Context.Navigator.GetNextPathPosition();
Vector2 direction = (nextPos - Context.Enemy.GlobalPosition).Normalized();
Context.Enemy.Velocity = direction * MoveSpeed;
Context.Enemy.MoveAndSlide();
Context.Enemy.Scale = new Vector2(Mathf.Sign(direction.X), 1);
}
}
/// <summary>
/// 眩晕状态
/// </summary>
public class StunnedState : StateBase<EnemyAIContext>
{
public override void Enter()
{
Context.Enemy.PlayAnimation("stunned");
Context.Enemy.Velocity = Vector2.Zero;
}
public override void PhysicsUpdate(float deltaTime)
{
// 眩晕时不移动
Context.Enemy.Velocity = Vector2.Zero;
}
}
#endregion
}
5.5.2 敌人控制器
// 文件路径: Scripts/Enemy/EnemyController.cs
using Godot;
using GameFramework.StateMachine;
namespace GameFramework.Enemy
{
/// <summary>
/// 敌人控制器
/// </summary>
[GlobalClass]
public partial class EnemyController : CharacterBody2D
{
#region 导出属性
[Export] public float DetectionRange { get; set; } = 200f;
[Export] public float AttackRange { get; set; } = 40f;
[Export] public float LoseInterestRange { get; set; } = 400f;
[Export] public float MaxHealth { get; set; } = 100f;
[Export] public float AttackDamage { get; set; } = 10f;
#endregion
#region 属性
public float Health { get; private set; }
public bool IsStunned { get; private set; }
private float _stunTimer;
#endregion
#region 组件
private EnemyAIStateMachine _stateMachine;
private EnemyAIContext _context;
private AnimationPlayer _animator;
#endregion
public override void _Ready()
{
Health = MaxHealth;
_animator = GetNode<AnimationPlayer>("AnimationPlayer");
// 查找玩家
var player = GetTree().GetFirstNodeInGroup("Player") as PlayerController;
// 创建上下文
_context = new EnemyAIContext(this)
{
Player = player
};
// 创建状态机
_stateMachine = new EnemyAIStateMachine(_context);
_stateMachine.SetupEnemyStates();
_stateMachine.Initialize();
}
public override void _Process(double delta)
{
_stateMachine.Update((float)delta);
}
public override void _PhysicsProcess(double delta)
{
// 更新眩晕状态
if (IsStunned)
{
_stunTimer -= (float)delta;
if (_stunTimer <= 0)
{
IsStunned = false;
}
}
_context.StateTime += (float)delta;
_stateMachine.PhysicsUpdate((float)delta);
}
/// <summary>
/// 播放动画
/// </summary>
public void PlayAnimation(string animName)
{
if (_animator.HasAnimation(animName))
{
_animator.Play(animName);
}
}
/// <summary>
/// 受伤
/// </summary>
public void TakeDamage(float damage)
{
Health -= damage;
if (Health <= 0)
{
Die();
return;
}
// 眩晕
Stun(0.3f);
}
/// <summary>
/// 眩晕
/// </summary>
public void Stun(float duration)
{
IsStunned = true;
_stunTimer = duration;
}
/// <summary>
/// 死亡
/// </summary>
private void Die()
{
_stateMachine.Stop();
PlayAnimation("die");
// 延迟销毁
GetTree().CreateTimer(2f).Timeout += () => QueueFree();
}
}
}
5.6 使用下推自动机的UI导航系统
下推自动机非常适合UI导航系统,因为它天然支持"返回"操作。
// 文件路径: Scripts/UI/UIManager.cs
using Godot;
using GameFramework.StateMachine;
using System;
namespace GameFramework.UI
{
/// <summary>
/// UI状态枚举
/// </summary>
public enum UIState
{
MainMenu,
Settings,
LevelSelect,
Pause,
Inventory,
Shop,
Dialogue
}
/// <summary>
/// UI上下文
/// </summary>
public class UIContext
{
public Control UIRoot { get; }
public GameManager GameManager { get; }
public UIContext(Control uiRoot, GameManager gameManager)
{
UIRoot = uiRoot;
GameManager = gameManager;
}
}
/// <summary>
/// UI状态基类
/// </summary>
public abstract class UIStateBase : StateBase<UIContext>
{
protected Control Panel { get; private set; }
public void SetPanel(Control panel)
{
Panel = panel;
}
public override void Enter()
{
Panel?.Show();
Panel?.SetProcess(true);
}
public override void Exit()
{
Panel?.Hide();
Panel?.SetProcess(false);
}
}
/// <summary>
/// UI管理器 - 使用下推自动机
/// </summary>
public partial class UIManager : Node
{
[Export] public Control MainMenuPanel;
[Export] public Control SettingsPanel;
[Export] public Control PausePanel;
[Export] public Control InventoryPanel;
private PushdownStateMachine<UIContext> _uiStateMachine;
private UIContext _context;
public override void _Ready()
{
// 创建上下文
_context = new UIContext(GetParent<Control>(), GetNode<GameManager>("/root/GameManager"));
// 创建下推状态机
_uiStateMachine = new PushdownStateMachine<UIContext>(_context);
// 注册UI状态
RegisterUIStates();
// 默认打开主菜单
OpenUI("MainMenuState");
}
public override void _Process(double delta)
{
_uiStateMachine.Update((float)delta);
// 处理返回键
if (Input.IsActionJustPressed("ui_cancel"))
{
GoBack();
}
}
/// <summary>
/// 注册UI状态
/// </summary>
private void RegisterUIStates()
{
// 主菜单
var mainMenu = new MainMenuState();
mainMenu.SetPanel(MainMenuPanel);
_uiStateMachine.RegisterState("MainMenuState", mainMenu);
// 设置
var settings = new SettingsState();
settings.SetPanel(SettingsPanel);
_uiStateMachine.RegisterState("SettingsState", settings);
// 暂停
var pause = new PauseState();
pause.SetPanel(PausePanel);
_uiStateMachine.RegisterState("PauseState", pause);
// 背包
var inventory = new InventoryState();
inventory.SetPanel(InventoryPanel);
_uiStateMachine.RegisterState("InventoryState", inventory);
}
/// <summary>
/// 打开UI(推入新状态)
/// </summary>
public void OpenUI(string stateId)
{
_uiStateMachine.PushState(stateId);
}
/// <summary>
/// 返回上一UI(弹出状态)
/// </summary>
public void GoBack()
{
// 如果栈中只有主菜单,不返回
if (_uiStateMachine.StackCount <= 1)
return;
_uiStateMachine.PopState();
}
/// <summary>
/// 替换当前UI
/// </summary>
public void ReplaceUI(string stateId)
{
_uiStateMachine.ReplaceState(stateId);
}
/// <summary>
/// 清空并打开UI
/// </summary>
public void ClearAndOpen(string stateId)
{
_uiStateMachine.ClearAndPush(stateId);
}
/// <summary>
/// 主菜单状态
/// </summary>
public class MainMenuState : UIStateBase
{
public override void Enter()
{
base.Enter();
// 暂停游戏
Context.GameManager?.PauseGame();
}
public override void Update(float deltaTime)
{
// 处理主菜单输入
}
}
/// <summary>
/// 设置状态
/// </summary>
public class SettingsState : UIStateBase
{
public override void Enter()
{
base.Enter();
// 加载设置数据
}
}
/// <summary>
/// 暂停状态
/// </summary>
public class PauseState : UIStateBase
{
public override void Enter()
{
base.Enter();
Context.GameManager?.PauseGame();
// 显示暂停菜单
}
public override void Exit()
{
base.Exit();
Context.GameManager?.ResumeGame();
}
}
/// <summary>
/// 背包状态
/// </summary>
public class InventoryState : UIStateBase
{
public override void Enter()
{
base.Enter();
// 刷新背包显示
}
public override void Update(float deltaTime)
{
// 处理背包操作
}
}
}
}
5.7 最佳实践
5.7.1 状态设计原则
- 单一职责:每个状态只负责一种行为,避免状态过于复杂
- 状态纯净:状态不应保存长期数据,数据应存储在上下文中
- 转换明确:状态转换条件应清晰明确,避免歧义
5.7.2 性能优化
// 文件路径: Scripts/Framework/StateMachine/StateMachineOptimizer.cs
using System.Collections.Generic;
namespace GameFramework.StateMachine
{
/// <summary>
/// 状态机优化工具
/// </summary>
public static class StateMachineOptimizer
{
/// <summary>
/// 条件缓存 - 避免每帧重复计算
/// </summary>
public class ConditionCache<T>
{
private readonly Dictionary<string, bool> _cache = new();
private int _frameCounter;
private int _cacheFrame;
/// <summary>
/// 获取或计算条件值
/// </summary>
public bool GetOrCompute(string key, System.Func<T, bool> condition, T context, int currentFrame)
{
// 新帧时清空缓存
if (currentFrame != _cacheFrame)
{
_cache.Clear();
_cacheFrame = currentFrame;
}
if (_cache.TryGetValue(key, out var cached))
{
return cached;
}
var result = condition(context);
_cache[key] = result;
return result;
}
}
/// <summary>
/// 延迟评估包装器 - 只在必要时评估条件
/// </summary>
public static StateCondition<T> Lazy<T>(StateCondition<T> condition)
{
return context => condition(context);
}
/// <summary>
/// 组合条件 - 短路求优
/// </summary>
public static StateCondition<T> And<T>(params StateCondition<T>[] conditions)
{
return context =>
{
foreach (var condition in conditions)
{
if (!condition(context))
return false;
}
return true;
};
}
/// <summary>
/// 任一条件满足
/// </summary>
public static StateCondition<T> Or<T>(params StateCondition<T>[] conditions)
{
return context =>
{
foreach (var condition in conditions)
{
if (condition(context))
return true;
}
return false;
};
}
/// <summary>
/// 条件取反
/// </summary>
public static StateCondition<T> Not<T>(StateCondition<T> condition)
{
return context => !condition(context);
}
/// <summary>
/// 添加冷却时间的条件
/// </summary>
public static StateCondition<T> WithCooldown<T>(StateCondition<T> condition, float cooldown, System.Func<T, float> getLastTime)
{
return context =>
{
if (!condition(context))
return false;
float currentTime = Godot.Time.GetTimeDict()["tick"] / 1000f;
return currentTime - getLastTime(context) >= cooldown;
};
}
}
}
5.7.3 调试工具
// 文件路径: Scripts/Framework/StateMachine/StateMachineDebugger.cs
using Godot;
using System.Text;
namespace GameFramework.StateMachine
{
/// <summary>
/// 状态机调试器
/// </summary>
[GlobalClass]
public partial class StateMachineDebugger : Node
{
[Export] public bool EnableDebug { get; set; } = true;
[Export] public Vector2 DebugPosition { get; set; } = new Vector2(10, 10);
private Label _debugLabel;
public override void _Ready()
{
if (!EnableDebug)
return;
// 创建调试显示
_debugLabel = new Label();
_debugLabel.Position = DebugPosition;
AddChild(_debugLabel);
}
/// <summary>
/// 显示状态机信息
/// </summary>
public void ShowStateMachineInfo<T>(StateMachine<T> stateMachine)
{
if (!EnableDebug || _debugLabel == null)
return;
var sb = new StringBuilder();
sb.AppendLine($"状态机: {stateMachine.GetType().Name}");
sb.AppendLine($"当前状态: {stateMachine.CurrentStateId}");
_debugLabel.Text = sb.ToString();
}
/// <summary>
/// 显示下推状态机信息
/// </summary>
public void ShowPushdownStateMachineInfo<T>(PushdownStateMachine<T> stateMachine)
{
if (!EnableDebug || _debugLabel == null)
return;
var sb = new StringBuilder();
sb.AppendLine($"下推状态机");
sb.AppendLine($"当前状态: {stateMachine.CurrentStateId}");
sb.AppendLine($"栈深度: {stateMachine.StackCount}");
sb.AppendLine("栈内容:");
foreach (var stateId in stateMachine.GetStackStateIds())
{
sb.AppendLine($" - {stateId}");
}
_debugLabel.Text = sb.ToString();
}
public override void _Process(double delta)
{
// 可以在这里更新调试显示
}
}
}
5.8 小结
本章介绍了三种状态管理模式:
- 有限状态机(FSM):最基础的状态管理,适合状态数量较少、转换关系简单的场景
- 分层状态机(Hierarchical FSM):通过状态嵌套解决状态爆炸问题,适合复杂角色AI
- 下推自动机(Pushdown Automata):使用栈管理状态,支持历史记录和返回操作,适合UI导航
选择合适的状态管理模式应根据具体需求:
- 玩家角色控制:FSM或Hierarchical FSM
- 敌人AI:Hierarchical FSM
- UI导航系统:Pushdown Automata
- 游戏流程控制:Pushdown Automata
状态模式的核心价值在于将复杂的行为逻辑分解为独立的状态单元,每个单元只关注自身的逻辑,通过清晰的转换规则连接起来,使代码更易于理解和维护。