Help us understand the problem. What is going on with this article?

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away