3.2 UMG HUD 与伤害飘字:数据驱动 UI
核心结论
HUD 与飘字是反馈层里最"晚到"的(50~200ms 那一档),但工程上最容易乱:飘字要随机抖动、要跟随 3D 位置、要合并同源伤害……做法的核心是数据驱动 + 独立的 Widget 池,把表现和逻辑彻底分家(UMG 最佳实践 https://www.wanxiangyundang.top/read/unreal-engine-5-8-docs/umg-best-practices-in-unreal-e-1da5 讲设计原则,我讲落地)。
HUD 的组件分层
W_HUD_Root(Canvas)
+-- W_HUD_SafeLayer -- 永远不被遮挡的玩家状态(血条/体力)
+-- W_HUD_Gameplay -- gameplay 相关(小地图、刷怪计数)
+-- W_FloatingLayer -- 飘字/boos 提示(3D 投影到屏幕)
+-- W_HUD_Menu -- 菜单(开关)
规则:HUD 不写任何玩法逻辑,它只订阅事件、显示数据。血条变化 = 订阅 OnHealthChanged,连"什么时候低血量变红"都写在 Widget 的样式逻辑里。
伤害飘字的工程化
飘字看着简单,做好要处理五个问题:
| 问题 | 我的做法 |
|---|---|
| 位置 | 受击者骨骼位置 Project 到屏幕,再加随机偏移 |
| 合并 | 同一目标 100ms 内的伤害合并显示("x3"或累加) |
| 暴击 | 大字号 + 金色 + 特殊曲线 |
| 卡顿 | 对象池预生成 30 个飘字 Widget |
| 边界 | 目标在屏幕外时贴边显示(指引方向) |
飘字的 UI 池实现
W_FloatingLayer (CanvasPanel)
生成时:预创建 20~30 个 W_DamageNumber(隐藏)
触发:FindHidden -> Fill Data -> PlayFloatAnim
动画完:Hide(交还池)
关键:飘字每帧重算目标屏幕位置(如果跟随 3D 目标),所以动画用偏移动画而不是绝对位置动画(绝对位置在目标移动时会跳)。
HUD 数据流(数据驱动)
[ASC 属性变化]
-> OnHealthChanged 事件
-> PlayerController 广播 HUD Update
-> W_HUD_SafeLayer 的 HealthBar -> 设置百分比与颜色
不要:Widget 里 Cast 到角色读 Attribute(联机时客户端数据时机错)。要:OnAttributeChanged 广播到 UI(GAS 的标准做法)。
常见坑与解法
坑 1:HUD 也在暂停时被冻结
原因:HUD 的动画用同一个时间轴。
解法:暂停时把 HUD 动画设 Pause 或给飘字 Widget 用 GameViewportClient 的独立计时(常见做法:暂停时飘字立即结算消失,不再播新飘字)。
坑 2:飘字数量爆炸(群攻时满屏数字)
解法: - 合并(100ms 内同目标); - 同屏上限(最多 12 个,多的直接丢弃或合并); - 群攻的 AOE 伤害只显示"总伤害"大字 + 目标身上小飘字。
坑 3:UI 挡住操作
解法:所有纯表现 Widget 的 Visibility 设 Hit Test Invisible(不是 Self Hit Test)。能点的才接受点击。
坑 4:性能:HUD Tick 每帧刷数据
解法:事件驱动——血条只在变化时更新;小地图用低频 Tick(5~10Hz)+ 插值动画平滑。UMG 的 Tick 是游戏线程的债。
相关节点速查
- UMG 最佳实践:https://www.wanxiangyundang.top/read/unreal-engine-5-8-docs/umg-best-practices-in-unreal-e-1da5
- HUD 组织:https://www.wanxiangyundang.top/read/unreal-engine-5-8-docs/user-interfaces-and-huds-in-un-e50b
- 3D->屏幕投影:
ProjectWorldToScreen - 暴击判定:伤害 GE 里带 Tag(Crit),UI 看 Tag 选样式
心得:飘字是玩家"看得见的数值",我花在它上的时间比血条多三倍。调好合并与对象池后,群攻打 boss 时数字清晰、不卡——细节反馈做到位,玩家会说"这游戏打击感好",虽然他说不出为什么。
