BlueRoseNote/03-UnrealEngine/Gameplay/GAS/(4)UGameplayEffect.md
2023-06-29 11:55:02 +08:00

355 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## UGameplayEffect
UGameplayEffect在框架中主要负责各种数值上的效果如果技能cd、类似黑魂中的异常效果堆叠与buff甚至连角色升级时的属性点添加都可以使用它来实现。
因为大多数逻辑都是设置数据子类的操作,所以对于这个类,本人推荐使用蓝图来进行操作。
## 简单使用教程
通过继承UGameplayEffect来创建一个新的GameplayEffect类并在构造函数中对相应的属性进行设置。之后在Ability类中调用ApplyGameplayEffectToOwner函数让GameplayEffect生效。
```
if (CommitAbility(Handle, ActorInfo, ActivationInfo)) // ..then commit the ability...
{
// Then do more stuff...
const UGameplayEffect* GameplayEffect = NewObject<UGE_DamageBase>();
ApplyGameplayEffectToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, GameplayEffect, 5, 1);
K2_EndAbility();
}
```
具体操作可以参考ActionRPG模板或者是我的项目代码。
## Modifiers
本质是一个FGameplayModifierInfo结构体数组用于存储所有数值修改信息。FGameplayModifierInfo包含以下属性
- Attribute 修改的目标属性集中的属性。
- ModifierOp 修改方式。例如Override、Add、Multiply。
- Magnitude已被废弃
- ModifierMagnitude 修改的数值与类型,可以配置数据表。
- EvaluationChannelSettings (不知道为什么没在编辑器中显示,而且代码中只有一处调用,所以直接跳过)
- SourceTags 本身标签行为Effect生效所需或者忽略的标签
- TargetTags 目标标签行为Effect生效所需或者忽略的标签
可以看得出ModifierMagnitude才是Modifiers的关键而它的本质是FGameplayEffectModifierMagnitude结构体。但是我们只需要学会初始化它即可。它具有以下四种类型
- ScalableFloat 较为简单的使用方式使用ScalableFloat进行计算
- AttributeBased 基于属性执行计算。
- CustomCalculationClass 能够捕获多个属性进行自定义计算
- SetByCaller 被蓝图或者代码显式设置
### ScalableFloat的调用示例
ScalableFloat类型是用于设置固定值的简单方式同时它也支持通过CurveTable配合技能等级设置倍率。最后结果=固定值*倍率当然如果你向完全通过CurveTable来控制参数那就把固定值设置为1即可。
```
FGameplayModifierInfo info;
info.Attribute = FGameplayAttribute(FindFieldChecked<UProperty>(URPGAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(URPGAttributeSet, Health)));
info.ModifierOp = EGameplayModOp::Additive;
//固定值
//info.ModifierMagnitude = FGameplayEffectModifierMagnitude(FScalableFloat(100.0));
//CurveTable控制倍率
FScalableFloat damageValue = {1.0};
FCurveTableRowHandle damageCurve;
static ConstructorHelpers::FObjectFinder<UCurveTable> curveAsset(TEXT("/Game/ActionRPG/DataTable/Damage"));
damageCurve.CurveTable = curveAsset.Object;
damageCurve.RowName = FName("Damage");
damageValue.Curve = damageCurve;
info.ModifierMagnitude = FGameplayEffectModifierMagnitude(damageValue);
Modifiers.Add(info);
```
PS.技能等级在ApplyGameplayEffectToOwner函数中设置。
### AttributeBased的调用示例
最终计算过程可以在CalculateMagnitude函数中找到。
1. 如果尝试捕获到数值不为None则将赋值给AttribValue。
2. 判断AttributeCalculationType来计算对应的AttribValue。我不太了解代码中channel的概念如果channel不存在AttribValue为原本值
3. 如果AttributeCurve存在则将AttribValue作为x轴值来查找y轴值并进行插值计算最后将结果赋值给AttribValue。
4. 最终计算公式:`$((Coefficient * (AttribValue + PreMultiplyAdditiveValue)) + PostMultiplyAdditiveValue)$`
### BackingAttribute
为GameplayEffect捕获GameplayAttribute的选项。你可以理解为Lambda表达式的捕获
- AttributeToCapture捕获属性
- AttributeSource捕获的目标自身还是目标对象
- bSnapshot属性是否需要被快照没仔细看如果为false每次都会重新获取吧
### AttributeCalculationType
默认值为AttributeMagnitude。
- AttributeMagnitude使用最后通过属性计算出来的级数
- AttributeBaseValue使用属性基础值
- AttributeBonusMagnitude使用最后计算值-基础值)
- AttributeMagnitudeEvaluatedUpToChannel不清楚使用方法关键是在编辑器中这个选项默认是不显示的
```
FGameplayModifierInfo info;
info.Attribute = FGameplayAttribute(FindFieldChecked<UProperty>(URPGAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(URPGAttributeSet, Health)));
info.ModifierOp = EGameplayModOp::Additive;
FAttributeBasedFloat damageValue;
damageValue.Coefficient = { 1.2f };
damageValue.PreMultiplyAdditiveValue = { 1.0f };
damageValue.PostMultiplyAdditiveValue = { 2.0f };
damageValue.BackingAttribute = FGameplayEffectAttributeCaptureDefinition(FGameplayAttribute(FindFieldChecked<UProperty>(URPGAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(URPGAttributeSet, Health)))
, EGameplayEffectAttributeCaptureSource::Source, false);
FCurveTableRowHandle damageCurve;
static ConstructorHelpers::FObjectFinder<UCurveTable> curveAsset(TEXT("/Game/ActionRPG/DataTable/Damage"));
damageCurve.CurveTable = curveAsset.Object;
damageCurve.RowName = FName("Damage");
damageValue.AttributeCurve = damageCurve;
damageValue.AttributeCalculationType = EAttributeBasedFloatCalculationType::AttributeMagnitude;
info.ModifierMagnitude = damageValue;
Modifiers.Add(info);
```
### CustomCalculationClass的调用示例
与AttributeCalculationType相比少了属性捕获多了CalculationClassMagnitudeUGameplayModMagnitudeCalculation类
```
FGameplayModifierInfo info;
info.Attribute = FGameplayAttribute(FindFieldChecked<UProperty>(URPGAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(URPGAttributeSet, Health)));
info.ModifierOp = EGameplayModOp::Additive;
FCustomCalculationBasedFloat damageValue;
damageValue.CalculationClassMagnitude = UDamageMagnitudeCalculation::StaticClass();
damageValue.Coefficient = { 1.2f };
damageValue.PreMultiplyAdditiveValue = { 1.0f };
damageValue.PostMultiplyAdditiveValue = { 2.0f };
info.ModifierMagnitude = damageValue;
Modifiers.Add(info);
```
PS.如果这个计算过程还取决于外部非GameplayAbility框架的条件那么你可能需要重写GetExternalModifierDependencyMulticast()函数以获得FOnExternalGameplayModifierDependencyChange委托。从而实现当外部条件发生改变时及时更新计算结果。
### UGameplayModMagnitudeCalculation
你可以通过继承UGameplayModMagnitudeCalculation来创建自定义的Calculation类。所需实现步骤如下
1. 在构造函数中向RelevantAttributesToCapture数组添加需要捕获的属性。
2. 实现CalculateBaseMagnitude事件。因为BlueprintNativeEvent类型所以既可以在c++里实现也可以在蓝图中实现关于两者结合可以参考UGameplayAbility类中ActivateAbility()的写法。
案例代码如下:
```
UCLASS(BlueprintType, Blueprintable, Abstract)
class ACTIONRPG_API UDamageMagnitudeCalculation : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
UDamageMagnitudeCalculation();
float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
};
```
```
UDamageMagnitudeCalculation::UDamageMagnitudeCalculation()
{
RelevantAttributesToCapture.Add(FGameplayEffectAttributeCaptureDefinition(FGameplayAttribute(FindFieldChecked<UProperty>(URPGAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(URPGAttributeSet, Health)))
, EGameplayEffectAttributeCaptureSource::Source, false));
}
float UDamageMagnitudeCalculation::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
float damage{ 0.0f};
FAggregatorEvaluateParameters InEvalParams;
//捕获失败的容错语句
if (!GetCapturedAttributeMagnitude(FGameplayEffectAttributeCaptureDefinition(FGameplayAttribute(FindFieldChecked<UProperty>(URPGAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(URPGAttributeSet, Health)))
, EGameplayEffectAttributeCaptureSource::Source, false), Spec, InEvalParams, damage)) {
//如果这个变量会作为除数的话不能为0
damage = 1.0f;
}
return damage;
}
```
## Executions
Executions更为简单而且更加自由。只需要编写Calculation Class即可。它与Modifiers的不同之处在于一个Modifiers只能修改一个属性而Executions可以同时改动多个属性。
### UGameplayEffectExecutionCalculation
这里我就直接复制actionRPG模板的代码了。
开头的RPGDamageStatics结构体与DamageStatics函数可以减少后面的代码量。可以算是FGameplayEffectAttributeCaptureDefinition的语法糖吧。
```
UCLASS()
class ACTIONRPG_API UDamageExecutionCalculation : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UDamageExecutionCalculation();
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
```
```
struct RPGDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(DefensePower);
DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower);
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
RPGDamageStatics()
{
// Capture the Target's DefensePower attribute. Do not snapshot it, because we want to use the health value at the moment we apply the execution.
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, DefensePower, Target, false);
// Capture the Source's AttackPower. We do want to snapshot this at the moment we create the GameplayEffectSpec that will execute the damage.
// (imagine we fire a projectile: we create the GE Spec when the projectile is fired. When it hits the target, we want to use the AttackPower at the moment
// the projectile was launched, not when it hits).
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, AttackPower, Source, true);
// Also capture the source's raw Damage, which is normally passed in directly via the execution
DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, Damage, Source, true);
}
};
static const RPGDamageStatics& DamageStatics()
{
static RPGDamageStatics DmgStatics;
return DmgStatics;
}
UDamageExecutionCalculation::UDamageExecutionCalculation()
{
RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef);
RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
}
void UDamageExecutionCalculation::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
UAbilitySystemComponent* TargetAbilitySystemComponent = ExecutionParams.GetTargetAbilitySystemComponent();
UAbilitySystemComponent* SourceAbilitySystemComponent = ExecutionParams.GetSourceAbilitySystemComponent();
AActor* SourceActor = SourceAbilitySystemComponent ? SourceAbilitySystemComponent->AvatarActor : nullptr;
AActor* TargetActor = TargetAbilitySystemComponent ? TargetAbilitySystemComponent->AvatarActor : nullptr;
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
// Gather the tags from the source and target as that can affect which buffs should be used
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
// --------------------------------------
// Damage Done = Damage * AttackPower / DefensePower
// If DefensePower is 0, it is treated as 1.0
// --------------------------------------
//计算捕获属性的数值。
float DefensePower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DefensePowerDef, EvaluationParameters, DefensePower);
//因为要做除数所以需要加入容错语句
if (DefensePower == 0.0f)
{
DefensePower = 1.0f;
}
float AttackPower = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().AttackPowerDef, EvaluationParameters, AttackPower);
float Damage = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageDef, EvaluationParameters, Damage);
//伤害计算公式
float DamageDone = Damage * AttackPower / DefensePower;
if (DamageDone > 0.f)
{
//这里可以修改多个属性
OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone));
}
}
```
## Period
Period指的是周期一般用于制作周期性技能。
```
//持续类型只有设置为HasDuration技能才能变成周期性的
DurationPolicy = EGameplayEffectDurationType::HasDuration;
//持续时间
DurationMagnitude = FGameplayEffectModifierMagnitude(1.0);
//周期,技能生效次数=持续时间/周期
Period = 2.0;
```
# FGameplayEffectContainer与Spec
EPIC实现了这个结构体
调用URPGGameplayAbility::MakeEffectContainerSpecFromContainer
使用FGameplayEffectContainer生成FGameplayEffectContainerSpec结构体。
Spec是实例版本存储TargetDataHandle与EffectSpecHandle。通过MakeEffectContainerSpecFromContainer进行实例化但本质是通过Spec的AddTarget进行数据填充
之后再通过
```
ReturnSpec.TargetGameplayEffectSpecs.Add(MakeOutgoingGameplayEffectSpec(EffectClass, OverrideGameplayLevel));
```
填充EffectSpec数据。
**MakeEffectContainerSpec则是个快捷函数**
通过FGameplayTag寻找对应的Effect与Target数据。EventData则用于调用TargetType类的GetTarget函数用于获取符合要求的目标Actor
在ActionRPG中URPGTargetType_UseEventData的GetTarget用到了EventData。大致逻辑为首先寻找EventData里是否带有EventData.HitResult信息可以在Send Event To Actor中设置如果没有则返回EventData.Target信息。
```
void URPGTargetType_UseEventData::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
const FHitResult* FoundHitResult = EventData.ContextHandle.GetHitResult();
if (FoundHitResult)
{
OutHitResults.Add(*FoundHitResult);
}
else if (EventData.Target)
{
OutActors.Add(const_cast<AActor*>(EventData.Target));
}
}
```
ApplyEffectContainer则是个方便函数。
# 实现在AnimNotify中向指定目标引用GameplayEffect
从GameplayAbilityComponent或者从GameplayAbility中设置.
MakeOutgoingGameplayEffectSpec=>
ApplyGameplayEffectSpecToTarget 位于UGameplayAbility
ApplyGameplayEffectToTarget
GameplayEffectSpec.GetContext().AddTarget()
RemoveGrantedByEffect()函数可以移除Ability中Instance类型的Effect。非常适合来清除翻滚免伤、技能硬直效果。
```
FRPGGameplayEffectContainerSpec URPGBlueprintLibrary::AddTargetsToEffectContainerSpec(const FRPGGameplayEffectContainerSpec& ContainerSpec, const TArray<FHitResult>& HitResults, const TArray<AActor*>& TargetActors)
{
FRPGGameplayEffectContainerSpec NewSpec = ContainerSpec;
NewSpec.AddTargets(HitResults, TargetActors);
return NewSpec;
}
TArray<FActiveGameplayEffectHandle> URPGBlueprintLibrary::ApplyExternalEffectContainerSpec(const FRPGGameplayEffectContainerSpec& ContainerSpec)
{
TArray<FActiveGameplayEffectHandle> AllEffects;
// Iterate list of gameplay effects
for (const FGameplayEffectSpecHandle& SpecHandle : ContainerSpec.TargetGameplayEffectSpecs)
{
if (SpecHandle.IsValid())
{
// If effect is valid, iterate list of targets and apply to all
for (TSharedPtr<FGameplayAbilityTargetData> Data : ContainerSpec.TargetData.Data)
{
AllEffects.Append(Data->ApplyGameplayEffectSpec(*SpecHandle.Data.Get()));
}
}
}
return AllEffects;
}
```
```
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(Handle, ActorInfo, ActivationInfo, GameplayEffectClass, GameplayEffectLevel);
if (SpecHandle.Data.IsValid())
{
SpecHandle.Data->StackCount = Stacks;
SCOPE_CYCLE_UOBJECT(Source, SpecHandle.Data->GetContext().GetSourceObject());
EffectHandles.Append(ApplyGameplayEffectSpecToTarget(Handle, ActorInfo, ActivationInfo, SpecHandle, Target));
}
```
# 实用函数
Wait Input Release