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

34 KiB
Raw Blame History

tags, created
tags created
Lyra
UE5
GAS
学习笔记
GameFeatures
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 中已经定义好的 ULyraAbilitySetULyraInputConfigGameFeatureAction 这些基础设施,通过数据配置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 函数 → LyraPawnDataLyraPlayerState::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/cppAbilityInputTagPressed/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.h60行— ASC 在哪AttributeSet 在哪?什么时候 InitAbilityActorInfo
  2. LyraCharacter.hGetAbilitySystemComponent 函数)— Character 如何获取 ASC
  3. LyraPawnExtensionComponent.h100行— InitializeAbilitySystem / UninitializeAbilitySystem / HandleControllerChanged

目标回答三个问题

  • 谁的组件拥有 ASCPlayerState
  • 谁调用 InitAbilityActorInfoPawnExtensionComponent
  • 谁是 Owner谁是 AvatarPlayerState=Owner, Pawn=Avatar

第 2 天:理解"技能从哪来"

  1. LyraAbilitySet.h — 三个 TArrayAbility / GE / AttributeSet
  2. LyraAbilitySet.cppGiveToAbilitySystemTakeFromAbilitySystem
  3. LyraPawnData.h — 一个 Pawn 类型用哪些 AbilitySet
  4. LyraPlayerState.cppSetPawnData 函数

目标画一张图——AbilitySet → PawnData → PlayerState → ASC → AbilitySpec

第 3 天:理解"输入怎么变成技能激活"

  1. LyraInputConfig.h — InputAction ↔ InputTag 映射结构
  2. LyraAbilitySystemComponent.hAbilityInputTagPressed + 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_MyDamageGE_MyCooldownGE_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_FighterGameInputMappingContext
  4. 创建 InputData_FighterGameInputConfig
  5. 创建 B_FighterGame.uassetExperience 定义),配置:
    • 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。