Files
BlueRoseNote/07-Other/AI/AI Agent/UnrealEngine/RPGGameplayAbility重构/Lyra系统学习指南.md

657 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- Lyra
- UE5
- GAS
- 学习笔记
- GameFeatures
created: 2026-05-31
---
# Lyra Starter Game 系统学习指南
> 从零开始理解 Lyra 的逻辑主干、GameModule vs GameFeature、以及如何迭代开发
---
## 一、Lyra 整体逻辑架构 — 游戏从启动到运行的全貌
学 Lyra 最大的障碍不是某个类复杂,而是"不知道这些类是怎么串起来的"。这一章先画地图,再看路标。
### 1.1 架构分层:三层金字塔
```
┌─────────────────────────────────────────────────────────┐
│ 第三层GameFeature │
│ ShooterCore / TopDownArena / ShooterExplorer │
玩法内容DataAsset + 蓝图 + 地图 + 少量专用 C++
│ 职责:"这个游戏模式有什么技能按键怎么绑UI什么样" │
│ 加载方式:运行时按需加载,通过 Experience 触发 │
├─────────────────────────────────────────────────────────┤
│ 第二层LyraGame │
│ GameModuleC++ 编译单元,启动时强制加载) │
游戏框架ASC、Ability、AttributeSet、Character、输入
│ 职责:"提供所有 GAS 基类、GameFeatureAction 机制、 │
│ Pawn 扩展系统、Experience 管理、UI 框架" │
├─────────────────────────────────────────────────────────┤
│ 第一层UE5 引擎 + 插件 │
│ Engine: GameplayAbilities / EnhancedInput / GameFeatures│
│ Engine: ModularGameplay / CommonUI / GameplayMessageRouter│
│ 职责:"提供 GAS 核心、输入框架、Feature 加载/卸载" │
└─────────────────────────────────────────────────────────┘
```
**依赖方向**GameFeature → LyraGame → Engine。上层依赖下层永不反向。
### 1.2 启动流程全景图:谁先动、谁跟着动
这是整个 Lyra 最重要的时序图,看懂了这张图就看懂了 Lyra 的一半:
```
┌─ 阶段0引擎启动 ─────────────────────────────────────────┐
│ UE5 引擎启动 │
│ → 加载 LyraGame.dllGameModule不可卸载
│ → 所有 UCLASS 注册到反射系统 │
│ → ULyraGameEngineSubsystem::Initialize() │
│ → UGameFeaturesSubsystem 初始化 │
└───────────────────────────────────────────────────────────┘
┌─ 阶段1进入地图 ─────────────────────────────────────────┐
│ World 创建 │
│ → GameState 创建 → ULyraExperienceManagerComponent 创建 │
│ → GameMode 创建 → 调用 ChoosePlayerStart 等 │
│ │
│ 重点:此时还没有加载任何 GameFeature。 │
│ 只有 LyraGame 的 C++ 类在内存中。 │
└───────────────────────────────────────────────────────────┘
┌─ 阶段2Experience 加载(最关键!)────────────────────────┐
│ GameMode::InitGame() │
│ → ExperienceManager->SetCurrentExperience(ExperienceId) │
│ → StartExperienceLoad() │
│ │
│ 2a. 加载 Experience DataAsset │
│ Experience.GameFeaturesToEnable = [ │
│ "ShooterCore", │
│ "ShooterMaps" │
│ ] │
│ │
│ 2b. 逐个加载 GameFeature Plugin │
│ UGameFeaturesSubsystem::LoadPlugin("ShooterCore") │
│ → 加载 ShooterCore.dll如果有 C++
│ → 扫描 ShooterCore 的 Content 目录 │
│ → 注册其中的所有 UGameFeatureAction │
│ → 调用每个 Action 的 OnGameFeatureRegistering() │
│ │
│ 2c. 激活 GameFeature │
│ 对每个 Action 调用 OnGameFeatureActivating(): │
│ │
│ GameFeatureAction_AddInputContextMapping │
│ → 把 IMC_ShooterGame 注册到 EnhancedInput 子系统 │
│ │
│ GameFeatureAction_AddInputBinding │
│ → 把 InputData_ShooterGame_AddOns 绑定到输入系统 │
│ │
│ GameFeatureAction_AddAbilities │
│ → 向 GameFrameworkComponentManager 注册: │
│ "当 ALyraCharacter 创建时,自动给它这些技能" │
│ │
│ GameFeatureAction_AddWidgets │
│ → 注册 Shooter 专用 HUD Widget │
│ │
│ ★ 注意:这时候还没有玩家加入,只是"注册了规则" │
│ │
│ 2d. 广播 Experience 加载完成 │
│ Broadcast OnExperienceLoaded │
└───────────────────────────────────────────────────────────┘
┌─ 阶段3玩家加入 + Pawn 生成 ──────────────────────────────┐
│ Login → APlayerController 创建 │
│ → ALyraPlayerState 创建(这是关键对象!) │
│ → 构造函数: ASC = CreateDefaultSubobject (ASC 诞生) │
│ → 构造函数: HealthSet = CreateDefaultSubobject │
│ → 构造函数: CombatSet = CreateDefaultSubobject │
│ → PostInitializeComponents: │
│ → ASC->InitAbilityActorInfo(this, GetPawn()) │
Owner=PlayerState, Avatar=暂无/旧Pawn
│ → 注册 OnExperienceLoaded 回调 │
│ │
│ → 如果 Experience 已加载: │
│ → OnExperienceLoaded() │
│ → GameMode::GetPawnDataForController() │
│ → SetPawnData(PawnData) │
│ → 遍历 PawnData->AbilitySets │
│ → AbilitySet->GiveToAbilitySystem(ASC) ← 技能注入!│
│ → Broadcast NAME_LyraAbilityReady │
│ → 触发 GameFeatureAction_AddAbilities 的 │
│ HandleActorExtension为 PlayerState 注入 │
│ GameFeature 专属技能) │
│ │
│ → RestartPlayer() │
│ → Pawn 创建 (ALyraCharacter) │
│ → Possess(Pawn) │
│ → ULyraHeroComponent::OnPawnReadyToInitialize() │
│ → PawnExtComp->InitializeAbilitySystem( │
│ PS->GetASC(), PS) ← ASC 桥接到新 Pawn
│ → ASC->InitAbilityActorInfo(PlayerState, Pawn) │
Owner=PlayerState, Avatar=新Pawn
│ → 设置 TagRelationshipMapping │
│ → Broadcast OnAbilitySystemInitialized │
│ │
│ → PlayerController 绑定输入 │
│ → LyraHeroComponent 绑定 EnhancedInput │
│ → InputConfig 映射 InputAction → InputTag │
│ → 玩家可以操作了 │
└───────────────────────────────────────────────────────────┘
```
**核心理解**:三个阶段是**异步链式触发**的,不是硬编码顺序。机制是:
- Experience 加载完 → 广播 `OnExperienceLoaded` → PlayerState 收到 → 执行 SetPawnData
- Pawn 生成 + 输入绑定 → 广播 `InitState.GameplayReady` → 所有组件就绪
### 1.3 各模块入口速查:从哪里开始读
按关注领域,给出每个模块的**入口文件**和**为什么从这里开始**
| 关注领域 | 入口文件 | 理由 |
|----------|----------|------|
| **整个游戏的启动** | `LyraExperienceManagerComponent.cpp` | `SetCurrentExperience()``StartExperienceLoad()` 是唯一入口 |
| **Experience 是什么** | `LyraExperienceDefinition.h` | 只有 3 个字段GameFeaturesToEnable, DefaultPawnData, Actions |
| **GameFeature 如何注入能力** | `GameFeatureAction_AddAbilities.cpp` | `AddToWorld()``HandleActorExtension()``AddActorAbilities()` 完整链路 |
| **ASC 桥接Pawn 切换)** | `LyraPawnExtensionComponent.cpp` | `InitializeAbilitySystem()``UninitializeAbilitySystem()` 是核心 |
| **Pawn 和 PlayerState 如何粘合** | `LyraHeroComponent.cpp` | `OnPawnReadyToInitialize()` 中的 `InitializeAbilitySystem(PS->GetASC(), PS)` |
| **输入如何变成技能** | `LyraAbilitySystemComponent.h` | 搜索 `AbilityInputTagPressed` + `ProcessAbilityInput` |
| **技能从哪来** | `LyraAbilitySet.cpp` | `GiveToAbilitySystem()``TakeFromAbilitySystem()` |
| **属性怎么设计** | `LyraHealthSet.h` | 先学 HealthSet4属性再学 CombatSet2属性 |
| **伤害怎么算** | `LyraDamageExecution.cpp` | `CalculateDamage_Implementation()``Capture` 机制 |
| **网络怎么同步** | `LyraPlayerState.cpp` | `GetLifetimeReplicatedProps()` 看复制了什么 |
| **AI/Bot 的 ASC** | `LyraCharacterWithAbilities.h` | 独立 ASC 的自给自足模式 |
| **GameFeature 测试怎么测** | `ShooterTests/` | 看自动化测试如何验证 Feature 加载 |
| **UI 框架** | `GameFeatureAction_AddWidgets.cpp` | 看 Widget 如何通过 GameFeature 注入 |
| **游戏阶段** | `LyraGamePhaseSubsystem.cpp` | 看 WorldSubsystem 如何管理阶段状态 |
### 1.4 LyraGame 模块内部组织
`Source/LyraGame/` 目录按功能分为 14 个文件夹。你不必全部吃透,按关注度分三级:
**红色必读**GAS + Gameplay 核心):
```
AbilitySystem/
├── LyraAbilitySystemComponent.h/cpp ← ASC 扩展(输入缓冲 + ActivationGroup
├── LyraAbilitySystemGlobals.h/cpp ← 全局 GAS 配置
├── LyraGameplayEffectContext.h/cpp ← 扩展 EffectContext
├── LyraAbilitySet.h/cpp ← 技能打包 DataAsset
├── LyraAbilityTagRelationshipMapping.h/cpp ← Tag 互斥关系
├── LyraGlobalAbilitySystem.h/cpp ← 全局技能分发 WorldSubsystem
├── Abilities/
│ ├── LyraGameplayAbility.h/cpp ← 技能基类
│ └── LyraAbilityCost*.h/cpp ← 可扩展 Cost 系统
├── Attributes/
│ ├── LyraAttributeSet.h/cpp ← 属性集基类
│ ├── LyraHealthSet.h/cpp ← 生命值+Meta属性管线
│ └── LyraCombatSet.h/cpp ← 战斗属性
├── Executions/
│ ├── LyraDamageExecution.h/cpp ← 伤害计算
│ └── LyraHealExecution.h/cpp ← 治疗计算
└── Phases/
├── LyraGamePhaseAbility.h/cpp ← 游戏阶段 Ability
└── LyraGamePhaseSubsystem.h/cpp ← 阶段管理器
Character/
├── LyraCharacter.h/cpp ← Character 基类(委托 ASC
├── LyraCharacterWithAbilities.h/cpp ← AI/Bot 独立 ASC 版本
├── LyraHeroComponent.h/cpp ← 玩家 Hero 粘合层 ★ 关键
├── LyraPawnExtensionComponent.h/cpp ← ASC 桥接层 ★ 关键
└── LyraPawnData.h ← Pawn 配置 DataAsset
Player/
├── LyraPlayerState.h/cpp ← ASC 持有者 ★ 关键
└── LyraPlayerController.h/cpp ← 玩家控制器
```
**橙色按需**(输入/GameFeature/UI
```
Input/
├── LyraInputConfig.h ← InputTag 映射 DataAsset
└── LyraInputModifiers.h/cpp ← 输入修饰器
GameFeatures/
├── GameFeatureAction_AddAbilities.h/cpp ← 能力注入 ★ 关键
├── GameFeatureAction_AddInputBinding.h/cpp
├── GameFeatureAction_AddInputContextMapping.h/cpp
├── GameFeatureAction_AddWidgets.cpp
├── GameFeatureAction_WorldActionBase.h/cpp ← Action 基类
└── GameFeatureAction_SplitscreenConfig.cpp
GameModes/
├── LyraExperienceDefinition.h ← Experience DataAsset ★ 关键
├── LyraExperienceManagerComponent.h/cpp ← Experience 加载器 ★ 关键
└── LyraGameMode.h/cpp ← GameMode 基类
UI/
├── LyraHUD.h/cpp ← HUD 布局
└── LyraHUDLayout.h/cpp ← Widget 堆栈管理
```
**灰色可选**(辅助系统):
```
System/ ← GameplayTagStack、LyraGameData、Significance 等
Teams/ ← 队伍系统
Messages/ ← VerbMessage 消息系统
Weapons/ ← 武器 Actor + 武器状态机
Equipment/ ← 装备管理器 + 装备实例
Camera/ ← 摄像机模式
Performance/ ← 性能统计
Interaction/ ← 交互接口
Cosmetics/ ← 外观系统(皮肤/角色模型)
```
### 1.5 初始化状态机InitState
Lyra 不用 BeginPlay 的硬编码顺序,而是通过 `IGameFrameworkInitStateInterface` 实现状态机:
```
Spawned ← BeginPlay 后立即进入
CanChangeInitState(DataAvailable) ?
↓ Yes
DataAvailable ← 组件数据已加载PawnData Replicated
CanChangeInitState(DataInitialized) ?
→ "PawnExtensionComponent 说 ASC 初始化完成了?"
→ "HeroComponent 说输入准备好了?"
↓ Yes
DataInitialized ← 所有组件初始化完成
CanChangeInitState(GameplayReady) ?
→ "所有 Feature Component 准备好了?"
↓ Yes
GameplayReady ← 可以开始游戏了
```
这个机制的关键在于 `UGameFrameworkComponentManager`——它是来自 `ModularGameplay` 插件的全局注册中心。每个组件通过 `AddExtensionHandler` 注册自己关心哪些 Actor 类型,当该类 Actor 创建或状态变化时自动通知。
---
## 零、先搞清楚GameModule 和 GameFeature 到底有什么区别
这是最容易困惑的地方。一句话:
**GameModule.Build.cs是 C++ 的编译单元。GameFeature.uplugin + UGameFeatureAction是游戏内容的加载/卸载单元。**
| | GameModule | GameFeature Plugin |
|---|---|---|
| **本质** | C++ 编译单元,定义链接依赖 | 游戏功能的加载/卸载容器 |
| **粒度** | 大(整个 .dll| 小(一个玩法模式) |
| **加载时机** | 引擎启动时强制加载 | 运行时按需加载(通过 Experience |
| **卸载** | 不可卸载 | 可以卸载(切换模式时) |
| **包含什么** | C++ 类ASC、AttributeSet、Character | DataAsset + 蓝图 + 少量 C++ + 地图 |
| **依赖方向** | GameFeature 依赖 GameModule | 不反向 |
**Lyra 具体例子**
```
LyraGameGameModule — 编译时加载)
│ 定义基础设施ULyraAbilitySystemComponent、ULyraGameplayAbility、
│ ULyraHealthSet、ULyraAbilitySet、UGameFeatureAction_AddAbilities...
│ 这些东西永远在内存中,不可卸载。
├── ShooterCoreGameFeature Plugin — 运行时加载)
│ │ 没有 C++ 基类定义,只有:
│ │ - AbilitySet_ShooterHeroDataAsset有哪些技能
│ │ - IMC_ShooterGameInputMappingContext按键绑定
│ │ - InputData_ShooterGame_AddOnsInputConfig按键→Tag映射
│ │ - UAimAssistInputModifier少量专用 C++
│ │ - 武器蓝图、地图、Experience 定义
│ │
│ │ 加载时:通过 UGameFeatureAction_AddAbilities 把 AbilitySet
│ │ 里的技能"注入"到 PlayerState 的 ASC 上。
│ │ 卸载时:全部回收。
└── TopDownArenaGameFeature Plugin — 运行时加载)
完全不同的玩法模式,不同 AbilitySet、不同 InputConfig。
可以和 ShooterCore 共存或切换,互不依赖。
```
**核心理解**ShooterCore 自己**没有定义任何新的 GAS 类**。它只是"使用" LyraGame 中已经定义好的 `ULyraAbilitySet``ULyraInputConfig``GameFeatureAction` 这些基础设施,通过**数据配置**DataAsset + 蓝图)来组织射击游戏的特定内容。
---
## 二、逻辑主干 — Lyra 的 5 个核心流程
学 Lyra 最重要的不是记类名,而是理解这 5 个流程。每个流程是一条"主干链路",挂载着相关的"枝叶类"。
### 流程 1游戏启动 → Experience 加载
```
服务器进入地图
→ ULyraExperienceManagerComponent::SetCurrentExperience(ExperienceId)
→ StartExperienceLoad()
→ 加载 Experience 对应的 PrimaryAsset
→ 遍历 Experience.GameFeaturesToEnable
→ UGameFeaturesSubsystem::LoadPlugin(每个Feature)
→ OnGameFeaturePluginLoadComplete() ← 等所有Feature加载完
→ OnExperienceFullLoadCompleted()
→ 执行 Experience.ActionsUGameFeatureAction 列表)
→ GameFeatureAction_AddAbilities::OnGameFeatureActivating()
→ GameFeatureAction_AddInputContextMapping::OnGameFeatureActivating()
→ GameFeatureAction_AddInputBinding::OnGameFeatureActivating()
→ Broadcast OnExperienceLoaded → PlayerState 开始授权技能
```
**关键文件**
- `LyraExperienceManagerComponent.h/cpp` — 整个游戏的启动器
- `LyraExperienceDefinition.h` — 纯 DataAsset定义了"启用哪些Feature + 用哪个 Pawn + 哪些 Action"
- `GameFeatureAction_AddAbilities.h/cpp` — 把 Ability/Attribute/AbilitySet 注入 ASC
**学习顺序**:先看懂 `LyraExperienceDefinition`只有3个字段再看 `LyraExperienceManagerComponent` 的状态机Unloaded → Loading → LoadingGameFeatures → ExecutingActions → Loaded最后看 `GameFeatureAction_AddAbilities::AddToWorld`
### 流程 2PlayerState 初始化 → 技能授权
```
ALyraPlayerState::PostInitializeComponents()
→ ASC->InitAbilityActorInfo(this, GetPawn()) ← Owner=PlayerState, Avatar=Pawn
→ 注册 OnExperienceLoaded 回调
Experience 加载完成
→ OnExperienceLoaded()
→ SetPawnData(PawnData)
→ 遍历 PawnData->AbilitySets
→ AbilitySet->GiveToAbilitySystem(ASC)
→ 逐个 GiveAbility注入 InputTag 到 DynamicSpecSourceTags
→ 逐个 ApplyGameplayEffect初始化属性
→ 逐个 AddAttributeSetSubobject
→ Broadcast NAME_LyraAbilityReady
→ 触发 GameFeatureAction_AddAbilities::HandleActorExtension
为所有已注册ActorClass授予GameFeature专属能力
```
**关键文件**
- `LyraPlayerState.h/cpp` — 拥有 ASC是"数据的主人"
- `LyraPawnData.h` — DataAsset定义"这个Pawn类型用哪些 AbilitySet + 哪个 TagRelationshipMapping"
- `LyraAbilitySet.h/cpp` — 不可变 DataAsset包含 Abilities + Effects + AttributeSets
**学习顺序**`LyraAbilitySet` 的结构 → `GiveToAbilitySystem` 函数 → `LyraPawnData``LyraPlayerState::SetPawnData`
### 流程 3Pawn 生成 → ASC 桥接
```
GameMode::RestartPlayer()
→ Pawn 被创建
→ Possess(Pawn)
→ ULyraHeroComponent::OnPawnReadyToInitialize()
→ PawnExtComp->InitializeAbilitySystem(PS->GetASC(), PS)
ULyraPawnExtensionComponent::InitializeAbilitySystem(InASC, InOwnerActor)
1. 如果已有 ASC → UninitializeAbilitySystem清理旧绑定
2. 如果 ASC 已有其他 Avatar → 踢掉旧的
3. AbilitySystemComponent = InASC ← 瞬态缓存,不拥有
4. InASC->InitAbilityActorInfo(InOwnerActor, Pawn)
5. 设置 ASC 的 TagRelationshipMapping来自 PawnData
6. Broadcast OnAbilitySystemInitialized
→ ALyraCharacter::OnAbilitySystemInitialized()
→ HealthComponent->InitializeWithAbilitySystem()
→ InitializeGameplayTags()
```
**关键文件**
- `LyraHeroComponent.h/cpp` — "粘合层",触发 ASC 初始化
- `LyraPawnExtensionComponent.h/cpp` — "桥接层",热插拔管理
- `LyraCharacter.h/cpp` — Character 本身不持有 ASC通过 PawnExtComp 委托
**核心理解**Character 的生命周期是短暂的死亡→重生。ASC 不能放在 Character 上——否则死亡时数据全丢。PawnExtensionComponent 确保"新 Pawn 连接旧 ASC"。
### 流程 4输入处理 → Ability 激活
```
玩家按左键
→ EnhancedInput 触发 UInputAction
→ ULyraHeroComponent 收到 InputAction
→ 查 ULyraInputConfig 找到对应的 InputTag
→ ASC->AbilityInputTagPressed(InputTag)
遍历 GetActivatableAbilities():
匹配 DynamicSpecSourceTags.HasTagExact(InputTag)
→ InputPressedSpecHandles.AddUnique(Handle)
每帧 Tick
→ ASC->ProcessAbilityInput(DeltaTime, bGamePaused)
1. 检查 InputBlocked Tag如果被阻断清空全部缓冲
2. Held handles: WhileInputActive → TryActivateAbility
3. Pressed handles: OnInputTriggered → 设置 InputPressed → TryActivateAbility
4. 统一批量激活所有待激活 Ability
5. Released handles: 设置 InputPressed=false → InputReleased 事件
6. 清空缓存
```
**关键文件**
- `LyraInputConfig.h` — InputAction ↔ InputTag 映射表
- `LyraAbilitySystemComponent.h/cpp``AbilityInputTagPressed/Released/Held` + `ProcessAbilityInput`
- `LyraHeroComponent.h/cpp` — 输入事件入口
**核心理解**:三层解耦——硬件按键(UInputAction) → 逻辑输入(InputTag) → 技能(AbilitySpec.DynamicSpecSourceTags)。策划可以在任意一层修改映射。
### 流程 5网络复制
```
服务端:
ASC 在 PlayerState 上Replicated
属性变化 → ReplicatedUsing → OnRep 回调
AbilitySpec 通过 ASC 复制
GE 通过 Mixed 模式复制
客户端预测:
ULyraGameplayAbility 默认 NetExecutionPolicy = LocalPredicted
TryActivateAbility → 客户端先执行 → 服务器确认 → ScopedPredictionKey
如果服务器拒绝 → 客户端回滚
属性复制细节:
HealthSet: COND_OwnerOnly仅复制给Owner减少带宽
CombatSet: COND_OwnerOnly
Mixed 复制模式: Minimal 用于纯服务器属性Mixed 用于可修改属性
```
**关键文件**
- `LyraPlayerState.h` — ASC 是 Replicated 组件
- `LyraGameplayAbility.h` — ActivationPolicy/Group 控制预测行为
- `LyraHealthSet.h/cpp` — OnRep_Health 客户端通知
---
## 三、学习路径 — 从哪里开始
### 第 1 天:理解"三个 Actor 各管什么"
只读这些文件,不要跳:
1. `LyraPlayerState.h`60行— ASC 在哪AttributeSet 在哪?什么时候 InitAbilityActorInfo
2. `LyraCharacter.h``GetAbilitySystemComponent` 函数)— Character 如何获取 ASC
3. `LyraPawnExtensionComponent.h`100行— InitializeAbilitySystem / UninitializeAbilitySystem / HandleControllerChanged
**目标回答三个问题**
- 谁的组件拥有 ASCPlayerState
- 谁调用 InitAbilityActorInfoPawnExtensionComponent
- 谁是 Owner谁是 AvatarPlayerState=Owner, Pawn=Avatar
### 第 2 天:理解"技能从哪来"
1. `LyraAbilitySet.h` — 三个 TArrayAbility / GE / AttributeSet
2. `LyraAbilitySet.cpp``GiveToAbilitySystem``TakeFromAbilitySystem`
3. `LyraPawnData.h` — 一个 Pawn 类型用哪些 AbilitySet
4. `LyraPlayerState.cpp``SetPawnData` 函数
**目标**画一张图——AbilitySet → PawnData → PlayerState → ASC → AbilitySpec
### 第 3 天:理解"输入怎么变成技能激活"
1. `LyraInputConfig.h` — InputAction ↔ InputTag 映射结构
2. `LyraAbilitySystemComponent.h``AbilityInputTagPressed` + `ProcessAbilityInput`
3. `LyraGameplayAbility.h` — ActivationPolicy + ActivationGroup 枚举
**目标**画一张图——按键→InputAction→InputTag→DynamicSpecSourceTags→ProcessAbilityInput→TryActivateAbility
### 第 4 天:理解 GameFeature 的"注入"机制
1. `LyraExperienceDefinition.h` — GameFeaturesToEnable + Actions
2. `GameFeatureAction_AddAbilities.h/cpp` — 完整的激活/停用流程
3. `GameFeatureAction_WorldActionBase.h/cpp` — 基类抽象
**目标**:理解 GameFeatureAction 的扩展机制——它本质上是一个Observer模式监听特定ActorClass的创建/销毁,自动注入能力。
### 第 5 天:理解"初始化顺序"
不看代码,先画 Lyra 的 InitState 状态机:
```
Spawned → DataAvailable → DataInitialized → GameplayReady
```
每个组件通过 `IGameFrameworkInitStateInterface` 实现自己对这些状态转换的判断。
然后跟踪 `LyraHeroComponent::CheckDefaultInitialization` 的逻辑。
### 第 6-7 天:综合阅读 LyraExperience 和 GameFeatureAction_AddInputContextMapping
完整跟踪从"服务器选择地图"到"玩家按键激活技能"的全链路。
---
## 四、Lyra 的分支逻辑 — 哪些是主干、哪些是枝叶
### 核心主干(必须理解)
```
LyraExperienceDefinition ← 游戏"模式"的定义
└── LyraExperienceManagerComponent ← 加载/卸载管理器
LyraPlayerState ← 数据持有者ASC + AttributeSet + PawnData
└── LyraAbilitySet ← 技能打包授权
└── LyraPawnData ← Pawn 的配置数据
LyraPawnExtensionComponent ← ASC 桥接层
└── LyraCharacter ← 通过 PawnExtComp 间接获取 ASC
└── LyraHeroComponent ← 玩家专用的粘合层
LyraAbilitySystemComponent ← ASC 扩展(输入缓冲/ActivationGroup/ProcessAbilityInput
└── LyraGameplayAbility ← Ability 基类ActivationPolicy/Group/Cost
GameFeatureAction_AddAbilities ← GameFeature 注入能力的关键 Action
└── GameFeatureAction_WorldActionBase ← 通用 Action 基类
```
### 枝叶(按需学习)
```
LyraHealthSet / LyraCombatSet ← 战斗属性→先学 HealthSet 的 Meta 属性模式
LyraDamageExecution ← 伤害计算→在你需要自定义伤害公式时学
LyraAbilityCost_* ← 消耗系统→在你需要特殊消耗(弹药/能量)时学
GlobalAbilitySystem ← 全局技能分发→在你需要全局Buff时学
LyraGamePhaseSubsystem ← 游戏阶段→在你需要"热身/进行/结束"阶段时学
GameplayTagStack ← Tag栈计数→在你需要弹药/消耗品时学
LyraEquipmentManagerComponent ← 装备系统→在你需要武器/装备切换时学
LyraGameplayCueManager ← Cue加载管理→在你需要大量特效时学
ShooterCore/* ← 射击游戏示范→在你需要参考 Feature 怎么做时学
TopDownArena/* ← 俯视角示范→同上
```
---
## 五、如何迭代增加新功能
### 场景 1新增一个技能最简
只需要编辑 DataAsset不需要写 C++
1. 创建 `GA_MyAbility` 蓝图,父类选 `LyraGameplayAbility`
2. 配置 ActivationPolicy / ActivationGroup
3. 创建 `GE_MyDamage``GE_MyCooldown``GE_MyCost`
4.`AbilitySet_ShooterHero` 中新增一条 `GrantedGameplayAbilities`,配好 InputTag
5. 如果新增了按键,在 `InputData_ShooterGame_AddOns` 中新增 InputAction→InputTag 映射
6. 如果需要阻挡其他技能,在 `TagRelationshipMapping` 中配置
### 场景 2新增一个游戏模式如 FighterGame
1. 创建新的 GameFeature 插件 `FighterCore`(参考 ShooterCore 的 .uplugin 结构)
2. 创建 `AbilitySet_FighterHero`,放入格斗技能
3. 创建 `IMC_FighterGame`InputMappingContext
4. 创建 `InputData_FighterGame`InputConfig
5. 创建 `B_FighterGame.uasset`Experience 定义),配置:
- `GameFeaturesToEnable = ["FighterCore"]`
- `Actions = [GameFeatureAction_AddAbilities, GameFeatureAction_AddInputBinding...]`
6. 如需专用 C++ 组件(连击管理器、输入缓冲),加在 `FighterCore/Source`
7. 选择地图时指定这个 Experience
### 场景 3扩展现有的 GAS 基础设施
1. **新增属性**:新建 AttributeSet 类(如 `URPGStaminaSet`),在 `LyraPlayerState` 构造函数中 `CreateDefaultSubobject`
2. **新增 AbilityTask**:继承 `UAbilityTask`,放在 LyraGame 模块中
3. **新增 GameFeatureAction**:继承 `UGameFeatureAction_WorldActionBase`,重写 `AddToWorld`
4. **新增 AbilityCost 类型**:继承 `ULyraAbilityCost`(如果是 LyraGame 级别)或在 Feature 中自定义
### 场景 4修改初始化顺序
Lyra 的 InitState 是通过 `IGameFrameworkInitStateInterface` 实现的。不要直接改 BeginPlay 顺序,而是:
1. 你的组件实现 `IGameFrameworkInitStateInterface`
2.`CanChangeInitState` 中声明"我要等到哪些条件才能进入下一个状态"
3.`HandleChangeInitState` 中执行状态转换逻辑
4. 其他组件通过 `BindOnActorInitStateChanged` 监听你的状态变化
---
## 六、最重要的 10 个概念速查
| 概念 | 一句话解释 | 在哪里 |
|------|-----------|--------|
| **Experience** | 游戏"模式"的配置清单启用哪些Feature、用哪个Pawn | `LyraExperienceDefinition` |
| **AbilitySet** | 把 Ability + GE + AttributeSet 打包成一个 DataAsset | `LyraAbilitySet` |
| **PawnData** | 一个 Pawn 类型的配置:用哪些 AbilitySet、用什么 TagRelationshipMapping | `LyraPawnData` |
| **PawnExtensionComponent** | ASC 的"插座"Pawn 通过它连接/断开 ASC | `LyraPawnExtensionComponent` |
| **GameFeatureAction** | Observer 模式:监听 Actor 创建→自动注入能力 | `GameFeatureAction_AddAbilities` |
| **InputTag** | EnhancedInput 和 Ability 之间的"中间语言" | `LyraInputConfig` + `DynamicSpecSourceTags` |
| **ActivationGroup** | 技能激活后的"排他性"规则Independent / Replaceable / Blocking | `LyraGameplayAbility` |
| **ProcessAbilityInput** | 帧级输入缓冲,批量激活→避免时序竞争 | `LyraAbilitySystemComponent` |
| **InitState** | 组件的初始化状态机Spawned→DataAvailable→DataInitialized→GameplayReady | `IGameFrameworkInitStateInterface` |
| **VerbMessage** | 松散耦合的事件广播谁受到伤害→UI/音频/统计各管各的) | `FLyraVerbMessage` + `UGameplayMessageSubsystem` |
---
## 七、调试技巧
### 跟踪 Experience 加载过程
`ULyraExperienceManagerComponent::OnExperienceFullLoadCompleted` 打断点。这个函数被调用时,所有 Feature 已加载、所有 Action 已执行。
### 跟踪 ASC 桥接
`ULyraPawnExtensionComponent::InitializeAbilitySystem` 打断点。每次 ASC 被绑定到新 Pawn重生、重连都会触发。
### 跟踪技能授权
`ULyraAbilitySet::GiveToAbilitySystem` 打断点。查看每个 Ability 的 InputTag 和 Level。
### 跟踪输入
`ULyraAbilitySystemComponent::ProcessAbilityInput` 中的三个数组Pressed/Held/Released handles设条件断点看当前帧哪些能力被收集了。
### 验证 GameFeature 是否正确注入
`GameFeatureAction_AddAbilities::AddActorAbilities` 中,检查 `FindOrAddComponentForActor` 返回的 ASC 是不是你期望的那个。
---
## 八、常见误解纠正
1. **"GameFeature 一定会被加载"** → 错。GameFeature 是按需加载的。在 Lyra 中由 Experience 触发加载。没有 Experience 引用它的 Feature 永远不会被加载。
2. **"每个 GameFeature 需要自己的 C++ 模块"** → 不必要。ShooterMaps 就是纯 Content 的 GameFeature没有任何 C++。
3. **"AttributeSet 必须放在 PlayerState 上"** → 不必然。Lyra 的选择是因为 PlayerState 跨地图存活。但也可以放在 Character 上(如 Bot 的 `ALyraCharacterWithAbilities`)。
4. **"InitAbilityActorInfo 只能调用一次"** → 错。Lyra 在重生时会用新的 Pawn 再次调用,关键的 `UninitializeAbilitySystem` 负责清理旧绑定。
5. **"EnhancedInput 的 InputAction 直接绑定到 Ability"** → 错。Lyra 中间插入了 InputTagGameplayTag作为桥梁。这就是为何策划可以独立修改按键和技能映射。
6. **"PawnExtensionComponent 拥有 ASC"** → 错。它的 `AbilitySystemComponent` 成员是 `Transient`,只是一个缓存的指针。真正的所有者是 PlayerState。