表现层
运行时状态如何到达 UI、单位视图、VFX、音频和浮动文本,以及为什么玩法从不等待缺失的视觉资产。
搭建自定义战斗 UI、接 FX 节点,或排查动画卡住阶段转换时,来查这一页。表现层让战斗状态变得可见。它观察运行时状态和事件、发出反馈请求、播放结果动画,但从不拥有玩法规则。一次出牌可以同时展开成伤害数字、状态图标、VFX 和镜头震动,全部由同一条事件流驱动:

表现边界
| 区域 | 拥有 | 不拥有 |
|---|---|---|
| Runtime | HP、护甲、能量、牌堆、状态、阶段、事件。 | 像素布局、声音路由、粒子质量。 |
| UI | 手牌 widget、单位血条、状态图标、意图 widget、选择/奖励面板。 | 战斗规则或出牌合法性。 |
| Unit view | 单位 sprite、程序化运动、受击/治疗/死亡反应、状态视觉。 | 伤害计算或状态层数。 |
| FX nodes | VFX、SFX、浮动文本、闪烁、滑动、震屏、动画的请求。 | 最终资产打磨、渲染管线兼容、mixer 设置。 |
即使视觉效果播放失败,玩法也照常结算。
事件驱动的 UI
反应式循环始终是同一套:controller 修改状态并触发事件,你的 handler 被唤醒,通过 GCSApi 读取最新状态,再更新 widget。
自定义 UI 应该订阅运行时事件,然后读取当前状态:
private IDisposable _energySub;
void OnEnable()
{
_energySub = GCSApi.OnEnergyChanged(_ => RefreshEnergy());
RefreshEnergy();
}
void OnDisable()
{
_energySub?.Dispose();
}
void RefreshEnergy()
{
energyLabel.text = $"{GCSApi.PlayerEnergy}/{GCSApi.MaxEnergy}";
}
事件告诉你什么时候该刷新,GCSApi 告诉你当前值是什么。
核心 UI 刷新事件
| UI 区域 | 监听事件 | 读取状态 |
|---|---|---|
| 回合与阶段标签 | OnTurnStarted、OnTurnEnded、OnEnemyTurnStarted、OnBattleEnded | GCSApi.Phase、GCSApi.TurnNumber、GCSApi.IsPlayerTurn |
| 能量 | OnEnergyChanged、OnEnergyGained、OnEnergySpent | GCSApi.PlayerEnergy、GCSApi.MaxEnergy |
| 手牌 | 卡牌生命周期事件、OnCardModified、OnEnergyChanged | GCSApi.Hand()、GCSApi.CanPlayCard、GCSApi.GetEffectiveEnergyCost |
| 单位条 | OnUnitHpChanged、OnArmorGained、OnArmorLost、OnUnitDied | GCSApi.Hp、GCSApi.MaxHp、GCSApi.Armor、GCSApi.IsAlive |
| 状态 widget | OnStatusChanged、OnStatusTicked、状态 transition 事件 | GCSApi.StatusesOn(unit) |
| 敌人意图 | OnEnemyUnitIntentChanged、OnEnemyPhaseChanged | GCSApi.PendingIntents、GCSApi.CurrentIntentTag、GCSApi.CurrentIntentValue |
| 选择 UI | OnEffectChoiceOffered、OnEffectChoiceSelected、OnEffectChoiceSkipped | Choice payload 加 GCSApi.IsWaitingForChoice |
| 奖励 UI | OnBattleRewardOffered、OnBattleRewardSelected、OnBattleRewardSkipped | Reward payload 加 GCSApi.IsWaitingForReward |
UnitView 行为
内置的 UnitView 绑定到一个 UnitState,并订阅:
OnUnitHpChangedOnUnitDiedOnStatusChangedOnStatusTicked
它用这些事件播放受击、治疗、死亡和状态动画,同时按 UnitId 注册自身,让表现节点能找到目标单位的 view。
自定义项目可以复用 UnitView、派生自己的 view 行为,或整个替换掉它。稳定的集成点是运行时事件 payload 加 UnitId。
浮动文本
FloatingTextSystem 暴露一个表现请求事件:
| 请求字段 | 含义 |
|---|---|
Target | 接收文本的单位。 |
Kind | Damage、Heal、Armor、Block 或 Custom。 |
Content | Amount 或 Text。 |
Amount | 要显示的数字。 |
Text | 自定义标签。 |
Color, UseCustomColor | 可选的颜色覆盖。 |
IncludeSign | 是否显示正负号。 |
ScaleMultiplier, DurationMultiplier | 单次请求的视觉微调。 |
Offset | 目标附近的位置偏移。 |
FlowGraph 的 Show Floating Text 节点会发出这些请求。自定义浮动文本 presenter 可以订阅 FloatingTextSystem.Requested。
FlowGraph 表现节点
| 节点 | 运行时效果 |
|---|---|
| Play VFX | 在单位或位置上实例化或请求 VFX prefab。 |
| Play Animation | 向目标单位的表现发送 animation trigger。 |
| Play SFX | 播放一次性的 audio clip。 |
| Flash Unit | 目标 view 存在时调用 UnitView.Flash。 |
| Slide Unit | 短暂移动目标的表现。 |
| Show Floating Text | 发送 FloatingTextRequest。 |
| Shake Camera | 请求镜头震动。 |
在玩法状态改变之后再用这些节点,让视觉反映真实结果。
动画 Gate 与等待
状态机可以推迟转换,等表现类等待完成。
| 功能 | 用途 |
|---|---|
| Animation gate | 在 gated coroutine 或动画运行期间扣住阶段转换。 |
| Flow wait | 让图按秒或按 animation id 延迟后续执行。 |
| Enemy gaps | 在敌人行动之间加入可读的间隔。 |
Game Card Monitor 的 Gate 页签显示当前谁持有 animation gate、acquire/release 日志和超时时间,用于定位卡住的转换:

这也是自定义 UI 应该反应式处理状态变化的原因。一次出牌可能引发状态变化、动画、等待、选择和后续转换。
VFX、音频与渲染管线范围
表现的图契约是稳定的,最终资产则是另一回事:
- VFX prefab 可以之后替换或升级。
- SFX clip 和 mixer 路由可以之后配置。
- 渲染管线相关内容(材质、shader、后处理)属于资产层,不属于图。
不要把管线专属假设写进玩法图。图只负责请求玩法反馈,如何渲染交给资产和表现层决定。
失败行为
表现层要能体面地降级。
| 缺失项 | 预期结果 |
|---|---|
| 缺少 unit view | 玩法照常结算;针对该单位的视觉请求可能被跳过。 |
| 缺少 VFX prefab | 玩法照常结算;没有粒子出现。 |
| 缺少 SFX clip | 玩法照常结算;没有声音。 |
| 缺少浮动文本 presenter | 玩法照常结算;请求没有可见的订阅者。 |
| Animation wait 未安排 | 执行立即继续。 |
用运行时日志和 Monitor 做诊断,但绝不要因为可选表现资产缺失而阻塞战斗。