总览
GAS 系列的第一篇先不讲技能和伤害,而是讲 UAbilitySystemComponent。这是 GAS 里最容易被“能跑就行”糊过去、后面又最难补救的部分。ASC 放错位置,InitAbilityActorInfo 时机不完整,OwnerActor 和 AvatarActor 混用,都会让 Ability 激活、预测、Cue、属性复制在多人环境里变得很难排查。
本篇目标是把 ASC 当成一个有明确生命周期的状态容器来设计:谁拥有它,谁作为当前表现载体,何时初始化,服务器和客户端分别做什么,复制模式怎么选。
源码依据
UE5.8 里 ASC 的主入口在 AbilitySystemComponent.h。源码注释把它定义成 GameplayAbilities、GameplayEffects、GameplayAttributes 的接口层。它继承自 UGameplayTasksComponent,实现 IGameplayTagAssetInterface 和 IAbilitySystemReplicationProxyInterface,这说明 ASC 不是普通组件,而是同时承担任务调度、Tag 查询和复制代理职责。
关键源码点:
InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)缓存FGameplayAbilityActorInfo。AbilityActorInfo里保存 OwnerActor、AvatarActor、PlayerController、ASC、SkeletalMesh、AnimInstance、MovementComponent。GiveAbility只应该在 Authority 上执行。ActivatableAbilities是FGameplayAbilitySpecContainer,会复制并触发OnRep_ActivateAbilities。EGameplayEffectReplicationMode提供Minimal、Mixed、Full三档复制策略。
源码里 InitAbilityActorInfo 还会在 Avatar 变化时调用 Ability 的 OnAvatarSet,处理延迟 GameplayCue,并重置 Montage 信息。所以它不是“填两个指针”的小函数,而是 ASC 生命周期切换点。
ASC 的职责边界
ASC 是状态中枢,不是角色业务脚本。项目里应该让 ASC 负责这些事情:
| 责任 | 说明 |
|---|---|
| 能力授予 | 服务器调用 GiveAbility 生成 FGameplayAbilitySpec |
| 能力激活 | 通过 Handle、Class、Tag 或事件触发 Ability |
| GE 应用 | 应用 FGameplayEffectSpec 到自己或目标 ASC |
| Tag 聚合 | 汇总 Ability、GE、Loose Tag 和 Block Tag |
| 属性复制 | 驱动 AttributeSet 的复制和预测对账 |
| 预测窗口 | 维护 ScopedPredictionKey 与本地预测副作用 |
| TargetData | 复制、确认或取消目标数据 |
| Cue 调度 | 根据 GE 或显式调用触发表现 |
不建议把背包、任务、商城、AI 决策、武器换弹等业务状态塞进 ASC。它们可以调用 ASC,但不要变成 ASC 的内部职责。ASC 越“万能”,越难判断某个 Tag 或 Effect 到底来自哪里。
OwnerActor 与 AvatarActor
GAS 的两个 Actor 概念要分清:
| 概念 | 典型对象 | 生命周期 | 负责什么 |
|---|---|---|---|
OwnerActor |
PlayerState |
跨 Pawn 持续存在 | 拥有 ASC 和长期状态 |
AvatarActor |
Character 或 Pawn |
会死亡、重生、替换 | 承载 Mesh、Movement、动画、碰撞和表现 |
玩家角色推荐 ASC 放在 PlayerState。原因很直接:Pawn 会死,会重生,会从大厅角色换成战斗角色;如果 ASC 跟 Pawn 走,Buff、冷却、已授予技能和持续 GE 需要额外迁移。PlayerState 更适合承载玩家长期状态。
AI 或一次性怪物可以把 ASC 放在 Pawn/Character 上。它们通常没有重生后保留状态的需求,也不需要跨 Pawn 持续保留冷却。
AYourPlayerState::AYourPlayerState()
{
AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem"));
AbilitySystem->SetIsReplicated(true);
AbilitySystem->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
CombatAttributes = CreateDefaultSubobject<UCombatAttributeSet>(TEXT("CombatAttributes"));
}
UAbilitySystemComponent* AYourPlayerState::GetAbilitySystemComponent() const
{
return AbilitySystem;
}
这段代码的重点不是“创建一个组件”,而是把 ASC 的生命周期绑定到 PlayerState。角色换 Pawn 时,ASC 保持不变,只更新 AvatarActor。
初始化时机
玩家角色至少要在两个地方初始化 ActorInfo:
- 服务器的
PossessedBy。 - 客户端的
OnRep_PlayerState。
只在服务器初始化,客户端 Ability 里经常拿不到正确的 Controller、Mesh 或 AnimInstance。只在客户端初始化,服务器授予 Ability、应用 GE 和权威判定又会缺上下文。
void AYourCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
AYourPlayerState* PS = GetPlayerState<AYourPlayerState>();
if (!PS)
{
return;
}
AbilitySystem = PS->GetAbilitySystemComponent();
if (!AbilitySystem)
{
return;
}
AbilitySystem->InitAbilityActorInfo(PS, this);
if (HasAuthority())
{
GrantStartupAbilities();
ApplyStartupEffects();
}
}
void AYourCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
AYourPlayerState* PS = GetPlayerState<AYourPlayerState>();
if (!PS)
{
return;
}
AbilitySystem = PS->GetAbilitySystemComponent();
if (AbilitySystem)
{
AbilitySystem->InitAbilityActorInfo(PS, this);
}
}
项目里还要处理重生:旧 Character 失效后,新 Character 被 Possess,需要再次 InitAbilityActorInfo(PS, NewCharacter)。如果 Ability 依赖 Mesh 或 MovementComponent,Avatar 切换后还要确认 Ability 的缓存没有拿旧组件。
ReplicationMode 怎么选
ASC 的复制模式不是性能小开关,而是“谁能看到多少 GAS 状态”的策略。
| 模式 | 语义 | 适用 |
|---|---|---|
Full |
完整复制 Active Gameplay Effects 给所有相关客户端 | 少量重要角色、调试、需要远端完整查看 Buff |
Mixed |
拥有者完整复制,非拥有者只拿必要标签和 Cue | 玩家角色最常见选择 |
Minimal |
尽量少复制 GE 细节,保留关键表现与 Tag | 大量 AI、小怪、非玩家单位 |
玩家角色一般用 Mixed,因为拥有者需要完整 UI、冷却、Buff 和预测对账,远端通常只需要看到状态表现和必要 Tag。AI 大量存在时用 Minimal 更省。
项目落地模板
推荐做一个接口或基类,让任何需要 ASC 的 Actor 都能统一取组件:
UINTERFACE(BlueprintType)
class UYourAbilitySystemProvider : public UInterface
{
GENERATED_BODY()
};
class IYourAbilitySystemProvider
{
GENERATED_BODY()
public:
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const = 0;
};
角色、PlayerState、AI Pawn 都实现统一接口。Ability、UI、武器、交互系统只依赖“能拿到 ASC”,不关心 ASC 实际挂在哪里。
初始化顺序建议写成检查清单:
- 构造函数创建 ASC 和 AttributeSet。
- ASC
SetIsReplicated(true)。 - 设置
ReplicationMode。 - 服务器
PossessedBy调InitAbilityActorInfo。 - 客户端
OnRep_PlayerState调InitAbilityActorInfo。 - 服务器授予初始 Ability。
- 服务器应用初始 Attribute GE。
- UI 在 ASC 准备好后绑定属性变化委托。
常见坑
- 把 ASC 放在 Character 上又想保留死亡前 Buff。 这会迫使你写复杂迁移逻辑,除非是 AI,否则玩家优先放 PlayerState。
- 只在
BeginPlay初始化 ASC。 PlayerState、Controller 和 Pawn 的复制顺序不保证此时都已就绪。 - 客户端没跑
InitAbilityActorInfo。 Ability 本地预测、动画和 UI 会缺 ActorInfo。 - 在客户端
GiveAbility。 源码路径要求 Authority,客户端授予只会制造错觉。 - PlayerState 网络相关性太低。 ASC 放 PlayerState 时,Cue 和复制代理要仔细测试远端可见性。
- 把 ASC 当业务对象。 ASC 管 GAS 状态,背包、武器库存、任务系统应通过接口和 GE/Ability 协作。
本篇结论:先把 ASC 生命周期定稳,再谈 Ability、GE 和预测。GAS 里很多“技能为什么没触发”的问题,根源都不是技能本身,而是 ActorInfo、Authority 或复制模式一开始就错了。
源码路径索引
Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemComponent.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cppEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cppEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/Abilities/GameplayAbility.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffect.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AttributeSet.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayPrediction.h