系列导航

主题
架构哲学总览
Experience 系统
Character 组件体系
GAS 集成层(本文)
Equipment & Inventory
Input 系统
网络同步框架
UI 系统

一句话

Lyra 的 GAS 层不是简单的”使用”原生 GameplayAbilitySystem——它用五个封装层将 GAS 改造成完全 DataAsset 驱动、GameplayTag 中介、模块化注入的子系统。同一个 ALyraCharacter 在不同 Experience 中呈现完全不同的能力组合,这一切不需要写一行硬编码的 GiveAbility()


一,与标准 GAS 的核心差异

标准 GAS 的做法

1
2
3
4
5
6
7
8
9
10
// 传统 UE:在 Actor 构造中硬编码所有能力
AMyCharacter::AMyCharacter() {
ASC = CreateDefaultSubobject<UAbilitySystemComponent>("ASC");
AttributeSet = CreateDefaultSubobject<UMyAttributeSet>("AttributeSet");
}
void AMyCharacter::BeginPlay() {
ASC->GiveAbility(FGameplayAbilitySpec(UMyAttack::StaticClass()));
ASC->GiveAbility(FGameplayAbilitySpec(UMyJump::StaticClass()));
// ... 每个新能力加一行
}

问题:

  • 同一 Character 类无法在不同模式中拥有不同能力组合
  • 卸载能力困难(ClearAbility 后 SpecHandle 丢失)
  • 输入绑定与能力代码耦合

Lyra 的做法

1
2
3
4
5
6
// Lyra: 零硬编码。能力全部从外部 DataAsset 注入
ALyraCharacter::ALyraCharacter() {
// 不创建 ASC。不创建 AttributeSet。不 GiveAbility。
PawnExtComp = CreateDefaultSubobject<ULyraPawnExtensionComponent>("PawnExt");
// PawnExtComp 内部动态创建 ASC,但能力通过外部管道注入
}

二,五大封装层

封装层 解决的原生问题 核心机制
AbilitySet 能力/属性/效果硬编码在构造函数中 将 GA + GE + AttributeSet 打包为单一 DataAsset,支持批量授予与撤销
InputTag 桥接 InputAction 指针直接引用 → 无法热卸载 GameplayTag 作为中间层:InputConfig 和 AbilitySet 通过 Tag 字符串耦合
ActivationGroup 互斥 原生仅靠 Block/Cancel Tags 做粗粒度互斥 三级分组:Independent / Exclusive_Replaceable / Exclusive_Blocking
GlobalAbility 系统 无全局能力概念 WorldSubsystem 级别的全局能力,新 ASC 注册时自动继承
Experience 注入管道 能力配置分散在各处,无统一入口 Experience → GameFeatureAction → AbilitySet → ASC 的完整注入链

三,AbilitySet:打包、授予、撤销

ULyraAbilitySet 是 Lyra GAS 封装层的核心——它把三个独立的 GAS 概念打包在一起:

1
2
3
4
5
ULyraAbilitySet : UPrimaryDataAsset {
TArray<FLyraAbilitySet_GameplayAbility> GrantedAbilities; // GA + AbilityLevel + InputTag
TArray<FLyraAbilitySet_GameplayEffect> GrantedEffects; // GE + EffectLevel
TArray<FLyraAbilitySet_AttributeSet> GrantedAttributes; // AttributeSet 类型
};

GiveToAbilitySystem:原子授予

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void GiveToAbilitySystem(UAbilitySystemComponent* ASC, 
FLyraAbilitySet_GrantedHandles* OutHandles,
UObject* SourceObject) {
// 1. 授予 AttributeSet(在 ASC 上创建)
for (auto& Attr : GrantedAttributes)
OutHandles->AddAttributeSet(ASC->InitStats(Attr.AttributeSet));

// 2. 应用 GameplayEffect(初始化属性值)
for (auto& Effect : GrantedEffects)
OutHandles->AddGameplayEffectHandle(
ASC->ApplyGameplayEffectToSelf(Effect.GameplayEffect, Effect.EffectLevel));

// 3. 授予 GameplayAbility
for (auto& Ability : GrantedAbilities) {
FGameplayAbilitySpec Spec(Ability.GameplayAbility, Ability.AbilityLevel);
Spec.SourceObject = SourceObject; // ← 回溯到 Equipment/PawnData
Spec.DynamicSpecSourceTags.AddTag(Ability.InputTag); // ← InputTag 桥接的关键
OutHandles->AddAbilitySpecHandle(ASC->GiveAbility(Spec));
}
}

GrantedHandles:可撤销句柄

FLyraAbilitySet_GrantedHandles 记录每个 AbilitySet 授予的所有 Handle。调用 TakeFromAbilitySystem() 时,按授予的反序统一撤销——GA 被 ClearAbility()、GE 被 RemoveActiveGameplayEffect()、AttributeSet 被移除。


四,InputTag 桥接:DataAsset 间的”软耦合”

这是 Lyra 最精妙的设计模式之一。InputAction → GAS 的链路不是直接引用,而是通过 GameplayTag:

1
2
3
4
5
6
7
8
9
10
11
ULyraInputConfig.DataAsset:
AbilityInputActions[0] = { InputAction = IA_WeaponFire, InputTag = "InputTag.Weapon.Fire" }

ULyraAbilitySet.DataAsset:
GrantedAbilities[0] = { GameplayAbility = GA_WeaponFire, InputTag = "InputTag.Weapon.Fire" }

运行时桥接(LyraAbilitySystemComponent::AbilityInputTagPressed):
for (auto& Spec : ActivatableAbilities) {
if (Spec.DynamicSpecSourceTags.HasTagExact("InputTag.Weapon.Fire"))
InputPressedSpecHandles.AddUnique(Spec.Handle); // 收集匹配的 Ability
}

两个 DataAsset 之间的唯一耦合点是一个 GameplayTag 字符串。这意味着:

  • 可以修改按键绑定(改 InputConfig)而不影响能力定义
  • 可以替换能力实现(改 AbilitySet)而不影响输入绑定
  • GameFeature 卸载时只需要移除 InputConfig 和 AbilitySet,无残余引用

五,ActivationGroup:三级互斥

传统 GAS 通过 BlockAbilitiesWithTag / CancelAbilitiesWithTag 做互斥,但配置分散、关系不直观。Lyra 提供了三级分组:

策略 行为 用例
Independent 独立运行,不影响其他 移动、跳跃、被动技能
Exclusive_Replaceable 同组新能力激活时,自动取消旧能力 武器射击(切枪时取消前一武器的射击)
Exclusive_Blocking 激活期间阻止同组其他能力激活 处决技、大招动画(播放中不可被其他动作打断)

LyraAbilitySystemComponent 维护 ActivationGroupCounts,在 TryActivateAbility 时检查分组约束,在 NotifyAbilityEnded 时递减计数。


六,GlobalAbility 系统

ULyraGlobalAbilitySystem 是一个 UWorldSubsystem,解决了一个实际问题:某些能力应该在整个 World 范围内生效,而非挂在某个特定 ASC 上

1
2
3
4
5
6
7
8
9
// 向全局系统施加一个效果
GlobalAbilitySystem->ApplyAbilityToAll(GA_DoubleDamage, MyASC);
// 任何新加入的 ASC 自动获得该效果

// 新角色加入时自动继承
void RegisterASC(UAbilitySystemComponent* ASC) {
for (auto& AppliedAbility : AppliedAbilities)
GiveAbilityToASC(ASC, AppliedAbility);
}

典型使用场景:Boss 房间的全局 debuff、天气系统对全场景的影响、团队光环。


七,Experience → GAS 端到端 8 步时间线

1
2
3
4
5
6
7
8
1. Experience 加载 → GameFeature 激活
2. GameFeatureAction_AddAbilities 注册
3. GameMode::RestartPlayer → SpawnDefaultPawn
4. SetPawnData(Experience.DefaultPawnData) → BeginPlay
5. PawnData.AbilitySets → GiveToAbilitySystem(ASC)
6. NAME_LyraAbilityReady 事件广播
7. GameFeatureAction_AddAbilities 响应 → ActorAbilities.GiveToAbilitySystem(ASC)
8. GameplayReady → 玩家可以激活能力

两阶段注入:PawnData.AbilitySets(角色固有)先注入,GameFeatureAction(模式专属)后注入。这保证了”固有”和”模式”两层能力的分离与覆盖。


八,ARPG 落地要点

对于 TowerChallenge 的 Souls-like 战斗系统,我们直接利用了这些 Lyra 封装:

  1. 每种武器 = 一个 AbilitySet:装备时 GiveTo,卸下时 TakeFrom。武器的 GA(轻攻/重攻/战技)都在 AbilitySet 中
  2. 体力消耗用 GAS 原生 Cost GE:而非在 GA 中手动检查属性。UGameplayAbility::GetCostGameplayEffect() 返回体力消耗 GE
  3. 弹反/处决用 ActivationGroup:弹反 GA 设为 Exclusive_Replaceable(同一按键下不同窗口阶段自动切换),处决 GA 设为 Exclusive_Blocking(执行中不可打断)
  4. 直接继承 ULyraGameplayAbility 而非创建 UTowerGameplayAbility。我们曾误判 MinimalAPI + UE_API virtual 的含义,以为不可跨模块子类化——实际上可以。踩坑详见 Lyra-WarTower 复盘

下一篇Equipment & Inventory — 拆解 Lyra 的三段式装备架构:Fragment 模式、Definition/Instance 分离、QuickBar 桥接、以及 SourceObject 反向引用链的精妙设计。