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 的核心特性
- 一个 Actor 可以同时激活多个 GA(同时"冲刺"+"射击");
- 可用 Blueprint 或 C++ 创建;
- GA 不运行在 Simulated Proxy 上——它们只在拥有客户端和/或服务器上运行;其他客户端看到的技能表现,通过 AbilityTask 复制的动画、GameplayCue 等通道传达。
适合 / 不适合用 GA
| 适合用 GA | 不适合用 GA |
|---|---|
| 跳跃、冲刺、射击 | 基础移动输入(WASD) |
| 被动格挡、使用药水 | UI 交互(商店购买) |
| 开门、采集资源、建造 | 简单物理碰撞响应 |
| 基础动画状态机切换 |
判断标准:这个动作是否需要消耗、冷却、等级系统、网络同步或复杂的异步逻辑?需要就值得用 GA。
GA 的内置功能
- 等级系统:通过等级修改属性变化量或改变行为;
- 消耗与冷却:可选的 Cost GE 和 Cooldown GE;
- 预测支持:通过 Net Execution Policy 控制本地预测;
- AbilityTask 集成:处理持续性操作(等待事件/属性变化/选目标等)。
6.2 生命周期详解
激活 → 执行逻辑 → 提交消耗/冷却(CommitAbility)→ 结束(EndAbility)
简单能力流程(一次性火球):
- 玩家按技能键 → 2. ASC 检查 Tag 需求/消耗/冷却 → 3.
ActivateAbility()→ 4.CommitAbility()(扣蓝、启动冷却)→ 5. 施加 GE → 6.EndAbility()。
复杂能力流程(蓄力射击):
- 按下技能键 → 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 并验证。