UnrealLIFE!

UE4やプログラミング言語に関するブログです。

C++でBlueprintから使えるInterfaceを作る

FIX: ソースコードのclass宣言で末尾にセミコロンが抜けてたので修正。
FIX: TArrayの型パラメータが特殊記法と判断されて隠れてたので修正。
FIX: はてブロの色んな記法を試してる途中で記事を非公開にしてしまっていたので修正。
FIX: 上の記法を間違えて記事にしていたので修正。

初っ端からUnrealC++の記事です。我ながら大胆。

タイトル通り、C++でBlueprintから使えるInterfaceに関する記事です。

C++でBlueprintから使えるInterfaceを作る際に、日本語情報は見当たらず、英語情報も少なめで苦労したので記事にしました。ただし、コンパイルエラーが出まくって、エラーメッセージを頼りに実装したので、マクロなどの動作までは説明できないので悪しからず。

実装:

まずはInterfaceを書きます。 漠然と書くのは厳しいので、Debug用の架空のInterfaceを想定します。

// IDebugPrintable.h

class IDebugPrintable
{
    GENERATED_IINTERFACE_BODY()
    
public:
    UFUNCTION(BlueprintImplementableEvent)
    void DebugPrint(UDebugData* DebugData, UDebugStream DebugStream);
};

インターフェースクラス名の頭にIを付けるのはC++プログラマの方にはおなじみかと思います。

GENERATED_IINTERFACE_BODY()でInterface用の情報を生成しているのはUnrealC++のGENERATED_BODY()やGENERATED_UCLASS_BODY()に似ていますね。ここは特に弄る必要は無いでしょう。

重要なのは次のUFUNCTIONとその対象の関数、そしてその関数の引数です。 UFUNCTIONではBlueprintImplementableEventを指定しています。少しBlueprintNativeEventと似ていますが一番の違いは恐らくデフォルト実装を提供するかどうかだと思います。(自信少なめ) BlueprintNativeEventを指定した場合は[関数名]_Implementationを実装する必要がありますが、BlueprintImplementableEventの場合はデフォルト実装は要求されませんでした。(Interface関数なので当たり前ですが) ここもInterfaceでは弄る必要は無いでしょう。

そして関数定義をするわけですが、関数定義の注意点として引数はどのようなタイプかによってノードの引数と戻り値に別れます。結構分かりにくい所だと思います。 具体的には、型をそのまま(int32など)やポインタ型(UObject*など)やconst(const TArray<uint8>&など)を指定した場合は引数、参照を指定した場合は戻り値になります。 このような仕様になっているのは一つの値しか返せないC++の関数の特性を考慮してだと思います。

次に、GENERATED_IINTERFACE_BODY()によって生成されたコードがUDebugPrintableを要求するので、UInterfaceを継承したクラスを作成します。

// IDebugPrintable.h

class UDebugPrintable : public UInterface
{
    GENERATED_UINTERFACE_BODY()
};

これだけです。ただ、GENERATED_UINTERFACE_BODY()はコンストラクタは生成してくれないみたいなので、.cppファイルにコンストラクタの空実装を書きます。

// IDebugPrintable.cpp

UDebugPrintable::UDebugPrintable(const class FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
}

これで実装完了です。後はUE4Editor側で適当なブループリントを作ってInterfaceが実装できるかどうか試して終わりです。

ただ、私がまだインターフェースを使った関数呼び出しなどをやっていないので、そこまでは記事にできませんでした。一応する予定はあるので、やった場合はできれば記事にしたいと思います。

最後に全ソースを載せておきます。ちなみにヘッダーのインクルードはEngine.hと自動生成されたヘッダーと.cpp側でIDebugPrintable.hとプロジェクトのヘッダーをインクルードしておけば大丈夫だと思います。

// IDebugPrintable.h

// #include "[ModuleName].h"
#include "Engine.h"
#include "IDebugPrintable.generated.h"

class UDebugPrintable : public UInterface
{
    GENERATED_UINTERFACE_BODY()
};

class IDebugPrintable
{
    GENERATED_IINTERFACE_BODY()
    
public:
    UFUNCTION(BlueprintImplementableEvent)
    void DebugPrint(UDebugData* DebugData, UDebugStream DebugStream);
};
// IDebugPrintable.cpp

// #include "[ModuleName]PrivatePCH.h"
#include "IDebugPrintable.h"

UDebugPrintable::UDebugPrintable(const class FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
}