FSM状态切换必须由显式事件驱动,禁止轮询;状态类Update只执行行为,跳转统一通过TransitionTo接口;推荐用std::unique_ptr管理状态生命周期,并用std::map+enum实现可维护的跳转表。

FSM 状态切换必须用显式事件触发,不能靠轮询判断
游戏 AI 的 FSM 容易写成“每帧检查所有条件”,结果状态跳变不稳、逻辑耦合严重。真正可控的 FSM 要求每个状态只负责自身行为,状态变更由外部明确事件(如 OnPlayerSeen、OnHealthLow)驱动,而非在 Update() 里反复 if-else。
- 状态类的
Update()只做行为执行(移动、攻击、播放动画),不包含跳转逻辑 - 跳转统一收口到
TransitionTo(State* newState)或类似接口,确保跳转可追踪、可打断 - 避免在状态内部调用
SetState(AttackState)—— 这会让调用链散落在各处,调试时根本不知道谁触发了切换 - 事件建议用 enum 或轻量 struct 封装,别直接传 raw string 或 int,否则
switch (event)易漏处理、难补全
用指针管理状态对象时,注意生命周期和重入风险
C++ 里常见写法是 m_currentState 指向堆上分配的 State*,但容易踩两个坑:状态析构后指针悬空;同一帧内多次调用 TransitionTo 导致 new 出的对象没 delete 就被覆盖。
- 推荐用
std::unique_ptr<state></state>管理,构造新状态后立刻 swap,旧状态自动析构 - 在
TransitionTo开头加 guard:if (newState == m_currentState) return,防止重复切换引发初始化逻辑重跑 - 不要在状态构造函数里访问
GameObject成员(如m_owner->GetPosition()),因为此时 owner 可能还没完成初始化,或刚被销毁 - 若需共享数据,把上下文抽成
AiContext结构体传入每个状态,而不是让状态持有Enemy*强引用
std::map + enum 实现状态跳转表,比硬编码 switch 更易维护
当状态数超过 4–5 个,或者跳转条件开始交叉(比如 PatrolState 在听到声音时进 AlertState,而 AttackState 听到声音却要进 EvadeState),硬写 if/else 或嵌套 switch 很快失控。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 定义
enum class StateId { Patrol, Alert, Attack, Evade };,所有状态类实现virtual StateId GetId() const = 0; - 用
std::map<:pair eventid>, StateId></:pair>存跳转规则,加载配置或初始化时填好 - 切换时查表:
auto it = m_transitionTable.find({m_currentState->GetId(), event}); if (it != m_transitionTable.end()) TransitionTo(CreateState(it->second)); - 这样增删状态、调整跳转逻辑只需改表,不碰状态类代码;也方便运行时热重载跳转规则
FSM 不适合处理“渐变行为”,比如从巡逻到追击的平滑过渡
纯 FSM 天然离散——要么在 Patrol,要么在 Chase,中间没有“半巡逻半追击”状态。但玩家感知上,AI 突然转向、加速会显得僵硬。
立即学习“C++免费学习笔记(深入)”;
- 不要为了“平滑”硬塞一个
ChasingButNotSureYet状态,而是把渐变逻辑下沉:在 PatrolState 的Update()里根据玩家距离动态调整移动速度/转向速率,视觉上已有过渡感 - 需要混合行为时(边后退边射击),优先考虑分层:上层 FSM 控制宏观意图(
Engage/Retreat),下层用 Behavior Tree 或直接脚本组合原子动作 - 如果项目后期发现 FSM 经常要拆出子状态、加并行分支,说明它已超出适用边界——这时候不是 FSM 写得不够好,是该换架构了
事情说清了就结束

















