GAS 系统深度学习指南UE 5.4 / 5.5

06 GameplayAbilities 能力系统

目标:掌握 GA 的本质与适合场景、完整生命周期、复制/实例化/网络执行/安全四类策略的选择、输入绑定的完整接线(含竞态处理)、能力授予与六种激活方式、Ability Tags 九大容器、向能力传递数据的四种方式、Cost/Cooldown 的灵活时机、能力升级与 AbilitySet、RPC 批处理与 Net Security Policy。


6.1 GA 是什么

UGameplayAbility(GA)是技能的执行逻辑:一个技能具体"做什么"。每个具体技能都是一个 GA 子类:UGA_Fireball、UGA_MeleeAttack、UGA_Heal、UGA_PassiveRegen……

GA 的核心特性

  1. 一个 Actor 可以同时激活多个 GA(同时"冲刺"+"射击");
  2. 可用 Blueprint 或 C++ 创建;
  3. GA 不运行在 Simulated Proxy 上——它们只在拥有客户端和/或服务器上运行;其他客户端看到的技能表现,通过 AbilityTask 复制的动画、GameplayCue 等通道传达。

适合 / 不适合用 GA

适合用 GA 不适合用 GA
跳跃、冲刺、射击 基础移动输入(WASD)
被动格挡、使用药水 UI 交互(商店购买)
开门、采集资源、建造 简单物理碰撞响应
基础动画状态机切换

判断标准:这个动作是否需要消耗、冷却、等级系统、网络同步或复杂的异步逻辑?需要就值得用 GA。

GA 的内置功能

  1. 等级系统:通过等级修改属性变化量或改变行为;
  2. 消耗与冷却:可选的 Cost GE 和 Cooldown GE;
  3. 预测支持:通过 Net Execution Policy 控制本地预测;
  4. AbilityTask 集成:处理持续性操作(等待事件/属性变化/选目标等)。

6.2 生命周期详解

激活 → 执行逻辑 → 提交消耗/冷却(CommitAbility)→ 结束(EndAbility)

简单能力流程(一次性火球):

  1. 玩家按技能键 → 2. ASC 检查 Tag 需求/消耗/冷却 → 3. ActivateAbility() → 4. CommitAbility()(扣蓝、启动冷却)→ 5. 施加 GE → 6. EndAbility()。

复杂能力流程(蓄力射击):

  1. 按下技能键 → 2. 条件通过 → 3. ActivateAbility() → 4. PlayMontageAndWait 播放蓄力动画 → 5. WaitGameplayEvent 等 "Release" 事件 → 6. 事件触发 → CommitAbility() 扣弹药/启冷却 → 7. 执行射击 → 8. 等动画结束 → 9. EndAbility()。

关键:GA 本身只在一帧内执行。所有跨帧操作(播放动画、等待输入、监听事件)必须通过 AbilityTask(见第 07 章)。

关键调用

bool CanActivateAbility(...);   // 激活前检查(Tag/Cost/Cooldown)

void ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);  // 核心逻辑

bool CommitAbility(...);    // 同时提交消耗+冷却
bool CommitCost(...);       // 只提交消耗
bool CommitCooldown(...);   // 只提交冷却

void EndAbility(Handle, ActorInfo, ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled);

最常见的 Bug:忘记调用 EndAbility()。激活后所有代码路径(正常结束、被打断、出错)都必须最终到达 EndAbility()。

最小实现模板

void UGA_MyAbility::ActivateAbility(
    const FGameplayAbilitySpecHandle Handle,
    const FGameplayAbilityActorInfo* ActorInfo,
    const FGameplayAbilityActivationInfo ActivationInfo,
    const FGameplayEventData* TriggerEventData)
{
    Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
    if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
    {
        EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
        return;
    }
    // ... 核心逻辑(异步任务:动画/等待输入) ...
}

6.3 复制策略(Replication Policy)——不要用

不要使用这个选项。名字具有误导性: - GameplayAbilitySpec 默认就从服务器复制到拥有客户端; - GA 不在 Simulated Proxy 上运行,视觉变化通过 AbilityTask/Cue 复制; - Epic 的 Dave Ratti 表示希望未来移除这个选项。

常见陷阱:初学者以为要设置它才能让能力联网,实际上 GAS 默认行为已处理所有必要复制。

Server Respects Remote Ability Cancellation——通常禁用

含义:客户端 GA 因取消/自然结束而终止时,会强制服务器上的版本也终止——无论服务器 GA 是否已完成。

问题场景:高延迟下客户端 GA 已自然结束,服务器 GA 还在应用伤害,客户端结束信号到达强制终止服务器 GA → 伤害未应用,状态不一致。

最佳实践:通常禁用,让服务器独立管理 GA 生命周期。

Replicate Input Directly——不推荐

设置后会把输入按下/释放事件始终复制到服务器。Epic 不推荐,建议用内置 AbilityTask(WaitInputPress 等)内部的 Generic Replicated Events 机制(更高效)。

6.4 输入绑定详解

6.4.1 输入枚举(名称必须与项目设置输入动作名完全匹配)

UENUM(BlueprintType)
enum class EGDAbilityInputID : uint8
{
    None    UMETA(DisplayName = "None"),
    Confirm UMETA(DisplayName = "Confirm"),
    Cancel  UMETA(DisplayName = "Cancel"),
    Ability1 UMETA(DisplayName = "Ability1"),   // 鼠标左键
    Ability2 UMETA(DisplayName = "Ability2"),   // 鼠标右键
    Ability3 UMETA(DisplayName = "Ability3"),   // Q
    Ability4 UMETA(DisplayName = "Ability4"),   // E
    Ability5 UMETA(DisplayName = "Ability5"),   // R
    Sprint  UMETA(DisplayName = "Sprint"),
    Jump    UMETA(DisplayName = "Jump")
};

枚举名称必须与项目设置中输入动作名称完全匹配(DisplayName 不影响匹配)。唯一例外:Confirm/Cancel 通过 BindAbilityActivationToInputComponent 显式映射,不需匹配。

6.4.2 BindAbilityActivationToInputComponent(UE 5.1+ 用 FTopLevelAssetPath)

void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    FTopLevelAssetPath EnumPath = FTopLevelAssetPath(
        FName("/Script/MyGame"), FName("EGDAbilityInputID"));
    AbilitySystemComponent->BindAbilityActivationToInputComponent(
        PlayerInputComponent,
        FGameplayAbilityInputBinds(
            FString("ConfirmTarget"),       // 确认输入动作名
            FString("CancelTarget"),        // 取消输入动作名
            EnumPath,                        // 枚举路径(5.1+ 用 FTopLevelAssetPath)
            static_cast<int32>(EGDAbilityInputID::Confirm),
            static_cast<int32>(EGDAbilityInputID::Cancel)));
}

6.4.3 ASC 在 PlayerState 时的绑定竞态

若 ASC 在 PlayerState,PlayerState 可能在 SetupPlayerInputComponent() 时还没复制到客户端——需在两个地方尝试绑定(用布尔确保只绑一次):

void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    TryBindASCInput();
}
void AMyCharacter::OnRep_PlayerState()
{
    Super::OnRep_PlayerState();
    TryBindASCInput();
}
void AMyCharacter::TryBindASCInput()
{
    if (!bASCInputBound && AbilitySystemComponent.IsValid() && InputComponent)
    {
        // 执行绑定逻辑
        bASCInputBound = true;
    }
}

关键:OnRep_PlayerState() 单独不够——PlayerState 可能在 Controller 调用 ClientRestart() 创建 InputComponent 之前就复制了。两处都试,布尔保证只绑一次。

6.4.4 不自动激活的输入绑定(bActivateOnInput)

有时要绑定输入但不自动激活(让 Task 用)。自定义 GA 加 bActivateOnInput,在自定义 ASC 中重写 AbilityLocalInputPressed():

// 自定义 GA 子类
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ability")
bool bActivateOnInput = true;

// 自定义 ASC 子类
void UMyASC::AbilityLocalInputPressed(int32 InputID)
{
    if (IsGenericConfirmInputBound(InputID)) { LocalInputConfirm(); return; }
    if (IsGenericCancelInputBound(InputID))  { LocalInputCancel();  return; }

    ABILITYLIST_SCOPE_LOCK();
    for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items)
    {
        if (Spec.InputID == InputID && Spec.Ability)
        {
            Spec.InputPressed = true;
            if (Spec.IsActive())
            {
                AbilitySpecInputPressed(Spec);
                InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed,
                    Spec.Handle, Spec.ActivationInfo.GetActivationPredictionKey());
            }
            else if (UMyGameplayAbility* GA = Cast<UMyGameplayAbility>(Spec.Ability);
                     GA && GA->bActivateOnInput)
            {
                TryActivateAbility(Spec.Handle);
            }
        }
    }
}

6.5 能力授予(Granting Abilities)

6.5.1 GiveAbility 与 FGameplayAbilitySpec

// 在服务器上授予能力
ASC->GiveAbility(FGameplayAbilitySpec(
    StartupAbility,    // GA 类
    GetAbilityLevel(StartupAbility.GetDefaultObject()->AbilityID),  // 等级
    static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID),  // 输入绑定
    this));            // SourceObject

授予时机:游戏开始(基础能力)、拾取道具、升级解锁、装备更换。

6.5.2 SourceObject 的意义

SourceObject 是授予此 GA 的对象。在 GA 内通过它获取授予者引用:

// GASShooter:把枪作为 SourceObject 授予射击能力
UObject* SourceObj = GetSourceObject(Handle, ActorInfo);
AWeaponBase* Weapon = Cast<AWeaponBase>(SourceObj);
if (Weapon) { float Damage = Weapon->GetDamage(); }

6.5.3 授予后自动复制

服务器授予的 GA 会自动复制 GameplayAbilitySpec 到拥有客户端;其他客户端/模拟代理不会收到。用 bCharacterAbilitiesGiven 防止重复授予。

6.6 六种激活方式

方式 API 场景
输入自动激活 绑定 InputID 最常见
按标签 TryActivateAbilitiesByTag(TagContainer) 多能力共享 Tag
按类 TryActivateAbilityByClass(Class) 确切知道类
按 Handle TryActivateAbility(Handle) 精确控制实例
按事件 SendGameplayEventToActor + Triggers 传递数据,事件驱动
授予并激活一次 GiveAbilityAndActivateOnce(Spec, &EventData) 临时一次性能力

事件激活(含数据传递)

FGameplayEventData Payload;
Payload.Instigator = AttackerActor;
Payload.Target = TargetActor;
Payload.EventMagnitude = 50.f;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(
    TargetActor,
    FGameplayTag::RequestGameplayTag(FName("Event.Hit")),
    Payload);

前提:GA 的 Triggers 配置对应 Tag 与 GameplayEvent 源。

// GA 构造函数
Triggers.Add(FAbilityTriggerData{
    .TriggerTag = FGameplayTag::RequestGameplayTag(FName("Event.Hit")),
    .TriggerSource = EGameplayAbilityTriggerSource::GameplayEvent});
// 也可在 OwnedTagAdded 时触发(如 State.Dead)

关键:事件激活的 Payload 会从客户端复制到服务器,适合传数据;但不能同时用输入绑定激活同一个 GA。

6.7 被动能力实现

重写 OnAvatarSet()(Epic 明确这是启动被动和 BeginPlay 类逻辑的正确位置),加 ActivateAbilityOnGranted 布尔:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ability")
bool ActivateAbilityOnGranted = false;

void UMyGameplayAbility::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo,
                                     const FGameplayAbilitySpec& Spec)
{
    Super::OnAvatarSet(ActorInfo, Spec);
    if (ActivateAbilityOnGranted)
        ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle, false);
}

被动能力通常用 ServerOnly 的 Net Execution Policy(服务器跑逻辑,视觉靠 GameplayCue)。

6.8 Activation Failed Tags(失败原因诊断)

在 DefaultGame.ini 配置失败标签,激活失败时日志会给出具体原因:

+GameplayTagList=(Tag="Activation.Fail.BlockedByTags",DevComment="")
+GameplayTagList=(Tag="Activation.Fail.CantAffordCost",DevComment="")
+GameplayTagList=(Tag="Activation.Fail.IsDead",DevComment="")
+GameplayTagList=(Tag="Activation.Fail.MissingTags",DevComment="")
+GameplayTagList=(Tag="Activation.Fail.Networking",DevComment="")
+GameplayTagList=(Tag="Activation.Fail.OnCooldown",DevComment="")

[/Script/GameplayAbilities.AbilitySystemGlobals]
ActivateFailIsDeadName=Activation.Fail.IsDead
ActivateFailCooldownName=Activation.Fail.OnCooldown
ActivateFailCostName=Activation.Fail.CantAffordCost
ActivateFailTagsBlockedName=Activation.Fail.BlockedByTags
ActivateFailTagsMissingName=Activation.Fail.MissingTags
ActivateFailNetworkingName=Activation.Fail.Networking
LogAbilitySystem: Display: InternalServerTryActivateAbility. Rejecting ClientActivation
of Default__GA_FireGun_C. InternalTryActivateAbility failed: Activation.Fail.BlockedByTags

配合 showdebug AbilitySystem 可实时查看。

6.9 取消能力

// 内部取消
CancelAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo);  // 内部调 EndAbility(WasCancelled=true)

// 外部取消(ASC 上)
void CancelAbility(UGameplayAbility* Ability);                    // 按 CDO
void CancelAbilityHandle(const FGameplayAbilitySpecHandle& H);    // 按 Handle
void CancelAbilities(WithTags, WithoutTags, Ignore);              // 按 Tag 过滤
void CancelAllAbilities(UGameplayAbility* Ignore);                // 全部

常见陷阱:CancelAllAbilities 对 Non-Instanced GA 可能表现异常(遇到 Non-Instanced 就停止);CancelAbilities 处理 Non-Instanced 更好(示例项目用后者,因 Jump 是 Non-Instanced)。

6.10 获取活跃能力

TArray<FGameplayAbilitySpec> All = ASC->GetActivatableAbilities();
for (const FGameplayAbilitySpec& Spec : All)
    if (Spec.IsActive()) { /* ... */ }

// 按 Tag 搜索(第三个参数 = 只返回满足 Tag 需求的)
TArray<FGameplayAbilitySpec*> Matching;
FGameplayTagContainer SearchTags;
SearchTags.AddTag(FGameplayTag::RequestGameplayTag(FName("Ability.Skill")));
ASC->GetActivatableGameplayAbilitySpecsByAllMatchingTags(SearchTags, Matching, true);

可以有多个 GA 同时活跃,没有单一的"活跃能力",需按 Tag/类搜索。

6.11 实例化策略(Instancing Policy)

策略 实例 特点 使用场景
Instanced Per Actor 每 ASC 1 个实例,复用 激活间有持久性,需手动重置变量 最常用默认
Instanced Per Execution 每次激活新建 变量自动重置,性能较差 需完全隔离的能力(极少用)
Non-Instanced 无(用 CDO) 性能最好但不能存状态、不能绑 AbilityTask 委托 高频简单能力(小兵攻击、跳跃)

最佳实践:在自定义 GA 基类构造函数中默认 InstancedPerActor。使用 AbilityTask 的技能必须用 InstancedPerActor 或 InstancedPerExecution(Non-Instanced 不能绑 Task 委托)。

6.12 网络执行策略(Net Execution Policy)

策略 行为 适用
LocalOnly 仅在拥有客户端运行 纯本地视觉能力
LocalPredicted 客户端先预测执行 + 服务器纠正 大多数玩家技能(手感最好)
ServerOnly 仅服务器运行 被动能力、伤害结算;单人游戏应使用此策略
ServerInitiated 服务器先激活再通知客户端 较少用

选择指南:单人 → ServerOnly;多人+需即时反馈 → LocalPredicted;多人+不需预测 → ServerOnly;纯视觉 → LocalOnly。

6.13 Ability Tags 九大容器

Tag 容器 含义
Ability Tags 描述性标签,标识这个 GA
Cancel Abilities with Tag 激活时取消带这些 Tag 的其他 GA
Block Abilities with Tag 激活期间阻止带这些 Tag 的 GA 激活
Activation Owned Tags 激活期间给拥有者这些 Tag(不复制)
Activation Required Tags 拥有者必须拥有所有这些 Tag 才能激活
Activation Blocked Tags 拥有者拥有任一这些 Tag 则不能激活
Source Required / Blocked Tags Source 要求(仅事件触发有效)
Target Required / Blocked Tags Target 要求(仅事件触发有效)
UGDGA_FireGun::UGDGA_FireGun()
{
    AbilityTags.AddTag(FGameplayTag::RequestGameplayTag("Ability.Skill.Ability1"));
    ActivationOwnedTags.AddTag(FGameplayTag::RequestGameplayTag("Ability.Skill.Ability1"));
    ActivationBlockedTags.AddTag(FGameplayTag::RequestGameplayTag("Ability.Skill"));
    ActivationBlockedTags.AddTag(FGameplayTag::RequestGameplayTag("State.Dead"));
    ActivationBlockedTags.AddTag(FGameplayTag::RequestGameplayTag("State.Debuff.Stun"));
}

这些标签不参与网络复制,只用于本地逻辑;需要客户端感知的状态用 GE Granted Tags 或 Cue。

6.14 GameplayAbilitySpec

GA 授予后的运行时表示(存在于 ASC 上):

字段 描述
Ability GA 的类(CDO 引用)
Level 能力等级
InputID 绑定的输入 ID
Handle 唯一 Spec 句柄
SourceObject 授予对象
bIsActive 是否活跃
InputPressed 输入是否按下

6.15 向能力传递数据(四种方式对比)

方式 复制支持 与输入绑定兼容 数据保证
事件激活 + GameplayEventData ✅ 自动复制 ❌ ✅
WaitGameplayEvent Task ❌ 不复制 ✅ ⚠️ 仅 Local/Server Only
TargetData ✅ 手动复制 ✅ ✅
OwnerActor/AvatarActor 复制变量 ✅ 自动复制 ✅ ⚠️ 顺序不保证
// ① 事件激活(数据随事件复制到服务器)
// 在 ActivateAbility 的 TriggerEventData 中取
AActor* Attacker = TriggerEventData->Instigator;
float Damage = TriggerEventData->EventMagnitude;

// ② WaitGameplayEvent(GA 激活后仍可监听,但不复制)
UAbilityTask_WaitGameplayEvent* Task = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
    this, EventTag, nullptr, true);
Task->EventReceived.AddDynamic(this, &UMyAbility::OnEventReceived);
Task->ReadyForActivation();

// ④ 复制变量(最灵活但顺序不保证)
Character->StoredDamage = 100.f;
ASC->TryActivateAbility(Handle);

6.16 Cost 与 Cooldown(CommitAbility)

CheckCost + CheckCooldown

CanActivateAbility() 内部自动检查 CheckCost()(能否负担)与 CheckCooldown()(是否冷却中)。

CommitCost + CommitCooldown + CommitAbility

void UMyAbility::ActivateAbility(...)
{
    if (!CommitAbility(Handle, ActorInfo, ActivationInfo))   // 同时提交
    { EndAbility(Handle, ActorInfo, ActivationInfo, true, true); return; }

    // 或分开提交(不同时间点):
    if (!CommitCost(Handle, ActorInfo, ActivationInfo))      // 先扣消耗
    { EndAbility(...); return; }
    // ... 执行逻辑 ...
    if (!CommitCooldown(Handle, ActorInfo, ActivationInfo))  // 再启冷却
    { EndAbility(...); return; }
}

提交时机灵活("最后检查")

提交消耗/冷却的时机可灵活选择——这是"最后检查"的机会,ASC 属性可能在激活后变化。

实际案例:

  • 射击游戏:先播瞄准动画,动画事件触发时才提交弹药消耗;
  • 蓄力技能:先消耗法力启动蓄力,蓄力完成后再启动冷却。

引擎内置冷却 API

float GetCooldownTimeRemaining() const;                       // BlueprintCallable
void GetCooldownTimeRemainingAndDuration(Handle, ActorInfo, TimeRemaining, Duration);
FGameplayTagContainer GetCooldownTags() const;

实例化策略坑:GetCooldownTimeRemaining() 在 InstancedPerExecution 下若在 CDO 上调用会返回 0——务必在技能实例上调用。多个技能配同一个冷却 Tag 可实现"技能组共享冷却"。

6.17 能力升级(两种方式)

// 方式 1:取消授予再重新授予(会终止活跃实例)
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromClass(AbilityClass);
if (Spec)
{
    ASC->ClearAbility(Spec->Handle);
    ASC->GiveAbility(FGameplayAbilitySpec(AbilityClass, NewLevel, Spec->InputID, Spec->SourceObject));
}

// 方式 2:直接修改 Spec.Level(不会终止活跃实例)
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromHandle(Handle);
if (Spec) { Spec->Level = NewLevel; ASC->MarkAbilitySpecDirty(*Spec); }

用自定义布尔变量决定用哪种方式。

6.18 Ability Sets(能力组)

UDataAsset 便利类,打包输入绑定 + 启动能力列表 + 授予逻辑:

UCLASS()
class UGDAbilitySet : public UDataAsset
{
    GENERATED_BODY()
public:
    UPROPERTY(EditDefaultsOnly, Category = "Abilities")
    TArray<TSubclassOf<UGDGameplayAbility>> GrantedAbilities;

    void GiveToAbilitySystem(UGDAbilitySystemComponent* ASC, UObject* SourceObject = nullptr);
};

Paragon 每个英雄一个 AbilitySet。小项目直接 Character 管理能力列表即可;大型项目(几十个英雄)用 AbilitySet 组织复用。

6.19 Ability Batching(RPC 批处理)

传统 GA 生命周期至少 2-3 个客户端→服务器 RPC:CallServerTryActivateAbility → ServerSetReplicatedTargetData → ServerEndAbility。

若 GA 在一帧内原子性完成(如即时命中枪械:激活→射线→TargetData→结束),可合并为 1 个 RPC:

// 自定义 ASC
virtual bool ShouldDoServerAbilityRPCBatch() const override { return true; }

bool UMyASC::BatchRPCTryActivateAbility(FGameplayAbilitySpecHandle Handle, bool EndImmediately)
{
    bool Activated = false;
    if (Handle.IsValid())
    {
        FScopedServerAbilityRPCBatcher Batcher(this, Handle);  // 作用域内 RPC 合并
        Activated = TryActivateAbility(Handle, true);
        if (EndImmediately)
        {
            if (FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle))
                Cast<UMyGameplayAbility>(Spec->GetPrimaryInstance())->ExternalEndAbility();
        }
    }
    return Activated;
}
  • 半自动武器:激活+TargetData+结束 → 1 个 RPC;
  • 全自动武器:第一发合并(1 个),后续每发独立 TargetData RPC,停止时独立 EndAbility RPC。

批处理只能在 C++ 中、且只能通过 Handle 激活。调试日志:AbilitySystem.ServerRPCBatching.Log 1。

6.20 Net Security Policy(网络安全策略)

策略 行为
ClientOrServer 无安全限制,客户端/服务器都可执行和终止
ServerOnlyExecution 客户端请求执行被忽略,但仍可请求取消/结束
ServerOnlyTermination 客户端请求取消/结束被忽略,但仍可请求执行
ServerOnly 服务器控制执行与终止,客户端任何请求被忽略

使用场景:玩家主动能力 → ClientOrServer;服务器权威伤害能力 → ServerOnlyExecution;不应被客户端终止的持续效果 → ServerOnlyTermination;被动/管理员能力 → ServerOnly。

6.21 使用场景架构参考

MOBA 英雄:

  • 被动(ServerOnly + InstancedPerActor + ActivateAbilityOnGranted);
  • Q/W/E/R(LocalPredicted + InstancedPerActor + 各自 Cost/Cooldown/Tags);
  • R 大招:Cancel Abilities with Tag: Ability.Skill(释放时取消其他);
  • 基础攻击:LocalPredicted + Ability Batching。

射击武器:

  • FireGun(LocalPredicted + Batching + SourceObject=武器 + AnimNotify 事件生成投射物);
  • Reload(LocalPredicted,播动画后恢复弹药);
  • AimDownSights(LocalOnly,OwnedTag: State.AimDownSights,改 FOV);
  • 切换武器:CancelAbilities(Weapon) + 授予新武器 AbilitySet。

RPG 技能树:基础能力(开局授予)+ 职业能力(按职业)+ 天赋能力(升级授予,AbilitySet 管理天赋节点,能力等级随投入提升)+ 装备能力(换装授予/取消)。

6.22 本章验证

  • [ ] 建一个近战 GA,走通 激活→CommitAbility→播动画→EndAbility 全流程;
  • [ ] 配 Cost GE + Cooldown GE,验证扣蓝、冷却 Tag 阻塞再次激活;
  • [ ] 用 Enhanced Input + InputID 成功触发技能;
  • [ ] 配好 Ability Tags,验证 Block/Required/Owned Tags 行为;
  • [ ] 用事件激活一个能力并传递数据;
  • [ ] 实现一个被动能力(OnAvatarSet 自动激活,ServerOnly);
  • [ ] 能说出四类策略(复制/实例化/网络执行/安全)的选择理由;
  • [ ] 给即时命中技能启用 RPC Batching 并验证。

下一章:07 AbilityTasks 异步任务