UI 框架

前言

UE5 Lyra项目UI框架拆解 - 知乎

Lyra项目中的UI功能主要由游戏UI管理子系统(GameUIManagerSubsystem)游戏UI策略(GameUIPolicy)游戏布局UI([PrimaryGameLayout]三个部分构成UI框架,CommonUI插件以及UIExtension插件负责UI层面的使用。

在插件中主要有三个类:

  • PrimaryGameLayout

  • GameUIManagerSubsystem

  • GameUIPolicy

  • 与UI相关的类在Lyra项目中的继承关系如下:

  • GamePlay

    • B_LyraGameInstance(蓝图) -> LyraGameInstance -> CommonGameInstance ->GameInstance
    • LyraLocalPlayer -> CommonLocalPlayer ->LocalPlayer
    • LyraPlayerController -> CommonPlayerController -> ModularPlayerController -> PlayerController
  • UIMangaer

    • LyraUIManagerSubsystem -> GameUIManagerSubsystem ->GameInstanceSubsystem

    • B_LyraUIPolicy(蓝图) -> GameUIPolicy -> Object

    • W_OverallUILayout(蓝图)-> PrimaryGameLayout -> CommonUserWidget(CommonUI插件中的类)

  • 类作用
    • GameUIManagerSubsystem负责GameUIPolicy的配置管理、生命周期等;
    • GameUIPolicy负责PrimaryGameLayout的配置管理、生命周期等;
    • PrimaryGameLayout负责游戏中UI的分层布局等;

Subsystem

GameUIManagerSubsystem

1.判断创建哪一个游戏UI管理子系统

  • 在ShouldCreateSubsystem函数中会判断子系统是否有子类,如果没子类就创建该子系统,有子类就不创建该子系统,由于Lyra项目创建了LyraUIManagerSubsystem并继承GameUIManagerSubsystem,所以项目会创建LyraUIManagerSubsystem而不会创建GameUIManagerSubsystem.

  • 由于GameUIManagerSubsystem标记了Abstract宏,所以GameUIManagerSubsystem默认不会创建,所以需要自己继承一个或者修改掉插件里的宏定义。

    • UCLASS(MinimalAPI, Abstract, config = Game)
      class UGameUIManagerSubsystem : public UGameInstanceSubsystem
      

2.配置GameUIPolicy(游戏策略)类

  • 在游戏UI管理子系统中有一个GameUIPolicy(游戏策略)类的配置项,配置位置在DefaultGame.ini文件中

    img

3.创建GameUIPolicy(游戏策略)对象

  • Initialize
方法 参数 返回值 说明
Initialize void Initialize(FSubsystemCollectionBase& Collection) Collection:子系统依赖集合 void 初始化子系统,加载默认 UI 策略类并创建策略实例。若未配置策略(CurrentPolicy为空)且 DefaultUIPolicyClass有效,则同步加载策略类并创建实例 。
Deinitialize void Deinitialize() - void 反初始化子系统,清空当前 UI 策略(触发策略清理逻辑)。
ShouldCreateSubsystem bool ShouldCreateSubsystem(UObject* Outer) Outer:外部对象(指向 UGameInstance bool 判断是否创建子系统实例: • 专用服务器跳过创建 • 非专用服务器无派生类覆盖时 才创建基类实例(避免多重实例)。
NotifyPlayerAdded void NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer) LocalPlayer:加入的本地玩家实例 void 处理新玩家加入事件: • 验证玩家指针有效性(ensure) • 转发事件至当前 UI 策略(CurrentPolicy)。
NotifyPlayerRemoved void NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer) LocalPlayer:被移除的本地玩家 void 处理玩家移除事件: • 非空检查后转发事件至当前 UI 策略 。
NotifyPlayerDestroyed void NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer) LocalPlayer:销毁中的玩家实例 void 处理玩家销毁事件: • 非空检查后转发事件至当前 UI 策略(通常用于释放玩家关联资源)。
SwitchToPolicy void SwitchToPolicy(UGameUIPolicy* InPolicy) InPolicy:新 UI 策略实例 void 切换 UI 策略: • 新旧策略不同时更新 CurrentPolicy旧策略自动释放(由引擎垃圾回收管理) • 新策略接管后需手动初始化(如玩家事件绑定)

LyraUIManagerSubsystem

在LyraUIManagerSubsystem添加了Tick函数,并根据AHUD的bShowHUD布尔值决定游戏布局UI的显示与隐藏。

方法 参数 返回值 说明
Initialize void Initialize(FSubsystemCollectionBase& Collection) Collection:子系统依赖集合 void 子系统初始化 • 注册 FTicker 委托实现每帧更新 • 通过 CoreTicker确保帧同步 • 父类初始化后立即执行
Deinitialize void Deinitialize() - void 子系统反初始化 • 移除 FTicker 委托避免无效调用 • 执行父类资源释放 • 确保无内存泄漏
Tick bool Tick(float DeltaTime) DeltaTime:帧间隔时间 bool 每帧更新逻辑 • 调用 SyncRootLayoutVisibilityToShowHUD同步可见性 • 始终返回 true保持持续触发 • 轻量化设计避免性能问题
SyncRootLayoutVisibilityToShowHUD void SyncRootLayoutVisibilityToShowHUD() - void HUD 与布局可见性同步 • 当 HUD 隐藏时折叠 UI 布局(如过场动画) • 遍历所有本地玩家控制器 • 状态映射: - bShowHUD=trueSelfHitTestInvisible(可交互) - bShowHUD=falseCollapsed(完全隐藏) • 避免冗余更新

UIPolicy

GameUIPolicy

在GameUIPolicy(游戏策略)中,有一个游戏主UI布局类的配置项, Lyra创建B_LyraUIPolicy蓝图类并配置游戏主UI布局类。

基础接口

函数签名 参数 返回值 说明
GetGameUIPolicy static UGameUIPolicy* GetGameUIPolicy(const UObject* WorldContextObject) WorldContextObject:世界上下文对象 UGameUIPolicy* 获取当前场景的 UI 策略实例。通过游戏实例→UI 管理子系统→当前策略的三级查询实现
GetOwningUIManager UGameUIManagerSubsystem* GetOwningUIManager() const - UGameUIManagerSubsystem* 获取所属的 UI 管理子系统(通过 Outer 链追溯)
GetWorld virtual UWorld* GetWorld() const override - UWorld* 获取关联的游戏世界(重写 AActor 方法)
GetRootLayout UPrimaryGameLayout* GetRootLayout(const UCommonLocalPlayer* LocalPlayer) const LocalPlayer:目标本地玩家 UPrimaryGameLayout* 获取指定玩家的根布局实例(不存在时返回 nullptr)

玩家生命周期管理

函数签名 参数 返回值 说明
NotifyPlayerAdded void NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer) LocalPlayer:加入的玩家 void 处理玩家加入事件: • 监听控制器设置事件 • 创建/添加玩家专属布局
NotifyPlayerRemoved void NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer) LocalPlayer:移除的玩家 void 处理玩家移除事件: • 从视口移除布局 • SingleToggle 模式自动转移控制权到主玩家
NotifyPlayerDestroyed void NotifyPlayerDestroyed(UCommonLocalPlayer* LocalPlayer) LocalPlayer:销毁的玩家 void 处理玩家销毁事件: • 解绑事件委托 • 释放布局资源

布局视口管理

函数签名 参数 返回值 说明
AddLayoutToViewport void AddLayoutToViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
  • LocalPlayer:目标玩家
  • Layout:布局实例
void 添加布局到视口: • 设置玩家上下文(FLocalPlayerContext) • ZOrder=1000 确保顶层显示 • 触发添加回调
RemoveLayoutFromViewport void RemoveLayoutFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)
  • LocalPlayer:目标玩家
  • Layout:布局实例
void 从视口移除布局: • 检查 Slate 控件有效性 • 记录移除日志(含泄漏警告)

控制权管理

函数签名 参数 返回值 说明
RequestPrimaryControl void RequestPrimaryControl(UPrimaryGameLayout* Layout) Layout:请求控制权的布局 void 分屏模式下切换控制权: • 仅 SingleToggle 模式生效 • 停用当前活跃布局 • 激活目标布局

布局创建接口

函数签名 参数 返回值 说明
GetLayoutWidgetClass virtual TSubclassOf<UPrimaryGameLayout> GetLayoutWidgetClass(UCommonLocalPlayer* LocalPlayer) LocalPlayer:目标玩家 TSubclassOf<UPrimaryGameLayout> 获取布局控件类(可被子类覆盖): • 默认返回 LayoutClass 配置的软引用 • 支持按玩家差异化布局

Widget

CommonUserWidget

构造函数与基础设置

函数签名 参数 返回值 说明
UCommonUserWidget UCommonUserWidget(const FObjectInitializer&) ObjectInitializer:对象初始化器 - 初始化控件属性: • 编辑器模式下设置面板分类为 Common UI • 默认开启指针输入消耗(bConsumePointerInput=true

输入子系统访问

函数签名 参数 返回值 说明
GetInputSubsystem UCommonInputSubsystem* GetInputSubsystem() - UCommonInputSubsystem* 获取当前本地玩家的输入子系统实例(用于处理平台差异化输入)
GetUISubsystem UCommonUISubsystemBase* GetUISubsystem() - UCommonUISubsystemBase* 获取游戏实例中的通用UI子系统(管理全局UI状态)
GetOwnerSlateUser TSharedPtr<FSlateUser> GetOwnerSlateUser() - TSharedPtr<FSlateUser> 获取关联的Slate用户对象(分屏场景中隔离玩家输入)

输入事件处理

(所有函数均根据 bConsumePointerInput决定是否拦截事件)

函数签名 参数 返回值 说明
SetConsumePointerInput void SetConsumePointerInput(bool) bInConsumePointerInput:是否消耗事件 void 启用时拦截所有鼠标/触摸事件(防止穿透点击底层UI)
NativeOnMouseButtonDown FReply NativeOnMouseButtonDown(...) InGeometry:控件几何信息 InMouseEvent:鼠标事件 FReply 鼠标按下事件处理(返回 Handled时阻止事件传递)
NativeOnMouseWheel FReply NativeOnMouseWheel(...) 同上 FReply 鼠标滚轮事件处理(与滚动接收器协同工作)
NativeOnTouchStarted FReply NativeOnTouchStarted(...) InGeometry:控件几何信息 InGestureEvent:触摸事件 FReply 触摸开始事件处理(移动设备专用)

动作绑定管理

函数签名 参数 返回值 说明
RegisterUIActionBinding FUIActionBindingHandle RegisterUIActionBinding(FBindUIActionArgs) BindActionArgs:绑定参数(动作类型/输入事件) FUIActionBindingHandle 注册UI动作: • 通过 CommonUIActionRouter统一管理 • 若 bDisplayInActionBar=true强制显示在动作栏
RemoveActionBinding void RemoveActionBinding(FUIActionBindingHandle) ActionBinding:绑定句柄 void 移除单个动作绑定(自动从路由系统解绑)
AddActionBinding void AddActionBinding(FUIActionBindingHandle) ActionBinding:绑定句柄 void 动态添加已存在的绑定(用于UI重组)

滚动接收器管理

函数签名 参数 返回值 说明
RegisterScrollRecipient void RegisterScrollRecipient(const UWidget&) AnalogScrollRecipient:目标滚动控件 void 注册滚动接收器: • 允许将滚动手势委托给子控件(如嵌套的 ScrollBox) • 支持多级嵌套容器
UnregisterScrollRecipient void UnregisterScrollRecipient(const UWidget&) 同上 void 取消滚动接收器注册
RegisterScrollRecipientExternal void RegisterScrollRecipientExternal(const UWidget*) AnalogScrollRecipient:目标滚动控件指针 void 外部注册(空指针安全检查)

生命周期管理

函数签名 参数 返回值 说明
OnWidgetRebuilt void OnWidgetRebuilt() - void 控件重建时回调: • 清理无效动作绑定 • 向 ActionRouter重新注册控件和滚动接收器
NativeDestruct void NativeDestruct() - void 控件销毁时回调: • 通知 ActionRouter清理绑定 • 确保无内存泄漏

编辑器集成

函数签名 参数 返回值 说明
GetPaletteCategory const FText GetPaletteCategory() - FText 获取控件面板分类(固定返回 Common UI分类)

PrimaryGameLayout

  • 这就是游戏的布局UI,在这个UI中会添加不同的栈UI控件,来达到UI分层的目的。Lyra项目中将UI分为游戏层,游戏菜单层,菜单层,modol层。
方法 参数 返回值 说明
GetPrimaryGameLayoutForPrimaryPlayer static UPrimaryGameLayout* GetPrimaryGameLayoutForPrimaryPlayer(const UObject* WorldContextObject) WorldContextObject:世界上下文对象 UPrimaryGameLayout* 获取主玩家根布局: • 通过 UGameplayStatics::GetGameInstance获取游戏实例 • 调用 GetPrimaryGameLayout(PlayerController)实现
GetPrimaryGameLayout static UPrimaryGameLayout* GetPrimaryGameLayout(APlayerController* PlayerController) PlayerController:玩家控制器实例 UPrimaryGameLayout* 通过控制器获取布局: • 转换控制器为 UCommonLocalPlayer • 调用本地玩家版 GetPrimaryGameLayout
GetPrimaryGameLayout static UPrimaryGameLayout* GetPrimaryGameLayout(ULocalPlayer* LocalPlayer) LocalPlayer:本地玩家实例 UPrimaryGameLayout* 通过本地玩家获取布局: 1. 验证本地玩家有效性 2. 通过游戏实例获取 UGameUIManagerSubsystem 3. 从当前策略获取关联布局
SetIsDormant void SetIsDormant(bool InDormant) InDormant:是否休眠 void 设置布局休眠状态: • 分屏场景暂停非活动玩家 UI 渲染 • 状态变更时记录日志并触发 OnIsDormantChanged回调 • 设计意图:节省资源(如折叠 UI/停用输入)
RegisterLayer void RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget)
  • LayerTag:层级标识(GameplayTag)
  • LayerWidget:层级容器控件
void 注册 UI 层级: • 绑定过渡事件 OnWidgetStackTransitioning • 禁用过渡动画(确保手柄焦点正确) • 支持四层结构: - Game:实时 HUD(血条/弹药) - GameMenu:局内菜单(暂停) - Menu:主菜单系统 - Modal:模态弹窗(确认框)
OnWidgetStackTransitioning void OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase* Widget, bool bIsTransitioning)
  • Widget:发生过渡的控件栈
  • bIsTransitioning:过渡状态
void 处理控件栈过渡事件: • 过渡开始时暂停输入(SuspendInputForPlayer) • 过渡结束时恢复输入(ResumeInputForPlayer) • 令牌系统管理并发过渡
FindAndRemoveWidgetFromLayer void FindAndRemoveWidgetFromLayer(UCommonActivatableWidget* ActivatableWidget) ActivatableWidget:待移除控件 void 跨层级移除控件: • 遍历所有层级容器移除目标控件 • 典型场景:动态关闭弹窗/场景切换清理
GetLayerWidget UCommonActivatableWidgetContainerBase* GetLayerWidget(FGameplayTag LayerName) LayerName:层级标识 UCommonActivatableWidgetContainerBase* 获取层级容器: • 通过 GameplayTag检索注册的容器控件

Loading UI

CommonUI插件以及UIExtension等插件

主要是 CommonLoadingScreen 插件 :

主Manager : ULoadingScreenManager

方法 参数 返回值 说明
Initialize Collection: FSubsystemCollectionBase& void 初始化子系统,注册地图加载/卸载委托(PreLoadMapWithContextPostLoadMapWithWorld)。
Deinitialize void 清理资源:停止输入屏蔽、移除加载控件、注销委托并禁用后续 Tick
ShouldCreateSubsystem Outer: UObject* bool 仅在客户端创建子系统(服务器无需加载界面)。
Tick DeltaTime: float void 每帧更新加载屏幕状态,并递减心跳日志计时器。
ShowLoadingScreen void 显示加载屏幕:创建控件、屏蔽输入、禁用世界渲染并调整性能设置(如延长心跳超时阈值)。
HideLoadingScreen void 隐藏加载屏幕:移除控件、恢复性能设置、触发垃圾回收并恢复卡顿检测。
ChangePerformanceSettings bEnabingLoadingScreen: bool void 性能优化控制: • 启用时:设置着色器缓存为Fast模式、挂起卡顿检测、禁用世界渲染 • 禁用时:恢复默认设置。
StartBlockingInput void 注册输入预处理器(FLoadingScreenInputPreProcessor),屏蔽非编辑器下的用户输入。
StopBlockingInput void 注销输入预处理器,恢复用户输入响应。
UpdateLoadingScreen void 核心状态机:调用 ShouldShowLoadingScreen决策显示/隐藏,管理心跳检测与日志。
CheckForAnyNeedToShowLoadingScreen bool 综合检测条件: 1. 控制台强制显示 2. 地图加载中(bCurrentlyInLoadMap) 3. 无缝旅行(IsInSeamlessTravel) 4. 玩家控制器未就绪 5. 外部处理器要求显示。

UIExtension

UI 扩展系统 API 文档

方法 参数 返回值 说明
Initialize Collection: FSubsystemCollectionBase& void 初始化子系统,注册地图加载/卸载委托(PreLoadMapWithContextPostLoadMapWithWorld)。
Deinitialize void 清理资源:停止输入屏蔽、移除加载控件、注销委托并禁用后续 Tick
RegisterExtensionPoint
  • ExtensionPointTag: FGameplayTag
  • ExtensionPointTagMatchType: EUIExtensionPointMatch
  • AllowedDataClasses: TArray<UClass*>
  • ExtensionCallback: FExtendExtensionPointDelegate
FUIExtensionPointHandle 注册扩展点: • 校验标签有效性、回调绑定及数据类非空 • 支持上下文对象匹配(ContextObject) • 触发 NotifyExtensionPointOfExtensions通知现有扩展。
RegisterExtensionAsData
  • ExtensionPointTag: FGameplayTag
  • ContextObject: UObject*
  • Data: UObject*
  • Priority: int32
FUIExtensionHandle 注册数据扩展: • 校验标签与数据有效性 • 支持优先级排序(Priority) • 自动通知关联扩展点(NotifyExtensionPointsOfExtension)。
UnregisterExtension ExtensionHandle: FUIExtensionHandle void 注销扩展: • 安全检查句柄所属子系统 • 触发 EUIExtensionAction::Removed回调 • 清理空扩展列表。
NotifyExtensionPointsOfExtension
  • Action: EUIExtensionAction
  • Extension: TSharedPtr<FUIExtension>
void 核心通知逻辑: • 遍历扩展标签的父级层级 • 匹配时调用扩展点回调(ExactMatch/PartialMatch) • 使用副本避免遍历时修改。
CreateExtensionRequest Extension: TSharedPtr<FUIExtension> FUIExtensionRequest 构建请求结构: • 封装句柄、标签、优先级、数据及上下文对象。
K2_RegisterExtensionAsWidget
  • ExtensionPointTag: FGameplayTag
  • WidgetClass: TSubclassOf<UUserWidget>
  • Priority: int32
FUIExtensionHandle 蓝图兼容控件注册: • 内部调用 RegisterExtensionAsData • 自动处理空上下文。

关键类型说明

类型 作用
FUIExtensionPointHandle 扩展点句柄: • Unregister()注销扩展点 • IsValid()检查句柄有效性。
FUIExtensionHandle 扩展句柄: • Unregister()注销扩展 • IsValid()检查句柄有效性。
FUIExtensionRequest 扩展请求: • 包含数据、优先级、上下文对象等运行时信息。