04 - Gameplay Effect
本文主要说明了UE5中Gameplay技能系统(Gameplay Ability System, GAS)中有关Gameplay Effect的相关内容。
概述
GAS使用GE来改变目标Actor的Attribute值或Gameplay Tags,它由一组改变Actor属性的函数库组成,包括一些立即效果(如施加伤害)和持续性效果(如中毒)。Gameplay Effect(后文简称GE)属于UE5中的静态资产,因此不能在运行时改变它的相关配置;而Gameplay Effect Spec(后文简称GESpec)则是GE的运行时版本,它是封装GE和相关工具函数的实例,在运行时很有用。
配置项
GE的常用配置项如下:
Duration
该配置项可设置GE的持续时间,是否可重复以及重复周期。具体可设置成如下三项:
- Instant:让GE立即生效;
- Has Duration:让GE持续生效一段时间,时间到后去除;
- Infinite:让GE无限持续生效,只有主动移除它才会失效;
其中Instant选项会永久改变受影响Attribute的基础值;后两个选项则是会暂时改变受影响Attribute的当前值,且可撤回,只有修改周期Period后才永久改变受影响Attribute的基础值。
Component
组件可赋予GE各种各样的行为,具体可添加的组件如下:
UChanceToApplyGameplayEffectComponent:让GE变成概率施加;UBlockAbilityTagsGameplayEffectComponent:负责阻止基于Gameplay Tags激活的Gameplay Ability,针对拥有GE的目标Actor;UAssetTagsGameplayEffectComponent:让GE资产拥有Tags,不会转移至任何Actor;UAdditionalEffectsGameplayEffectComponent:添加附加的GEs,它们可在特定条件下激活(或无条件);UTargetTagsGameplayEffectComponent:将Tags授予GE的目标或拥有者Actor;UTargetTagRequirementsGameplayEffectComponent:特定对目标(GE的拥有者)Tags的需求,并据此决定该GE是否施加或继续执行;URemoveOtherGameplayEffectComponent:基于特定条件,移除其他GE;UCustomCanApplyGameplayEffectComponent:负责配置CustomApplicationRequirement函数,确认该GE是否应被施加;UImmunityGameplayEffectComponent:负责阻止其他GESpec的施加;
Modifier
GE通过Modifier来改变目标Actor属性值,它的配置项主要有计算符号Modifier Op和计算类型Modifier Magnitude。
计算符号 Modifier Op
计算符号如下所示:
- Add:加法
- Multiply:乘法
- Divide:除法
- Override:直接覆盖
计算类型 Modifier Magnitude
计算类型则如下所示:
- Scalable Float:硬编码值或曲线上某点的值
- Attribute Based:基于Source或Target的属性计算值
- Custom Calculation Class:继承
UGameplayModMagnitudeCalculation类,自定义值的计算 - Set by Caller:在运行时由GESpec对值进行设置
我们常用的是第二和第三种计算类型:
Attribute Based
该计算类型可以基于Source或Target的属性值计算要修改属性的值,计算公式为:
该计算类型主要有如下配置项:
- Coefficient、PreMulplyAdditiveValue 和 PostMultiplyAdditiveValue:参与上述计算公式的配置项,可以是一个值,也可以是一个曲线;
- Backing Attribute:此项是
要参考的属性的相关设置,包含属性名,属性来源对象和是否快照snapshot。如果需要快照,那么参考的属性值在该GE应用时及以后均被确定;如果不需要快照,那么参考的属性值将会实时读取;
Custom Calculation Class
如果基于Actor属性的计算无法满足需求,可以选择该计算类型,它通过继承UGameplayModMagnitudeCalculation类(后文简称MMC类),重写float CalculateBaseMagnitude_Implementation()函数以自定义属性值的计算(例如获取其他Actor的属性和它的变量来计算该属性值)。
例如在Aura项目中,主角Aura的MaxHealth属性基于如下公式计算:
可以发现,MaxHealth属性依赖于参考属性Vigor和玩家等级变量PlayerLevel,适合通过该计算类型计算。相关MMC类代码如下:
MaxHealthMMC.h点击展开/折叠代码
UCLASS()
class AURA_API UMaxHealthMMC : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UMaxHealthMMC();
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
private:
FGameplayEffectAttributeCaptureDefinition VigorDef;
};
MaxHealthMMC.cpp点击展开/折叠代码
UMaxHealthMMC::UMaxHealthMMC()
{
// 1.初始化要捕获的Attribute信息:
VigorDef.AttributeToCapture = UAuraAttributeSet::GetVigorAttribute();
VigorDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;
VigorDef.bSnapshot = false;
// 2.将该Attribute信息加入要捕获的Attribute数组中
RelevantAttributesToCapture.Add(VigorDef);
}
float UMaxHealthMMC::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
// 获取Source Actor和Target Actor的GameplayTags待用
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
// 用于获取参考Attributes值函数的参数
FAggregatorEvaluateParameters EvalParams;
EvalParams.SourceTags = SourceTags;
EvalParams.TargetTags = TargetTags;
// 获取目标Attribute值
float TargetVigorValue = 0.f;
GetCapturedAttributeMagnitude(VigorDef, Spec, EvalParams, TargetVigorValue);
// 自定义计算: MaxHealth = 80.0 + 2.5 * Vigor + 10.0 * PlayerLevel
TargetVigorValue = FMath::Max<float>(TargetVigorValue, 0.f);
float MaxHealth = 0.f;
if (UObject* Player = Spec.GetContext().GetSourceObject();
Player && Player->Implements<UCombatInterface>())
{
const TScriptInterface<ICombatInterface> PlayerCombatInterface = TScriptInterface<ICombatInterface>(Player);
const int32 PlayerLevel = PlayerCombatInterface->GetActorLevel();
MaxHealth = 80.f + 2.5f * TargetVigorValue + 10.f * PlayerLevel;
}
else
{
UE_LOG(LogTemp, Error, TEXT("[%hs] Target actor is not player (Not implements ICombatInterface)"), __FUNCTION__);
}
return MaxHealth;
}
其中:
- 为了获取Aura参考属性Vigor,要定义并初始化属性描述相关信息
FGameplayEffectAttributeCaptureDefinition,并将其加入要捕获Attribute数组RelevantAttributesToCapture中; - 在MMC计算函数中,通过
GetCapturedAttributeMagnitude()获取要参考的Attribute信息,最终返回计算好的float类型的MaxHealth值;
实现MMC类后,就能在编辑器中编辑相关GE了:
多个Modifier的执行顺序
如果一个GE中有多个Modifier,执行顺序为该数组的索引顺序(从小到大)。
Execution
GE通过Execution实现更多自定义逻辑,这需要我们自行编写GameplayEffectExecutionCalculation逻辑。
Gameplay Cues
GE还可触发一些Gameplay Cues,有关Gameplay Cues详见 文章(TODO)。
Stacking
负责多个GE生效时的堆叠处理,可通过Limit Count设置该类GE的最大堆叠上限,通过Stacking Type设置堆叠种类。后者的有两个选项:
- Aggregate by Source:多个Source Actor为当前Target Actor施加GE时,为每个Source Actor持有Stack计数。即同一Source Actor对任意Target Actor施加超过当前计数的GE时,超过计数的GE暂时不生效。
- Aggregate by Target:多个Source Actor为当前Targe t Actor施加GE时,为当前Target Actor持有Stack计数。即任意Source Actor对同一Target Actor施加超过计数的GE时,超过计数的GE暂时不生效。
常用操作
对目标Actor施加GE
要想对目标Actor施加GE,需要完成如下步骤:
- 获取目标Actor自己的ASC;
- 通过
TargetASC->MakeEffectContext(),创建FGameplayEffectContexxtHandle(封装GEContext和工具函数),并通过它的AddSourceXXX()成员函数添加Source Actor或其他SourceUObject类; - 通过
TargetASC->MakeOutgoingSpec()创建FGameplayEffectSpecHandle(封装GESpec和工具函数),将GE实例化; - 通过
TargetASC->ApplayGameplayEffectSpecToSelf(),对目标Actor的ASC施加GE; - 【可选】对于持续时间无限的GE,需要存储激活它的
FActiveGameplayEffectHandle,以便后续通过TargetASC->RemoveActiveGameplayEffect()移除;
例如在Aura项目中,Source Actor 对 Target Actor 施加 GE 的通用函数如下:
AuraEffectActor.cpp点击展开/折叠代码
void AAuraEffectActor::ApplyEffectToTarget(AActor* InTargetActor, const FEffectActorGE& InEffectActorGE)
{
if (InTargetActor->Implements<UAbilitySystemInterface>())
{
checkf(InEffectActorGE.GEClass, TEXT("[%hs] GEClass is empty, please fill out in editor!"), __FUNCTION__);
// 1. 获取Target自己的ASC
const TScriptInterface<IAbilitySystemInterface> ASCInterface = TScriptInterface<IAbilitySystemInterface>(
InTargetActor);
UAbilitySystemComponent* TargetActorAsc = ASCInterface->GetAbilitySystemComponent();
// 2. 创建GEContextHandle(封装GEContext + 工具函数)
FGameplayEffectContextHandle GEContextHandle = TargetActorAsc->MakeEffectContext();
GEContextHandle.AddSourceObject(this);
// 3. 创建GESpecHandle(封装GESpec + 工具函数)
const FGameplayEffectSpecHandle GESpecHandle = TargetActorAsc->MakeOutgoingSpec(
InEffectActorGE.GEClass, InEffectActorGE.GELevel, GEContextHandle);
// 4. 对Target自己的ASC施加GE
const FActiveGameplayEffectHandle ActiveGEHandle = TargetActorAsc->ApplyGameplayEffectSpecToSelf(
*GESpecHandle.Data);
// 5. 对于Infinite类GE, 需要存储激活它的FActiveGameplayEffectHandle, 以便后续移除它
if (GESpecHandle.Data->Def->DurationPolicy == EGameplayEffectDurationType::Infinite &&
InEffectActorGE.GERemovalPolicy == EEffectRemovalPolicy::EERP_RemoveOnEndOverlap)
{
ActiveInfiniteGEHandles.Add(ActiveGEHandle, TargetActorAsc);
}
}
// 获取ASC的另一个方法:
// UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target);
}
