UE Lyra
前言
本篇为Lyra 的解析 目前解析了:
- UI
- Dash
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默认不会创建,所以需要自己继承一个或者修改掉插件里的宏定义。
- ```cpp
UCLASS(MinimalAPI, Abstract, config = Game)
class UGameUIManagerSubsystem : public UGameInstanceSubsystem1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
2.配置GameUIPolicy(游戏策略)类
- 在游戏UI管理子系统中有一个GameUIPolicy(游戏策略)类的配置项,配置位置在DefaultGame.ini文件中

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=true`→ `SelfHitTestInvisible`(可交互) - `bShowHUD=false`→ `Collapsed`(完全隐藏) • 避免冗余更新 |
## 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)` | <ul><li>`LocalPlayer`:目标玩家</li><li>`Layout`:布局实例</li></ul> | `void` | 添加布局到视口: • 设置玩家上下文(FLocalPlayerContext) • ZOrder=1000 确保顶层显示 • 触发添加回调 |
| **RemoveLayoutFromViewport** `void RemoveLayoutFromViewport(UCommonLocalPlayer* LocalPlayer, UPrimaryGameLayout* Layout)` | <ul><li>`LocalPlayer`:目标玩家</li><li>`Layout`:布局实例</li></ul> | `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)` | <ul><li>`LayerTag`:层级标识(GameplayTag)</li><li>`LayerWidget`:层级容器控件</li></ul> | `void` | **注册 UI 层级**: • 绑定过渡事件 `OnWidgetStackTransitioning` • 禁用过渡动画(确保手柄焦点正确) • 支持四层结构: - **Game**:实时 HUD(血条/弹药) - **GameMenu**:局内菜单(暂停) - **Menu**:主菜单系统 - **Modal**:模态弹窗(确认框) |
| **OnWidgetStackTransitioning** `void OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase* Widget, bool bIsTransitioning)` | <ul><li>`Widget`:发生过渡的控件栈</li><li>`bIsTransitioning`:过渡状态</li></ul> | `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` | 初始化子系统,注册地图加载/卸载委托(`PreLoadMapWithContext`、`PostLoadMapWithWorld`)。 |
| **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` | 初始化子系统,注册地图加载/卸载委托(`PreLoadMapWithContext`、`PostLoadMapWithWorld`)。 |
| **`Deinitialize`** | 无 | `void` | 清理资源:停止输入屏蔽、移除加载控件、注销委托并禁用后续 `Tick`。 |
| **`RegisterExtensionPoint`** | <ul><li>`ExtensionPointTag: FGameplayTag`</li><li>`ExtensionPointTagMatchType: EUIExtensionPointMatch`</li><li>`AllowedDataClasses: TArray<UClass*>`</li><li>`ExtensionCallback: FExtendExtensionPointDelegate`</li></ul> | `FUIExtensionPointHandle` | 注册扩展点: • 校验标签有效性、回调绑定及数据类非空 • 支持上下文对象匹配(`ContextObject`) • 触发 `NotifyExtensionPointOfExtensions`通知现有扩展。 |
| **`RegisterExtensionAsData`** | <ul><li>`ExtensionPointTag: FGameplayTag`</li><li>`ContextObject: UObject*`</li><li>`Data: UObject*`</li><li>`Priority: int32`</li></ul> | `FUIExtensionHandle` | 注册数据扩展: • 校验标签与数据有效性 • 支持优先级排序(`Priority`) • 自动通知关联扩展点(`NotifyExtensionPointsOfExtension`)。 |
| **`UnregisterExtension`** | `ExtensionHandle: FUIExtensionHandle` | `void` | 注销扩展: • 安全检查句柄所属子系统 • 触发 `EUIExtensionAction::Removed`回调 • 清理空扩展列表。 |
| **`NotifyExtensionPointsOfExtension`** | <ul><li>`Action: EUIExtensionAction`</li><li>`Extension: TSharedPtr<FUIExtension>`</li></ul> | `void` | 核心通知逻辑: • 遍历扩展标签的父级层级 • 匹配时调用扩展点回调(`ExactMatch`/`PartialMatch`) • 使用副本避免遍历时修改。 |
| **`CreateExtensionRequest`** | `Extension: TSharedPtr<FUIExtension>` | `FUIExtensionRequest` | 构建请求结构: • 封装句柄、标签、优先级、数据及上下文对象。 |
| **`K2_RegisterExtensionAsWidget`** | <ul><li>`ExtensionPointTag: FGameplayTag`</li><li>`WidgetClass: TSubclassOf<UUserWidget>`</li><li>`Priority: int32`</li></ul> | `FUIExtensionHandle` | 蓝图兼容控件注册: • 内部调用 `RegisterExtensionAsData` • 自动处理空上下文。 |
------
## **关键类型说明**
| **类型** | **作用** |
| :---------------------------: | :----------------------------------------------------------: |
| **`FUIExtensionPointHandle`** | 扩展点句柄: • `Unregister()`注销扩展点 • `IsValid()`检查句柄有效性。 |
| **`FUIExtensionHandle`** | 扩展句柄: • `Unregister()`注销扩展 • `IsValid()`检查句柄有效性。 |
| **`FUIExtensionRequest`** | 扩展请求: • 包含数据、优先级、上下文对象等运行时信息。 |
# Dash
- ShooterCore
- IMC_Default
- IA_Ability_Dash
- LyraHeroComponent::InitializePlayerInput()
- LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);
- ULyraAbilitySystemComponent::AbilityInputTagPressed -- ASC
- for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items) 遍历所有能力(AbilitySpec)
- 有对应InputTag 进入
- UAbilitySystemComponent::TryActivateAbility -- ASC
- GA_Hero_Dash (在插件文件夹中) -- 蓝图
- Select Directional Montage 选择蒙太奇
- Play Montage And Wait 播放动画
- UAbilityTask_PlayMontageAndWait* UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(..)
- UAbilitySystemComponent::PlayMontage
- Apply Root Motion Constant Force 位移
- MovementComponent->ApplyRootMotionSource(ConstantForce)
# 一次攻击流程
- BT_Lyra_Shooter_Bot
- BTS_Shoot
- Tick
- Send Gameplay Event to Actor (传入Event.Tag -- FireTag)
- UAbilitySystemComponent::**HandleGameplayEvent**(FGameplayTag EventTag, const FGameplayEventData* Payload) 常用接口
- UAbilitySystemComponent::TriggerAbilityFromGameplayEvent(..)
- UAbilitySystemComponent::InternalTryActivateAbility(..)
- UGameplayAbility::CallActivateAbility(..)
- K2_ActivateAbility() 调用GA
- GA_Weapon_Fire (父类)
- GA_Weapon_Fire_Rifle_Auto(步枪) 数据类实际逻辑在父类中
- 继承链:GA_Weapon_Fire_Rifle_Auto -> GA_Weapon_Fire -> ULyraGameplayAbility_RangedWeapon -> ULyraGameplayAbility_FromEquipment -> ULyraGameplayAbility
- GA_Weapon_Fire 蓝图中 Start Ranged Weapon Targeting
- ULyraGameplayAbility_RangedWeapon::StartRangedWeaponTargeting()
- ULyraGameplayAbility_RangedWeapon::OnTargetDataReadyCallback()
- 射线检测找出一个他需要攻击的目标
- OnRangedWeaponTargetDataReady() -- 蓝图调用
- 蓝图中:On Ranged Weapon Target Data Ready
- Apply Gameplay Effect To Target 通过GA叠加GE (导入GE Damage)
- ULyraHealthSet::PostGameplayEffectExecute()
- 射线检测
- 生命值 <0 死亡 (死亡也是一个GA)
- ULyraHealthComponent::HandleOutOfHealth
- 通过委托去播死亡GA HandleGameplayEvent()
- LyraGameplayAbility_Death -> ULyraGameplayAbility
```cpp
void ULyraHealthSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
// 调用父类实现以保留基础后处理逻辑
Super::PostGameplayEffectExecute(Data);
// 判断此次效果是否带有“自爆伤害”标签(用于绕过某些免伤/作弊检测)
const bool bIsDamageFromSelfDestruct = Data.EffectSpec.GetDynamicAssetTags().HasTagExact(TAG_Gameplay_DamageSelfDestruct);
// 初始化最小允许生命值,默认 0(有些作弊或调试模式下会设置为 1)
float MinimumHealth = 0.0f;
#if !UE_BUILD_SHIPPING
// 在非发行构建中,检查 GodMode 或 UnlimitedHealth 作弊标签(自爆伤害仍然有效)
if (!bIsDamageFromSelfDestruct &&
(Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_GodMode) || Data.Target.HasMatchingGameplayTag(LyraGameplayTags::Cheat_UnlimitedHealth)))
{
// 如果满足作弊条件,则把最小生命值设为 1,防止被伤害致死(仅调试/作弊用途)
MinimumHealth = 1.0f;
}
#endif // #if !UE_BUILD_SHIPPING
// 获取效果上下文(包含 instigator、causer 等信息)
const FGameplayEffectContextHandle& EffectContext = Data.EffectSpec.GetEffectContext();
// 从上下文获取原始施放者(可能为触发该效果的 actor)
AActor* Instigator = EffectContext.GetOriginalInstigator();
// 从上下文获取直接造成者(EffectCauser)
AActor* Causer = EffectContext.GetEffectCauser();
// 如果该效果修改的是 Damage 属性,进入伤害处理分支
if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
// 若伤害量大于 0,发送一个标准化的伤害消息供其他系统观察/记录
if (Data.EvaluatedData.Magnitude > 0.0f)
{
// 构造 Lyra 的动词消息结构并填充信息
FLyraVerbMessage Message;
Message.Verb = TAG_Lyra_Damage_Message; // 设置动词标签为 Lyra 的伤害消息标签
Message.Instigator = Data.EffectSpec.GetEffectContext().GetEffectCauser(); // 填充 instigator(causer)
Message.InstigatorTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags(); // 填充 instigator 的捕获标签
Message.Target = GetOwningActor(); // 填充目标 Actor(属性集的拥有者)
Message.TargetTags = *Data.EffectSpec.CapturedTargetTags.GetAggregatedTags(); // 填充目标的捕获标签
// @TODO: 补充上下文标签,以及来自非能力系统的来源/施放者标签
// @TODO: 判断是否为敌对队伍击杀、自杀、队友击杀等情形
Message.Magnitude = Data.EvaluatedData.Magnitude; // 填充伤害数值
// 获取全局消息子系统并广播该消息
UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(GetWorld());
MessageSystem.BroadcastMessage(Message.Verb, Message);
}
// 将 Damage 转换为对 Health 的减值,并在 MinimumHealth 与 MaxHealth 之间夹紧后写回
SetHealth(FMath::Clamp(GetHealth() - GetDamage(), MinimumHealth, GetMaxHealth()));
// 清除已消费的 Damage 值
SetDamage(0.0f);
}
else if (Data.EvaluatedData.Attribute == GetHealingAttribute())
{
// 如果是 Healing 属性,将其作为对 Health 的加值并夹紧到合法范围
SetHealth(FMath::Clamp(GetHealth() + GetHealing(), MinimumHealth, GetMaxHealth()));
// 清除已消费的 Healing 值
SetHealing(0.0f);
}
else if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
// 如果直接修改的是 Health,则对其进行夹紧(后续会有掉血处理)
SetHealth(FMath::Clamp(GetHealth(), MinimumHealth, GetMaxHealth()));
}
else if (Data.EvaluatedData.Attribute == GetMaxHealthAttribute())
{
// 如果修改的是 MaxHealth,触发最大生命变化通知(是否同时夹紧当前生命由 TODO 决定)
OnMaxHealthChanged.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, MaxHealthBeforeAttributeChange, GetMaxHealth());
}
// 若 Health 实际变化,触发 OnHealthChanged 回调,通知监听者变更前后值
if (GetHealth() != HealthBeforeAttributeChange)
{
OnHealthChanged.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
}
// 若当前 Health 掉至 0 且之前没有处于无生命状态,则触发 OnOutOfHealth 事件
if ((GetHealth() <= 0.0f) && !bOutOfHealth)
{
OnOutOfHealth.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
}
// 再次检查当前 Health,以防上面事件回调修改了生命值;更新 bOutOfHealth 标志
bOutOfHealth = (GetHealth() <= 0.0f);
}
- ```cpp
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 零の領域!

