Phaser 回合制行动条:速度、插队、延迟行动和预览

讲解回合制战斗中的行动条系统,覆盖速度计算、行动预览、插队技能、延迟行动、状态影响和 UI 同步。

为什么值得单独做成系统

回合制 RPG 战斗里,角色和敌人不按固定你一刀我一刀行动,而是根据速度进入行动条。盗贼可以插队,法师蓄力会延迟,Boss 狂暴后行动频率变快。玩家需要在 UI 上提前看到这些变化。

行动条系统如果只用数组 shift,会很难支持速度变化、延迟、插队、眩晕和预览。更稳的做法是把每个战斗单位当作时间轴上的事件,所有影响行动顺序的技能都修改时间戳或行动成本。 本文会按一个可上线的小系统来拆,不追求炫技,而是把数据结构、状态流、玩家反馈、调试工具和发布检查说清楚。Phaser 的优势是让画面和交互快速成型,但越是快速,越需要把规则层和表现层分开。

核心架构

flowchart TD
  N1["CombatantStats"] --> N2["InitiativeClock"]
  N3["StatusModifier"] --> N2["InitiativeClock"]
  N2["InitiativeClock"] --> N4["TimelineQueue"]
  N4["TimelineQueue"] --> N5["PreviewResolver"]
  N5["PreviewResolver"] --> N6["TimelineUI"]
  N7["玩家选择技能"] --> N8["TurnCommit"]
  N8["TurnCommit"] --> N4["TimelineQueue"]

这张图的重点是单向流动。CombatantStats、InitiativeClock、TimelineQueue、StatusModifier、PreviewResolver、TurnCommit、TimelineUI 不应该互相随意读写。输入或场景事件进入模型,模型输出快照或事件,Phaser 表现层再根据结果更新 Sprite、Graphics、Sound 和 UI。只要这条边界稳定,后续加内容、加难度、加存档或加多人同步,都不会把系统推倒重写。

时间轴比回合数组灵活

每个单位有 nextActTime。速度越高,行动间隔越短。TimelineQueue 每次取 nextActTime 最小的单位行动。行动结束后,根据技能耗时和速度重新排入队列。这样加速、减速、延迟和插队都能用同一套时间模型表达。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

速度变化要影响未来

给角色加速时,是否立刻提前行动?规则要明确。常见做法是状态影响下一次排队,已经快要行动的单位不会被突然大幅重排;插队技能则明确修改 nextActTime。把这两类效果区分开,玩家才会觉得 UI 预览可信。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

预览要显示技能后果

玩家选择技能时,TimelineUI 可以临时显示行动顺序变化。例如重攻击耗时长,会让角色下次行动变晚;轻攻击耗时短,下次更快。PreviewResolver 不提交状态,只用当前快照模拟一次。取消选择后预览消失。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

延迟行动要可控

有些战术游戏允许玩家等待,把行动推迟到稍后。Delay 不是跳过,而是把 nextActTime 加一个固定或按速度计算的值。玩家应看到等待后自己会排到哪里。若延迟会错过防御状态或 Buff 回合,也要在提示里说明。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

状态持续时间按时间还是行动

中毒、护盾、眩晕可以按行动次数消耗,也可以按时间轴消耗。两种都合理,但不能混乱。建议数据里明确 durationType: turns 或 ticks。TimelineQueue 推进时,统一让 StatusModifier 结算。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

插队技能要限制频率

如果很多技能都能把自己或队友提前,时间轴可能被玩坏。插队应有成本、冷却或最大提前幅度。PreviewResolver 也要能显示敌人被推后或队友被提前多少,不然玩家无法评估技能价值。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

UI 动画不能决定顺序

行动头像在时间轴上滑动只是表现。真正的队列顺序由 TimelineQueue 决定。动画播放时如果状态变化,UI 应重新插值到新位置。不要等头像动画结束才提交回合,否则快进和跳过会破坏战斗状态。

实现时建议先用最简单的调试图形验证规则,再接正式美术。比如先画出区域、方向、时间轴、占用格或检测范围,确认数据正确后再添加粒子、镜头、音效和过渡动画。这样做不花哨,但能避免很多“看起来对,规则其实错”的问题。

TypeScript 实现骨架

interface Combatant { id: string; speed: number; nextActTime: number; stunned?: boolean }
class TimelineQueue {
  constructor(private units: Combatant[]) {}
  next() {
    return this.units.filter(u => !u.stunned).sort((a, b) => a.nextActTime - b.nextActTime)[0];
  }
  commitAction(unitId: string, cost: number, now: number) {
    const unit = this.units.find(u => u.id === unitId);
    if (!unit) return;
    unit.nextActTime = now + cost / Math.max(1, unit.speed);
  }
  preview(unitId: string, cost: number, now: number) {
    return this.units.map(u => ({ ...u, nextActTime: u.id === unitId ? now + cost / Math.max(1, u.speed) : u.nextActTime }))
      .sort((a, b) => a.nextActTime - b.nextActTime);
  }
  delay(unitId: string, amount: number) {
    const unit = this.units.find(u => u.id === unitId);
    if (unit) unit.nextActTime += amount;
  }
}

这段代码只展示核心边界,不是完整项目代码。真实项目里还需要补配置加载、错误码、事件派发、对象池、性能采样和测试。关键是让核心规则能独立运行,Phaser 层只是把规则结果变成玩家能感知的反馈。

落地步骤

  1. 第一,先把 CombatantStats 和 InitiativeClock 写成普通 TypeScript 模型。不要让它们依赖 Phaser Scene、Sprite 或 Camera。核心模型越普通,越容易写测试、做编辑器预览和复现玩家问题。
  2. 第二,Phaser 层只做适配:接收输入、播放动画、更新图形、触发音效。它可以很薄,但必须清楚。只要某段规则开始读取 Sprite 的 visible、alpha 或动画状态,就说明边界正在变脏。
  3. 第三,给 TimelineQueue 或同等复杂的中间结果做调试显示。开发模式里能看到状态、阈值、候选对象、失败原因和耗时,后续调参才不会靠猜。
  4. 第四,准备三组测试夹具:正常流程、边界流程、错误配置。正常流程验证体验,边界流程验证稳定性,错误配置验证系统会报出人能看懂的问题。

检查清单

  • 确认 CombatantStats 的状态可以序列化,能写入存档或调试日志。
  • 确认 InitiativeClock 的配置有默认值、版本号和校验错误。
  • 确认快速点击、暂停、切后台、读档和切场景不会重复提交关键事件。
  • 确认失败反馈足够具体,玩家能知道是条件不足、输入中断、资源不够还是规则禁止。
  • 确认低端机有降级策略,尤其是粒子、音效、动态对象和调试图层。
  • 确认开发模式可以导出最近关键事件,方便复现玩家反馈。

常见误区

第一类误区,是把表现当成事实。动画播完、按钮亮着、Sprite 存在,都只能说明表现层当前长什么样,不能说明规则已经完成。规则事实应该存在于模型和事件里。

第二类误区,是只为了第一个关卡写逻辑。第一个关卡对象少、输入慢、节奏简单,临时判断很难暴露问题。等内容增加,重复触发、配置错误和性能峰值会一起出现,早期的边界会决定后期成本。

第三类误区,是没有设计失败路径。复杂系统一定会遇到失败,好的失败路径会告诉玩家和开发者发生了什么;坏的失败路径只会留下一句操作失败,甚至什么都不显示。

发布前验证

发布前至少跑一次规则级测试和一次运行时冒烟。规则级测试不需要启动浏览器,直接喂数据,断言状态和事件。运行时冒烟则在 Phaser 场景里验证输入、反馈、暂停、重开和边界情况。若系统涉及经济、存档或排行榜,还要记录 requestId 或事件 id,保证重复提交不会造成重复奖励或重复扣费。

额外实践建议

  • 时间轴系统要能在无 Phaser 环境下测试。
  • 技能预览和真实提交必须使用同一套计算函数。
  • 状态持续时间的单位要写进配置,避免策划误解。

运行时观测与调参

行动条系统的调参重点是“玩家是否相信预览”。建议记录每次玩家打开技能预览时的队列变化、最终是否选择该技能、技能提交后真实队列是否和预览一致。若大量玩家取消高耗时技能,可能说明技能价值不足,也可能是行动延迟提示太吓人。若预览和提交不一致,必须优先修,因为这会直接破坏战术信任。战斗复盘里还可以显示每个单位本场行动次数和平均间隔,帮助判断速度属性是否过强。回合制系统不怕复杂,怕的是复杂但不可解释。

还要特别观察 Boss 和召唤物。Boss 往往有多段行动,召唤物数量又会放大时间轴拥挤问题。UI 可以只展示未来若干个关键行动,隐藏低优先级召唤物的重复头像,避免时间轴变成无法阅读的长队列。

结语

回合制行动条:速度、插队、延迟行动和预览 的关键不是某个 API,而是把可解释的规则交给模型,把可感知的反馈交给 Phaser。只要这条线清楚,项目就能持续扩展;如果所有逻辑都塞进 Scene 回调,第一版越快,后面的维护压力越大。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页