GameplayAbilitySystem入门与实战(六):GameplayEffect(一)
前言
GameplayEffect在GAS框架中的重要性不言而喻, 内容非常多,,但是本身基本上只作为一个数据载体而存在,蓝图中也无法重写和执行任何事件.
本篇梳理一下GE的常用属性和基本概念, 实战部分我们在下一篇展开
基础定义
GE是属性修改的容器, 分为如下几个类型
| Duration Type | GameplayCue Event | When to use |
|---|---|---|
Instant |
Execute | 对BaseValue的立即生效的处理,GameplayTags 不会被应用到, 即使在一帧之内 |
Duration |
Add & Remove | 对CurrentValue持续修改.可以应用GameplayTags.持续事件在GE类中指定 |
Infinite |
Add & Remove | 类似Duration,但是是永久的直到手动移除(如通过ASC) |
Duration和Infinite模式会出现Period选项

如果ExecutePeriodicEffectOnApplication=true,那么每隔Period秒就执行
periodic inhibition policy刷新策略,如是否覆盖
如果Tag匹配或者不匹配, GE可以临时性的关闭或者开启,这移除操作不会移除GE,只是临时的移除GE的修改效果
如果要手动的直接应用GE的修改, 可以调用
1 | UAbilitySystemComponent::ActiveGameplayEffects.SetActiveGameplayEffectLevel(FActiveGameplayEffectHandle ActiveHandle, int32 NewLevel) |
GE的属性设置一般建议在编辑器用蓝图编辑, 理论上可以用cpp设定参数, 但是不直观也比较麻烦
一些重要的数据结构
FGameplayEffectContext
保存了GE相关的所有数据, 用于在GE执行的过程中传递重要信息
可以继承他用来扩展参数
1 | //发起者,即拥有ASC的actor, 本项目是ASRCharacterBase类 |
FGameplayEffectContextHandle
用来处理FGameplayEffectContext的数据,只保存了一个变量TSharedPtr<FGameplayEffectContext> Data;
可以直接用FGameplayEffectContext构造以及赋值, 以及可以通过此数据结构获取和修改FGameplayEffectContext中的大多数数据
GameplayEffectSpec
GameplayEffectSpec是GameplayEffect创建的,可以通过方法MakeOutgoingSpec()创建GameplayEffectSpecs被成功创建后返回一个结构体FActiveGameplayEffect.GameplayEffectSpec的级别 通常与创建“GameplayEffectSpec”的GA级别相同,但可以有所不同GameplayEffectSpec的持续时间一般与GE相同,但是可以不同- 同样的
GameplayEffectSpec的周期一般与GE相同但是也可以不同 GameplayEffectSpec的堆栈数量限制来自于GEGameplayEffectContextHandle告诉我们谁创建了GameplayEffectSpec.Attributes在GameplayEffectSpec创建时就已经捕获了- 除了
GE授予的GameplayTags之外,GameplayEffectSpec授予目标的DynamicGrantedTags。 GE拥有的AssetTags会被添加到GameplayEffectSpec的DynamicAssetTagsSetByCallerTMaps.
FGameplayEffectAttributeCaptureDefinition
这是一个定义捕获数据的结构体
也就是我们蓝图中在GEEC中可以看到的几个属性, 其中在类UGameplayEffectCalculation中申明了TArray<FGameplayEffectAttributeCaptureDefinition> RelevantAttributesToCapture; 在派生类UGameplayEffectExecutionCalculation中申明了TArray<FGameplayEffectAttributeCaptureDefinition> InvalidScopedModifierAttributes;
FGameplayEffectCustomExecutionParameters
GEEC的输入参数
成员变量基本都是私有的, 了解几个函数
1 | const FGameplayEffectSpec& GetOwningSpec() const; |
1 | UAbilitySystemComponent* GetTargetAbilitySystemComponent() const; |
上面几个比较简单, 字面意思
1 | bool AttemptCalculateCapturedAttributeMagnitude(const FGameplayEffectAttributeCaptureDefinition& InCaptureDef, const FAggregatorEvaluateParameters& InEvalParams, OUT float& OutMagnitude) const; |
这个方法比较有用, 函数翻译过来是尝试计算捕获属性量级, 其实简单使用的话可以理解为把Def转换成浮点值
这里也用到了FAggregatorEvaluateParameters
FAggregatorEvaluateParameters
寄存器计算时用于传递参数的结构体, 参数不多
1 | struct GAMEPLAYABILITIES_API FAggregatorEvaluateParameters |
应用
可以简单的用上述方法在GAS中应用GE
如果想监听来自ASC的持续的GE效果,可以用如下方式
1 | AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(this, &AVGCharacterBase::OnActiveGameplayEffectAddedCallback); |
同样的可以通过各种方法移除,如

通过如下方式监听移除事件
1 | AbilitySystemComponent->OnAnyGameplayEffectRemovedDelegate().AddUObject(this, &APACharacterBase::OnRemoveGameplayEffectCallback); |
- cpp的方式应用GE
1 | //初始效果,用来初始化属性 |
1 | FGameplayEffectContextHandle EffectContextHandle = AbilitySystemComponent->MakeEffectContext(); |
修改

如上图分为 加/乘/除/覆盖 4个方式
- 修改模式
Modifier Type |
Description |
|---|---|
Scalable Float |
FScalableFloats 一般可以用一个常量来定义,当然也可以用CurveTable来定义![]() |
Attribute Based |
基于一个属性的修改 |
Custom Calculation Class |
通过一个类自定义修改,一般需要在cpp中操作,蓝图无法展开结构体 |
Set By Caller |
SetByCaller 一般在外部需要实时修改值的时候用,比如玩家按键时间决定此GE参数大小的情况下 |
乘除方式会用如下方式计算,可以理解为都基于1计算,而非比较容易理解的叠加在一起
1 | 1 + (Mod1.Magnitude - 1) + (Mod2.Magnitude - 1) + ... |
比如如果有2个乘法计算,参数都是1.5,那么得到的结果并不是value*1.5*1.5,而是value*1*(0.5+0.5)
这里会有几个问题存在(每个系统都有各自的计算方法)
- Multipliers:0.5
- 1+(0.5-1)=0.5; 正确
- Multipliers:0.5,0.5
- 1 + (0.5 - 1) + (0.5 - 1) = 0; 错误
这里的问题在Paragon中是通过设计方面解决的,即在设计的时候就使用最多只有一个小于1的乘数
源码计算的代码如下
1 | float Multiplicitive = SumMods(Mods[EGameplayModOp::Multiplicitive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Multiplicitive), Parameters); |
如果要修改算法
那么需要修改引擎代码如下
1 | float FAggregatorModChannel::EvaluateWithBase(float InlineBaseValue, const FAggregatorEvaluateParameters& Parameters) const |
1 | float FAggregatorModChannel::MultiplyMods(const TArray<FAggregatorMod>& InMods, const FAggregatorEvaluateParameters& Parameters) |
注:
MultiplyModes函数原本是没有的
修改相关标签
SourceTags 和TargetTags的运行机制跟GA类似, 如果是有持续时间的GE只会在第一次运行的时候执行Tag操作
AttributeBase模式会有2个TagFilter过滤变量
叠加/堆
每一个GE会实例化一个GameplayEffectsSpec对象,无论之前是否已经存在
从GE对象可以得到当前堆的数量
堆类型有两种
| Stacking Type | Description |
|---|---|
| Aggregate by Source | 目标上的每个源“ASC”都有一个单独的实例。每个源可以应用X数量的堆 |
| Aggregate by Target | 无论源是什么,目标上只有一个堆实例。每个源都可以将堆应用到共享堆限制 |

授予技能/Grant Abilities
GE可以给ASC新的技能

| Removal Policy | Description |
|---|---|
| Cancel Ability Immediately | 当赋予该GA的GE从目标上移除时,该GA会立即被取消和移除 |
| Remove Ability on End | 被授予的GA被允许完成,然后从目标上移除。 |
| Do Nothing | 授予的GA不受从目标上移除GE的影响。目标具有永久的能力,直到后来被手动删除。 |
标签
| Category | Description |
|---|---|
| Gameplay Effect Asset Tags | GameplayEffect 它们自己不做任何功能,只用于描述GE |
| Granted Tags | 此类标签伴随着GE的生命周期,同时也会添加到ASC中;在GE移除以后也会移除;只在有持续时间的GE中生效 |
| Ongoing Tag Requirements | 此类标签会将GE暂时性的开/关,只作用于有持续时间的GE,举个例子: 一个 GE来模拟一个5秒的恢复效果, 那么在此标签给与一个Tag, 其他无论是GE还是GA添加了这个Tag后这个恢复就触发了 |
| Application Tag Requirements | 跟目标相关的标签,如果不符合就不能作用到目标 |
| Remove Gameplay Effects with Tags | 如果目标GE拥有此类标签,那么在GE生效后会移除这些标签的GE |
免疫/减免

可以通过代理FImmunityBlockGE OnImmunityBlockGameplayEffectDelegate来监听免疫能力的改变
SetByCaller
在Modifiers中,必须提前在GE中定义好,只能使用GameplayTag版本,如果GE定义后GameplaySpec没有拥有正确的tag,那么会报错
如果在其他地方,那么不需要提前定义
关于SetByCaller相关方法,在蓝图中可以用如下

在cpp中可以用
1 | void FGameplayEffectSpec::SetSetByCallerMagnitude(FName DataName, float Magnitude); |
1 | void FGameplayEffectSpec::SetSetByCallerMagnitude(FGameplayTag DataTag, float Magnitude); |
1 | float GetSetByCallerMagnitude(FName DataName, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const; |
1 | float GetSetByCallerMagnitude(FGameplayTag DataTag, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const; |
Modifier Magnitude Calculation
Modifier Magnitude Calculation与GameplayEffectExecutionCalculations有点类似,但是没有后者功能强大但是更重要
MMC的唯一目的就是通过方法CalculateBaseMagnitude返回一个浮点值

MMC可以在任意的GE中使用
MMC可以捕获任意的不管是Source还是Target的属性,并且完全访问GameplayEffectSpec对象来获取GameplayTags和SetByCaller

Snapshot的属性会在GE创建的时候被捕获,反之在应用时被捕获
| Snapshot | Source or Target | Captured on GameplayEffectSpec |
Automatically updates when Attribute changes for Infinite or Duration GE |
|---|---|---|---|
| Yes | Source | Creation | No |
| Yes | Target | Application | No |
| No | Source | Application | Yes |
| No | Target | Application | Yes |
重新计算不会触发函数PreAttributeChange,所以必须在这里进行必要的Clamp操作
MMC在蓝图中的使用不是很方便, 蓝图无法从FGameplayEffectSpec获取任何参数, 也无法调用GetCapturedAttributeMagnitude()获取捕获属性的值, 如果非要在蓝图中编辑 ,那么建议封装一个蓝图可见的函数包裹函数GetCapturedAttributeMagnitude(), 那么就可以获取到捕获属性的值了就可以魔改一些内容了,我们在下一篇会详细讲述
注意,
CalculateBaseMagnitude()函数是const函数
Gameplay Effect Execution Calculation
Gameplay Effect Execution Calculation下文简称GEEC功能类似MMC, 但是功能更强大
一般只应用于非持续性的GE
关于捕获方式如下表
| Snapshot | Source or Target | Captured on GameplayEffectSpec |
|---|---|---|
| Yes | Source | Creation |
| Yes | Target | Application |
| No | Source | Application |
| No | Target | Application |
在蓝图中可以重写方法Excute,但是意义不大, 参数无法在蓝图中获取什么有用的数据

关于这个的详细应用我们会在后续展开
Custom Application Requirement
自定义GE是否可以被应用

技能消耗/Cost Gameplay Effect
Cost Gameplay Effect(CTGE)定义了GA释放所需要的消耗,
当然可以直接定义一个浮点参数,记得给负值,必定消耗一般是扣除的
目前有两种方式来扩展
- 使用
MMC,然后重写方法CalculateBaseMagnitude - 重写
GA中的GetCostGameplayEffect方法,手动动态创建一个GE,然后读取Cost Value

因为默认情况下的
GameplayEffectSpec的变量是没有暴露给蓝图的,上图中我重新定义了一个结构体然后暴露给蓝图使用
冷却系统/Cooldown Gameplay Effect
Cooldown Gameplay Effect(CDGE)定义了技能的冷却时间,在CDGE中,我们可以不做任何属性修改,只需要提供一个Tag来标志冷却时间;当然类似CTGE,我们也可以用MMC来自定义算法
多数情况下,可以为每一个GA提供一个唯一的CDGE,如果想要复用同一个CDGE,那么我们可以在对GE创建的GameplayEffectSpec中的数据进行修改,这种方式只能在实例化技能(Instanced)使用
两种方式来扩展CDGE的计算
使用
SetByCaller我们意图把对CD的控制放到
GA中,那么我们在自定义的GA中声明一个FScalableFloat变量CD,然后声明FGameplayTagContainer CDTags即
1
2
3
4UPROPERTY(BlueprintReadOnly,EditAnywhere)
FScalableFloat CD;
UPROPERTY(BlueprintReadOnly, EditAnywhere)
FGameplayTagContainer CDTags;
然后需要重写两个基类方法,需要一个临时变量FGameplayTagContainer tempTags
1 | const FGameplayTagContainer* UGA_TestCpp1::GetCooldownTags() const |
1 | void UGA_TestCpp1::ApplyCooldown(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const |
注意
FGameplayTagContainer转FName用到的FString需要用ToStringSimple()
RequestGameplayTag()方法在默认情况下如果找不到标签会直接导致引擎崩溃
这样我们可以直接在GA中定义我们自己的冷却时间
- 使用
MMC
方法类似对Cost的处理, 重写MMC中的CalculateBaseMagnitude方法, 无论从蓝图还是cpp中都可以通过对返回的float值作为CD处理
