9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UE4 GameplayAbility Pluginについてのメモ

Last updated at Posted at 2019-10-02

概要

UnrealEngine のプラグイン GameplayAbility についてのメモです。
多機能なため、まとまっていません。

修正履歴

日付 内容
2019/11/06 GameplayAbilitySpecについて追記、GameplayEffectのカーブテーブルについて追記
2019/11/23 GameplayEffect の記述を追加
2019/12/13 スケルタルメッシュ切り替えについて追記
2021/09/06 アビリティタグ追記
2024/10/04 GameplayAbilityメソッドについて追記
2024/11/07 Retrigger Instanced Ability について追記

環境

Windows10
Visual Studio 2017
UnrealEngine 4.22, 4.26, 5.3

参考

以下を参考にさせて頂きました、ありがとうございます。

GameplayAbilitiesの使い方
GameplayAbility - GameplayAbility と コンポーネント の準備編
UnrealEngineマニュアル-ゲームプレイアビリティシステム
UnrealEngineマニュアル-ActionRPG のゲームプレイアビリティ
GASDocumentation

GameplayAbilityBP

AbilityTag

アビリティ同士の排他処理や条件管理ができます。
このAbilityTagは AbilitySystemComponentクラスにて管理されています。

AbilityTag.png

  • Ability Tags - アビリティ自身が持っているタグ
  • Cancel Abilities with Tag - このタグを持っている実行中のアビリティを中止させる
  • Block Abilities with Tag - このタグを持っているアビリティを実行させない
  • Activation Owned Tags - アビリティが有効な時のみ持つタグ
  • Activation Required Tags - 全てのタグを持っていないとアビリティを有効にしない
  • Activation Blocked Tags - いずれかのタグを持っていた場合、アビリティを有効にしない。

AbilityTagでの制御について追記1

以下のメソッドで、BPにて設定されたAbilityTagのキャンセル/ブロック等の機能を有効/無効に設定ができるようです。
インスタンス化されたアビリティのみ有効です。

// ブロックタグセットを有効/無効に設定
virtual void SetShouldBlockOtherAbilities(bool bShouldBlockAbilities);

// キャンセルタグセットを有効/無効に設定
virtual void SetCanBeCanceled(bool bCanBeCanceled);

AbilityTagでの制御について追記2

キャンセル/ブロックタグの途中での追加で制御するには以下のメソッドで可能です。

AbilitySystemComponent.h
/ **
*アビリティアクティベーションまたはネイティブコードから呼び出され、正しいアビリティブロックタグを適用し、既存のアビリティをキャンセルします。 サブクラスは動作をオーバーライドできます
* @paramAbilityTagsブロックフラグとキャンセルフラグを持つアビリティのタグ
* @param RequestingAbility変更を要求するゲームプレイアビリティは、ネイティブイベントの場合はNULLにすることができます
* @param bEnableBlockTags trueの場合はブロックタグが有効になり、falseの場合はブロックタグが無効になります
* @paramBlockTagsブロックするタグ
* @param bExecuteCancelTags trueの場合、タグに一致するアビリティをキャンセルします
* @paramCancelTagsキャンセルするタグ
* /
virtual void ApplyAbilityBlockAndCancelTags(const FGameplayTagContainer& AbilityTags, UGameplayAbility* RequestingAbility, bool bEnableBlockTags, const FGameplayTagContainer& BlockTags, bool bExecuteCancelTags, const FGameplayTagContainer& CancelTags);

UAbilitySystemComponent::ApplyAbilityBlockAndCancelTags(AbilityTags, this, true, BlockAbilitiesWithTag, true, CancelAbilitiesWithTag);

AbilityTagでの制御について追記3

自身のアビリティ起動中に再度アクティブにして再度動作させる(起動中のものは終了させる)には [Instancing Policy] が [Instanced Per Execution](起動させる毎)時はキャンセルタグに自身のタグを設定することで可能ですが、 [Instancing Policy] が [Instanced Per Actor](アクター生成時)時の場合は [Retrigger Instanced Ability]にチェックを入れる必要があります。

Advance.png

前述のキャンセルタグを使った方法は開始/終了タイミングが違う(後のアビリティの開始後に先のアビリティの終了がくる)ので注意が必要です。

コストやクールダウンの設定

Costs や Cooldowns に GameplayEffectのBPを設定できます。

Costs は DurationPolicy を Instant で必要なコストをアトリビュート経由で計算させます。
GE_Cost.png

Cooldowns は DurationPolicy を HasDuration で時間設定と GrantedTags にブロックするタグを追加する。
GE_Cooldown.png

GamePlay Effect の Executions

[GamePlay Effect] の [Executions] -> [Calculation Class] を設定すると計算用のクラス(UGameplayEffectExecutionCalculationを継承したクラス)を使うことができます。

  • ARPGサンプルのGE_DamageBase
    GE_DamageBase.png

UGameplayAbilityクラス

アビリティ情報のもとになるクラス。
継承可能なメソッドについての記載します。

CanActivateAbility

アビリティが実行可能かを調べるメソッドです。
アビリティ実行前に呼ばれる事を想定されているため、アビリティのプロパティ[Advanced-Instancing Policy]が[Instanced Per Execution]の場合はインスタンス化されていないため、コンストラクタ等で設定したメンバ変数の値になっていないので要注意。

InstanceingPolicy.png

これを回避するには[Instanced Per Actor]にする等の対応が必要。

CommitAbility

アビリティのコストやクールダウン情報をコミットする処理。

CommitAbility.png

CostやCooldownを独自に実装する場合は、CommitAbility も継承して書き換える必要がある場合があるので注意。

PreActivate

アビリティの実行前処理。TryActivateAbilityが実行成功すると直ちに呼ばれる?

ActivateAbility

アビリティ実行処理。

ActivateAbility.png

アビリティ開始時に即実行される処理のため、通信同期に必要なデータを送る場合はこれを継承して送る必要あり。

EndAbility

アビリティの終了、BP内などで呼ぶ。

EndAbility.png

CancelAbility

外部からのアビリティの中断時に呼ばれる。

CancelAbility.png

CheckCooldown

アビリティ使用のためのクールダウン時間が明けているかのチェック関数

通常はAbilityでGameplayEffectを設定して使う。
CooldownCost.png

必要なら継承して特殊な計算を行うことも可能。
その場合は、CheckCooldownApplyCooldownを両方継承して計算を行う。

ApplyCooldown

CommitAbility で呼ばれる。
クールダウン情報(gameplayEffect)の適用

必要なら継承して特殊な計算を行うことも可能。
その場合は、CheckCooldownApplyCooldownを両方継承して計算を行う。

CheckCost

アビリティ使用のためのコストが足りているかのチェック関数

通常はAbilityでGameplayEffectを設定して使う。
GECost.png

必要なら継承して特殊な計算を行うことも可能。
その場合は、CheckCostApplayCostを両方継承する、以下コード例。

コストを独自に計算する
// コストチェック(コストが払えるならtrue)
bool UMyGameplayAbility::CheckCost(const FGameplayAbilitySpecHandle _Handle, const FGameplayAbilityActorInfo* _ActorInfo, OUT FGameplayTagContainer* _OptionalRelevantTags) const
{
	const auto _ASC = Cast<UMyAbilitySystemComponent>(_ActorInfo->AbilitySystemComponent);

	return( (_ASC->GetMyCostNum() > 0)?true:false );
}

ApplyCost

CommitAbility で呼ばれる。
コスト情報(gameplayEffect)の適用

必要なら継承して特殊な計算を行うことも可能。
その場合は、CheckCostApplayCostを両方継承する、以下コード例。

コストを独自に計算する
// コスト適用
void UMyGameplayAbility::ApplyCost(const FGameplayAbilitySpecHandle _Handle, const FGameplayAbilityActorInfo* _ActorInfo, const FGameplayAbilityActivationInfo _ActivationInfo) const
{
	const auto _ASC = Cast<UMyAbilitySystemComponent>(_ActorInfo->AbilitySystemComponent);

	_ASC->SetMyCostNum( _ASC->GetMyCostNum()-1 );
}

アビリティシステムコンポーネントクラス(AbilitySystemComponent)

対象のアクターが所持する必要があるコンポーネントです。
このコンポーネントが各アビリティを管理しており、FGameplayTagContainerクラスではなく、カウントできるように拡張したFGameplayTagCountContainerでタグを管理しているようです。

FGameplayTagCountContainerクラスは、
"Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\GameplayEffectTypes.h"
に定義されています。

コンポーネント有効化/無効化時のイベントをC++で登録する

ActorComponentを継承しているコンポーネントクラスは皆一緒です。

MyAbilitySystemComponent.h
virtual void InitializeComponent() override;

// コンポーネントが有効化時に呼ばれる処理
UFUNCTION()
void OnActivated(UActorComponent* Component, bool bReset);
// コンポーネント無効化時に呼ばれる処理
UFUNCTION()
void OnDeactivated(UActorComponent* Component);
MyAbilitySystemComponent.cpp

void UMyAbilitySystemComponent::InitializeComponent()
{
	Super::BeginPlay();
	
	// デリゲート登録
	OnComponentActivated.AddDynamic(this, &UMyAbilitySystemComponent::OnActivated);
	OnComponentDeactivated.AddDynamic(this, &UMyAbilitySystemComponent::OnDeactivated);
}

上記のデリゲート登録処理ですが、コードを書き換え LiveCodingでコンパイル&パッチ適用したところうまく呼ばれなかったということが起きました。恐らくホットリロードも一緒かと思いますが、挙動が怪しい場合はビルドし直すと良いと思います。

HasMatchingGameplayTag

アビリティコンポーネントが管理するゲームプレイタグに対し、引き数で指定したゲームプレイタグを持っているか調べます。

AddLooseGameplayTag

GameplayEffectとは関係なくゲームプレイタグを付与するメソッドです。
タグコンテナを引き数に使う場合は、AddLooseGameplayTags になります。

RemoveLooseGameplayTag

GameplayEffectとは関係なくゲームプレイタグを削除するメソッドです。
タグコンテナを引き数に使う場合は、RemoveLooseGameplayTags になります。

ゲームプレイエフェクト(GameplayEffect) / アトリビュートセット(AttributeSet)

GameplayEffect のDurationPolicyについて

[Gameplay Effect]->[Duration Policy] は [Instant][Infinite][Has Duration]の3パターンあり、[Instant]は即時、[Infinite]は対象のゲームプレイエフェクトが適用中のみ、[Has Duration]は指定した時間中のみとなります。

DurationPolicy.jpg

GameplayEffect適用時のAttributeSetのメソッド呼び出しについて

ApplyGameplayEffectToOwnerにて GameplayEffectを適用した場合、DurationPolicy が [Infinite]か[HasDuration] の時は UAttributeSet::PostGameplayEffectExecute() が呼ばれない?

GE_DurationPolicy_Instant.png

GE_DurationPolicy.png

GameplayEffect 適用例(Infinite)

[Duration Policy]を[Infinite]にして、任意のタイミングで[RemoveGameplayEffectFromOwnerWith...]で適用を外すと適用していたGameplayEffectでの[Modifiers]での値変更がリセットされ、UPlayerAttributeSet::PreAttributeChange()で値変更のメソッドが呼ばれます。

RemoveGameplayEffectFromOwnerWithHandle.jpg

この時のアトリビュートで受け取るリセット値は 0.0f 固定となるようです。

GameplayEffect 適用例(Has Duration)

[Duration Policy]を[Has Duration]にして、適用時間を決めるとゲームプレイエフェクトが適用終了時に以下の様にタスクで受け取ることができます。
WaitForGameplayEffectRemoved.jpg
[On Removed]は適用時間の終了時、[Invalid Handle]は恐らく外部から消された時だと思われます。

Modifiersでのカーブテーブル適用について

適用時の[Gameplay Effect Level]を変更し、カーブテーブルを適用することによってパラメータを変えることができます。
GE_Modifier_CT.jpg

以下はカーブテーブルの元になるjsonの例です。レベル1~3まで定義しています。

CT_param.json
[
	{
		"Name": "MidiumAttack",
		"1": 10,
		"2": 15,
		"3": 20
	},
	{
		"Name": "HeveyAttack",
		"1": 20,
		"2": 25,
		"3": 30
	},
]

ゲームプレイアビリティスペック(FGameplayAbilitySpec)

アビリティに付加される情報(クラス、レベル、入力など)を保持するクラスです。ソースは
"\Engine\Plugins\Runtime\GameplayAbilities\Source\GameplayAbilities\Public\GameplayAbilitySpec.h"
です。

以下はアビリティコンポーネントから AlilitySpecの情報へアクセスするコード例です。

AbilitySystemComponent.cpp
// 指定のタグが付いたアビリティを調べる
FGameplayTagContainer _Container;
_Container.AddTag(FGameplayTag::RequestGameplayTag(FName("Ability.Test")));
TArray<FGameplayAbilitySpec*> AbilitiesSpcs;
GetActivatableGameplayAbilitySpecsByAllMatchingTags(_Container, AbilitiesSpcs);
for (FGameplayAbilitySpec* _Spec : AbilitiesSpcs) {
	// アビリティレベルを2へ変更
	_Spec->Level = 2;
}

指定したアビリティレベルは GameplayAbilityグラフで使えます。
GetabilityLevel.png

アビリティタスク(AbilityTask)

アビリティに対し、任意のタスクをつくることができます。
BPでは以下の様にいろいろでてきます。

AbilityTask.jpg

[WaitDelay]や[WaitGameplayEvent]あたりをよく使います。

アビリティタスクの作成

任意のアクターのメソッドを監視するアビリティタスクの作成例です。

.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWaitMyTaskDelegate);

UCLASS()
class TEST_API UMyAbilityTask_WaitMyTask : public UAbilityTask
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(BlueprintAssignable)
	FWaitMyTaskDelegate	OnFinish;

	// アクティベート
	virtual void Activate() override;

	// タスク生成
	UFUNCTION(BlueprintCallable, Category="Ability|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
	static UMyAbilityTask_WaitMyTask* WaitMyTask(UGameplayAbility* OwningAbility, class AActor* MyActor);

	// Tick処理(bTickingTask が true の時)
	virtual void TickTask(float DeltaTime) override;

private:
	// タスク終了時に呼ばれる処理
	void OnTaskFinish();

	class	AActor* MyActor;
	bool	bIsFinished;
};

MyActor の IsMyTask() というメソッドから終了を監視します。

.cpp

UMyAbilityTask_WaitMyTask::UMyAbilityTask_WaitMyTask(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	bTickingTask = true;
	bIsFinished = false;
	
}

// タスク生成
UMyAbilityTask_WaitMyTask*
UMyAbilityTask_WaitMyTask::WaitMyTask(UGameplayAbility* OwningAbility, class AActor* MyActor)
{
	UMyAbilityTask_WaitMyTask* MyObj = NewAbilityTask<UMyAbilityTask_WaitMyTask>(OwningAbility);
	MyObj->MyActor = MyActor;
	
	return(MyObj);
}

void UMyAbilityTask_WaitMyTask::Activate()
{
}

// Tick
void UMyAbilityTask_WaitMyTask::TickTask(float DeltaTime)
{
	if (bIsFinished){ return; }
	
	Super::TickTask(DeltaTime);
	
	if(MyActor){
		// メソッドチェック
		if( !MyActor->IsMyTask() ){
			// 終了
			bIsFinished = true;
			OnTaskFinish();
		}
	}

}

// タスク終了時に呼ばれる処理
void UMyAbilityTask_WaitMyTask::OnTaskFinish()
{
	if (ShouldBroadcastAbilityTaskDelegates())
	{
		OnFinish.Broadcast();
	}
	EndTask();
}

BPノードは以下の様に表示されます。
AbilityTask_WaitMyTask.png

ゲームプレイキュー(GameplayCue)

アトリビュートの変化時などにエフェクトやサウンドなどを発生させる時などに使います。

作成例

サウンドを再生するゲームプレイキューを作成して、アビリティから呼び出しをしてみます。

  1. GameplayCueNotify_Actor を親クラスとしてBPを作成。
    gameplaycue.png

  2. 作成したBPのBeginイベントにサウンドをスポーンするようにBPを組みます。なお自身はDestoryActorで始末します。また、[GameplayCueTag]にタグを設定します。

gameplaycue_sample.png

  1. アビリティから AddGameplayCueToOwnerで呼び出します、この時の GameplayCueTag はゲームプレイキューで設定したタグです。

addgameplaycue.png

これでアビリティが実行されるたびに、ゲームプレイキューが呼び出されます。

gameplayeffect_display.png

ゲームプレイエフェクトの[Display]に対応したGameplayCueTagを設定して呼び出す方法もあるようです。

その他

アビリティに対してイベントを発行する

アクターのアビリティに対し、イベントを発行します。アビリティ側は[Wait Gameplay Event]のタスクにて受け取りができます。

SendGameplayEventToActor.png

C++コードだと以下の様になります。

.cpp
#include "AbilitySystemBlueprintLibrary.h"

FGameplayEventData _Payload;
_Payload.Target = MyActor;
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(MyActor, FGameplayTag::RequestGameplayTag(FName("Event.Fall")), _Payload);

受け取りアビリティタスク
WaitGameplayEvent.png

複数のスケルタルメッシュを持つ場合のアニメ―ションモンタージュ再生先の切り替え方法

アクターが複数のスケルタルメッシュを持つ場合、UGameplayAbilityを継承したBPなどでPlayMontageなどを使ってアニメ―ションを再生する場合そのままだと1つのスケルタルメッシュに対してしか再生できません。
これはGameplayAbilityのアニメーションモンタージュ再生がAbilitySystemComponentが持つ情報で再生させているためです。

これを回避するには UGameplayAbilityクラスとUAbilitySystemComponentが持っているActorInfo情報(FGameplayAbilityActorInfo)を書き換える必要があります。

以下切り替えコード例です。

UMyPlayerGameplayAbility.h
UCLASS(Abstract)
class TEST_API UMyPlayerGameplayAbility : public UGameplayAbility
{
	GENERATED_BODY()
	
public:
	// アクター情報
	UPROPERTY()
	FGameplayAbilityActorInfo Sub_ActorInfo;
	
	FGameplayAbilityActorInfo* TmpCurrentActorInfo;
UMyPlayerGameplayAbility.cpp

	// 対象スケルタルメッシュ切り替えコード
	auto _ASC = MyPlayer->GetAbilitySystemComponent();
	auto _SMC_Main = MyPlayer->GetSkeletalMeshComponent();
	auto _SMC_Sub = MyPlayer->GetSubSkeletalMeshComponent();

	if( _Enable){
		// 対象を切り替え後のスケルタルメッシュにする
		TmpCurrentActorInfo = (FGameplayAbilityActorInfo*)GetCurrentActorInfo();
		
		Sub_ActorInfo = *GetCurrentActorInfo();
		Sub_ActorInfo.SkeletalMeshComponent = TWeakObjectPtr<USkeletalMeshComponent>(_SMC_Sub);
		
		SetCurrentActorInfo(CurrentSpecHandle, &Sub_ActorInfo);
		_ASC->AbilityActorInfo->SkeletalMeshComponent = TWeakObjectPtr<USkeletalMeshComponent>(_SMC_Sub);
		
	}else{
		// 対象を戻す
		_ASC->ClearActorInfo();
		_ASC->InitAbilityActorInfo(MyPlayer, MyPlayer);

		SetCurrentActorInfo(CurrentSpecHandle, (const FGameplayAbilityActorInfo*)TmpCurrentActorInfo);
	}

GameplayAbilityComponentとGameplayAbility両方のクラスに対して行っています。恐らくGameplayAbilityはインスタンスタイプ([Instancing Policy] が [Instanced Per Execution])でないとダメかと思います。

まとめ

サンプルプロジェクトARPGを読んでみましたがわからない処理が多いです。
GameplayAbilityプラグインが多機能のためいろいろな機能に目が行きがちなのですが、アトリビュートのパラメータ管理とアビリティのステート管理から導入すると良いと思います。

9
6
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
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?