概要
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時の適用時の初回実行の設定
即時適用以外の Infinite
、Has Duration
時にExecute Peridic Effect on Application
のチェックボックスでGE適用時の実行の有無を設定できます。
初回時(タイマーが0秒時)に適用するかどうかで周回数が変わってくることに注意が必要です。
上記設定例だとチェックを入れると6回実行されます。(チェックを外すと5回)
Periodタイマーのリセット
Periodic Inhibiton Policy
の設定が Never Reset
だとタイマーは一切リセットされないので、 Reset Period
に設定し何らかでGE適用が阻害された場合にリセットされる。
GEの阻害方法としては Target Tag Requirements Gameplay Effect Component
を設定しOngoing Tag Requirements
の Must Not Have Tags
に停止条件となるタグを設定するなどがあります。
クールダウンを単独で動かす
クールダウンが設定されているGameplayAbility
は通常、 CommitAbility
でクールダウン処理が開始されますが、単独で動かすこともできます。
外部から呼ぶこともできます。特定条件下でクールダウンを再計算したいときなどにつかえるかもしれません。以下、C++コード例。
// アクターで以下のようにアビリティを持っている
// 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
等でも作成はできますがレプリケーション時に問題がでるようです(シングルプレイで試したところ特に問題はでませんでした)。
以下サンプルコード。
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
クラスが呼ばれるのでそこでアクティベート用にコードを書く必要があります。以下コード例。
UCLASS()
class SAMPLE_API UPGA_Sample : public UPlayerGameplayAbility
{
GENERATED_BODY()
//..省略..
// アバター設定or切り替え時の処理
virtual void OnAvatarSet(const FGameplayAbilityActorInfo* _ActorInfo, const FGameplayAbilitySpec& _Spec) override;
};
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切り替え時に呼ばれます、なのでゲーム開始時にも呼ばれることに注意。
スタック
ゲームプレイエフェクトは適用された数をそのままスタックすることができます。
スタック後の動作をどのようにするかは以下の項目で設定を行うことができます。
項目 | 説明 |
---|---|
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
を継承して追加データを定義します。
以下サンプルコード。
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
};
};
個別のゲームプレイエフェクト削除時のコールバック登録
AbilitySystemComponent
の OnGameplayEffectRemoved_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]に指定タグをつける必要があります。
以下サンプルコード
// 指定タグがついたゲームプレイエフェクトを削除する
FGameplayTagContainer _EffectTagsToRemove;
_EffectTagsToRemove.AddTag( FGameplayTag::RequestGameplayTag(FName("Effect.RemoveOnDeath")) );
int32 _NumEffectsRemoved = AbilitySystemComponent->RemoveActiveEffectsWithTags(_EffectTagsToRemove);
UE_LOG(LogTemp, Log, TEXT("%d 個削除された。"), _NumEffectsRemoved);
対象のゲームプレイエフェクトのAssetTagsにタグが設定されている必要があります。
似たようなメソッドが他にもあります。RemoveActiveEffectsWithSourceTags
, RemoveActiveEffectsWithAppliedTags
, RemoveActiveEffectsWithGrantedTags
これらは細かく適用条件が違うようです
AbilitySystemGlobals
独自のAbilitySystemGlobalsを定義する
UAbilitySystemGlobals
を継承することと、DefaultGame.ini
の設定が必要です。
以下サンプルコード。
UCLASS()
class SAMPLE_API UMyAbilitySystemGlobals : public UAbilitySystemGlobals
{
GENERATED_BODY()
public:
UMyAbilitySystemGlobals(){}
// 独自定義のアビリティシステムグローバルクラスを取得
static UMyAbilitySystemGlobals& AppGet()
{
return dynamic_cast<UMyAbilitySystemGlobals&>(Get());
}
// 独自定義のゲームプレイエフェクトコンテキストを返す
virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
};
// 独自定義のゲームプレイエフェクトコンテキストを返す
FGameplayEffectContext* UMyAbilitySystemGlobals::AllocGameplayEffectContext() const
{
// プロジェクト固有の GameplayEffectContext 構造体を割り当てる必要があります。
// 呼び出し元には割り当て解除の責任があります
return( new FMyGameplayEffectContext() );
}
[/Script/GameplayAbilities.AbilitySystemGlobals]
AbilitySystemGlobalsClassName="/Script/MyGame.MyAbilitySystemGlobals"
コスト無視/クールダウン無視
UAbilitySystemGlobals
クラスに用意されている bIgnoreAbilitySystemCosts
bIgnoreAbilitySystemCooldowns
を使うとコスト無視/クールダウン無視ができます、主にデバッグ用機能と思われます。
このフラグの変更はコンソールコマンドを使って行うことができます。
AbilitySystem.IgnoreCosts 1 // コスト無視
AbilitySystem.IgnoreCosts 0 // コスト適用
AbilitySystem.IgnoreCooldowns 1 // クールダウン無視
AbilitySystem.IgnoreCooldowns 0 // クールダウン適用
C++から行う場合は ConsoleCommand
でコマンドを実行することができます。
以下コード例。
GetController()->ConsoleCommand("AbilitySystem.IgnoreCosts 1");
コンソールマネージャを使って変更することもできます。
以下コード例。
IConsoleVariable* _CVarIgnoreCosts = IConsoleManager::Get().FindConsoleVariable(TEXT("AbilitySystem.IgnoreCosts"));
if (_CVarIgnoreCosts)
{
_CVarIgnoreCosts->Set(1); // コストを無視する
}
まとめ
GameplayEffectアセットのわかりやすい管理方法を模索しています。