TD;DR
UnrealEngineを初めて触る機会があったので、まずはリフレクションまわりを調査
#概要
UnrealEngineでは、主にプログラマが作業するC++と
デザイナや企画なども触りやすいビジュアル言語 BluePrint が存在する
Unreal独自の リフレクション機能により、双方で関数や変数、構造体、列挙体をやり取りする仕組みがある
基本的に、C++でベースクラスを作成し、BluePrintはそれを継承し、変数や関数にアクセスをする
C++からBluePrintにアクセスするには、イベントという仕組みを使い
C++では実態のないメソッドを作り呼び出す事ができる
ただし、BluePrintではメモリ管理が自動で行われるため、リフレクションを使うC++部分は ガーベージコレクション等を行う必要がある
それらの仕組みを Unrealは提供している
#C++クラスを作成しBluePrintで継承する
上記のようにC++でクラスを作成する。
Actorというのが、Unrealの色々な機能を使うことができる基底クラス
それを親に、BluePrintクラスを作成
##C++コード
#pragma once
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class HOGE_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActor();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
};
上記が自動生成されたコード。
AActorクラスを継承している。 Unrealではクラス名の頭に意味があり
Uがつくものは UObject、AはActorを継承している
構造体やUnreal文字列等のリフレクションに渡せる汎用クラスはFで始まり
EnumはEで始まり、コンテナはTではじまる
ここで疑問が何個かあるとおもう
#include "MyActor.generated.h"
自動生成されたヘッダファイル。
ヘッダを変更するとUnrealBuildToolが自動的に生成する リフレクション用のファイル
後々中身を覗くと リフレクションが行われているのがわかると思う
UCLASS()
コードを追うと
#if UE_BUILD_DOCS
#define UCLASS(...)
#else
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#endif
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
とりあえず あまり気にせず、Unrealに管理させるクラスには UCLASS() が必要と。
HOGE_API
HOGEはプロジェクト名
これも気にしない
: public AActor
Actorから継承させなければ、Unrealの色々な機能が使えない。
UObject、AActor、ACharacter・・等色々あるが今回は AActorで行う
GENERATED_BODY()
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY)
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
これも特に気にせず、Unrealに管理させる場合に必要とおぼえておこう
構造体、enum、メンバ変数
プロジェクトは BluePrintのThirdPersonExanmple
プロジェクト名:BPTest
C++クラス: MyActor
派生したBluePrintクラス:BP_MyActor
構造体
構造体をBluePrintと両方で使いたい場合は、基底クラスのC++側で リフレクション付けて定義します
コード
USTRUCT(BlueprintType)
struct BPSAMPLE_API FHogeStruct {
GENERATED_USTRUCT_BODY()
public:
UPROPERTY() float x;
UPROPERTY(BlueprintReadOnly) float y;
UPROPERTY(BlueprintReadWrite) float z;
};
名前
今回の構造体名は FHogeStruct
構造体は頭に Fのプリフィックスを付けねば認識してくれないのは Unrealの仕様
USTRUCT()
BluePrintに公開するときに必要
ただし、これだけではガーベージコレクションに入らないので、メンバ1つずつに対して UPROPERTY設定必要
UPROPERTY()
メンバそれぞれに対して、BluePrintにリフレクションするパラメータを設定する
今回は3種類書いてみた
パラメーター無し、BlueprintReadOnly、BlueprintReadWrite
結果から言うと
パラメータ無しだと Blueprintに公開されない
BlueprintReadOnlyだと 書き込み不能
BlueprintReadWrite だと読み書き可能
みた通りの挙動
BluePrintでの結果
BP_MyActorのブループリントを開き、HogeStructのノードを開く
見て分かる通り、IN (書き込み)は Zのみ、OUT(読み込み)はYとZで、Xは読み書きどちらも不能
これで 構造体をBluePrintとC++で使えるようになった
Enum
EnumをBluePrintと両方で使いたい場合は、基底クラスのC++側で リフレクション付けて定義します
コード
Class宣言の前に
UENUM(BlueprintType)
enum class EHogeEnum : uint8 {
HOGE,
HOGE2,
HOGE3
};
名前
今回の構造体名は EHogeEnum
Enumは頭に Eのプリフィックスを付けねば認識してくれないのは Unrealの仕様
また、内部表現は8ビット固定
UENUM()
BluePrintに公開するときに必要
BluePrintでの結果
BP_MyActorのブループリントを開き、HogeEnumのノードを開く
これで EnumをBluePrintとC++で使えるようになった
メンバ変数
メンバ変数をBluePrintに公開するには、基底クラスのC++側で リフレクション付けて定義します
コード
Class
UCLASS(Blueprintable)
class BPSAMPLE_API AMyActor : public AActor
{
GENERATED_BODY()
private:
// UPROPERTY(BlueprintReadWrite, Category = "MyTest") float private_; // privateではアクセスを書くとコンパイルエラー
UPROPERTY() float private_;
protected:
UPROPERTY(BlueprintReadOnly, Category = "MyTest") float protected_;
public:
UPROPERTY(BlueprintReadWrite, Category = "MyTest") float public_;
// Sets default values for this actor's properties
AMyActor();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
};
UPROPERTY()
構造体の時と同じく、BlueprintReadOnly、BlueprintReadWrite を選べる
また、Categoryをつけると、BluePrintでノードを検索するときに カテゴリー名で検索できるので便利
Privateの場合は BluePrintから呼び出せないため、それらのパラメータを付けることは不可能
パラメータ詳細は下記
BluePrintでの結果
private_ 変数はアクセス不能
protected_ 変数はアクセス可能
public_ 変数はアクセス可能
#メソッド
*パラメータ一覧
下記をみるとよい
今回調査するのは
パラメータなし、BluePrintCallable、BlueprintImplementableEvent
のみ
パラメータ無し
UFUNCTION(Category = "MyFunc")
virtual void FuncNone() {};
UFUNCTION(Category = "MyFunc")
virtual void FuncNone2(int n) {};
UFUNCTION(Category = "MyFunc")
virtual int FuncNone3(int n) { return 1; };
パラメータを付けない場合は、BluePrintと繋がることはない
だが、後に出てくる レプリケーション等のUnrealEngineで使う関数には指定する必要がある
BluePrintCallable
UFUNCTION(BluePrintCallable, Category="MyFunc")
virtual void FuncCallable() {};
UFUNCTION(BluePrintCallable, Category = "MyFunc")
virtual void FuncCallable2(int n) {};
UFUNCTION(BluePrintCallable, Category = "MyFunc")
virtual int FuncCallable3(int n) { return 1; };
BluePrintからC++のコードを実行する際に使う、最も多い使い方
基本的にUnrealではC++のクラスを継承しBluePrintを作る
BluePrintで関数を Override可能
BlueprintImplementableEvent
UFUNCTION(BlueprintImplementableEvent, Category = "MyFunc")
void FuncImplementableEvent() ;
UFUNCTION(BlueprintImplementableEvent, Category = "MyFunc")
void FuncImplementableEvent2(int n);
UFUNCTION(BlueprintImplementableEvent, Category = "MyFunc")
int FuncImplementableEvent3(int n);
C++からBluePrintの関数を呼ぶための仕組み
名前から推測するに イベントのような形で実行するのだろう
実際にBluePrintでもイベントとして表示される
virtual不可能&C++側で関数をImplement不可能
関数定義だけ用意し、C++からこのコードを呼べば、BluePrintで定義した関数が呼べる
イベントなので 引数は取れるが戻り値は返せない
コンパイルエラーにはならないが、戻り値をつけるとBluePrintから見えなくなる
BlueprintPure
// UFUNCTION(BlueprintPure, Category = "MyFunc")
// virtual void FuncPure(){};
// UFUNCTION(BlueprintPure, Category = "MyFunc")
// void FuncPure2(int n) {};
UFUNCTION(BlueprintPure, Category = "MyFunc")
virtual int FuncPure3(int n) { return 1; };
ヘルプによると 所有するオブジェクトに影響を与えることなく・・・
らしく、戻り値がなければダメのようだ
パラメータ無しはBluePrintに表示されない
ImplementableEventは イベントとして表示。ただし戻り値を設定すると表示されない
Callableは実行品が両端についた通常の関数
Pureは実行ピンのない、値をかえすだけのもの
#ついでに、リフレクションに指定できる型
全ての型がリフレクションで使えるわけではない
基本的には Unrealで管理されたもの(UObjectを継承し、ガーベージコレクション等に対応したもの)しか送れない
一部のプリミティブ型は送れる
UENUM(BlueprintType)
enum class EHogeEnum : uint8 {
HOGE,
HOGE2,
HOGE3
};
USTRUCT(BlueprintType)
struct BPSAMPLE_API FHoge {
GENERATED_USTRUCT_BODY()
float x;
};
USTRUCT(BlueprintType)
struct BPSAMPLE_API FHogeStruct {
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(BlueprintReadWrite) FHoge hoge_;
UPROPERTY(BlueprintReadWrite) EHogeEnum enum_;
UPROPERTY(BlueprintReadWrite) uint8 uint8_;
// UPROPERTY(BlueprintReadWrite) uint16 uint16_;
// UPROPERTY(BlueprintReadWrite) uint32 uint32_;
// UPROPERTY(BlueprintReadWrite) uint64 uint64_;
// UPROPERTY(BlueprintReadWrite) int8 int8_;
// UPROPERTY(BlueprintReadWrite) int16 int16_;
UPROPERTY(BlueprintReadWrite) int32 int32_;
// UPROPERTY(BlueprintReadWrite) int64 int64_;
UPROPERTY(BlueprintReadWrite) float float_;
// UPROPERTY(BlueprintReadWrite) double double_;
UPROPERTY(BlueprintReadWrite) bool bool_;
UPROPERTY(BlueprintReadWrite) FString FString_;
UPROPERTY(BlueprintReadWrite) FName FName_;
UPROPERTY(BlueprintReadWrite) AActor *AActor_;
UPROPERTY(BlueprintReadWrite) TArray<int32> TArray_;
// UPROPERTY(BlueprintReadWrite) TMap<int32, int32> TMap_;
// UPROPERTY(BlueprintReadWrite) TSet<int32> TSet_;
};
コメントになっているのは、渡せないもの。
構造体、Enumは USTRUCT,UENUM 付きのものは渡せる
プリミティブ型では
uint8、int32、float、bool のみ
コンテナは TArrayのみ
UObjectを継承しているクラスポインタは渡せる