Edited at

UnrealEngine4 リフレクション(C++とBluePrint連携)まわりの調査

TD;DR

UnrealEngineを初めて触る機会があったので、まずはリフレクションまわりを調査


概要

UnrealEngineでは、主にプログラマが作業するC++と

デザイナや企画なども触りやすいビジュアル言語 BluePrint が存在する

Unreal独自の リフレクション機能により、双方で関数や変数、構造体、列挙体をやり取りする仕組みがある

基本的に、C++でベースクラスを作成し、BluePrintはそれを継承し、変数や関数にアクセスをする

C++からBluePrintにアクセスするには、イベントという仕組みを使い

C++では実態のないメソッドを作り呼び出す事ができる

ただし、BluePrintではメモリ管理が自動で行われるため、リフレクションを使うC++部分は ガーベージコレクション等を行う必要がある

それらの仕組みを Unrealは提供している


C++クラスを作成しBluePrintで継承する

https://docs.unrealengine.com/latest/JPN/Programming/QuickStart/2/index.html

上記のように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++側で リフレクション付けて定義します


コード


MyActor.h

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設定必要

パラメータは下記

https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Structs/Specifiers/index.html


UPROPERTY()

メンバそれぞれに対して、BluePrintにリフレクションするパラメータを設定する

今回は3種類書いてみた

パラメーター無し、BlueprintReadOnly、BlueprintReadWrite

結果から言うと

パラメータ無しだと Blueprintに公開されない

BlueprintReadOnlyだと 書き込み不能

BlueprintReadWrite だと読み書き可能

みた通りの挙動

パラメータ詳細は下記

https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Properties/Specifiers/index.html


BluePrintでの結果

BP_MyActorのブループリントを開き、HogeStructのノードを開く

SnapCrab_BP_MyActor_2016-9-7_18-3-49_No-00.png

見て分かる通り、IN (書き込み)は Zのみ、OUT(読み込み)はYとZで、Xは読み書きどちらも不能

これで 構造体をBluePrintとC++で使えるようになった


Enum

EnumをBluePrintと両方で使いたい場合は、基底クラスのC++側で リフレクション付けて定義します


コード

Class宣言の前に


MyActor.h

UENUM(BlueprintType)

enum class EHogeEnum : uint8 {
HOGE,
HOGE2,
HOGE3
};


名前

今回の構造体名は EHogeEnum

Enumは頭に Eのプリフィックスを付けねば認識してくれないのは Unrealの仕様

また、内部表現は8ビット固定


UENUM()

BluePrintに公開するときに必要


BluePrintでの結果

BP_MyActorのブループリントを開き、HogeEnumのノードを開く

SnapCrab_BP_MyActor_2016-9-7_18-19-8_No-00.png

これで EnumをBluePrintとC++で使えるようになった


メンバ変数

メンバ変数をBluePrintに公開するには、基底クラスのC++側で リフレクション付けて定義します


コード

Class


MyActor.h

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から呼び出せないため、それらのパラメータを付けることは不可能

パラメータ詳細は下記

https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Properties/Specifiers/index.html


BluePrintでの結果

private_ 変数はアクセス不能

protected_ 変数はアクセス可能

public_ 変数はアクセス可能

SnapCrab_BP_MyActor_2016-9-7_18-32-49_No-00.png


メソッド

*パラメータ一覧

下記をみるとよい

今回調査するのは

パラメータなし、BluePrintCallable、BlueprintImplementableEvent

のみ

https://docs.unrealengine.com/latest/JPN/Programming/UnrealArchitecture/Reference/Functions/index.html


パラメータ無し


MyActor.h

    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で使う関数には指定する必要がある

SnapCrab_BP_MyActor_2016-9-7_20-11-32_No-00.png


BluePrintCallable


MyActor.h

    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


MyActor.h

    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


MyActor.h

//  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; };

ヘルプによると 所有するオブジェクトに影響を与えることなく・・・

らしく、戻り値がなければダメのようだ


結果

SnapCrab_BP_MyActor_2016-9-7_20-11-32_No-00.png

パラメータ無しはBluePrintに表示されない

ImplementableEventは イベントとして表示。ただし戻り値を設定すると表示されない

Callableは実行品が両端についた通常の関数

Pureは実行ピンのない、値をかえすだけのもの


ついでに、リフレクションに指定できる型

全ての型がリフレクションで使えるわけではない

基本的には Unrealで管理されたもの(UObjectを継承し、ガーベージコレクション等に対応したもの)しか送れない

一部のプリミティブ型は送れる


hoge.h

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を継承しているクラスポインタは渡せる

SnapCrab_BP_MyActor_2016-9-7_22-14-16_No-00.png