UE5 HUD 简介(参考 Lyra)
1. 模块概述
- 「全局 UI 容器」——基建层的
UPrimaryGameLayout/UUIManagerSubSystem/UHUDLayout/AHUD提供整套基于 CommonUI Layer/Stack 范式的 UI 注入框架,支撑全项目所有 UI 的注册、激活、HitTest 联动、GameFeature 接入、手柄热插拔、键鼠/手柄输入模式自适配等横切关注点。这层基建是项目里所有 UI(背包、技能、菜单、加载、登录等)能够工作的前提。 - 模块的关键设计:
- Layer 自动收集 + HitTest 联动:
UPrimaryGameLayout通过RequestGameplayTagChildren自动收集所有UI.Layer.*子 Tag 注册的 Layer,按面板ChildIndex隐式排优先级,监听每个 Layer 的OnDisplayedWidgetChanged,高优先级有内容时自动把低优先级 Layer 设为HitTestInvisible防止焦点穿透。 - 手柄热插拔 + PS/Xbox 自动识别:
UUIManagerSubSystem在 Initialize 时运行时修改 ControllerData CDO 的GamepadName/GamepadHardwareIdMapping,让BP_Input_PS/BP_Input_Xbox配置正确生效;订阅IPlatformInputDeviceMapper::OnInputDeviceConnectionChange在手柄热插拔时调用LocalPlayer->SetControllerId重映射,避免 ControllerId 与 InputDeviceId 错位导致按键失灵。 - GameFeature ↔ HUD 注入:
AHUD::PreInitializeComponents注册为GameFrameworkComponentReceiver,BeginPlay发送NAME_GameActorReady,让所有已激活的GameFeatureAction_AddWidgets把 Widget 注入到指定 Layer/ExtensionPoint。
- Layer 自动收集 + HitTest 联动:
2. 设计思路与架构决策
2.1 PrimaryGameLayout 的 Layer HitTest 自动联动
- 问题:CommonUI 的 PrimaryGameLayout 把 UI 分到多个 Layer Stack,但当 Modal 层弹出后,下方 Game/Menu 层的按钮仍然可以被鼠标点击或被手柄焦点导航选中——违反”模态窗口阻塞下层”的常识。
- 方案:在子类
USamplerPrimaryGameLayout中:NativeConstruct时通过UGameplayTagsManager::Get().RequestGameplayTagChildren(UI.Layer)自动收集所有已注册 Layer Tag。- 用每个 Layer 容器在父面板中的
ChildIndex决定优先级(索引越大越靠前)——美术配 UI 时是按”想让谁覆盖谁”放置的,这个隐式契约直接复用,避免再写一份配置表。 - 给每个 Layer 注册
OnDisplayedWidgetChanged,任何 Layer 内容变化时遍历所有 Layer:找到当前最高优先级的有内容的 Layer,把比它优先级低的 Layer 设为HitTestInvisible(保留视觉,剥夺交互),最高那一层设为SelfHitTestInvisible。
- 理由:
- 比手动维护”谁压谁”配置更难出错——美术调整堆叠顺序时 UI 程序无需改代码。
- 比”打开高优先级时强制 Collapse 低优先级”友好——Modal 弹出时 HUD 仍然可见(玩家能看到血量),只是无法点击。
- 备选方案是给每个 Activatable 自己声明优先级,但要把这种横切关注点散落到 N 个业务 Widget 里,且无法处理”同一 Layer 内同时多个 Widget”的情况;放在 PrimaryGameLayout 这一处统管最干净。
2.2 输入设备热插拔与 PS/Xbox 自动识别
- 问题:开发期用 PS5 DualSense 和 Xbox 手柄交替测试。CommonUI 默认配置下:
- 编辑器 GetOptions 下拉里只有 “Generic”,没有 “PlayStation” 选项,蓝图里无法手选。
- 手柄热插拔后 LocalPlayer 的 ControllerId 不会自动同步,导致 EnhancedInput Subsystem 取到的 PlatformUserId 与手柄真正的 InputDeviceId 错位,按键完全失灵。
- PS DualSense 在 Windows 上识别为
DeviceManager.WindowsDualsense,Xbox 走XInputInterface,需要给两类 ControllerData 分别配 HardwareIdMapping 才能让 CommonInput 自动检测并切换 GamepadInputType。
- 方案:在
USamplerUIManagerSubSystem::Initialize中:FixupControllerDataGamepadSettings:用LoadClass拿到项目的BP_Input_PS/BP_Input_XboxControllerData 蓝图,运行时直接修改其 CDO 的GamepadName与GamepadHardwareIdMapping,绕过编辑器的 GetOptions 限制。HandleInputDeviceConnectionChange:监听IPlatformInputDeviceMapper::Get().GetOnInputDeviceConnectionChange,手柄插入时用GetUserIndexForPlatformUser算出新 UserIndex,与 LocalPlayer 当前 ControllerId 不一致就SetControllerId重映射。
- 理由:这两个问题是 5.x 版本 CommonUI 在 Windows 多手柄场景的实际坑,没有引擎补丁,业务层这么处理是当前最经济的方案。
2.3 GameFeature + UIExtensionPoint 解耦 HUD 布局
- 问题:HUD 上的子模块(血条组、技能格子组、Buff 列表等)在不同关卡 / 不同战斗模式下需要不同布局;硬编码进 HUD 类会造成”加一个新 Widget 就要改 ASamplerHUD” 的耦合。
- 方案:
ASamplerHUD::PreInitializeComponents中调用AddGameFrameworkComponentReceiver注册自身。BeginPlay调用SendGameFrameworkComponentExtensionEvent(NAME_GameActorReady),让所有已激活的 GameFeature Action(如GameFeatureAction_AddWidgets)知道 HUD 已就绪,可以注入 Widget 到指定 Layer/ExtensionPoint。- HUDLayout 中放
USamplerUIExtensionPointWidget占位,运行时被 GameFeature 注入的 Widget 填充;编辑器中可以配置 PreviewEntryClass 显示预览。
- 理由:
- 走 GameFeature 的标准
AddWidgetsAction,UI 程序员不需要为每个新需求改 HUD 类,UX/策划侧的同事可以通过 GFP 直接配置。 - 不同 GameMode(Town / Battle / Boss)激活不同 GFP,HUD 自动呈现不同内容,无需改 C++。
- 走 GameFeature 的标准
3. Q&A
3.1 你怎么处理 PS/Xbox 手柄热插拔?
- 参考回答:
- 这是 5.x 版本 CommonUI 在 Windows 多手柄场景的实际坑,主要有三个问题。
- 第一,编辑器里 ControllerData 蓝图的 GamepadName 字段 GetOptions 下拉只有 Generic,没有 PlayStation 选项,蓝图里手选不了。我在 UIManagerSubSystem::Initialize 时通过 LoadClass 拿到 BP_Input_PS 的 CDO,运行时直接修改它的 GamepadName 为 “PlayStation”,绕过编辑器限制。
- 第二,PS DualSense 在 Windows 上是
DeviceManager.WindowsDualsense设备,DualSense / DualSenseEdge / DualShock4 三种 HardwareIdentifier;Xbox 则走XInputInterface的 XInputController。需要给两类 ControllerData 分别配 GamepadHardwareIdMapping,CommonInputPreprocessor 才能在硬件检测时正确切换 GamepadInputType。我在同一个 Initialize 里运行时配置这两份映射。 - 第三,热插拔时 LocalPlayer.ControllerId 不会自动同步——比如先用 PS5 接入是 ControllerId=0,然后切到 Xbox 通常会成 InputDeviceId=1,但 LocalPlayer 还认 0。导致 EnhancedInput Subsystem 取的 PlatformUserId 与手柄的 InputDeviceId 错位,按键全部失灵。
- 解决办法是订阅
IPlatformInputDeviceMapper::GetOnInputDeviceConnectionChange,连接事件触发时用GetUserIndexForPlatformUser算出新 UserIndex,与 LocalPlayer 当前 ControllerId 不一致就 SetControllerId 重映射。这样玩家拔 PS 插 Xbox 时切换无感知。
评论






