投稿日:2021/12/22

UE4のC++は何故実装していないはずの処理まで動作するのか?

   

UE4のC++は何故実装していないはずの処理まで動作するのか?

こんにちは!
代表兼プログラマーの川本です。

今回はUE4でC++のコードをコンパイルする際に何がおきているのかを簡単(?)に解説してみようかと思います。

 

UE4におけるC++のコード

UE4ではC++のクラスを定義する際にUCLASSやUPROPERTYといったおまじないのようなコードが頻繁に出てきます。

例としては以下のようなコードです。

#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    AMyActor();

    // ブループリントから参照や変更が可能な変数
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    int32 TotalDamage;
};

今回はAActorを継承したクラスを定義しています。
クラスの頭にはUCLASS()やGENERATED_BODY()という通常のC++には存在しない記述があります。
メンバ変数のTotalDamageではUPROPERTYでブループリントから編集可能な値と指定することまでできます。

これら通常のC++に存在していない記述は一体何者なのでしょうか?

学生やUE4に明るくない方はマクロの定義に飛んで以下のコード見ることで更に『?』が浮かんでくるかと思います。

#define UCLASS(...)
#define UPROPERTY(...)
#define GENERATED_BODY(...)

なんとマクロの中身は空!?

では何故、UPROPERTYで指定するだけでブループリントから参照できるようになったりと動作が変わるのでしょうか?
まさかUE4の魔法なのか…?

 

UE4における謎のマクロの正体

上で記載したようにUE4では空のマクロにドキュメントに記載されている設定を打ち込むだけで様々な動作を簡単に記述することができます。

マクロの中身を見ても空なのでUE4独自のC++で普通のC++ではないように感じますが
実はUE4も一般的なC++と何ら違わないプログラムなのでちゃんとC++のルールに基づいて実装されています。

では自分では実装していないブループリントから参照する処理は何故マクロに設定を記載するだけで使えるようになるのでしょうか?

これはUE4のコンパイル前の処理に秘密があります。
UE4ではUCLASSやUPROPERTYといった記述を元にコンパイル前にコードを解析して *.generated.h といった自動生成のヘッダーを出力してくれています。

今回もこのコードを記載していますよね。

#include "MyActor.generated.h"

これはマクロに指定した設定を元に生成されるヘッダーをインクルードするための記述です。
勘のいい人は作成していないヘッダーでgeneratedという名前から気付いていたかもしれませんね。

この自動生成されるヘッダーにGENERATED_BODYのマクロとして設定に応じた定義が出力されて
クラス内におまじないのように記述したマクロ達が実際のコードとして動作するようになります。

なのでUE4のUCLASSやUPROPERTYといったマクロは
C++のコンパイル前にコードを自動生成するための設定だったということです。

Windows(64bit)環境のビルドでは出力されたコードが次のフォルダに格納されています。
[プロジェクトルート]/Intermediate/Build/Win64/[プロジェクト名]/Inc

全てマクロでかつ別フォルダに出力されることでVisual Studioの定義参照から飛べなくなっているため
慣れない人には理解しにくい流れになっていますが、分かってしまえばそこまで難しいことはありません。
※ちなみに定義と一部の実装は*.generated.hに出力されますが、残りの実装は*.gen.cppに出力されます。

 

まとめ

ここまで記載してきた通り、UE4ではプログラムコードからプログラムを作り出すという仕組みが導入されています。
コンパイル直前にこのコード自動生成処理を入れることで少ないコード(コスト)で大きな恩恵を得られるような仕組みなっています。

通常であればブループリントで参照するためにコード量が数十倍に膨らんでいるはずです。
UE4はこういったアプローチでC++でありながらC#にも劣らない自由度と簡単なプログラミングを実現しています。

設定によっては、関数を呼び出しただけで同期が取れる通信処理やメンバ変数のセーブ処理なんかも実現できます。

UE4が何故実装していない複雑な機能を簡単に実現できるのか?というのはこれらのエンジンの仕組みの上で実装を行っているからなのです。

もしもUE4のプログラムをよく分からずに使っている方はコンパイルの流れを知って一歩進んだUE4ライフをお楽しみください!