--- 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 │ │ GameModule(C++ 编译单元,启动时强制加载) │ │ (游戏框架: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.dll(GameModule,不可卸载) │ │ → 所有 UCLASS 注册到反射系统 │ │ → ULyraGameEngineSubsystem::Initialize() │ │ → UGameFeaturesSubsystem 初始化 │ └───────────────────────────────────────────────────────────┘ ↓ ┌─ 阶段1:进入地图 ─────────────────────────────────────────┐ │ World 创建 │ │ → GameState 创建 → ULyraExperienceManagerComponent 创建 │ │ → GameMode 创建 → 调用 ChoosePlayerStart 等 │ │ │ │ 重点:此时还没有加载任何 GameFeature。 │ │ 只有 LyraGame 的 C++ 类在内存中。 │ └───────────────────────────────────────────────────────────┘ ↓ ┌─ 阶段2:Experience 加载(最关键!)────────────────────────┐ │ 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` | 先学 HealthSet(4属性),再学 CombatSet(2属性) | | **伤害怎么算** | `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 具体例子**: ``` LyraGame(GameModule — 编译时加载) │ 定义基础设施:ULyraAbilitySystemComponent、ULyraGameplayAbility、 │ ULyraHealthSet、ULyraAbilitySet、UGameFeatureAction_AddAbilities... │ 这些东西永远在内存中,不可卸载。 │ ├── ShooterCore(GameFeature Plugin — 运行时加载) │ │ 没有 C++ 基类定义,只有: │ │ - AbilitySet_ShooterHero(DataAsset:有哪些技能) │ │ - IMC_ShooterGame(InputMappingContext:按键绑定) │ │ - InputData_ShooterGame_AddOns(InputConfig:按键→Tag映射) │ │ - UAimAssistInputModifier(少量专用 C++) │ │ - 武器蓝图、地图、Experience 定义 │ │ │ │ 加载时:通过 UGameFeatureAction_AddAbilities 把 AbilitySet │ │ 里的技能"注入"到 PlayerState 的 ASC 上。 │ │ 卸载时:全部回收。 │ └── TopDownArena(GameFeature 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.Actions(UGameFeatureAction 列表) → 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`。 ### 流程 2:PlayerState 初始化 → 技能授权 ``` 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` ### 流程 3:Pawn 生成 → 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 **目标回答三个问题**: - 谁的组件拥有 ASC?(PlayerState) - 谁调用 InitAbilityActorInfo?(PawnExtensionComponent) - 谁是 Owner,谁是 Avatar?(PlayerState=Owner, Pawn=Avatar) ### 第 2 天:理解"技能从哪来" 1. `LyraAbilitySet.h` — 三个 TArray(Ability / 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 中间插入了 InputTag(GameplayTag)作为桥梁。这就是为何策划可以独立修改按键和技能映射。 6. **"PawnExtensionComponent 拥有 ASC"** → 错。它的 `AbilitySystemComponent` 成员是 `Transient`,只是一个缓存的指针。真正的所有者是 PlayerState。