LoginSignup
0
0

【UE5】GameplayAttributeをBPのみで定義する

Posted at

1. はじめに

UE5では、GameplayAbilitySystem の機能が Blueprint のみでも使えるようになってきました。
しかし、GameplayAttribute やその利用が前提の GameplayEffect に関しては、現状 Blueprint のみで使うことはできません。
GASCompanion九里江めいくさんのプラグイン のように、C++を書かずに GameplayAttribute を定義する方法はありますが、C++を使わずに定義する方法は現状ないはずです。)

本記事では、そんな我々BP信者に優しくない GameplayAttribute を、無理やり Blueprint のみで定義する方法を紹介します。

本記事は半分ネタです。実用性はそんなにありません。

2. 方法

2.1. プラグインをダウンロードする

以下のリンクより GameplayAttributeForBP プラグインをダウンロードして、プロジェクトの Plugins フォルダ(なければ作る)にぶっこんでください。

image.png

本プラグインはUE5.1で作成しました。パッケージにおける動作は未検証です。

2.2. AttributeSet に変数を追加する

プラグインをビルドしてエディタを起動したら、Plugins/GameplayAttributeForBP Content/AttributeSets/BP_GABPAttributeSetを開き、任意の GameplayAttributeData 型の変数を追加します。

サンプルの変数として「Health」が最初からありますが、削除しても構いません。
Plugins/GameplayAttributeForBP Content/AttributeSets以下に AttributeSet を継承した Blueprint Class を新規作成し、その中に変数を追加してもよいです。
image.png
image.png

Plugins フォルダが表示されない場合は、コンテンツブラウザ右上の設定から「Show Plugin Content」にチェックを入れてください。

image.png

2.3. GABPボタンをオンにする

適当なアセットエディタを開き、ツールバーの左の方にある「GABP」と書かれたボタンをクリックしてオンにします。
GABPボタンがオンの時は、2.2.で追加した GameplayAttributeData が Attribute の一覧に表示されるようになります。

GABPボタンは、Attribute の一覧を使う時のみオンにしてください。
GABPボタンをオンにしたままにすると、高確率でエディタがクラッシュします(理由は後述)。

GABPボタンがオフの時
image.png
image.png

GABPボタンがオンの時
image.png
image.png

2.4. 実装する

例として、以下の画像の GameplayEffect を使い、
image.png
以下の画像のBPを実行してみると、
image.png
GameplayAttribute が正しく機能していることが確認できます。
image.png

3. 解説

3.1. 問題点と解決策

BPのみで GameplayAttribute が使えない理由として、BPの AttributeSet に定義した GameplayAttributeData が Attribute の一覧に表示されないという問題がありました。

そこで、エンジンソースにある Attribute の一覧の表示に関する処理を見てみます。

SGameplayAttributeWidget.cpp
...
TSharedPtr<FAttributeViewerNode> SAttributeListWidget::UpdatePropertyOptions()
{
	...

	// Gather all UAttribute classes
	for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
	{
		UClass *Class = *ClassIt;
		if (Class->IsChildOf(UAttributeSet::StaticClass()) && !Class->ClassGeneratedBy)
		{
            ...

			for (TFieldIterator<FProperty> PropertyIt(Class, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt)
			{
                ...

                TSharedPtr<FAttributeViewerNode> SelectableProperty = MakeShareable(new FAttributeViewerNode(Property, FString::Printf(TEXT("%s.%s"), *Class->GetName(), *Property->GetName())));
				PropertyOptions.Add(SelectableProperty);
			}
		}
    ...

ここでは、TObjectIteratorを用いてロード済みのBPとC++の全UClassの中から、UAttributeSetの子クラスかつC++クラス (ClassGeneratedBy == nullptr) であるUClassを収集し、そのUClassのプロパティを Attribute の一覧に追加しています。

つまり、Attribute の一覧にBPの AttributeSet に定義した GameplayAttributeData を表示させるには、その AttributeSet のBPをロードし、C++クラスに偽装 (ClassGeneratedBy = nullptr;) すればよいということです。

3.2. プラグインの実装

2.1.で紹介した GameplayAttributeForBP プラグインでは、GABPボタンが押された時にFARFilterを用いて特定のパス下にあるBPをロードし、そのBPの中からUAttributeSetの子クラスを収集しています。

収集したUAttributeSetの子BPクラスは、GABPボタンがオンになった時にC++クラスに偽装し、GABPボタンがオフになった時に偽装を解除します。

GABPToolBarButton.cpp
...
// GABPボタンが押された時
void UGABPToolBarButton::ToggleActivation()
{
	// FARFilter を設定
	FARFilter ARFilter;
	ARFilter.PackagePaths.Add(FName(TEXT("/GameplayAttributeForBP/AttributeSets")));
	ARFilter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
	ARFilter.bRecursivePaths = true;
	ARFilter.bRecursiveClasses = true;
	
	// BPをロード
	TArray<FAssetData> Assets;
	IAssetRegistry::Get()->GetAssets(ARFilter, Assets);

	TArray<TSubclassOf<UObject>> AttributeSetClasses;
	
	// UAttributeSet の子BPクラスを収集
	for (const FAssetData& Asset : Assets)
	{
		const UBlueprint* Blueprint = Cast<UBlueprint>(Asset.GetAsset());
		const TSubclassOf<UObject> Class = Blueprint->GeneratedClass;

		if (Class && Class->IsChildOf<UAttributeSet>())
		{
			AttributeSetClasses.Emplace(Class);
		}
	}

	bIsActivated ? Deactivate(AttributeSetClasses) : Activate(AttributeSetClasses);
}

// GABPボタンがオンになった時
void UGABPToolBarButton::Activate(const TArray<TSubclassOf<UObject>>& AttributeSetClasses)
{
	// C++クラスに偽装
	for (const TSubclassOf<UObject>& AttributeSetClass : AttributeSetClasses)
	{
		ClassGeneratedBys.Emplace(AttributeSetClass, AttributeSetClass->ClassGeneratedBy);
		AttributeSetClass->ClassGeneratedBy = nullptr;
	}

	bIsActivated = true;
	Data.Icon = ActiveIcon;
}

// GABPボタンがオフになった時
void UGABPToolBarButton::Deactivate(const TArray<TSubclassOf<UObject>>& AttributeSetClasses)
{
	// C++クラスの偽装を解除
	for (const TSubclassOf<UObject>& AttributeSetClass : AttributeSetClasses)
	{
		UObject** ClassGeneratedBy = ClassGeneratedBys.Find(AttributeSetClass);

		if (*ClassGeneratedBy)
		{
			AttributeSetClass->ClassGeneratedBy = *ClassGeneratedBy;
		}
	}

	ClassGeneratedBys.Empty();

	bIsActivated = false;
	Data.Icon = InactiveIcon;
}

ただし、BPをC++クラスに偽装すると、その間そのBPは無効なクラスとして認識されてしまいます。
これは、クラスの生成の原因となったBPを指すClassGeneratedByに、偽装のために無理やりnullptrを代入したためです。

何らかのタイミングでこの無効なクラスのロードが発生すると、ぬるぽでエディタがクラッシュします。
GABPボタンをオンにしたままにすると高確率でエディタがクラッシュするというのは、そういうことです。

我々BP信者は、命懸けで GameplayAttribute を使わなければならないのです。

4. おわりに

命を懸けなくてもいい方法があれば教えてください。

5. おまけ

UEditorUtilityToolMenuEntryを使うと、簡単に(BPのみでも)ツールバーボタンが作れるのでおすすめです。

また、プラグインではEditorPerProjectUserSettings.iniを上書きできず、こちら の方法でエディタ起動時に EditorUtilityBlueprint を実行することができなかったため、C++でゴリ押しています。
GameplayAttributeForBP.cppに実装があるので、ぜひ参考にしてみてください。

0
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
0
0