GameplayAbilitiesの使い方(アトリビュートセット・エフェクト編)

現在設定しているキャラクターのアビリティではアニメーションモンタージュの再生とウェイトを行ってますが、それ以外にもHP,MPなどのパラメータ変更を担うエフェクト(GameplayEffect)、エフェクトに伴って画面上にパーティクルなどの演出を表示するキュー(GameplayCue)、特定のイベント(GameplayEvent)が発生したときにコールバックを受けて処理行うタスク(GameplayTask)を実行することができます。

今回は、まずキャラクターのHP、MPや移動速度といったパラメータのセットであるアトリビュートセットを定義し、アビリティの使用によりそのアトリビュートセットに変更を加えるエフェクトを実行してみたいと思います。

アトリビュートセットの定義

 まずアトリビュートセットに最大ヒットポイント、ヒットポイント、移動スピード、ダメージの4つのパラメータを定義します。C++での作成となるのでコンテンツブラウザのコンテンツ→C++クラス→GPA内で右クリックし新規C++クラスを指定します。

f:id:nca03132:20180801000622p:plain

親クラスは、全てのクラスを表示にチェックを入れ検索でAttributeSetを指定します。

f:id:nca03132:20180801000831p:plain

名前はGPAAttributeSetという名称にして「クラスを作成」をクリックします

 次にソースの修正ですが、今回はフルリストを掲載します。

[GPAAttributeSet.h]

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "GPAAttributeSet.generated.h"

// AttributeSet.hで紹介されているアトリビュートへのSetter,Getter定義マクロ
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

/**
 * 
 */
UCLASS()
class GPA_API UGPAAttributeSet : public UAttributeSet
{
	GENERATED_BODY()
	
public:
	// 初期値設定用コンストラクタ定義
	UGPAAttributeSet();

	// Blueprintアクセス可能としたMaxHealth定義、ATTRIBUTE_ACCESSORSによるアクセサ追記
	UPROPERTY(Category = "GPAAttributes", EditAnywhere, BlueprintReadWrite)
	FGameplayAttributeData MaxHealth;
	ATTRIBUTE_ACCESSORS(UGPAAttributeSet, MaxHealth);
	FGameplayAttribute MaxHealthAttribute();	// アトリビュート型取得関数

	// Blueprintアクセス可能としたHealth定義、ATTRIBUTE_ACCESSORSによるアクセサ追記
	UPROPERTY(Category = "GPAAttributes", EditAnywhere, BlueprintReadWrite)
	FGameplayAttributeData Health;
	ATTRIBUTE_ACCESSORS(UGPAAttributeSet, Health);
	FGameplayAttribute HealthAttribute();		// アトリビュート型取得関数

	// Blueprintアクセス可能としたMaxSpeed定義、ATTRIBUTE_ACCESSORSによるアクセサ追記
	UPROPERTY(Category = "GPAAttributes", EditAnywhere, BlueprintReadWrite)
	FGameplayAttributeData MaxSpeed;
	ATTRIBUTE_ACCESSORS(UGPAAttributeSet, MaxSpeed);
	FGameplayAttribute MaxSpeedAttribute();		// アトリビュート型取得関数

	// Blueprintアクセス可能としたダメージ定義、ATTRIBUTE_ACCESSORSによるアクセサ追記
	UPROPERTY(Category = "GPAAttributes", EditAnywhere, BlueprintReadWrite)
	FGameplayAttributeData Damage;
	ATTRIBUTE_ACCESSORS(UGPAAttributeSet, Damage);
	FGameplayAttribute DamageAttribute();		// アトリビュート型取得関数

	/** エフェクトによりアトリビュートが変化した場合のPost処理。主にUE4で直接管理しているメンバへの書き戻しを行う */
	virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
};
[GPAAttributeSet.cpp]
// Fill out your copyright notice in the Description page of Project Settings.

#include "GPAAttributeSet.h"
#include "GameplayEffectExtension.h"	// PostGameplayEffectExecuteのContextデータなどを扱うのに必要
#include "GPACharacter.h"				// GPACharacterの関数呼び出しに必要

/** コンストラクタにより初期値設定 */
UGPAAttributeSet::UGPAAttributeSet()
	: MaxHealth(100.f)
	, Health(100.f)
	, MaxSpeed(600.f)
	, Damage(0.f)
{
}

// アトリビュート型取得関数
FGameplayAttribute UGPAAttributeSet::MaxHealthAttribute()
{
	static UProperty* Property = FindFieldChecked<UProperty>(UGPAAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UGPAAttributeSet, MaxHealth));
	return FGameplayAttribute(Property);
}

// アトリビュート型取得関数
FGameplayAttribute UGPAAttributeSet::HealthAttribute()
{
	static UProperty* Property = FindFieldChecked<UProperty>(UGPAAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UGPAAttributeSet, Health));
	return FGameplayAttribute(Property);
}

// アトリビュート型取得関数
FGameplayAttribute UGPAAttributeSet::MaxSpeedAttribute()
{
	static UProperty* Property = FindFieldChecked<UProperty>(UGPAAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UGPAAttributeSet, MaxSpeed));
	return FGameplayAttribute(Property);
}

// アトリビュート型取得関数
FGameplayAttribute UGPAAttributeSet::DamageAttribute()
{
	static UProperty* Property = FindFieldChecked<UProperty>(UGPAAttributeSet::StaticClass(), GET_MEMBER_NAME_CHECKED(UGPAAttributeSet, Damage));
	return FGameplayAttribute(Property);
}

void UGPAAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	Super::PostGameplayEffectExecute(Data);

	// 受け取ったデータから各種情報を取得
	FGameplayEffectContextHandle Context = Data.EffectSpec.GetContext();
	UAbilitySystemComponent* Source = Context.GetOriginalInstigatorAbilitySystemComponent();
	const FGameplayTagContainer& SourceTags = *Data.EffectSpec.CapturedSourceTags.GetAggregatedTags();

	// GameplayEffectにより指定されたアトリビュート変化値を計算
	float DeltaValue = 0;
	if (Data.EvaluatedData.ModifierOp == EGameplayModOp::Type::Additive)
	{
		// If this was additive, store the raw delta value to be passed along later
		DeltaValue = Data.EvaluatedData.Magnitude;
	}

	// 受け取ったデータからターゲットアクター、コントローラ、キャラクタの取得
	AActor* TargetActor = nullptr;
	AController* TargetController = nullptr;
	AGPACharacter* 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<AGPACharacter>(TargetActor);
	}

	// 受け取ったデータがMaxSpeedだった場合にCharacter側の最大移動スピードに反映させる
	if (Data.EvaluatedData.Attribute == GetMaxSpeedAttribute())
	{
		if (TargetCharacter)
		{
			TargetCharacter->HandleMaxSpeedChanged(DeltaValue, SourceTags);
		}
	}
}
ソースが長いので必要なことはコメントに書きました。

大枠でいうと、ヘッダではアトリビュートとしてMaxHealth,Health,MaxSpeedを定義して、アクセッサを用意しています。cppファイルではコンストラクタで初期値を与え、変数ごとにアトリビュート型を取得する関数を用意します。

またPostGameEffectExecute関数は、アトリビュート値を変更するエフェクト(GameplayEffect)が実行されたときに、その変更されたアトリビュート値をGPACharacterクラスに書き戻す用に呼び出される関数です。今回の場合は、最大スピードが変更された際、GPACharacter::HandleMaxSpeedChanged関数を呼び出していますが、この関数の中ではGPACharacterが保持しているCharacterMovementComponentのMaxSpeedを書き換える予定です。ですが、現在はこの関数は定義されていないので、このままだとコンパイルが通りませんので、次にGPACharacter側のソースに変更を加えます。

 

アトリビュート変更をCharacterへ反映

〇GPACharacter.hの変更

  • #include "GPAAttributeSet.h"の追加
    GPAAttributeSetを扱うためにヘッダのインクルードが必要です。GPACharacter.generated.hのインクルードの前あたりに入れておきましょう。
  • AttributeSetのメンバ、MaxSpeed書き換え時のハンドリングメソッド宣言
    クラス定義の一番下、PossessedByの定義の下あたりに
    /** AttributeSet */
    UPROPERTY()
    UGPAAttributeSet* AttributeSet;	// キャラクターが保持するアトリビュートセット
    
    void HandleMaxSpeedChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags);	// アトリビュート変更時のキャラクタへの反映関数
    といった感じで追記してください。

〇GPACharacter.cppの変更 

  • アトリビュートセットの実体を生成
    AGPACharacterのコンストラクタでアトリビュートセットの実体を生成してヘッダで定義したAttributeSetのポインタに設定します。
    // create the attribute set
    AttributeSet = CreateDefaultSubobject<UGPAAttributeSet>(TEXT("AttributeSet"));
  • 以下のソースをファイルの最後に加えて、アトリビュートのMaxSpeedが書き換わった時にCharacterMovementのMaxWalkSpeedを書き換える関数の実体を定義します。この関数は先ほど作成したGPAAttributeSet.cppのPostGameplayEffectExecute関数から呼び出されます。

    void AGPACharacter::HandleMaxSpeedChanged(float DeltaValue, const struct FGameplayTagContainer& EventTags)
    {
    	GetCharacterMovement()->MaxWalkSpeed = AttributeSet->GetMaxSpeed();
    }

    書き換えたら、ここで一旦コンパイルします。

ここまで来たら、上記を反映させるために一旦エディタを再起動しておきましょう。

 

エフェクトの作成

 次に、アトリビュートを書き換えるエフェクトを作成します。

コンテンツブラウザのコンテンツ→Abilitiesの中で右クリックしてブループリントクラスを選択。

f:id:nca03132:20180802225534p:plain

親クラスはGameplayEffectを選びます。

f:id:nca03132:20180802225852p:plain

作ったクラスはGE_MaxSpeedHighとします。

f:id:nca03132:20180802231710p:plain

GE_MaxSpeedHighをダブルクリックして詳細を確認してください。

ここで変更するアトリビュートに関しての設定を粉います。

Modifiersで+して1項目追加し、0の項目を開いて以下の設定を行います。

f:id:nca03132:20180802232222p:plain

  • AttributeはGPAAttributeSet.MaxSpeed
    設定するアトリビュートはMaxSpeed
  • Modifier OpはOverride
    変更方式は指定値で上書き(ちなみにAddを選ぶと以前の値に加算になります)
  • Modifier Magnitude→MagnitudeCalculationTypeはScalableFloat
    変更時の計算はFloat値を使う
  • Modifier Magnitude→ScalableFloatMagnitudeは1200
    設定値は1200

これで、このエフェクトを実行すると最大移動スピードが1200になります。

この状態でコンパイル・保存して閉じてください。

次に、GE_MaxSpeedHighをコピーしてGE_MaxSpeedNormalを作成し

f:id:nca03132:20180802233157p:plain

ScalableFloatMagnitudeを600にしてコンパイル・保存しましょう。

f:id:nca03132:20180802233329p:plain

さて、これで最大スピードを変更するエフェクトができたので、これを実行するアビリティを作成します。

アビリティの作成

移動速度をアップするアビリティを作成します。

コンテンツブラウザのコンテンツ→Abilitiesの中で右クリックしてブループリントクラスを選択 f:id:nca03132:20180803005703p:plain

 

親クラスはGameplayAbilityとします。

f:id:nca03132:20180803005602p:plain

 

作ったアビリティはGA_Dashとします。

f:id:nca03132:20180803005839p:plain

このGA_DashをダブルクリックしてBPを開き、クラスのデフォルトでタグの設定を

f:id:nca03132:20180803000556p:plain

このようにAbility TagsはAbility.Action.Dash、BlockAbilitiesWithTagは、Ability.Actionと指定し、ほかのアビリティと排他して動作するように指定します。

 次にブループリントを編集します。

 f:id:nca03132:20180803004324p:plain

上記のようにActivateAbilityからいつものCommitAbilityして、自分へエフェクトを実行するApplyGameplayEffectToOwnerを呼び出します。このときGameplayEffectClassには先ほど作った"GE_MaxSpeedHigh"を指定します。その後5秒ウェイトしアビリティ終了です。

OnEndAbilityでは移動速度を通常に戻すためApplyGameplayEffectToOwnerで"GE_MaxSpeedNormal"を指定しています。

 アビリティの登録と呼び出し

 次にキャラクター側にアビリティを登録します。

BP_BPchanを開いて、クラスのデフォルトを指定し、詳細のAbilitiesにGA_Dashを追加しましょう。

f:id:nca03132:20180803003911p:plain

 最後にキーを押されたらアビリティを呼び出すノードを追加します。
BP_BPchanのイベントグラフで、他のアビリティ呼び出しと同様に3キーが押されたらGA_Dashが呼ばれるようにしておきます。

f:id:nca03132:20180803004021p:plain

これで設定は完了です。プレイして3キーを押すとスピードが上り、5秒後に元に戻るか確認しましょう。

 キュー(GameplayCue)

最後にパーティクルや演出を表示するキューについて軽く触れておきましょう。

エフェクトはHP回復やダメージを与えるなど、アトリビュートの値を変更するために使われますが、それに伴って画面上に演出を出したいケースは多いはずです。

そんなときに使われるのがこのキューです。

今回、スピードアップのエフェクトを作りましたが、それに伴って表示されるキューを作ってみましょう。

いつものようにコンテンツブラウザのAbilitiesの中で右クリックし”基本アセットを作成”の中にあるブループリントクラスを選びます。親クラスはGameplayCueNofify_Actorとして選択を押します。

f:id:nca03132:20180803084256p:plain

作ったアセットの名前はGC_DashEffectとしておきましょう。

f:id:nca03132:20180803084349p:plain

 GC_DashEffectをダブルクリックし「フルブループリンタを開く」を選んで、ブループリントエディタを開いてください。

以下のようにBeginPlayからアクターの位置にEmitterを発生させてアクターは削除します(EmitterはAutoDestoryにチェックが入ってるので再生後自動削除)

今回再生させるパーティクルはスターターコンテンツにあるP_Explosionとします。

f:id:nca03132:20180804083553p:plain

次に、クラスのデフォルトを選択し、詳細の中のGameplayCueで、GameplayCueTagを"Effect.Dash"としておきます。

f:id:nca03132:20180804083916p:plain

次に、GE_MaxSpeedHigh側でキューを発生させる設定を行います。

f:id:nca03132:20180804084108p:plain

GE_MaxSpeedHighをダブルクリックしてクラスのデフォルトを表示し、

Display項目のGameplay Cuesを+して項目を追加し、GameplayCueTagsに先ほどのEffect.Dashを設定します。

f:id:nca03132:20180804085138p:plain

これによりこのエフェクトが発生したときにEffect.Dashのタグが付いているキューが自動的に生成されるようになります。

これで準備は完了です。すべて保存してプレイで開始してダッシュしてみてください。

f:id:nca03132:20180804084542p:plain

上手くいっていればこんな感じでブループリントちゃんに爆発エフェクトが発生すると思います。

なお、今回はエフェクト発生時にエフェクトを1回発生させる作りになっていますが、GameplayEffectをInstantではなくHasDurationで効果時間を持たせればエフェクト実行中の間パーティクルを表示し続けるなどの演出が可能です。パーティクルは特定のソケットに張り付けることもできるので、魔法キャスト中、エネルギーが手に集中していくようなエフェクトを出すといったこともできます。