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
的堆栈数量限制来自于GE
GameplayEffectContextHandle
告诉我们谁创建了GameplayEffectSpec
.Attributes
在GameplayEffectSpec
创建时就已经捕获了- 除了
GE
授予的GameplayTags
之外,GameplayEffectSpec
授予目标的DynamicGrantedTags
。 GE
拥有的AssetTags
会被添加到GameplayEffectSpec
的DynamicAssetTags
SetByCaller
TMaps
.
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
处理