Skip to content

State——游戏状态机

到目前为止,我们写的程序只有一种“活法”:开局是什么样,跑到退出还是什么样。真实的游戏不是这样——它有全局阶段:主菜单、游戏中、暂停、结算……每个阶段有自己的一套系统该跑,有自己的一批实体该在场,阶段切换的瞬间还有搭台拆台的活要干。

用现有的工具硬做,你大概会定义一个 enum Phase 资源,然后:

  • 每个系统自己把关。移动、AI、计分,每个函数开头都来一段 if *phase != Phase::Playing { return; }——漏写一处就出鬼:人都回主菜单了,子弹还在某个角落飞。
  • “切换的瞬间”没有挂载点。进入菜单要搭 UI,离开菜单要拆——可“进入”和“离开”不是任何一帧的常态,而是两帧之间的边沿。你得自己存一份“上一帧的阶段”,每帧对比,手写边沿检测。
  • 该退场的实体没人管。一局游戏里生成的敌人、子弹、特效,回菜单时得一个不漏地清掉。靠手工列清单,加一种实体就要记一笔,漏一笔就留一地“幽灵”。

这三件事——按阶段启停系统、在切换瞬间做事、按阶段管理实体——Bevy 打包成了一套机制:State(状态)。你用一个 enum 声明全部阶段,引擎把它变成一台类型安全的状态机:run_if(in_state(...)) 负责启停,OnEnter/OnExit 调度负责切换瞬间,DespawnOnExit 组件负责自动清场。

本章先用最普通的 States 走通“定义状态 → 切换状态 → 系统跟着开关”的全流程,看清转换发生的精确时机;然后学转换瞬间的三个调度和状态作用域实体;最后是状态的两种进阶组织方式——依附于其他状态存在的 SubStates(子状态),和由其他状态推导出来的 ComputedStates(计算状态)。

本章的舞台在灰岩镇——商队上一章刚到的那个镇子。杂货铺角落立着一台老街机,《勇者斗史莱姆》,护卫工作告一段落的罗兰正好路过。待机画面、投币开局、中途暂停,一台街机一天的营业,恰好是一台状态机。开机。