UE5 C++でGameplayEffectを定義して適用してみるメモ

# 概要
UnrealEngine のプラグインGameplayAbilitySystemで使われる GameplayEffect をC++で定義して適用してみたメモ書きです。
ゲームプレイエフェクトアセットが多くなると管理が大変になるので少ない設定のGEをC++のみで定義したほうが扱いやすいのではないかと思って試してみました。
ただモディファイアの値だけなら `SetByCaller`や`CustomCalculationClass` を使うべきです。


## 更新履歴
| 日付 | 内容 |
|:----|:-----|
|2025/04/24| 初版 |


## 参考
以下を参考にさせて頂きました、ありがとうございます。
[UE公式：GameplayEffects](https://dev.epicgames.com/documentation/en-us/unreal-engine/BlueprintAPI/GameplayEffects?application_version=5.5)
[tranek /
GASDocumentation ](https://github.com/tranek/GASDocumentation)


### 関連過去記事
[UE5 GameplayEffectの適用に関するメモ](https://qiita.com/unknown_ds/items/93f1f51d0009856c85e4)
[UE5 GameplayEffectComponentについてのメモ](https://qiita.com/unknown_ds/items/9504833e3ee8d418f8d5)


## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.5.1

## 関連ソース
"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\GameplayEffect.h"
"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\GameplayEffectTypes.h"


# GameplayEffectについて
プラグイン `GameplayAbilitySystem` の機能で `Attributes` として設定される値を一時的or恒常的に変化させたり、`GameplayTag`を付与/削除などができるシステムです。主にゲーム中で使用される バフ/デバフや状態の変化の情報付与といったことに使われます。

### ゲームプレイエフェクトの作成（BPの場合）
通常 GameplayEffect を継承したBPアセットを作成して適用します。

▼ゲームプレイエフェクトアセットの作成
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/67700417-0946-4ea9-9d8a-bb997941325a.png">

▼ゲームプレイエフェクトアセット
![GameplayEffectBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/41466d03-ecf2-4f97-932b-4df3e559e93a.png)


▼BPでのゲームプレイエフェクト適用例
![ApplyGE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/4e86bccb-c249-4180-8d4f-a8369b1d4cbd.png)

ですが、C++のみで定義と適用もできますし、適用だけC++を使ったりも可能です。


# 実装例
## C++ のみでゲームプレイエフェクトクラスを定義する
HasDurationタイプで、タグを付与するコンポーネントとアトリビュートを変化させるモディファイアを定義しています。
以下、コード例。

```C++:MyGameplayEffect.h
#pragma once

#include "CoreMinimal.h"
#include "GameplayEffect.h"
#include "MyGameplayEffect.generated.h"

UCLASS()
class SAMPLE00_API UGameplayEffect_Test : public UGameplayEffect
{
	GENERATED_BODY()

public:
	UGameplayEffect_Test(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
};

```

UE5.3から基本設定以外の機能はコンポーネント化されていますので、コンポーネントを作成して追加する必要があります。

```C++:MyGameplayEffect.cpp
#include "MyGameplayEffect.h"
#include "GameplayEffectComponents/TargetTagsGameplayEffectComponent.h"
#include "GameplayTagContainer.h"

UGameplayEffect_Test::UGameplayEffect_Test(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// GameplayEffectの基本設定
	DurationPolicy = EGameplayEffectDurationType::HasDuration;
	DurationMagnitude = FScalableFloat(1.0f);
	
	// タグを付与するゲームプレイコンポーネントを作成
	UTargetTagsGameplayEffectComponent* _Component = CreateDefaultSubobject<UTargetTagsGameplayEffectComponent>(TEXT("GEComponent0"));

	// コンポーネントに追加するタグを設定
	FInheritedTagContainer _Tags;
	_Tags.AddTag(FGameplayTag::RequestGameplayTag("State.Tag"));
	_Component->SetAndApplyTargetTagChanges(_Tags);
	// コンポーネント追加
	GEComponents.Add(_Component);

	// モディファイアの設定
	FGameplayModifierInfo Modifier;
	Modifier.Attribute = UMyAttributeSet::GetHealthAttribute();		// アトリビュートを設定
	Modifier.ModifierOp = EGameplayModOp::Additive;
	Modifier.ModifierMagnitude = FScalableFloat(50.0f);		// 50の値を加算
	// モディファイア追加
	Modifiers.Add(Modifier);

}

```


### SetByCallerタイプのモディファイア設定
直接指定ではないモディファイア設定の場合、設定が少し複雑になります。以下、`SetByCaller`でゲームプレイタグ `Data.Value` を設定した場合のサンプルコード。

```C++:MyGameplayEffect_SetByCaller.cpp
UGameplayEffect_Test::UGameplayEffect_Test(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// GameplayEffectの設定
	DurationPolicy = EGameplayEffectDurationType::Infinite;
	
	// モディファイア設定
	{
		FGameplayModifierInfo _Modifier;
		_Modifier.Attribute = UMyAttributeSet::GetMoveSpeedAttribute();
		_Modifier.ModifierOp = EGameplayModOp::AddBase;
        // SetByCallerでの設定
		FSetByCallerFloat _SetByCallerData;
		_SetByCallerData.DataTag = FGameplayTag::RequestGameplayTag("Data.Value");
		_Modifier.ModifierMagnitude = _SetByCallerData;

		Modifiers.Add(_Modifier);
	}
}
```


### CustomCalculationタイプでのモディファイア設定
`CustomCalculation`での設定も可能で、カスタム計算を行うクラス`UGameplayModMagnitudeCalculation`の指定が必要です。以下サンプルコード。

```C++:MyGameplayEffect_SetByCaller.cpp
UGameplayEffect_Test2::UGameplayEffect_Test2(const FObjectInitializer& _ObjectInitializer)
	: Super(_ObjectInitializer)
{
	// GameplayEffectの設定
	DurationPolicy = EGameplayEffectDurationType::Infinite;

	// モディファイア設定
	{
		FGameplayModifierInfo _Modifier;
		_Modifier.Attribute = UMyAttributeSet::GetMoveSpeedAttribute();
		_Modifier.ModifierOp = EGameplayModOp::AddBase;
        // CustomCalculationでの設定
		FCustomCalculationBasedFloat _CustomCalc;
		_CustomCalc.CalculationClassMagnitude = UMyGameplayMMC_MoveSpeed::StaticClass();
		_Modifier.ModifierMagnitude = _CustomCalc;

		Modifiers.Add(_Modifier);
	}
}
```

`UGameplayModMagnitudeCalculation` はゲーム仕様に応じたパラメータ計算をする場合にクラスを継承して実装メソッドに計算コードを書きます。以下サンプルコード。

```C++:MyGameplayMMC.h
#pragma once

#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "MyGameplayMMC.generated.h"

UCLASS()
class SAMPLE_API UMyGameplayMMC_MoveSpeed : public UGameplayModMagnitudeCalculation
{
	GENERATED_BODY()
	
	float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& _Spec) const override;
};
```

```C++:MyGameplayMMC.cpp
#include "MyGameplayMMC.h"

float UMyGameplayMMC_MoveSpeed::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& _Spec) const
{
	const auto _Owner = Cast<AMyCharacter>(_Spec.GetContext().GetEffectCauser());

	// ゲーム仕様に応じたパラメータ計算のコード
	float _Speed = _Owner->GetSpeed();

	return( _Speed );
}

```




## C++で作成したゲームプレイエフェクトを適用する
適用する処理はあまり変わりません。ここでは`MakeOutgoingSpec`へ渡すクラスを直接指定しています。
以下サンプルコード。

```C++:.cpp
auto _ASC = GetAbilitySystemComponent();

// ゲームプレイエフェクトを作成する
FGameplayEffectSpecHandle _SpecHandle = _ASC->MakeOutgoingSpec(UGameplayEffect_Test::StaticClass(), 1.0f, _ASC->MakeEffectContext());
if(_SpecHandle.IsValid()){
   	// 自分に適用する
	_ASC->ApplyGameplayEffectSpecToSelf(*_SpecHandle.Data.Get());
}
```


## C++で作成したゲームプレイエフェクトをBPで設定して適用する
C++でアクタークラス等を定義しそれを継承したBP側で C++のみで作成したゲームプレイエフェクトを設定してから適用もできます。
以下コード例

```C++:MyActor.h
UCLASS()
class AMyActor : public AActor
{
	GENERATED_BODY()
//..省略..

	// ゲームプレイエフェクト
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<UGameplayEffect> GE_Test;
};
```

```C++:MyActor.cpp
void AMyActor::BeginPlay()
{
	if( ::IsValid(GE_Test) ){
		FGameplayEffectSpecHandle _SpecHandle = _ASC->MakeOutgoingSpec(GE_Test, 1.0f, _ASC->MakeEffectContext());
		if(_SpecHandle.IsValid()){
		   	// 自分に適用する
			_ASC->ApplyGameplayEffectSpecToSelf(*_SpecHandle.Data.Get());
		}
	}
}
```


# ゲームプレイエフェクトの動的実装
## ローカルでゲームプレイエフェクトを作成して適用
`DurationPolicy` が `Instant` なゲームプレイエフェクトのケースではローカルコードで作成して適用できます。`Instant` 以外の`HasDuration` 等でも作成はできますがレプリケーション時に問題がでるようです（シングルプレイで試したところ特に問題はでませんでした）。
以下サンプルコード。

```cpp:.cpp
auto _ASC = GetAbilitySystemComponent();

// アトリビュート MoveSpeed を変更する GEを作成して適用する
UGameplayEffect* _GETest = NewObject<UGameplayEffect>(GetTransientPackage(), FName(TEXT("DynamicGETest")));
_GETest->DurationPolicy = EGameplayEffectDurationType::Instant;
int32 _Idx = _GETest->Modifiers.Num();
_GETest->Modifiers.SetNum(_Idx + 1);

FGameplayModifierInfo& _Info = _GETest->Modifiers[_Idx];
_Info.ModifierMagnitude = FScalableFloat(1000.0f);
_Info.ModifierOp = EGameplayModOp::Additive;
_Info.Attribute = UMyAttributeSet::GetMoveSpeedAttribute();	// 変更するアトリビュート
// 自身に適用する
_ASC->ApplyGameplayEffectToSelf(_GETest, 1.0f, _ASC->MakeEffectContext());
```



# まとめ
C++のみで作成したゲームプレイエフェクトはパラメータの変更が気軽にできないことや参照している先を調べるのが難しいという問題点があります。参照についてはアセットレジストリ等を駆使すれば調べられそうですが、BPほど気軽ではないですね。