そうだ、ゲームを作ろう

現状や学んだことなど記録するブログ。間違ってたらごめんね

第15回ぷちコン「たぬ吉の大冒険」振り返りその2<GameplayAttribute、GameplayEffect編>

注》この記事はこの記事の続きです。

この記事では第15回ぷちコンの振り返りとして、GameplaySystemのうちGameplayAttributeとGameplayEffectを解説します。

 

お品書き

・前回の手直し

・GameplayAttributeについて

・AttributeSetについて

・GameplayAttributeを導入してみる

・GameplayEffectについて

・GameplayEffectを導入してみる

・GameplayModMagnitudeCalculationについて

・GameplayEffectExecutionCalculationについて

 

・前回の手直し

前回作成したプロジェクトなのですが、今回解説する内容と合わせるために若干手を入れようかと思います。まず前回作成した「GPA_Test」の名前を「GPA_Attack」に変更します。

f:id:wvigler:20210425000518p:plain

次に攻撃にCollisionを付けます。

BP_GASCharacterにBoxCollisionを追加します。

f:id:wvigler:20210425001712p:plain

こんな感じで。そうしたらCollision設定をこうして…

f:id:wvigler:20210425005348p:plain

次にBoxCollisionにComponentTagを付けます。名前は「Attack」としておきます。

f:id:wvigler:20210425002321p:plain

次にAnimationNotifyを作成します。

f:id:wvigler:20210425002800p:plain

名前は「NF_ActivateAttack」としておきます。

f:id:wvigler:20210425003208p:plain

RecievedNotifyをOverrideして…

f:id:wvigler:20210425010522p:plain

f:id:wvigler:20210425010601p:plain

このようにノードを組みます。要は「Attack」のTagが付いているBoxCollisionにOverlappingしているBP_GASCharacterのダメージAbilityを発動させる、ということです。これを…

f:id:wvigler:20210425011211p:plain

攻撃モーションのこの辺りに設置します。

これでBP_GASCharacterをThirdPersonExampleMapに新たに置くと…

f:id:wvigler:20210425011542p:plain

このように攻撃すると相手がのけぞるようになります。後でまた弄りますが、インタラクションがあったほうが楽しいので一旦こうしておきます。今回はここからスタートです。

・GameplayAttributeについて

GameplayAttributeはGameplayAbilitySystem内で個々のActorの持つ様々なパラメーター(HPやMP、攻撃力、防御力など)をfloat値として統括管理するシステムです。ゲームにはこれらの数値の管理が殆どの場合必須です。

GameplayAttribute自身はFGameplayAttributeDataという変数型で提供されています。AttributeはBaseValueとCurrentValueという2つの数値を持っており、このどちらにもアクセスできるようになっています。

GameplayAttributeを扱うにはAttributeSetというC++クラスが必要です。

・AttributeSetについて

AttributeSetはGameplayAttributeを定義し、それを扱うための関数やマクロを提供するためのC++クラスで、これをASCに付与することでAttributeにアクセスすることができるようになります。また、AttributeSetは複数違うものを持つことも実は可能です。

AttributeSetにはいくつかの仮想関数が定義されています。これらは重要なので少し見ていきましょう。

>void PreAttributeChange(const FGameplayAttribute& Attribute, float NewValue)

>void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float NewValue)

Attribute(BaseValue or CurrentValue)が書き換えられる直前に処理が走ります。Clamp処理を掛けたりする際に使用します。

>bool PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)

GameplayEffectによりAttributeに変更が入る直前に処理が走ります。GameplayEffectの処理自体を一定条件で拒否したりといった用途に使用します。

>void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)

GameplayEffectによりAttributeに変更が入った直後に処理が走ります。Actorへの通知は基本的にここで行うことが推奨されているので、おそらく一番使う機会が多いと思います。

・GameplayAttributeを導入してみる

なにはともあれAttributeSetのC++クラスを作成しましょう。

f:id:wvigler:20210425013854p:plain

名前は「GASAttributeSet」としておきましょう。

f:id:wvigler:20210425015947p:plain

エディタを開いてGASAttributeSet.hに書き込んでいきます。

まずはAbilitySystemComponent.hをincludeします

f:id:wvigler:20210425041311p:plain

続いてアクセサマクロを設定します。

f:id:wvigler:20210425041414p:plain

これはGameplayAttributeからfloat値の取り出しやfloat値による書き換えをする際に使用します。

次にそれぞれのGameplayAttributeを宣言します。宣言するのはHealth、MaxHealth、AttackBase、AttackMultiplier、DefenseMultiplier、そしてDamageです。Publicに記述することを忘れないようにしましょう。(忘れるとコンパイルエラーが出ます)

f:id:wvigler:20210425033308p:plain

ここでちょっと疑問に思うかもしれません。「Damageってパラメーターなの?」と。

DamageはMetaAttributeと呼ばれるもので、一般的なパラメーターではなく、ダメージ計算時に使用するものです。非常に便利なのですが、あとでGameplayEffectExecutionCalculationのところで具体的な使い方を説明します。

ついでにPostGameplayEffectExecuteを宣言します。

f:id:wvigler:20210425042340p:plain

今の所中身は空ですが、cppファイル側で一応定義はしておきます。

f:id:wvigler:20210425042600p:plain

 さて、お次はGASCharacterクラスを弄っていきます。エディタを開いて、まずはGASCharacter.hでGASAttributeSet.hをincludeします。

f:id:wvigler:20210425075841p:plain

宣言に使用するUPROPERTYマクロは引数なしで構いません。アクセスはPublicで。

f:id:wvigler:20210425080503p:plain

追加の仕方は通常のコンポーネントと全く変わりません。しかし、もちろんコンポーネントではないのでBP側には何も表示されません。

f:id:wvigler:20210425080735p:plain

とりあえずC++側はこれで終わりです。コンパイルしましょう。

さて、終わったらBP_GASCharacterの中身を見ていきます。

f:id:wvigler:20210425082118p:plain

ダメージモーションは確認したのでもうこのノードは要らないです。なので…

f:id:wvigler:20210425082428p:plain

Attributeにアクセスするためにアクセサを介した独自ノードをActorに実装する方法もあるのですが、個人的には必要無いと思います(分かりやすいので説明や習熟するためとしてはアリだと思いますが)。

上のようにAttributeをGetしたい時にはGetFloatAttributeFromAbilitySystemCompoentかGetFloatAttributeBaseFromAbilitySystemComponent、Setしたい時にはGameplayEffectを使用すれば問題ないと思います。

じゃあそもそもアクセサ要らないじゃん、というとそういうわけでもなく、C++内の関数で結局必要になったりします。

それからAbilitySystemが更新されているので、EventGraphのAbilitySystemノードを繋ぎ直す必要が出てきます。これは前の記事内でAbilitySystemを直接参照できるようにした弊害なのですが、まあこのぐらいは許容範囲とします。

それはさておき、実行してみましょう。

f:id:wvigler:20210425083336p:plain

まだ何も弄っていないので数字は0です。ただこのノードはアクセスに失敗しても0を返すため、数字だけでは成功しているかが不明です。しかしBooleanがtrueになっているため、たしかにアクセスできていることが分かります。

さて、GameplayAttributeによってキャラのパラメーターを用意することに成功しましたが、パラメーター自体を弄ることがまだできていません。それをするために必要なのがGameplayEffectです。

・GameplayEffectについて

さあGameplayEffectは長いぞ。

GameplayEffectはAttributeに対し影響(Effect)を与えるものです。しかしそれに留まらず、めちゃくちゃ色んな事ができるようになっています。基本的にこのクラス自体はC++での実装は要らず、BPのみで取り扱えます。とりあえずGameplayEffectのBPクラスを作成して、中身を覗いていきましょう。

f:id:wvigler:20210425085537p:plain

まずは初期化に使用したいので、名前は「GPE_Initialize」とします。

f:id:wvigler:20210425085817p:plain

中身を開くとこのようになります。

f:id:wvigler:20210425090017p:plain

GameplayEffectクラスでは弄るのはDetailsのみなので、左側は一切必要ありません!

そして肝心の右側には…

f:id:wvigler:20210425090257p:plain

うんざりするぐらい色々並んでいます。これらを全て解説すると記事がもう2、3個必要になってきりがないので、必要なところだけを解説します。(ここでだいたい書かれているので、気になる人は見てください)まずは一番上の「GameplayEffect」カテゴリです。

f:id:wvigler:20210425090621p:plain

DurationPolicyはInstant、HasDuration、Infiniteの三種類があり、それぞれEffectをどのぐらいの期間適用するかを決定します。Instantは即時で一回だけ。HasDurationは一定期間ずっと。Infiniteは永久に適用します。またこれはAttributeのBaseValueとCurrentValueを同時に変更するのか?それともCurrentValueのみ変更するのかに関わってきます。前者がInstantであり、後者がHasDurationとInfiniteです。

またこの選択によって下のカテゴリである「Period」が変化します。

[Instant]

f:id:wvigler:20210425091715p:plain

[HasDuration] or [Infinite]

f:id:wvigler:20210425091814p:plain

PeriodはEffectを適用する周期です。1/60とか1/24とか設定することが多いですが、0のまま使用することもあります。

ExecutePeriodicEffectOnApplicationはオンにするとEffectが適用された瞬間にAttributeが書き換わり、その後Period秒毎にAttributeが再び書き換わります。オフにするとEffectが適用された瞬間には書き換わらず、その後Period秒後に初めてAttributeが書き換わります。

PeriodicInhibitionPolicyは何らかの理由でEffectが中断された場合、NeverResetの場合は中断された場所から何事もなかったかのように再び適用が始まります。ResetPeriodの場合、Periodが一旦リセットされ、再開すると最初にEffectが付与された時と同じように振る舞います。ExecuteAndResetPeriodの場合、基本的にはResetPeriodと同じですが、中断された際に一度だけEffectが適用されます。

それからHasDurationを選択した場合は「GameplayEffect」カテゴリにDurationMagnitudeという項目ができます。

f:id:wvigler:20210425101102p:plain

これはEffectの適用期間を決定するものです。

さてお次の項目はModifiersです。配列になっているのでちょっと追加して覗いてみると…

f:id:wvigler:20210425101855p:plain

上から解説していきます。AttributeはEffectによって変更するTarget(GameplayEffectのSourceとTargetについては後で説明します)のAttributeを選択します。

f:id:wvigler:20210425113525p:plain

現在はこんな風に選択できます。

ModifierOpはAttributeに対する計算方法です。

f:id:wvigler:20210425113843p:plain

これは説明不要ですね。マイナスの値をAddに入れると数値の減少を表現することができます。

MagnitudeCalculationTypeは変更する元となる数値を決定します。

f:id:wvigler:20210425122839p:plain

ScalableFloatは直接入力で数値を決定します。FCurveFloat型です。

f:id:wvigler:20210425123539p:plain

AttributeBasedはSource又はTargetのAttributeを参照して数値を決定します(ある程度は変更もできます)。

f:id:wvigler:20210425123912p:plain

CustomCalculationClassはGameplayModMagnitudeCalculationクラスを使用して数値を決めます。MagnitudeCalculationTypeの中では最も自由度が高くなりまが、C++での実装が必須となります。

f:id:wvigler:20210425124520p:plain

最後にSetByCallerです。これは見てみた方が早いので(そして初期化に向いているので)、実装しながら見ていきましょう。

…と言いたいところですが、まだ一つ説明することがあります。GameplayEffectの適用方法についてです。

GameplayEffectは基本的に以下のようなノード構成で適用します。

[AbilitySystemComponentから適用する場合]

f:id:wvigler:20210425132816p:plain

f:id:wvigler:20210425133106p:plain

見ての通りAbilitySystemComponentからSpecHandleを作成して、それをノードに送って適用する、という流れです。そしてGameplayEffectにはASCに対してSource(適用する側)とTarget(適用される側)という区別があります。上の2つの例の場合、どちらも上側のASCがTarget側、下側がSource側になります。(この場合参照しているのは全く同じASCではありますが…)

[GameplayAbilityから適用する場合]

f:id:wvigler:20210425133616p:plain

f:id:wvigler:20210425133804p:plain

GameplayAbilityから適用する場合、Sourceは強制的にAbilityを所持している側のASCになります。そして上の例はとても単純にTarget側もAbility所持者のASCが担当している、ということなのですが…下側はTargetingというシステムが絡んでいます。これは名前こそ同じですがGameplayEffect内のTargetの概念とはまた違うものでして…一応オフラインでも用いることはできるものの、上手く活用するためにはネットワークが絡んでくるみたいなので、僕もまだ良くわかってません。なので触れないでおきます。

・GameplayEffectを導入してみる

さて、やりたいことは各AttributeのInitializeです。それにはどのMagnitudeCalculationTypeが最適でしょうか?

ScalableFloatとAttributeBasedは便利な場面ももちろんありますが、基本的にBP側のfloat値を参照できません。例えばパラメーターをInitializeする場合、キャラクターのレベルに応じた数値をDataTableから引っ張ってくることやBPエディタに公開したVariableを参照する方法が考えられます。その場合BPのfloat値が使えないと数値を持ってこれません。CustomCalculationClassは最も自由度が高い方法なので、もちろんBPの数字を持ってくることができますが、C++での実装が必須になるのでお手軽ではありません。というわけで、この場合Initializeに最適なのはSetByCallerとなると思います。

SetByCallerにはAssignTagSetByCallerMagnitudeというノードを使用します。AssignSetByCallerMagnitudeというノードもありますが、こちらはどうやらC++無しでは今の所機能しないようです。

f:id:wvigler:20210425232229p:plain

このように繋げると…

f:id:wvigler:20210425232516p:plain

Tagとfloatが紐付けられた情報であるSetByCallerがSpecにAssignされます。EventBeginPlayノードにこれをどんどん繋げて…(初期値はVariable化しています)

f:id:wvigler:20210426031344p:plain

GPE_Initializeの方もずらずらずらっと全部SetByCallerで…

f:id:wvigler:20210426032948p:plain

f:id:wvigler:20210426032913p:plain

Xキーを押したら可視化できるようにしていきましょう。

f:id:wvigler:20210426032707p:plain

f:id:wvigler:20210426032756p:plain

これで実行中にXキーを押すと…

f:id:wvigler:20210426033111p:plain

このようにGameplayEffectを介してAttributeを変更することができたことが分かります。

・GameplayModMagnitudeCalculationについて

最も自由度が高いMagnitudeCalculationTypeであるCustomCalculationClassにおいて計算に使用するのがGameplayModMagnitudeCalculation(以下GMMC)クラスです。これはBPでは中身を操作できずC++実装オンリーのクラスで、その本体はCalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) constというfloat値を返す関数です。他のMagnitudeCalculationTypeでできる全てのことが可能で、かつそれらの値を使用した複雑な計算式や条件式を作成することができます。

ではGMMCを使用した攻撃ダメージ処理を作っていきましょう。GMMCの派生クラスを作成します。

f:id:wvigler:20210426043746p:plain

名前は「GMMC_Attack」としました。

f:id:wvigler:20210426044702p:plain

ヘッダを開いて、まずは宣言から…

f:id:wvigler:20210426051912p:plain

コンストラクタとAttribute情報を格納するためのFGameplayEffectAttributeCaptureDefinitionという型の変数を3つ用意します。それから本体となるCalculationBaseMagnitude_Implementationですね。

次はcppファイルを開きます。まずAttributeの参照に必要なAttributeSetクラスをincludeします。

f:id:wvigler:20210426053117p:plain

次にコンストラクタでSource側のAttackBase及びAttackMultiplier、Target側のDefenseMultiplierをCaptureします。

f:id:wvigler:20210426055412p:plain

SnapshotをtrueにするとEffectSpecが作成された瞬間、falseにするとそのSpecがGameplayEffectによって適用された瞬間にAttribute情報がCaptureされます。活用例としてProjectileのActorなどが分かりやすいですが、射出時に射出側のActorでAttributeをSnapshotしてSpecを作成し、ProjectileはそのSpecの情報のみを保存して運び、被弾側のActorに至って初めてそのSpec情報を参照してGameplayEffectとして適用すれば、ProjectileのSpecが持っているのは射出時点のAttribute情報となります(逆にSnapshotしていなければ被弾時点でのAttribute情報が参照されます)。はっきり言って、このようなMeleeな攻撃では効果を実感しづらい代物ですが、一応Source側はtrue、Target側はfalseにしておきます。

次はCalculationBaseMagnitude_Implementationを弄っていきます。

f:id:wvigler:20210426062125p:plain

まずはSourceとTargetのTagを集め、EvaluationParametersに投入します。TagによってはCaptureした数値が(PreAttributeChangeなどにより)変化する可能性があるからです。そして用意したLocal変数にGetCaptureAttributeMagnitudeを介して値を代入していき、最終的に計算を行ってその値を返り値にします。(除算がある場合はくれぐれもゼロ除算に注意しましょう)

ここまで書いたらコンパイルします。

次はBP側です。新しいGameplayEffectの派生BPクラスを作成します。名前は「GPE_Attack」とします。

f:id:wvigler:20210426070751p:plain

Detailsはこのようにします。

f:id:wvigler:20210426071858p:plain

Coefficientが-1.0になっていることに注意しましょう。

次にNF_ActivateAttackの中身を書き換え、TryActivate~の直前にGPE_Attackを適用します。

f:id:wvigler:20210426072727p:plain

と、これで一応プログラム上は問題ありませんが、可視化されていないため本当にEffectが動いているのかどうかが分かりません。なので様々な改良も含め、また少しプロジェクトを弄ります。

 

いきなりですが、まずはここからTryActivateAbilitiesByTagを削除します。

f:id:wvigler:20210426074139p:plain

次にGASCharacter.hを開き、Publicに以下のようにOnDamaged(float Damage, float Health, AActor* Attacker)とOnDied(float Damage, AActor* Attacker)という関数を宣言します。

f:id:wvigler:20210426075120p:plain

これは宣言のみで、cppファイル内に定義を書く必要はありません。

次に弄るのはGASAttributeSet.cppです。以前殆ど宣言しただけで放置したPostGameplayEffectExecuteの中身を書いていきます。

f:id:wvigler:20210427045655p:plain

まずは先程のOnDamagedやOnDiedを扱うためにAGASCharacterクラスへのアクセスが必要なので"GASCharacter.h"をincludeします。様々なGameplayEffect関連の情報を扱う必要もあるため、"GameplayEffect.h"と"GameplayEffectExtension.h"もincludeしておきましょう。そうしたらEffect処理後の処理を書いていきます。

f:id:wvigler:20210427061535p:plain
ここでやっていることはまずはTargetのActorとSourceのActorを取得して、それからEffectによってHealthの数値に変更があった場合に、Healthの変更がマイナス方向で、かつ変更後の数値が0以上ならTargetのOnDamagedを呼び出し、0以下ならOnDiedを呼び出す処理です。その後にDamage(前にAttributeSet内に定義したMetaAttribute)を0にし、最後にHealthをClamp処理して終了します。

さてC++で弄るのはここまでです。コンパイルしましょう。

これからBP側を弄るのですが、下準備としてダメージを受け続けて死んだ時用のAbilityを作成します。

f:id:wvigler:20210429233250p:plain

Animationの用意が面倒なのでPhysicsAnimationにしました。

f:id:wvigler:20210429233346p:plain

 

f:id:wvigler:20210427131616p:plain

TagはAbility.Deathで呼び出せるようにしました。

そして今度はダメージリアクションのAbilityをキャラクターに設定します。まずBP_GASCharacterを開きます。BlueprintImplementableEventで宣言したので、BP_GASCharacterのEventGraphでは"OnDamaged"と"OnDied"がEventとして呼べるようになっています。

f:id:wvigler:20210427125451p:plain

それぞれ以下のように繋ぎます。

f:id:wvigler:20210427141225p:plain

f:id:wvigler:20210427141305p:plain

DamageとDeath、それぞれリアクションとしてのAbilityの呼び出しと、パラメーター情報の表示をさせます。

f:id:wvigler:20210429234635p:plain

最後にAbilityListにAbilityを登録するのを忘れないようにしましょう(よく忘れる)。

次に新たなGameplayEffectを作成します。名前はGPE_ConvertDamageToHealthとします。

f:id:wvigler:20210428121419p:plain

中身はDamageの数値をマイナス方向のHealthに変換するだけです。AttributeSourceをTargetにするのを忘れないようにしましょう。

f:id:wvigler:20210428143242p:plain

GPE_Attackも少し直します。

f:id:wvigler:20210427083628p:plain

書き換えるAttributeをDamageに、Coefficientを1.0に変更しました。

そして…

f:id:wvigler:20210428150205p:plain

GameplayEffectsカテゴリのConditionalGameplayEffectsにGPE_ConvertDamageToHealthを追加します。ConditionalGameplayEffectsはGameplayEffectが成功した場合にここに登録されたEffectが続いて発動します。これはEffectで変更したAttributeを、変更した後に再びEffectで使用したい場合などに使用します。基本的にAttributeのCaptureは一度しか行われず、一つのEffect内では変更後のAttributeを扱うことができません。しかしEffectを何度も呼び出すのもノードが増えてスマートではない(更に言うと何度も書くのがそもそも面倒)のでこのような項目が用意されています。

基本構造としてはBP_GASCharacterがGPA_Attackを発動した際に、AttackCollision内にいる他のBP_GASCharacterにGPE_Attackを適用、続いてGPE_ConvertDamageToHealthが適用され、それがHealthに変換されます。Healthが変更されたのでPostGameplayEffectExecuteからTargetActorのEventの発動やHealthのClampが行われ、最後にDamageの値が0になって次の攻撃に備える、となります。

 

なぜDamageというMetaAttributeを用意したかというと、こうしてHealthとDamageを一旦切り離すことでBuff、Debuffやアイテムなどによる数値の変更がやりやすくなる、という効果があります。例えば防具などを装備しているとある程度のダメージを肩代わりしたり、ダメージを割合で減らしたりする場合、MetaAttributeが無く直接Healthを減算する形とすると、後のEffectなどでもともとのダメージ値が参照できなくなってしまい、効果を手軽に追加・削除できなくなってしまいます。小さなプロジェクトなら全く問題が無い場合もあるのでケースバイケースではあるのですが、この使い方自体は覚えておいたほうがいいと思います(GameplayAbilitySystem外でも有効です)。

またMetaAttributeを複数用意することも有用です。このプロジェクトでは現在毒や火傷などといったDOT系統のダメージを実装すると少々問題が起こります(Healthの変更が行われる度にAnimationが再生される仕様なので)。これは新たに「DOTDamage」などといったMetaAttributeを作成し、PostGameplayEffectExecuteを書き換えると解決するのですが、ここでは今は触れないでおきます。

さて、ここまでGMMCの実装について書いてきましたが、実はGameplayEffectにはもっと自由度の高い計算方式があります。それがGameplayEffectExecutionCalculationです。

・GameplayEffectExecutionCalculationについて

GameplayEffectExecutionCalculation(以下GEEC)はGameplayEffect内で最も自由度の高い計算方式です。GMMCでできることに加えて、AttributeからCaptureする数値自身をBPから操作することができます。

今回はこれを使用してHP吸収攻撃を作成していきたいと思います。

まずはGEECクラスを作成していきましょう。ちなみにこちらもBPでの実装はできず、C++オンリーのクラスとなっています。

f:id:wvigler:20210430013336p:plain

名前はGEEC_DrainAttackとします。

f:id:wvigler:20210430014951p:plain

GEEC_DrainAttack.hに宣言を書き込みます。宣言するのはコンストラクタとExecute_Implementationという引数がやたらに長い仮想関数です。

f:id:wvigler:20210430052106p:plain

今度はGEEC_DrainAttack.cppに移動し、まずは"GASAttributeSet.h"と"AbilitySystemComponent.h"をincludeします。

f:id:wvigler:20210430053318p:plain

次にcpp内に"DrainStats"というStructureを作成します。GEECにはCaptureについてのマクロが提供されているのでカンタンです。

f:id:wvigler:20210430094353p:plain

DEFINE_ATTRIBUTE_CAPTUREDEFの引数は左から順にクラス名、Attribute名、Target or Source、Snapshotとなっています。

次にコンストラクタです。

f:id:wvigler:20210430094825p:plain

Structureとマクロを使用しているだけで、やっていることはGMMCと殆ど変わりません。

続いてExecute_Implementationです。一気に完成形までやっていきます。

f:id:wvigler:20210430124021p:plain

f:id:wvigler:20210430124103p:plain

GMMCと引数や関数の名前が変わっていますが、ここでもやっている事自体は前回とあまり変わりません。変わったことは、RecoverMultiplierというSetByCallerを参照し、Damageに掛けたものでSourceを回復させている点です(回復方法はあまりスマートじゃありませんが…)。このようにGMMCやGEECではSetByCallerから数値を参照することもできます。

さて、これをコンパイルして…

終わったら今度はBP側です。GPE_DrainAttackというGameplayEffectを作成します。

f:id:wvigler:20210430130347p:plain

中身はこうします。

f:id:wvigler:20210430153312p:plain

ここでは使用しませんが、CalculationModifireを追加するとCaptureする値にModifierの項目と同じような変更を施すことができるようになります。これはGEECの強みの一つで、実行結果を見ながら少しづつ係数を上げたりといった数値の微調整をする場合などに役に立ちます。

f:id:wvigler:20210430155851p:plain

Abilityの作成の仕方はもうやっているので省略しまして、NotifyはNF_ActivateAttackをDuplicateしたところから…

f:id:wvigler:20210430160104p:plain

こうなってる部分を…

f:id:wvigler:20210430161216p:plain

こんな風に変更します。

それからちゃんと機能しているか可視化するために自身のHealthを減らす仕組みも必要ですね。"GPE_ReduceHealth"というGameplayEffectを用意しまして…

f:id:wvigler:20210430234101p:plain

一気に80減らします。

最終的に…

f:id:wvigler:20210430235356p:plain

ZキーとXキーで通常攻撃と吸収攻撃…

f:id:wvigler:20210430235626p:plain

CキーでHealthを減らして…

f:id:wvigler:20210501000349p:plain

Qキーを押すとステータスを表示するということにしました。

実行してみましょう。

f:id:wvigler:20210501005354p:plain

実行時にQキーを押すとこうなります。Cキーを押しまして…

f:id:wvigler:20210501012752p:plain

再びQキーを押すとこんな感じになります。Healthが80減って20になっていますね。

f:id:wvigler:20210501015003p:plain

敵に近付いてXキーを押して攻撃してみましょう。

f:id:wvigler:20210501011743p:plain

そうしてからまたQキーを押すとこうなります。

f:id:wvigler:20210501015203p:plain

元のHealthは20、そして攻撃力は20なのでその0.4倍である8回復して28になっています。これにてHP吸収攻撃を実装することに成功しました。

 

いやーやっと終わりました。長くなるんじゃないかとは思ってたんですが、めちゃくちゃ長くなってしまいましたね。GameplayEffectはこれでも一部分ではあるんですが…書きながら調べてるとどんどん書くべきことが増えてしまってどうにもならない…

 

さて、お次はGameplayEventとGameplayTask編です。正直ネットワークについて分からないと意味がない部分も多い感じですが頑張ります。それではまた。

 

ご指摘、ご質問、ご意見などあればコメントにお願いします。

 

続きの記事はこちら

wvigler.hatenablog.com