UE5.8 Gameplay Ability System 专题系列

UE5.8 GAS 专题(一):ASC、Owner/Avatar 与初始化

从 UE5.8 源码拆解 UAbilitySystemComponent 的职责、OwnerActor/AvatarActor、ReplicationMode、初始化时机和 PlayerState 持有 ASC 的工程做法。

总览

GAS 系列的第一篇先不讲技能和伤害,而是讲 UAbilitySystemComponent。这是 GAS 里最容易被“能跑就行”糊过去、后面又最难补救的部分。ASC 放错位置,InitAbilityActorInfo 时机不完整,OwnerActorAvatarActor 混用,都会让 Ability 激活、预测、Cue、属性复制在多人环境里变得很难排查。

本篇目标是把 ASC 当成一个有明确生命周期的状态容器来设计:谁拥有它,谁作为当前表现载体,何时初始化,服务器和客户端分别做什么,复制模式怎么选。

UE5.8 GAS 专题(一):ASC、Owner/Avatar 与初始化 配图
玩家角色常见结构:PlayerState 作为 Owner 持有 ASC,Character 作为 Avatar 承载 Mesh、Movement 和表现。

源码依据

UE5.8 里 ASC 的主入口在 AbilitySystemComponent.h。源码注释把它定义成 GameplayAbilities、GameplayEffects、GameplayAttributes 的接口层。它继承自 UGameplayTasksComponent,实现 IGameplayTagAssetInterfaceIAbilitySystemReplicationProxyInterface,这说明 ASC 不是普通组件,而是同时承担任务调度、Tag 查询和复制代理职责。

关键源码点:

  • InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor) 缓存 FGameplayAbilityActorInfo
  • AbilityActorInfo 里保存 OwnerActor、AvatarActor、PlayerController、ASC、SkeletalMesh、AnimInstance、MovementComponent。
  • GiveAbility 只应该在 Authority 上执行。
  • ActivatableAbilitiesFGameplayAbilitySpecContainer,会复制并触发 OnRep_ActivateAbilities
  • EGameplayEffectReplicationMode 提供 MinimalMixedFull 三档复制策略。

源码里 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:

  1. 服务器的 PossessedBy
  2. 客户端的 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 实际挂在哪里。

初始化顺序建议写成检查清单:

  1. 构造函数创建 ASC 和 AttributeSet。
  2. ASC SetIsReplicated(true)
  3. 设置 ReplicationMode
  4. 服务器 PossessedByInitAbilityActorInfo
  5. 客户端 OnRep_PlayerStateInitAbilityActorInfo
  6. 服务器授予初始 Ability。
  7. 服务器应用初始 Attribute GE。
  8. 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.h
  • Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cpp
  • Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp
  • Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/Abilities/GameplayAbility.h
  • Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffect.h
  • Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AttributeSet.h
  • Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayPrediction.h