ARPG GAS 核心 C++ 类剖析
ARPG GAS 核心 C++ 类剖析
承接架构概览,这篇展开看看一个 ARPG 项目里 GAS 关键 C++ 类的实际样貌——继承自 Epic 原生类与内部框架基类,再向上加一层项目层。
1. 项目级 Ability 基类
位置: Source/<Game>/GAS/Abilities/GameplayAbilityProject.h/.cpp
父类: 框架层的 UGameplayAbilityBase(再上面是 UGameplayAbility)
默认策略(写在 CDO):
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateNo
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted
NetSecurityPolicy = EGameplayAbilityNetSecurityPolicy::ClientOrServer
1.1 激活策略与激活组
UENUM(BlueprintType)
enum class EAbilityActivationPolicy : uint8
{
OnInputTriggered, // 输入按下时触发一次
WhileInputActive, // 输入持续时每帧尝试激活
OnSpawn, // 被授予时自动激活(被动能力)
};
UENUM(BlueprintType)
enum class EAbilityActivationGroup : uint8
{
Independent, // 独立运行
Exclusive_Replaceable, // 独占,被新的同组能力取代
Exclusive_Blocking, // 独占,阻止其他独占能力激活
MAX
};
1.2 成本系统
成本支持两种模式(GE 类 vs 运行时参数化 GE),并允许多个自定义成本叠加:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Costs")
EAbilityCostInstanceType CostInstanceType;
// CostGameplayEffectClass:使用标准 GE 类
// CostGameplayEffectParameterInstanced:运行时参数化 GE(支持耐力百分比)
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Costs|CostGameplayEffectParameterInstanced")
TArray<FAttributeCost> AttributeCosts; // 每条:Attribute + Magnitude
UPROPERTY(EditDefaultsOnly, Instanced, Category = "Costs|CustomCost")
TArray<TObjectPtr<UAbilityCustomCost>> CustomCosts;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Costs|Stamina")
float CostStaminaPercent = 1.f; // 基于武器属性的耐力消耗倍率
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Costs|Stamina")
bool CheckStaminaCost = true;
1.3 法术资源系统
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spell")
FGameplayTag SpellAssetTag; // 用于加载 USpellAsset
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spell")
bool bLoadSpellAssetOnGiveAbility = false; // 授予时预加载,避免激活卡顿
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spell")
bool bEnableWaitGameplayEvents = true; // 是否启用蒙太奇事件等待
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spell|Ring")
bool bShouldTickTraceInTask = false; // true=每帧追踪,false=AnimNotify 触发
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spell|Ring")
int32 AdditiveTraceFrame = 0; // 额外追踪帧数,提高命中可靠性
1.4 关键虚函数
// 激活检查:1.激活组阻断 → 2.父类标签/条件 → 3.自定义成本
virtual bool CanActivateAbility(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayTagContainer* SourceTags = nullptr,
const FGameplayTagContainer* TargetTags = nullptr,
OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;
// 应用成本:GE 成本(含耐力)→ 自定义成本
virtual void ApplyCost(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo) const override;
// 创建扩展的 GameplayEffectContext(含 AbilitySource、FightRecord)
virtual FGameplayEffectContextHandle MakeEffectContext(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo) const override;
// 支持 TagRelationshipMapping 的标签需求检查
virtual bool DoesAbilitySatisfyTagRequirements(
const UAbilitySystemComponent& AbilitySystemComponent,
const FGameplayTagContainer* SourceTags,
const FGameplayTagContainer* TargetTags,
OUT FGameplayTagContainer* OptionalRelevantTags) const override;
1.5 暴露给蓝图的事件
UFUNCTION(BlueprintImplementableEvent) void K2_OnAbilityAdded();
UFUNCTION(BlueprintImplementableEvent) void K2_OnAbilityRemoved();
UFUNCTION(BlueprintImplementableEvent) void K2_OnPawnAvatarSet();
UFUNCTION(BlueprintImplementableEvent) void K2_OnAbilityRetrigger();
UFUNCTION(BlueprintImplementableEvent) void K2_CollisionTraceHitEvent(const FHitResult& HitResult, AActor* TargetActor);
UFUNCTION(BlueprintImplementableEvent) void ScriptOnAbilityFailedToActivate(const FGameplayTagContainer& FailureReason);
1.6 法术资源加载流程
2. 项目级 AbilitySystemComponent
位置: Source/<Game>/GAS/AbilitySystemComponentProject.h/.cpp
2.1 激活组管理
int32 ActivationGroupCounts[(uint8)EAbilityActivationGroup::MAX];
bool IsActivationGroupBlocked(EAbilityActivationGroup Group) const;
void AddAbilityToActivationGroup(EAbilityActivationGroup Group, UGameplayAbilityProject* Ability);
void RemoveAbilityFromActivationGroup(EAbilityActivationGroup Group, UGameplayAbilityProject* Ability);
void CancelActivationGroupAbilities(
EAbilityActivationGroup Group,
UGameplayAbilityProject* IgnoreAbility,
bool bReplicateCancelAbility);
2.2 输入处理
每帧由角色 Tick 调用,按队列把按下/持续/释放输入派发给对应能力:
void ProcessAbilityInput(float DeltaTime, bool bGamePaused);
// 1. 若 ASC 已有 TAG_Gameplay_AbilityInputBlocked,则跳过并清空队列
// 2. InputHeldSpecHandles → WhileInputActive 策略每帧 TryActivateAbility
// 3. InputPressedSpecHandles → OnInputTriggered 策略按下时 TryActivateAbility
// 4. InputReleasedSpecHandles → 发送输入释放事件
// 5. 清空三个队列
void AbilityInputTagPressed(const FGameplayTag& InputTag);
void AbilityInputTagReleased(const FGameplayTag& InputTag);
2.3 动态标签 / 标签关系 / 能力查询
// 用临时 GE 添加/移除可复制的标签
void AddDynamicTagGameplayEffect(const FGameplayTag& Tag);
void RemoveDynamicTagGameplayEffect(const FGameplayTag& Tag);
// 设置标签关系映射(通常在角色 BeginPlay 中配置)
void SetTagRelationshipMapping(UAbilityTagRelationshipMapping* NewMapping);
// 被 DoesAbilitySatisfyTagRequirements 调用
void GetAdditionalActivationTagRequirements(
const FGameplayTagContainer& AbilityTags,
FGameplayTagContainer& OutActivationRequired,
FGameplayTagContainer& OutActivationBlocked) const;
// 激活时触发 Block 与 Cancel
void ApplyAbilityBlockAndCancelTags(
const FGameplayTagContainer& AbilityTags,
UGameplayAbility* RequestingAbility,
bool bIsActivating,
FGameplayTagContainer& OutBlockedAbilityTags,
FGameplayTagContainer& OutCancelAbilityTags);
// 运行时按标签查找能力 / SpecHandle
UGameplayAbility* GetAbilityInstanceWithTag(const FGameplayTag& Tag) const;
FGameplayAbilitySpecHandle GetAbilitySpecHandleWithTag(const FGameplayTag& Tag) const;
bool HasAttributeSubobject(const TSubclassOf<UAttributeSet> AttributeClass) const;
// 技能加速类 Buff 用
void ReduceEffectDurationRemaining(FActiveGameplayEffectHandle Handle, float ReduceTime);
2.4 生命周期回调
virtual void NotifyAbilityActivated(
const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability) override;
// AddAbilityToActivationGroup + 记录激活 + 广播委托
virtual void NotifyAbilityFailed(
const FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability,
const FGameplayTagContainer& FailureReason) override;
virtual void NotifyAbilityEnded(
FGameplayAbilitySpecHandle Handle, UGameplayAbility* Ability,
bool bWasCancelled) override;
// RemoveAbilityFromActivationGroup + 清理记录
3. AbilitySystemGlobals 单例
用途: 缓存常用 GameplayTag,避免运行时反复用字符串查找。
class UAbilitySystemGlobalsProject : public UAbilitySystemGlobalsBase
{
public:
static UAbilitySystemGlobalsProject& Get(); // 类型安全单例
// 缓存的关键标签
FGameplayTag DeadTag; // 角色死亡状态
FGameplayTag ActiveMeleeTag; // 近战激活状态
FGameplayTag StaggerImmunityTag; // 摇晃免疫
FGameplayTag BreakImmunityTag; // 碎甲免疫
FGameplayTag KnockOutImmunityTag; // 击倒免疫
FGameplayTag DismemberImmunityTag; // 肢解免疫
FGameplayTag IsPlayerTag; // 是否为玩家
FGameplayTag KnockOutTag; // 进入击倒触发
FGameplayTag BreakTag; // 进入碎甲触发
FGameplayTag StaggerTag; // 进入摇晃触发
FGameplayTag DismemberAbilityTag; // 肢解能力触发
FGameplayTag DeadAbilityTag; // 死亡能力触发
};
热路径上的使用方式:
const FGameplayTag& DeadTag = UAbilitySystemGlobalsProject::Get().DeadTag;
if (ASC->HasMatchingGameplayTag(DeadTag))
{
// 处理死亡状态
}
4. 标签关系映射 DataAsset
把"激活 A 时阻塞/取消 B"这类关系从能力代码里挪出来,做成 DataAsset,能力之间解耦:
struct FAbilityTagRelationship
{
FGameplayTag AbilityTag; // 触发方能力的标签
FGameplayTagContainer AbilityTagsToBlock; // 阻止哪些标签的能力激活
FGameplayTagContainer AbilityTagsToCancel; // 取消哪些标签的能力
FGameplayTagContainer ActivationRequiredTags; // 激活时要求目标拥有的额外标签
FGameplayTagContainer ActivationBlockedTags; // 激活时要求目标不能拥有的标签
};
class UAbilityTagRelationshipMapping : public UDataAsset
{
UPROPERTY(EditDefaultsOnly)
TArray<FAbilityTagRelationship> AbilityTagRelationships;
void GetAbilityTagsToBlockAndCancel(
const FGameplayTagContainer& AbilityTags,
FGameplayTagContainer* OutTagsToBlock,
FGameplayTagContainer* OutTagsToCancel) const;
void GetRequiredAndBlockedActivationTags(
const FGameplayTagContainer& AbilityTags,
FGameplayTagContainer* OutActivationRequired,
FGameplayTagContainer* OutActivationBlocked) const;
bool IsAbilityCancelledByTag(
const FGameplayTagContainer& AbilityTags,
const FGameplayTag& ActionTag) const;
};
5. 属性集基类与 MetaDelta 模式
class UAttributeSetBase : public UAttributeSet
{
protected:
// 钳制属性值(由子类 PostGameplayEffectExecute 调用)
virtual void ClampOnPostGameplayEffectExecute(
const FGameplayEffectModCallbackData& Data) {}
// Max 改变时等比调整 Current(防止 Current > Max)
void AdjustAttributeForMaxChange(
FGameplayAttributeData& AffectedAttribute,
const FGameplayAttributeData& MaxAttribute,
float NewMaxValue,
const FGameplayAttribute& AffectedAttributeProperty);
FTimerManager& GetWorldTimerManager() const;
};
MetaDelta 工作原理
伤害不直接打到 Health,而是先打到一个临时属性 MetaDelta,然后在 PostGameplayEffectExecute 里统一应用:
好处是:
- Clamp 在一处做,无论加伤还是回血都走同一入口;
- 状态切换(死亡/碎甲/击倒)和数值修改解耦,PostGameplayEffectExecute 是唯一发起方;
- 支持伤害汇总:同一帧多个 GE 可以累加进 MetaDelta,再一次性应用。
典型属性表
| 属性集 | 关键属性 | 备注 |
|---|---|---|
| AS_Health | Health, MaxHealth, MetaDelta | 死亡触发:Health<=0 |
| AS_Stamina | Stamina, MaxStamina, MetaDelta | 行动力消耗 |
| AS_Break | Break, MaxBreak, RegenValue, TrimPercent, ResetSeconds, MetaDelta | 碎甲 |
| AS_Stagger | + ThresholdValue | 摇晃,超过阈值即触发 |
| AS_Poise | + PoiseLevel | 平衡等级,影响受击反应强度 |
| AS_KnockOut | RegenValue, TrimPercent, ResetSeconds, MetaDelta | 击倒 |
| AS_Dismember | RegenValue, TrimPercent, ResetSeconds, MetaDelta | 肢解 |
| AS_Hatred | Hatred, MaxHatred, MetaDelta | 仇恨 |
6. 自定义 GameplayEffectContext
承接战斗记录、触发资产、命中信息等额外字段。重写 GetScriptStruct 与 NetSerialize 即可:
struct FGameplayEffectContextProject : public FGameplayEffectContextBase
{
UPROPERTY()
UFightRecord* FightRecord = nullptr;
UPROPERTY(BlueprintReadWrite)
UPrimaryDataAssetBase* RelatedAsset = nullptr;
int32 CartridgeID = -1; // 多发弹药 ID
TWeakObjectPtr<const UObject> AbilitySourceObject;
virtual UScriptStruct* GetScriptStruct() const override;
virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override;
static FGameplayEffectContextProject* ExtractEffectContext(
const FGameplayEffectContextHandle& Handle);
void SetAbilitySource(const IAbilitySourceInterface* InObject, float InSourceLevel);
const IAbilitySourceInterface* GetAbilitySource() const;
};
使用:
FGameplayEffectContextHandle ContextHandle = Ability->MakeEffectContext();
if (auto* Ctx = FGameplayEffectContextProject::ExtractEffectContext(ContextHandle))
{
Ctx->FightRecord = NewFightRecord;
Ctx->RelatedAsset = SpellRingAsset;
}
SourceASC->BP_ApplyGameplayEffectSpecToTarget(SpecHandle, TargetASC);
7. 蓝图友好的 ExecutionCalc 基类
Blueprintable + 一组 BlueprintPure 帮手,把伤害计算从 C++ 解放到蓝图:
UCLASS(BlueprintType, Blueprintable, Abstract)
class UExecutionCalcBase : public UGameplayEffectExecutionCalculation
{
public:
UFUNCTION(BlueprintPure)
float GetCapturedAttributeMagnitude(
const FGameplayAttribute& InAttribute,
bool bSourceOrTarget, // true=Source, false=Target
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
float ReturnValueIfCaptureFail) const;
UFUNCTION(BlueprintPure)
float GetSetByCaller(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
const FGameplayTag& Tag) const;
UFUNCTION(BlueprintPure)
int32 GetEffectLevel(const FGameplayEffectCustomExecutionParameters& ExecutionParams) const;
UFUNCTION(BlueprintPure)
AActor* GetSourceActor(const FGameplayEffectCustomExecutionParameters& ExecutionParams) const;
UFUNCTION(BlueprintPure)
AActor* GetTargetActor(const FGameplayEffectCustomExecutionParameters& ExecutionParams) const;
UFUNCTION(BlueprintPure)
void GetEffectContext(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectContextProject& ResultContext) const;
};
8. SpellRing AbilityTask
近战碰撞追踪的承载者:
class UAbilityTask_SpellRing : public UAbilityTask
{
public:
static UAbilityTask_SpellRing* CreateSpellRingTask(
UGameplayAbility* OwningAbility, FName TaskInstanceName);
void Setup(
AActor* InActorMaster,
const TSubclassOf<UInventoryItemDefinition>& InSourceWeaponDef,
USpellRingAsset* InRingAsset,
UAttackRequest_Melee* InMeleeRequest);
void SetIsWorking(bool bInIsWorking);
virtual void OnCollisionTraceResult(const FHitResult& SweepResult);
bool RingOnHit(AActor* InHitActor, const FSpellResultParam& SpellResult);
void GetHitTargetBloodDirection(AActor* HitTarget, EWorldDirection& OutBloodDirection);
};
从 Ring 碰撞到 GE 应用:
9. GameplayCue 管理组件
集中处理 Cue 的生成、参数缓存与生命周期,作为 Wwise 与 Niagara 的统一调度入口:
class UGameplayCueManageComponent : public UActorComponent
{
public:
FGameplayCueHandle ExecuteGameplayCueOnOwner(
const FGameplayTag& CueTag,
const FGameplayCueParametersProject& CueParams);
FGameplayCueHandle AddGameplayCueOnOwner(
const FGameplayTag& CueTag,
const FGameplayCueParametersProject& CueParams);
void RemoveGameplayCueOnOwner(
const FGameplayTag& CueTag,
const FGameplayCueParametersProject& CueParams);
FGameplayCueHandle GenerateHandle(FGameplayCueParametersProject& OutParam);
AGameplayCueNotify_Actor* GetSpawnedGCNActorByHandle(FGameplayCueHandle Handle);
FGameplayTag GetDamageFXType(USpellAsset* SpellAsset, AActor* Caster);
FGameplayTagContainer GetCasterCurrentWeaponInUseIDTags(AActor* Caster);
};
支持的 Cue 类型:
| 类名 | 用途 | 生命周期 |
|---|---|---|
AGameplayCueNotifyActorBurstLatent | 延迟爆发(如延迟爆炸特效) | 单次,有延迟 |
AGameplayCueNotifyActorLooping | 循环持续(如燃烧光环) | 持续,需手动移除 |
AGameplayCueNotifyStaticBurst | 静态爆发(无场景 Actor,最轻量) | 单次,无 Actor |
10. 蓝图函数库
把 GAS 常用操作做成 UBlueprintFunctionLibrary,减少蓝图里反复 Cast:
class UGASBPFunctionLibraryProject : public UBlueprintFunctionLibrary
{
public:
UFUNCTION(BlueprintPure)
static UAbilitySystemComponentProject* GetAbilitySystemComponentProject(const AActor* Actor);
UFUNCTION(BlueprintPure)
static UGameplayAbilityProject* GetPrimaryAbilityInstanceFromHandle(
UAbilitySystemComponent* ASC, FGameplayAbilitySpecHandle Handle);
UFUNCTION(BlueprintPure)
static bool GetEffectContextProject(
FGameplayEffectContextHandle Handle, FGameplayEffectContextProject& OutContext);
UFUNCTION(BlueprintCallable)
static FActiveGameplayEffectHandle ApplyGameplayEffectToActorWithLevel(
AActor* Source, AActor* Target,
TSubclassOf<UGameplayEffect> EffectClass, float Level);
UFUNCTION(BlueprintPure)
static bool HasExactGameplayTag(const UAbilitySystemComponent* ASC, const FGameplayTag& Tag);
};
串起来看
记住这几个抓手,整套 C++ 的脉络就清晰了:
- AbilityProject 承接策略 + 激活组 + 成本 + 法术资源加载;
- ASCProject 管理激活组计数、输入队列、动态标签、扩展标签需求;
- AttributeSetBase + MetaDelta 是属性变更的唯一入口;
- GameplayEffectContextProject 把战斗信息塞进 GE 上下文;
- ExecutionCalcBase / SpellRing Task / CueManageComponent / BPFunctionLibrary 各自简化常见痛点(蓝图编写、近战追踪、Cue 调度、类型转换)。
下一篇展开整个激活到表现的完整流程图。