目次
※この記事で使用しているUnrealのVersionは04.26.0です。
※この記事のサンプルプロジェクトは以下URLにアップされています。
皆様お久しぶりです。
Unreal Engine 4 (UE4) Advent Calendar 2020
14日目の記事となります!
昨日は
おかずさんの
【UE4】GUIフレームワーク「Dear ImGui」を使ってデバッグ・ツール用UIを作ってみよう! < 導入・基本編 >
でした。お手軽に導入出来て色々便利そうでとてもよさそうですね!
ということで14日目の記事は、4.26新機能系書こうかと思いましたが、それは他の神々にお任せしようと思います。
今回は自分がこれまで使ってきた様々なBlueprint小細工を一挙公開したいと思います。
※はじめにプログラマーの方には申しておきます。
こんな記事書いてすみませんorz
Blueprintマル秘小細工集~ソースを書かないプランナーがBlueprintでどこまで深淵に至れるか?~
レベル【★★★】
さて、Unreal Engineを使いこなすプランナー(レベルデザイナー)の人々は、「このプロパティだけあればできるのにな~」とか
「あぁ、この部分のソースをちょこっとだけいじらせてくれれば後はこっちでいい感じにやるのに…」とか
「このタイミングを明示的に取得できればこんな無意味なTickとかすぐなくすのに…」なんてことを何度も経験したことがあるかと思います。
このような悩みは、本実装時よりはむしろグレーボックスなどでお試しで気軽に動かしたい! なんてときにぶつかると思います。
そんなときに知っておくと便利な小細工テクニックをご紹介します。
※注意!
ここに乗っているテクニックは、基本的にはあなたが関わっているプロジェクトにおける「本実装」には載せないことを強くおすすめします。
(ここに書いてあることを本実装に載せてしまうと、可読性や処理速度などが著しく損なわれてしまいます)
プログラマーに怒られたくなかったら、素直にやりたいことをプログラマーに伝えて機能を作ってもらおう!
キンアジちゃんとの約束だよっ♫
小細工その1~ObjectやClassの参照ををStringから取得する~
実は4.22ぐらいから、StringからObjectReferenceやClassReferenceが”簡単”に取得できるようになってたんです。(最近知りました)
「Make Soft Object Path」「Make Soft Class Path」という関数で「SoftObjectPathStruct」や「SoftClassPathStruct」を作成します。
ここまでは4.21以前からもできたのですが、4.22からこの構造体から「SoftObjectReference」や「SoftClassReference」に変換できる用になってたのです。それらに変換できれば、「ObjectReference」や「ClassReference」に変換できてしまうのです。
ちなみにこれができなかったバージョンでは、この方法でなくてもStringからReferenceを取得する方法はあります。
その方法は、1週間前のAdventCalender
でも紹介されていた「Asset Registry」というInterfaceの機能。
この中の関数で「GetAssetByObjectPath」という関数があります。
実はこの関数は他の「Get Asset~~~」系と根本的に違いがあります。
(ソースを読めばわかると思います)
それは、取得するのは現在あるAssetからではなく現在生成されているUObjectすべてが対象となるのです。
なので、これで取得したAssetDataから「Get Asset」や「Get Class」関数で取得できるObject Referenceはアセット以外のオブジェクトも取得できてしまいます。
参照を持たないということは依存関係をなくせるということにつながります。
これはとても有用である気がしますね。
半面、依存関係がないので「どこで使っているか」などがとても分かりづらくなります。また、名前が変更されたりしたら機能しなくなります。
使う際は細心の注意を払って使いましょう。
具体的にPathから参照を取ってこれると何ができるの?
という部分は、以降で紹介する小細工で色々使っていますので参考にしてみてください。
小細工その2~Blueprintに公開されていない関数を呼び出す~
過去に、
【UE4】ConsoleCommand「CE,KE」について【★★☆】
で、便利なConsoleCommandとして紹介しました、「KE」コマンド。
これを使いこなすことで、本来のBlueprintではできないことができてしまいます。
C++ソースからBlueprintで関数として呼び出せるようにするには、
関数定義の部分に
UFUNCTION(BlueprintCallable)
void hoge();
//または
UFUNCTION(BlueprintPure)
bool hoge();
というマクロが必要になります。
ですが、「KE」コマンドを使えば、
「UFUNCTION()」さえついていれば、「BlueprintCallable」がついていなくても呼び出せる!
試しにやってみましょう。
まずはテスト用にC++のFunctionLibraryを作成します。
そしてテスト用の関数を2つ作ります。
MyBlueprintFunctionLibrary.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
/**
*
*/
UCLASS()
class KA_UEABYSS_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
//BlueprintCallableつけた関数
UFUNCTION(BlueprintCallable)
static void Test();
//UFUNCTIONのみの関数
UFUNCTION()
static void Test2();
};
MyBlueprintFunctionLibrary.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintFunctionLibrary.h"
void UMyBlueprintFunctionLibrary::Test()
{
UE_LOG(LogTemp, Log, TEXT("Test!!!"));
}
void UMyBlueprintFunctionLibrary::Test2()
{
UE_LOG(LogTemp, Log, TEXT("Test2!!!"));
}
そしてできたらビルドしてみます。
エディターが再び立ち上がったら、
こういった簡単なテストとかにも超お役立ちの「Editor Utility Widget」を作成します。
中身のDesign側はボタン入れてるだけです。
では、EventGraph側で処理を書きます。
当然ながら、普通に「Test」でBlueprint関数検索をしても、「void Test();」の方しか表示されません。
ではどうするか。
ここでConsoleCommand「KE」が登場します。
「KE」コマンドは、第一引数にObjectPathを入れることで、そのObjectの関数を呼び出せます。
…あれ?でもC++製のFunctioonLibraryのObjectPathってどうすればいいの?
それは、先程BlueprintCallableをつけた関数ノードをEventGraphに作成し、そのノードをコピーしてテキストに貼り付けてみましょう。
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_0"
FunctionReference=(MemberParent=Class'"/Script/KA_UEAbyss.MyBlueprintFunctionLibrary"',MemberName="Test")
…以下略
MemberParent=Class’ “…” ‘ の部分に書いてある
「/Script/KA_UEAbyss.MyBlueprintFunctionLibrary」この部分に注目します。
実は、「/Script//*ModuleName*/.Default__/*ClassName*/」というPathに対してKEイベントを発行すれば、しっかりと関数が呼ばれるのです!
↓以下実験
↑の法則に従って、「ExecuteConsoleCommand」関数で「KE /Script/KA_UEAbyss.Default__MyBlueprintFunctionLibrary Test2」
というコマンドを読んでみます。
BlueprintCallableがついてない関数でも呼び出すことができちゃいました!
やったね!
ちなみにFunctionLibrary以外でも、例えばActorClassなんかの関数を呼びたい場合は、「GetPathName」で取得したそのActorのPathを引数に入れてあげれば、個々のインスタンスに対しても呼び出せます。
※文字列でやっているため、ソースコードが変わればすぐに機能しなくなるので、ご利用は計画的にね!
小細工その3~通常のBlueprintとEditorUtilityBlueprintとの連携~
通常のBlueprint内では、EditorUtilityBlueprintなどで使えるEditorModuleの関数(Save AssetやGet Selected Actorsなど)は使えませんが、1で使った技を応用することで、擬似的にEditorUtility系のBlueprintを呼び出すことができます。
Editor内での作業の効率化などを図ったりするのにはとても使えます。
以下実例↓
プレイ中に特定のトリガーをプレイヤーが踏んだら、取得した情報をもとにデータテーブルに書き込んでそのデータテーブルを保存するような場合。
(Editor Utility Actor使えば済むのですが、今回は実験でただのActorClassでやります)
まずはトリガー用BPを作ります。
中に当たり判定用の「BoxCollision」を入れ、「OnComponentBeginOverlap」イベントを作成してそこに処理を書きます。
今回使いたいのは、「Fill DataTable from CSVString」と「Save Asset」という2つの関数です。
※「Save Asset」関数は「Editor Scripting Utilities」プラグインのものなので、Pluginが有効でないと使えません。
「Actor」クラスはプレイ中にも使える「Runtime」のクラスなので、その中のEventGraphでは、EditorOnlyでしか使えない「Editor」の関数は使えません。
そこで、データテーブルに行を追加してセーブするという機能を持ったEditorUtilityBlueprintを作成します。(今回はEditorUtilityObject)
中に「AddDataTableRow」という名前で関数を作成します。
↓中身はこんな感じ
あとは、テスト用にDTを作成します(Structureも一緒に)
DTのカラムは、「TestString」というStringのプロパティのみにします。
そしてDT作成。
これでひとまず空っぽのDTができました。
あとは、さっき作成したトリガーに「プレイヤーがあたったらさっき作ったEditor Utility Blueprintの関数を呼び出す」ものを作ります。
Editor Utility Blueprintの関数は、Editorでしか使えないので、Blueprintで作った関数だとしても、Actorクラス内では呼び出せません。
なので、ここでようやく「KE」コマンドさんの登場です!
こんな感じで組んで、適当なレベル上にこのアクターをおいてプレイします。
できました!!
※当然のことながら、この機能はEditorでしか使えないので、
Packageでは使えません。ご利用はご計画的に。
小細工その4~Blueprintable or BlueprintTypeでないUClassへの擬似的なキャスト~
例えば、EditorUtilityBlueprintで使える関数の中に
Save Packages(TArray<UPackage*> PackagetoSave,bool OnlyDirty);
という関数があります。
この関数は引数に入れたUPackageObjectをセーブするというものです。
このUPackageというクラス、BlueprintではCastが不可能なため、実質的には
「Get Dirty Map Packages()」という関数か、「Get Dirty Content Packages()」という関数で取得した、Dirtyなアセット(編集がされておりセーブがまだされてない、アセットの右下に花のようなグレーのマークがある状態)しか引数に入れることができず、「Only Dirty」というbool引数が、Blueprintではほぼ無意味とかしています。
C++でUPackage*を取得するには
UPackage* UMyBlueprintFunctionLibrary::GetPackageObject(UObject* InObject) { if (!InObject) { return nullptr; } return InObject->GetOutermost();; }
こん感じの関数をC++で作成し、引数にアセットを指定してやらなければなりません。
しかし、ここでKEコマンドの出番です。
テスト用にEditor Utility Widgetを作成して、その中に関数を作ります。
その引数には「Save Packages」の引数から「Make Array」をしたUPackageの参照を関数の開始ノードにドラッグ&ドロップでつなげて引数をUPackageの参照引数を作成します。
そして、「SavePackages」関数につなげます。
そして、EventGraphの方で、「KE」コマンドを使って以下のようにBPを組みます。
これを実行してみると、
しっかりSavePackagesが実行され、アセットがセーブされました。
UPackageオブジェクトのObjectPathは、基本的にアセットのPathと同義になります。なので、任意のアセットのPathを引数に入れると、作成した関数の引数にUPackage*として渡され、結果的にSavePackagesが実行されます。
ちなみに、UPackage*でないObjectが引数に入った場合引数はnullptrが渡されます。
小細工その5~GetEditorPropertyとSetEditorPropertyでBPでさわれないプロパティへアクセスする~
「GetEditorProperty」と「SetEditorProperty」というEditorOnlyですが、便利な関数があります。
これらは、指定したオブジェクトの中にあるプロパティをプロパティ名で取得できます。
ここまででも便利な関数だということはわかります。
ですが、この関数には裏技的な使い方があります。
これらの関数を使えば、本来Blueprintから読み取り、書き込みできないプロパティにもアクセスできちゃいます!
まず、ThirdPersonCharacterのクラスにテスト用にEditor上で見ることだけできるStringプロパティを追加します。
//テスト用にEditor上で見ることだけできるStringプロパティを追加 UPROPERTY(VisibleAnywhere, Category = Test) FString TestString = TEXT("Test");
これをビルドして見ます。
すると、ThirdPersonCharacterに「TestString」というプロパティが生成されています。
そして、Editor Utility Widgetを作成し、以下のように組んでみます。
そして実行してみます。
本来Blueprintでは取得ができないPropertyでも取得できちゃいました!
今度はプロパティを「EditAnywhere」にしてみます。
//テスト用にEditor上で編集だけできるStringプロパティを追加 UPROPERTY(EditAnywhere, Category = Test) FString TestString = TEXT("Test");
そしてEditor Utility Widgetに以下のように処理を書きます。
これを実行すると
Blueprintで編集できる設定になっていなくても、しっかり編集できたと思います!
まとめると、
GetEditorPropertyは、「VisibleAnywhere」や「EditAnywhere」などのEditor上でみれる設定になっていれば、取得は可能です。
なので、Editor上で見えているプロパティなら基本的に取得可能になります。
SetEditorPropertyは、Editor上で見えていても編集不可能な「VisibleAnywhere」なプロパティは編集できませんが、「EditAnywhere」なプロパティは編集が可能になります。
そしてラスト!
荒業なのでほぼ使ってほしくない使い方ですが、一応紹介します。
C++で、例えば以下のような構造体を定義します。
USTRUCT()
struct FTestStruct
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Test")
FString TestString = TEXT("Test");
UPROPERTY(EditAnywhere, Category = "Test")
bool bTestBool = true;
};
この構造体は、「BlueprintType」な構造体ではないため、「Breakノード(構造体の分解)」や「Makeノード「構造体の構築」」等を使うことはできません。
この構造体の型の変数をThirdPersonCharacterに持たせます。
UPROPERTY(EditAnywhere, Category = Test)
FTestStruct TestStructProperty;
これをビルドしてみると、キャラのプロパティに構造体のプロパティが追加されていると思います。
しかし、定義した構造体の型がBlueprintでは使えないため、このプロパティをBlueprintから取得・編集することは実質不可能……と思いきや、頑張ればできます。
まず、Structureアセットを作成し、その構造体の中にC++で定義したプロパティと同じものを同じ順番で定義しておきます。
さて、もう察したかもしれませんが、このStructureアセットで作った構造体を代用することで、プロパティを取得することができちゃいます。
例のごとく、Editor Utility Widgetに以下のような感じでBlueprintを書きます。
これを実行してみます。
なんか取得できてしまいました。おっかないですね。
まとめ
まだまだ便利小細工テクはいっぱいあるのですが、記事がかなり長くなってしまったのと雑になってきたのでこの辺で。
これらのテクニックは、基本的にEditorOnlyで使うものになります。
テストで実験的に機能などを作る際やエディター上のツールを作る際などには使えるかと思います。
が、使い方を間違えるとかなり危ないテクニックなので、使う場合はプログラマに一言相談するのが良いかと思います。
(本音を言えば極力使わないでください…)
記事にソースコードがちらほらあるって?
キンアジちゃんはソースコード書かないし読まないからなんのことかわからないぞっ♪
Unreal Engine 4 (UE4) Advent Calendar 2020
明日はながさんの「やっぱりInstancedMeshであそびます」です。
どんなお遊びをするのか今からワクワクです!!
以上!
※この記事のサンプルプロジェクトは以下URLにアップされています。