动作游戏中,角色在任意时刻可能处于站立、跳跃、攻击、受击等多种状态。状态之间存在复杂的转换和冲突——这是动作游戏开发中最经典的架构问题之一。

在开发一款轻度动作 RPG 时,我经历了三种方案的演进,最终落地于分层状态机。本文记录这个思考过程。

方案一
状态栈
方案二
冲突管理器
方案三 ✓
分层状态机
方案演进路线
==================== 状态基本结构 ====================

状态的基本结构

每个状态拥有独立的生命周期:

OnEnter
进入状态
初始化数据
OnUpdate
每帧执行
驱动逻辑
OnExit
离开状态
清理资源
状态生命周期三阶段

从持续性角度,状态分为两类:

类型行为典型例子
持久状态 条件满足就持续存在,每帧检测 Stand(在地面→生效)、Run(有速度→生效)
触发状态 事件触发,执行完毕自动退出 Attack(动画播完→退出)、Skill(释放完→退出)

角色通过一张状态表展示当前所有生效的状态。核心问题随之而来——当多个状态同时存在时,冲突怎么办?

==================== 方案一 ====================

方案一:状态栈

最直观的思路:每个按钮对应一个状态栈,栈内管理连招序列,所有按钮的状态栈并行执行。

攻击按钮
Attack_3 ← 执行中
Attack_2
Attack_1
跳跃按钮
Jump ← 执行中
闪避按钮
Dodge ← 执行中
每个按钮维护一个状态栈,栈顶为当前执行状态

对于状态混合(如角色在空中),有两种处理方式:

并行方式
Jump
Fall
同时存在,各自执行
VS
串联方式
Jump
Fall
到达最高点后自动切换
⚠️ 问题:耦合度高
每个状态必须知道"下一个是谁"。简单场景可以用配置表,但复杂场景下——
Jump(起跳中)
被击飞
KnockUp
被倒地
KnockDown
正常落下
Fall
每个状态都要维护这样的分支,复杂度 O(n²)
==================== 方案二 ====================

方案二:第三方冲突管理器

既然分散管理太混乱,那就集中——引入一个管理器统一仲裁。

状态 A"后继可以是 B,C,D"
冲突管理器查表裁决
进入状态 B
状态只声明候选后继,管理器负责裁决
⚠️ 问题:冲突关系没有消失
代码层面解耦了,但冲突表的规模仍随状态数增长而膨胀。从"分散在各状态中"变成"集中在一张大表里"——本质困境未解。
==================== 方案三 ====================

方案三:分层状态机(最终方案)

核心思路:按职责分层,层内用状态栈,层间用优先级仲裁。

P3 最高
受击层
Hit / KnockDown / KnockUp
▼ 高优先级直接打断低优先级
P2
技能层
Skill_1 → Skill_2 → Skill_3(层内连招)
▼ 高优先级直接打断低优先级
P1 最低
运动层
Stand / Run / Jump / Fall
分层状态机架构 — 层间优先级自动仲裁,层内独立管理
✓ 为什么这样更好?
  • 层内简单 — 技能层只处理技能间的连招和打断,不用关心运动状态
  • 跨层自然 — 受击层优先级最高,受击时直接打断技能,无需额外配置
  • 复杂度从 O(n²) 降到 O(n) — 每个状态只需关心同层内的少量状态
==================== 对比 ====================

三种方案对比

状态栈 冲突管理器 分层状态机
冲突管理 每个状态自行维护 集中在冲突表 按层隔离 + 层间优先级
耦合度
可维护性 O(n²) 表膨胀 O(n)
连招支持 栈式压入,直观 需额外设计 技能层内栈式管理
适用场景 状态极少的简单游戏 中等复杂度 动作游戏推荐方案

具体实现在 Skill.csSkillState 中,将普通状态与技能状态统一纳入分层状态机体系管理。相关项目详见 XYGame 项目页