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

  • 11
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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