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
の機能のコストとクールダウンを勉強していくよ。