34 KiB
tags, created
| tags | 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+ProcessAbilityInputLyraHeroComponent.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 各管什么"
只读这些文件,不要跳:
LyraPlayerState.h(60行)— ASC 在哪?AttributeSet 在哪?什么时候 InitAbilityActorInfo?LyraCharacter.h(GetAbilitySystemComponent函数)— Character 如何获取 ASC?LyraPawnExtensionComponent.h(100行)— InitializeAbilitySystem / UninitializeAbilitySystem / HandleControllerChanged
目标回答三个问题:
- 谁的组件拥有 ASC?(PlayerState)
- 谁调用 InitAbilityActorInfo?(PawnExtensionComponent)
- 谁是 Owner,谁是 Avatar?(PlayerState=Owner, Pawn=Avatar)
第 2 天:理解"技能从哪来"
LyraAbilitySet.h— 三个 TArray(Ability / GE / AttributeSet)LyraAbilitySet.cpp—GiveToAbilitySystem和TakeFromAbilitySystemLyraPawnData.h— 一个 Pawn 类型用哪些 AbilitySetLyraPlayerState.cpp—SetPawnData函数
目标:画一张图——AbilitySet → PawnData → PlayerState → ASC → AbilitySpec
第 3 天:理解"输入怎么变成技能激活"
LyraInputConfig.h— InputAction ↔ InputTag 映射结构LyraAbilitySystemComponent.h—AbilityInputTagPressed+ProcessAbilityInputLyraGameplayAbility.h— ActivationPolicy + ActivationGroup 枚举
目标:画一张图——按键→InputAction→InputTag→DynamicSpecSourceTags→ProcessAbilityInput→TryActivateAbility
第 4 天:理解 GameFeature 的"注入"机制
LyraExperienceDefinition.h— GameFeaturesToEnable + ActionsGameFeatureAction_AddAbilities.h/cpp— 完整的激活/停用流程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++:
- 创建
GA_MyAbility蓝图,父类选LyraGameplayAbility - 配置 ActivationPolicy / ActivationGroup
- 创建
GE_MyDamage、GE_MyCooldown、GE_MyCost - 在
AbilitySet_ShooterHero中新增一条GrantedGameplayAbilities,配好 InputTag - 如果新增了按键,在
InputData_ShooterGame_AddOns中新增 InputAction→InputTag 映射 - 如果需要阻挡其他技能,在
TagRelationshipMapping中配置
场景 2:新增一个游戏模式(如 FighterGame)
- 创建新的 GameFeature 插件
FighterCore(参考 ShooterCore 的 .uplugin 结构) - 创建
AbilitySet_FighterHero,放入格斗技能 - 创建
IMC_FighterGame(InputMappingContext) - 创建
InputData_FighterGame(InputConfig) - 创建
B_FighterGame.uasset(Experience 定义),配置:GameFeaturesToEnable = ["FighterCore"]Actions = [GameFeatureAction_AddAbilities, GameFeatureAction_AddInputBinding...]
- 如需专用 C++ 组件(连击管理器、输入缓冲),加在
FighterCore/Source中 - 选择地图时指定这个 Experience
场景 3:扩展现有的 GAS 基础设施
- 新增属性:新建 AttributeSet 类(如
URPGStaminaSet),在LyraPlayerState构造函数中CreateDefaultSubobject - 新增 AbilityTask:继承
UAbilityTask,放在 LyraGame 模块中 - 新增 GameFeatureAction:继承
UGameFeatureAction_WorldActionBase,重写AddToWorld - 新增 AbilityCost 类型:继承
ULyraAbilityCost(如果是 LyraGame 级别)或在 Feature 中自定义
场景 4:修改初始化顺序
Lyra 的 InitState 是通过 IGameFrameworkInitStateInterface 实现的。不要直接改 BeginPlay 顺序,而是:
- 你的组件实现
IGameFrameworkInitStateInterface - 在
CanChangeInitState中声明"我要等到哪些条件才能进入下一个状态" - 在
HandleChangeInitState中执行状态转换逻辑 - 其他组件通过
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 是不是你期望的那个。
八、常见误解纠正
-
"GameFeature 一定会被加载" → 错。GameFeature 是按需加载的。在 Lyra 中由 Experience 触发加载。没有 Experience 引用它的 Feature 永远不会被加载。
-
"每个 GameFeature 需要自己的 C++ 模块" → 不必要。ShooterMaps 就是纯 Content 的 GameFeature,没有任何 C++。
-
"AttributeSet 必须放在 PlayerState 上" → 不必然。Lyra 的选择是因为 PlayerState 跨地图存活。但也可以放在 Character 上(如 Bot 的
ALyraCharacterWithAbilities)。 -
"InitAbilityActorInfo 只能调用一次" → 错。Lyra 在重生时会用新的 Pawn 再次调用,关键的
UninitializeAbilitySystem负责清理旧绑定。 -
"EnhancedInput 的 InputAction 直接绑定到 Ability" → 错。Lyra 中间插入了 InputTag(GameplayTag)作为桥梁。这就是为何策划可以独立修改按键和技能映射。
-
"PawnExtensionComponent 拥有 ASC" → 错。它的
AbilitySystemComponent成员是Transient,只是一个缓存的指针。真正的所有者是 PlayerState。