2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UE5 GameplayEffectの適用に関するメモ

Last updated at Posted at 2024-06-20

概要

UnrealEngine5 のGameplayEffectについて適用時の細かい挙動のメモ書きです。
GameplayEffectの詳しい内容は参考サイトへ。

更新履歴

日付 内容
2024/06/20 初版
2024/09/04 AbilitySystemGlobals について修正
2024/10/11 スタックについて追記
2024/10/16 削除時コールバックについて追記
2024/10/22 RemoveActiveEffectsWithTagsメソッドについて追記

参考

以下の記事を参考にいたしました、ありがとうございます。
UE公式:GameplayEffects
GASDodument
[UE4]GameplayAbilitySystemで「スタミナ」を作る
GameplayEffect で継続ダメージ処理をやってみる。

関連記事

GameplayEffectComponentについてのメモ

環境

Windows10
Visual Studio 2022
UnrealEngine 5.3.2

関連ソース

"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\GameplayEffect.h"
"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\GameplayEffectTypes.h"

GameplayEffectについて

プラグイン GameplayAbilitySystem の機能で Attributes として設定される値を変化させるシステムです。バフ/デバフなどの適用などに使用することができます。
仕組みが複雑ですので全部を理解するのは大変です。

Period時の適用時の初回実行の設定

即時適用以外の InfiniteHas Duration時にExecute Peridic Effect on Application のチェックボックスでGE適用時の実行の有無を設定できます。
初回時(タイマーが0秒時)に適用するかどうかで周回数が変わってくることに注意が必要です。

ExecutePeridicEffect.png
上記設定例だとチェックを入れると6回実行されます。(チェックを外すと5回)

Periodタイマーのリセット

Periodic Inhibiton Policyの設定が Never Reset だとタイマーは一切リセットされないので、 Reset Period に設定し何らかでGE適用が阻害された場合にリセットされる。
PeriodSetting.png

GEの阻害方法としては Target Tag Requirements Gameplay Effect Component を設定しOngoing Tag RequirementsMust Not Have Tagsに停止条件となるタグを設定するなどがあります。
Example01.png

クールダウンを単独で動かす

クールダウンが設定されているGameplayAbilityは通常、 CommitAbility でクールダウン処理が開始されますが、単独で動かすこともできます。

外部から呼ぶこともできます。特定条件下でクールダウンを再計算したいときなどにつかえるかもしれません。以下、C++コード例。

.cpp
// アクターで以下のようにアビリティを持っている
// UPROPERTY(BlueprintReadOnly, EditAnywhere)
// TArray<TSubclassOf<class UGameplayAbility>> Abilities;

// 0番目のアビリティに対し、クールダウンを強制実行
auto _ASC = GetAbilitySystemComponent();
auto _Spec = _ASC->FindAbilitySpecFromClass(Abilities[0]);
if (_Spec) {
	auto _Ability = Cast<UPlayerGameplayAbility>(_Spec->GetPrimaryInstance());
	_Ability->K2_CommitAbilityCooldown(false, true);
}

C++で動的にゲームプレイエフェクトを適用

Instant な C++コードで動的にGEを作成して適用することができるようです。
Instant 以外のHasDuration 等でも作成はできますがレプリケーション時に問題がでるようです(シングルプレイで試したところ特に問題はでませんでした)。
以下サンプルコード。

.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());

GE適用時にアビリティを起動させる

GEコンポーネントの Abilities Gameplay Effect Componentを設定し、Abilityに発動させたいアビリティクラスを指定する。
起動時にアビリティクラス側の OnAvaterSetクラスが呼ばれるのでそこでアクティベート用にコードを書く必要があります。以下コード例。

.h
UCLASS()
class SAMPLE_API UPGA_Sample : public UPlayerGameplayAbility
{
	GENERATED_BODY()

//..省略..

// アバター設定or切り替え時の処理
virtual void OnAvatarSet(const FGameplayAbilityActorInfo* _ActorInfo, const FGameplayAbilitySpec& _Spec) override;

};
.cpp
void UPGA_Sample::OnAvatarSet(const FGameplayAbilityActorInfo* _ActorInfo, const FGameplayAbilitySpec& _Spec)
{
	Super::OnAvatarSet(_ActorInfo, _Spec);

	// GEからのパッシブ発動
	if (_Spec.GameplayEffectHandle.WasSuccessfullyApplied()) {
		_ActorInfo->AbilitySystemComponent->TryActivateAbility(_Spec.Handle, false);
	}
}

OnAvatarSetはアバター設定or切り替え時に呼ばれます、なのでゲーム開始時にも呼ばれることに注意。

スタック

ゲームプレイエフェクトは適用された数をそのままスタックすることができます。

GE_Stacking.png

スタック後の動作をどのようにするかは以下の項目で設定を行うことができます。

項目 説明
StackingType ソース毎にスタックするか否か
Stack Duration Refresh Policy スタック中のGEの持続時間の処理方法
Stack Period Reset Policy スタック中のGEの期間の処理方法
Stack Expiration Policy Durationの場合、期限切れになるGEスタックの処理方法

適用は通常と同様にできます。削除時は減らしたいスタック数を第2引数に入れてスタック数を減らすことができます。
以下コード例。

// 適用する(スタックを1つ増やす)
TSubclassOf<UGameplayEffect> _GE = /* 適用するStack設定されたGameplayEffect */;
FGameplayEffectSpecHandle _SpecHandle = _ASC->MakeOutgoingSpec(_GE, 1.0f, _ASC->MakeEffectContext());

if( _SpecHandle.IsValid() ){
	Handle = _ASC->ApplyGameplayEffectSpecToSelf(*_SpecHandle.Data.Get());
}

// スタックを1つ減らす(スタックがなくなったら削除)
if(Handle.IsValid()){
	_ASC->RemoveActiveGameplayEffect(Handle, 1);
}

// スタック数を取得する
int32 _StackCount = _ASC->GetCurrentStackCount(Handle);

GameplayEffectContextの拡張

FGameplayEffectContext を継承して独自データを追加する
AbilitySystemGlobals を継承して定義したゲームプレイエフェクトコンテキストを返すメソッドを用意する

独自のGameplayEffectContextを定義する

FGameplayEffectContext を継承して追加データを定義します。
以下サンプルコード。

MyGameplayEffectTypes.h
USTRUCT()
struct SAMPLE_API FMyGameplayEffectContext : public FGameplayEffectContext
{
	GENERATED_USTRUCT_BODY()

public:
	// ターゲットデータを取得する
	virtual FGameplayAbilityTargetDataHandle GetTargetData()
	{
		return(TargetData);
	}
	
	// ターゲットデータを追加する
	virtual void AddTargetData(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
	{
		TargetData.Append(TargetDataHandle);
	}
		
	//------------------------------------------------------------------------
	// FGameplayEffectContext のサブクラスがオーバーライドする必要がある関数
	//------------------------------------------------------------------------
	virtual UScriptStruct* GetScriptStruct() const override
	{
		return( FAppGameplayEffectContext::StaticStruct() );
	}

	virtual FAppGameplayEffectContext* Duplicate() const override
	{
		FAppGameplayEffectContext* NewContext = new FAppGameplayEffectContext();
		*NewContext = *this;
		NewContext->AddActors(Actors);
		if (GetHitResult())
		{
			NewContext->AddHitResult(*GetHitResult(), true);
		}
		
		NewContext->TargetData.Append(TargetData);
		
		return(NewContext);
	}

	virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;

// 追加データ
protected:
	// ターゲットデータ
	FGameplayAbilityTargetDataHandle TargetData;
};


template<>
struct TStructOpsTypeTraits<FAppGameplayEffectContext> : public TStructOpsTypeTraitsBase2<FAppGameplayEffectContext>
{
	enum
	{
		WithNetSerializer = true,
		WithCopy = true
	};
};

個別のゲームプレイエフェクト削除時のコールバック登録

AbilitySystemComponentOnGameplayEffectRemoved_InfoDelegate に登録することでできます。以下コード例。

void MyEffect::ApplayEffect()
{
	auto _ASC = GetAppAbilitySystemComponent();

..省略..
 
	// ゲームプレイエフェクトの適用
	FActiveGameplayEffectHandle Handle = _ASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());

	// エフェクト削除時のデリゲート登録
	_ASC->OnGameplayEffectRemoved_InfoDelegate(Handle)->AddUObject(this, &ThisClass::OnEffectRemoved);
}

void MyEffect::OnEffectRemoved(const FGameplayEffectRemovalInfo& _RemovalInfo)
{
	if (_RemovalInfo.bPrematureRemoval){
		UE_LOG(LogTemp, Log, TEXT("エフェクトは途中で削除されました"));
	}
	else{
		UE_LOG(LogTemp, Log, TEXT("エフェクトが正常に終了しました"));
	}
}

個別のゲームプレイエフェクトではなく全てのゲームプレイエフェクト削除を対象にする場合は、OnAnyGameplayEffectRemovedDelegate() で実現できます。

特定タグがついたゲームプレイエフェクトをまとめて削除する

AbilitySystemComponentクラスの RemoveActiveEffectsWithTags を使うと指定タグがついたゲームプレイエフェクトをまとめて削除できます。
その際にゲームプレイエフェクト側の[Asset Tags Gameplay Effect Component]の[AssetTags]に指定タグをつける必要があります。
以下サンプルコード

.cpp
// 指定タグがついたゲームプレイエフェクトを削除する
FGameplayTagContainer	_EffectTagsToRemove;
_EffectTagsToRemove.AddTag( FGameplayTag::RequestGameplayTag(FName("Effect.RemoveOnDeath")) );
int32 _NumEffectsRemoved = AbilitySystemComponent->RemoveActiveEffectsWithTags(_EffectTagsToRemove);

UE_LOG(LogTemp, Log, TEXT("%d 個削除された。"), _NumEffectsRemoved);

対象のゲームプレイエフェクトのAssetTagsにタグが設定されている必要があります。
AssetTags.png

似たようなメソッドが他にもあります。RemoveActiveEffectsWithSourceTags, RemoveActiveEffectsWithAppliedTags, RemoveActiveEffectsWithGrantedTags これらは細かく適用条件が違うようです

AbilitySystemGlobals

独自のAbilitySystemGlobalsを定義する

UAbilitySystemGlobals を継承することと、DefaultGame.iniの設定が必要です。
以下サンプルコード。

MyAbilitySystemGlobals.h
UCLASS()
class SAMPLE_API UMyAbilitySystemGlobals : public UAbilitySystemGlobals
{
	GENERATED_BODY()
	
public:
	UMyAbilitySystemGlobals(){}

	// 独自定義のアビリティシステムグローバルクラスを取得
	static UMyAbilitySystemGlobals& AppGet()
	{
		return dynamic_cast<UMyAbilitySystemGlobals&>(Get());
	}

	// 独自定義のゲームプレイエフェクトコンテキストを返す
	virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
};
MyAbilitySystemGlobals.cpp
// 独自定義のゲームプレイエフェクトコンテキストを返す
FGameplayEffectContext* UMyAbilitySystemGlobals::AllocGameplayEffectContext() const
{
	// プロジェクト固有の GameplayEffectContext 構造体を割り当てる必要があります。
	// 呼び出し元には割り当て解除の責任があります
	return( new FMyGameplayEffectContext() );
}

DefaultGame.ini
[/Script/GameplayAbilities.AbilitySystemGlobals]
AbilitySystemGlobalsClassName="/Script/MyGame.MyAbilitySystemGlobals"

コスト無視/クールダウン無視

UAbilitySystemGlobalsクラスに用意されている bIgnoreAbilitySystemCosts bIgnoreAbilitySystemCooldowns を使うとコスト無視/クールダウン無視ができます、主にデバッグ用機能と思われます。
このフラグの変更はコンソールコマンドを使って行うことができます。

コンソールコマンド
AbilitySystem.IgnoreCosts 1   // コスト無視
AbilitySystem.IgnoreCosts 0   // コスト適用

AbilitySystem.IgnoreCooldowns 1    // クールダウン無視
AbilitySystem.IgnoreCooldowns 0    // クールダウン適用

C++から行う場合は ConsoleCommand でコマンドを実行することができます。
以下コード例。

.cpp
GetController()->ConsoleCommand("AbilitySystem.IgnoreCosts 1");

コンソールマネージャを使って変更することもできます。
以下コード例。

.cpp
	IConsoleVariable* _CVarIgnoreCosts = IConsoleManager::Get().FindConsoleVariable(TEXT("AbilitySystem.IgnoreCosts"));
	if (_CVarIgnoreCosts)
	{
		_CVarIgnoreCosts->Set(1);  // コストを無視する
	}

まとめ

GameplayEffectアセットのわかりやすい管理方法を模索しています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?