1. はじめに
UE5では、GameplayAbilitySystem の機能が Blueprint のみでも使えるようになってきました。
しかし、GameplayAttribute やその利用が前提の GameplayEffect に関しては、現状 Blueprint のみで使うことはできません。
(GASCompanion や 九里江めいくさんのプラグイン のように、C++を書かずに GameplayAttribute を定義する方法はありますが、C++を使わずに定義する方法は現状ないはずです。)
本記事では、そんな我々BP信者に優しくない GameplayAttribute を、無理やり Blueprint のみで定義する方法を紹介します。
本記事は半分ネタです。実用性はそんなにありません。
2. 方法
2.1. プラグインをダウンロードする
以下のリンクより GameplayAttributeForBP プラグインをダウンロードして、プロジェクトの Plugins フォルダ(なければ作る)にぶっこんでください。
本プラグインはUE5.1で作成しました。パッケージにおける動作は未検証です。
2.2. AttributeSet に変数を追加する
プラグインをビルドしてエディタを起動したら、Plugins/GameplayAttributeForBP Content/AttributeSets/BP_GABPAttributeSet
を開き、任意の GameplayAttributeData 型の変数を追加します。
サンプルの変数として「Health」が最初からありますが、削除しても構いません。
Plugins/GameplayAttributeForBP Content/AttributeSets
以下に AttributeSet を継承した Blueprint Class を新規作成し、その中に変数を追加してもよいです。
2.3. GABPボタンをオンにする
適当なアセットエディタを開き、ツールバーの左の方にある「GABP」と書かれたボタンをクリックしてオンにします。
GABPボタンがオンの時は、2.2.で追加した GameplayAttributeData が Attribute の一覧に表示されるようになります。
GABPボタンは、Attribute の一覧を使う時のみオンにしてください。
GABPボタンをオンにしたままにすると、高確率でエディタがクラッシュします(理由は後述)。
2.4. 実装する
例として、以下の画像の GameplayEffect を使い、
以下の画像のBPを実行してみると、
GameplayAttribute が正しく機能していることが確認できます。
3. 解説
3.1. 問題点と解決策
BPのみで GameplayAttribute が使えない理由として、BPの AttributeSet に定義した GameplayAttributeData が Attribute の一覧に表示されないという問題がありました。
そこで、エンジンソースにある Attribute の一覧の表示に関する処理を見てみます。
...
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ボタンがオフになった時に偽装を解除します。
...
// 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
に実装があるので、ぜひ参考にしてみてください。