总览
UCommonActivatableWidget 是 Common UI 的核心。它不是 UUserWidget 的替代品,而是给“页面”加了一套激活协议。普通 UMG 的 Construct/Destruct 只告诉你 Widget 被创建和销毁;游戏菜单更需要的是“页面现在是否在前台、是否接收输入、是否该恢复焦点、是否该把鼠标/手柄输入模式切到 UI”。
生命周期顺序
NativeConstruct
-> 如果 bIsBackHandler,注册默认 Back Action
-> 如果 bAutoActivate,ActivateWidget
ActivateWidget
-> NativeOnActivated
-> 可选 SetVisibility
-> ActivateMappingContext
-> BP_OnActivated / OnActivated
DeactivateWidget
-> NativeOnDeactivated
-> 可选 SetVisibility
-> DeactivateMappingContext
-> ClearActiveHoldInputs
-> BP_OnDeactivated / OnDeactivated
NativeDestruct
-> DeactivateWidget
-> Unregister Back Action
关键点:Deactivate 不是 Destroy。 一个页面可以被池化、重新激活、重新放回栈里。所以每次激活都要刷新显示数据,每次停用都要清理定时器、异步请求、长按输入和临时状态。
Back Handler 怎么用
页面如果勾选 bIsBackHandler,NativeConstruct 会注册默认 Back Action。默认行为是调用 DeactivateWidget()。你可以在 BP_OnHandleBackAction 或 NativeOnHandleBackAction 里拦截:
bool UMySettingsScreen::NativeOnHandleBackAction()
{
if (bHasUnsavedChanges)
{
OpenConfirmDiscardDialog();
return true;
}
return Super::NativeOnHandleBackAction();
}
如果返回 true 表示你处理了;如果需要默认关闭,就让父类处理或自己 Deactivate。
Desired Focus 必须认真写
Common UI 会在 leaf-most active widget 变化时尝试给它焦点。NativeGetDesiredFocusTarget 先调用蓝图 BP_GetDesiredFocusTarget,没有的话回退到 UUserWidget 的 DesiredFocusWidget 属性。
生产项目建议每个页面都明确返回:
- 主菜单:默认选中的主按钮。
- 设置页:当前 Tab 或第一个设置项。
- 确认框:默认安全按钮,例如“取消”。
- 背包详情页:如果从列表进来,返回详情页的操作按钮;回列表时恢复上一项。
Input Config 是页面说“我要哪种输入模式”
GetDesiredInputConfig 返回可选 FUIInputConfig。常见三种:
| 页面 | InputMode | MouseCapture |
|---|---|---|
| 主菜单/设置 | Menu | NoCapture |
| HUD 上的交互轮盘 | All | NoCapture |
| 游戏内瞄准预览菜单 | All | CapturePermanently |
蓝图里实现 BP_GetDesiredInputConfig 即可。源码里如果整棵激活树都没有 Desired Config,并且 bEnableDefaultInputConfig 为 true,会回退到默认 Menu config。
Enhanced Input Mapping Context
UCommonActivatableWidget 有 InputMapping 和 InputMappingPriority。当 Common UI Enhanced Input Support 开启时,页面激活会把这个 IMC 加到 UEnhancedInputLocalPlayerSubsystem,停用时移除。适合页面专属输入,例如设置页的左右切换、相册页的缩放、地图页的拖拽。
不要把全游戏默认移动 IMC 放到页面上;页面上的 InputMapping 应该是 UI 专属、短生命周期。
Action Domain 简单理解
Action Domain 用来控制多个 UI 域之间输入如何流动。比如你有 HUD 域、菜单域、调试覆盖层域,某个域 active 时是否阻断其他域,由 UCommonInputActionDomain 的 Behavior 和 InnerBehavior 决定。新手可以先不建复杂 Domain,但要知道 bOverrideActionDomain 是页面级覆盖点。
常见坑
- 在
NativeConstruct里读存档并只初始化一次,页面从池里回来后显示旧数据。 NativeOnDeactivated忘记清定时器,页面关闭后还在 Tick/回调。- Back Handler 页面里打开确认框时先 Deactivate 自己,导致确认框下面页面没了。
- Desired Focus 返回隐藏或 Disabled 的按钮,焦点会失败。
- 页面 InputMapping 里绑定了和 gameplay 一样的攻击键,却没有明确 InputMode/优先级。
源码依据
CommonActivatableWidget.cpp 里 NativeConstruct 会注册 Back Action,NativeOnActivated 会设置可选可见性并调用 ActivateMappingContext,NativeOnDeactivated 会移除 Mapping Context 并 ClearActiveHoldInputs。NativeGetDesiredFocusTarget 会优先取 BP 实现,再取 UserWidget 的 Desired Focus。FActivatableTreeRoot::ApplyLeafmostNodeConfig 会把 leaf-most node 的 Desired Input Config 应用到 Action Router。
源码路径索引
CommonUI/Public/CommonActivatableWidget.hCommonUI/Private/CommonActivatableWidget.cppCommonUI/Private/Input/UIActionRouterTypes.cppCommonInput/Public/CommonInputActionDomain.h