UE4 C++ GameplayAbilitiesを勉強していくPart.3-1 GameplayEffect ModifierMagnitudeCalculationとExectuionCalculation
やあ
今回は、前回名前は出たけど触っていなかった、Modifier Magnitude CalculationとGameplay Effect Execution Calculationを例を交えて勉強するよ。
まぁ、細かいところの説明は前回で大体やったから、ほぼ例の実装だけどね。
間違いなどあれば教えてくださると助かります!
追記 : GameplayEffectExecutionCalculationについて
pto8913.hatenablog.com
目次
- やあ
- 目次
- 前回
- GameplayEffect
- オイオイオイ、全然活用できてないんじゃないの!?(大事なこと)
- 次回
前回
GameplayEffect
Modifier Magnitude Calculationについて
GameplayEffectのModifiersの一つ。
Modifier TypesのCustom Calculation Classで指定するクラスのこと。

なにをするクラスかというと、
前回まではここに-10や0.5といった定数を入れるだけだったのが、
コード内で指定した定数に対し、何か操作を加えその結果を返せるよというクラス。
例えば、毒で継続的に10ダメージを負うとすると、 1. この時、毒に耐性がある、ないをGameplayTagsを用いて判定 2.1 耐性がある : 毒のダメージ10 - 5の5ダメージを 2.2 耐性がない : 毒のダメージ10 + 10の20ダメージを 3. 結果の値を変えす もちろん`Has Duration`以外にも、`Instant`、`Infinity`でも使える
みたいなことができるよ。
え? そのくらいAttributeSetのPostGameplayEffectExecuteに書けばいいじゃん(笑)とか思わないでね。
あそこは変更された値の結果をセットするためのところであって、
計算をする場所ではないんだよ。
さらに言うと、ただでさえ長い関数が、そんな処理を書いてたらぐちゃぐちゃになって読みにくいよ。
Modifier Magnitude Calculationを実際に使ってみる
GameplayModMagnitudeCalculationを継承してMMC_Staminaを作成

今回は凝ったことはしないで、スタミナが半分以上なら2倍スタミナをしようするようにしました。
MMC_Stamina.hに追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameplayModMagnitudeCalculation.h" #include "MMC_Stamina.generated.h" UCLASS() class ABILITYTEST_API UMMC_Stamina : public UGameplayModMagnitudeCalculation { GENERATED_BODY() public: UMMC_Stamina(); virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override; UPROPERTY() FGameplayEffectAttributeCaptureDefinition StaminaDef; UPROPERTY() FGameplayEffectAttributeCaptureDefinition MaxStaminaDef; };
MMC_Stamina.cppに追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Abilities/Calculations/MMC_Stamina.h" #include "GameplayEffectTypes.h" #include "Abilities/AttributeSets/ptoAttributeSet.h" UMMC_Stamina::UMMC_Stamina() { StaminaDef.AttributeToCapture = UptoAttributeSet::GetStaminaAttribute(); StaminaDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target; StaminaDef.bSnapshot = false; MaxStaminaDef.AttributeToCapture = UptoAttributeSet::GetMaxStaminaAttribute(); MaxStaminaDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target; MaxStaminaDef.bSnapshot = false; RelevantAttributesToCapture.Add(StaminaDef); RelevantAttributesToCapture.Add(MaxStaminaDef); } float UMMC_Stamina::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const { const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags(); const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags(); FAggregatorEvaluateParameters EvaluationParameters; EvaluationParameters.SourceTags = SourceTags; EvaluationParameters.TargetTags = TargetTags; float Stamina = 0.f; GetCapturedAttributeMagnitude(StaminaDef, Spec, EvaluationParameters, Stamina); Stamina = FMath::Max<float>(Stamina, 0.f); float MaxStamina = 0.f; GetCapturedAttributeMagnitude(MaxStaminaDef, Spec, EvaluationParameters, MaxStamina); MaxStamina = FMath::Max<float>(MaxStamina, 1.f); float Res = -10.f; if (Stamina / MaxStamina > 0.5f) { Res *= 2.f; } return Res; }
書けたら、GE_JumpにMCC_Staminaを設定

テスト。

できてるね。
Gameplay Effect Execution Calculationについて
GameplayEffectのExecutionsのCalculation Class

このクラスはModifier Magnitude Calculation(MMC)クラスと同様に、Attributeを変更することができるよ。
だけど、MMCと違って複数のAttributeを変更することができるんだって。
例えば、敵からの攻撃で20ダメージを負うとする。 さらにその攻撃でスタミナを10奪われる みたいな処理もかける。
Gameplay Effect Execution Calculationを実際に使ってみる
敵からの攻撃で20ダメージを受け、 さらにその攻撃でスタミナを10奪われる。 おまけで、その攻撃で防御力が5減るデバフをつけるよ。
これをそのままやってみるよ。
Attributeを変更できるようにする。
MyAttributeSet.cppに追加
void UptoAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) { Super::PostGameplayEffectExecute(Data); FGameplayEffectContextHandle Context = Data.EffectSpec.GetContext(); UAbilitySystemComponent* Source = Context.GetOriginalInstigatorAbilitySystemComponent(); const FGameplayTagContainer& SourceTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags(); float DeltaValue = 0.f; if (Data.EvaluatedData.ModifierOp == EGameplayModOp::Type::Additive) { DeltaValue = Data.EvaluatedData.Magnitude; } /* これはこのAttributeSetのオーナーであるはずです */ AActor* TargetActor = nullptr; AController* TargetController = nullptr; ACharacterBase* TargetCharacter = nullptr; if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid()) { TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get(); TargetController = Data.Target.AbilityActorInfo->PlayerController.Get(); TargetCharacter = Cast<ACharacterBase>(TargetActor); } if (Data.EvaluatedData.Attribute == GetDamageAttribute()) { AActor* SourceActor = nullptr; AController* SourceController = nullptr; ACharacterBase* SourceCharacter = nullptr; if (Source && Source->AbilityActorInfo.IsValid() && Source->AbilityActorInfo->AvatarActor.IsValid()) { SourceActor = Source->AbilityActorInfo->AvatarActor.Get(); SourceController = Source->AbilityActorInfo->PlayerController.Get(); if (SourceController == nullptr && SourceActor != nullptr) { if (APawn* Pawn = Cast<APawn>(SourceActor)) { SourceController = Pawn->GetController(); } } if (SourceController) { SourceCharacter = Cast<ACharacterBase>(SourceController->GetPawn()); } else { SourceCharacter = Cast<ACharacterBase>(SourceActor); } } FHitResult HitResult; if (Context.GetHitResult()) { HitResult = *Context.GetHitResult(); } const float LocalDamageDone = GetDamage(); SetDamage(0.f); if (LocalDamageDone > 0.f) { const float OldHealth = GetHealth(); SetHealth(FMath::Clamp(OldHealth - LocalDamageDone, 0.f, GetMaxHealth())); if (TargetCharacter) { TargetCharacter->HandleDamage( LocalDamageDone, HitResult, SourceTags, SourceCharacter, SourceActor ); TargetCharacter->HandleHealthChanged( -LocalDamageDone, SourceTags ); } } } else if (Data.EvaluatedData.Attribute == GetDefensePowerAttribute()) { // 最大値をいじりたい場合はこう書く SetDefensePower(FMath::Clamp(GetDefensePower(), 0.f, GetDefensePower())); if (IsValid(TargetCharacter) == true) { TargetCharacter->HandleDefensePowerChanged(DeltaValue, SourceTags); } } else if (Data.EvaluatedData.Attribute == GetHealthAttribute()) { SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth())); if (IsValid(TargetCharacter) == true) { TargetCharacter->HandleHealthChanged(DeltaValue, SourceTags); } } else if (Data.EvaluatedData.Attribute == GetStaminaAttribute()) { SetStamina(FMath::Clamp(GetStamina(), 0.f, GetMaxStamina())); if (IsValid(TargetCharacter) == true) { TargetCharacter->HandleStaminaChanged(DeltaValue, SourceTags); } } else if (Data.EvaluatedData.Attribute == GetMaxStaminaAttribute()) { // 最大値をいじりたい場合はこう書く SetMaxStamina(FMath::Clamp(GetMaxStamina(), 0.f, GetMaxStamina())); if (IsValid(TargetCharacter) == true) { TargetCharacter->HandleStaminaChanged(DeltaValue, SourceTags); } } }
CharacterBase.hに追加
UFUNCTION(BlueprintImplementableEvent)
void OnDefensePowerChanged(float DeltaValue, const FGameplayTagContainer& EventTags);
UFUNCTION(BlueprintImplementableEvent)
void OnHealthChanged(float DeltaValue, const FGameplayTagContainer& EventTags);
UFUNCTION(BlueprintImplementableEvent)
void OnDamage(
float DamageAmount,
const FHitResult& HitInfo,
const struct FGameplayTagContainer& DamageTags,
ACharacterBase* InstigatorPawn,
AActor* DamageCauser
);
public:
UFUNCTION(BlueprintCallable)
virtual float GetDefensePower() const;
UFUNCTION(BlueprintCallable)
virtual float GetHealth() const;
UFUNCTION(BlueprintCallable)
virtual float GetMaxHealth() const;
// ptoAttributeSetから呼ばれて、上記のOn〇〇ChangedがBPから呼ばれます
virtual void HandleDefensePowerChanged(float DeltaValue, const FGameplayTagContainer& EventTags);
virtual void HandleHealthChanged(float DeltaValue, const FGameplayTagContainer& EventTags);
virtual void HandleDamage(
float DamageAmount,
const FHitResult& HitInfo,
const struct FGameplayTagContainer& DamageTags,
ACharacterBase* InstigatorPawn,
AActor* DamageCauser
);
CharacterBase.cppに追加
float ACharacterBase::GetHealth() const { if (IsValid(AttributeSet)) { return AttributeSet->GetHealth(); } return 1.f; } float ACharacterBase::GetMaxHealth() const { if (IsValid(AttributeSet)) { return AttributeSet->GetMaxHealth(); } return 1.f; } float ACharacterBase::GetDefensePower() const { if (IsValid(AttributeSet)) { return AttributeSet->GetDefensePower(); } return 1.f; } void ACharacterBase::HandleStaminaChanged(float DeltaValue, const FGameplayTagContainer& EventTags) { if (bAbilitiesInitialized) { OnStaminaChanged(DeltaValue, EventTags); } } void ACharacterBase::HandleDefensePowerChanged(float DeltaValue, const FGameplayTagContainer& EventTags) { if (bAbilitiesInitialized) { OnDefensePowerChanged(DeltaValue, EventTags); } } void ACharacterBase::HandleHealthChanged(float DeltaValue, const FGameplayTagContainer& EventTags) { if (bAbilitiesInitialized) { OnHealthChanged(DeltaValue, EventTags); } } void ACharacterBase::HandleDamage( float DamageAmount, const FHitResult& HitInfo, const struct FGameplayTagContainer& DamageTags, ACharacterBase* InstigatorPawn, AActor* DamageCauser ) { OnDamage(DamageAmount, HitInfo, DamageTags, InstigatorPawn, DamageCauser); }
Gameplay Effect Execution Calculationの作成
敵からの攻撃で20ダメージを受け、 さらにその攻撃でスタミナを10奪われる。 おまけで、その攻撃で防御力が5減るデバフをつけるよ。
流れ
1. まずダメージを与えるアビリティを実行。Gameplay Effect Execution Calculation(EC)を持ったGameplay Effectを適用。
2. アビリティが終了したら、デバフ用のGameplay Effectを発生。
ダメージを与え、スタミナを奪うGameplay Effectの作成

EC_DamageWithDFSDebuff.hに追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameplayEffectExecutionCalculation.h" #include "EC_DamageWithDFSDebuff.generated.h" UCLASS() class ABILITYTEST_API UEC_DamageWithDFSDebuff : public UGameplayEffectExecutionCalculation { GENERATED_BODY() public: UEC_DamageWithDFSDebuff(); virtual void Execute_Implementation( const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionParams ) const override; };
EC_DamageWithDFSDebuff.cppに追加
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Abilities/Calculations/EC_DamageWithDFSDebuff.h" #include "Abilities/AttributeSets/ptoAttributeSet.h" #include "AbilitySystemComponent.h" struct ptoDamageStatics { DECLARE_ATTRIBUTE_CAPTUREDEF(DefensePower); DECLARE_ATTRIBUTE_CAPTUREDEF(AttackPower); DECLARE_ATTRIBUTE_CAPTUREDEF(Damage); DECLARE_ATTRIBUTE_CAPTUREDEF(Stamina); ptoDamageStatics() { DEFINE_ATTRIBUTE_CAPTUREDEF(UptoAttributeSet, DefensePower, Target, false); DEFINE_ATTRIBUTE_CAPTUREDEF(UptoAttributeSet, AttackPower, Source, true); DEFINE_ATTRIBUTE_CAPTUREDEF(UptoAttributeSet, Damage, Source, true); DEFINE_ATTRIBUTE_CAPTUREDEF(UptoAttributeSet, Stamina, Source, true); } }; static const ptoDamageStatics& DamageStatics() { static ptoDamageStatics DamageStatics; return DamageStatics; } UEC_DamageWithDFSDebuff::UEC_DamageWithDFSDebuff() { RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef); RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef); RelevantAttributesToCapture.Add(DamageStatics().DamageDef); RelevantAttributesToCapture.Add(DamageStatics().StaminaDef); } void UEC_DamageWithDFSDebuff::Execute_Implementation( const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionParams ) const { UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent(); UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent(); AActor* SourceActor = SourceASC ? SourceASC->AvatarActor : nullptr; AActor* TargetActor = TargetASC ? TargetASC->AvatarActor : nullptr; const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec(); const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags(); const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags(); FAggregatorEvaluateParameters EvaluationParameters; EvaluationParameters.SourceTags = SourceTags; EvaluationParameters.TargetTags = TargetTags; float DefensePower = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( DamageStatics().DefensePowerDef, EvaluationParameters, DefensePower ); float AttackPower = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( DamageStatics().AttackPowerDef, EvaluationParameters, AttackPower ); float Damage = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( DamageStatics().DamageDef, EvaluationParameters, Damage ); float Stamina = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( DamageStatics().StaminaDef, EvaluationParameters, Stamina ); float StaminaDone = -10; float DamageDone = Damage * (AttackPower - DefensePower); if (DamageDone > 0.f) { OutExecutionParams.AddOutputModifier( FGameplayModifierEvaluatedData( DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone ) ); OutExecutionParams.AddOutputModifier( FGameplayModifierEvaluatedData( DamageStatics().StaminaProperty, EGameplayModOp::Additive, StaminaDone ) ); } }
とりあえずテストする
GamapleyEffectの作成

GamapleyAbilityの作成

アビリティを設定

UIの作成

テキストブロックにはCharacterRefからそれぞれに対応したものをバインドしてね。
テスト結果

デバフ用のGameplayEffectの作成

Has DurationのPeriodを0にすることで、経過時間後にAttributeの値が元に戻るよ。
これでデバフができたね。
このGameplayEffectをさっき作ったアビリティの最後に追加

これでテスト

ちょっとわかりにくいけど、アビリティが終わった時に、防御が-5されて、その5秒後に防御が50に戻ってるね。
オイオイオイ、全然活用できてないんじゃないの!?(大事なこと)

ここの値、全然活用してないじゃないの!?
何のための値なんだい!?って思ったよね。
例えば、
Attribute.Damageを100とする。
Attribute.Damageをキャプチャしたときに、
/* Execute_Implementationの中だと思ってください */ float Damage = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( DamageStatics().DamageDef, EvaluationParameters, Damage ); /* ここでキャプチャされた値に、Scalable Float Magnitudeで指定した 値がDamageに追加(Modifier Opで指定したオペレーション)される Scalable Float Magnitude を 2としたとき ModOp.Add : Damage = 100 + 2 の 102になる ModOp.Mul : Damage = 100 x 2 の 200になる ModOp.Div : Damage = 100 / 2 の 50になる ModOp.Override : Damage = 2 になる ModOp.InValid : よくわかってない この値をいじってAddOutputModifierしてやる。 AddOutputModifierするときに注意が必要で、 OutExecutionParams.AddOutputModifier( FGameplayModifierEvaluatedData( DamageStatics().DamageProperty, EGameplayModOp::Additive, Damage ) ); EGameplayModOp に指定したオペレーションによって、 AttributeSetのPostGameplayEffectExecuteでセットされる。 */ float DamageDone = Damage * 2; OutExecutionParams.AddOutputModifier( FGameplayModifierEvaluatedData( DamageStatics().DamageProperty, EGameplayModOp::Additive, DamageDone ) );
ということを理解すると、
UEC_DamageWithDFSDebuff::Execute_Implementationの中身を書き換えられて、
float Stamina = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude( DamageStatics().StaminaDef, EvaluationParameters, Stamina ); OutExecutionParams.AddOutputModifier( FGameplayModifierEvaluatedData( DamageStatics().StaminaProperty, EGameplayModOp::Additive, Stamina ) );
となり、
GE_DamageWithDFSDebuffは

とできる。

うまくいってるね。
だいぶわかってきたかも

今回はここまで、ありがとうございました。
次回
まだ触れてないGameplay Effectの機能のコストとクールダウンを勉強していくよ。