C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4/C++ Interface の作り方

おさらい: ふつうの C++ の “インターフェース"(=抽象型)

C++er 的には virtual なメンバー関数に =0; 定義を入れた Abstruct な class ( or struct ) を作れば広義のプログラミング言語の意味での Interface になるって思うじゃん・w・

// ふつうの C++ 的インターフェース(=抽象型)
struct my_interface
{
  virtual float get_my_value() = 0;
};

// ふつうの C++ 的インターフェースの実装
struct my_implementation
  : public my_interface
{
  float get_my_value() override { }
};

ほんだい: UE4/C++ で Blueprint 対応の「 UE4 の世界のインターフェース」

UE4/C++ で Blueprint 対応の「 UE4 の世界のインターフェース」を定義するのはこれに比べてしまうと少々めんどくさくなる。

// === MyInterface.h ===
// 手間暇(1): UINTERFACE で UInterface を public 継承した U プリフィックスのクラスを定義する
UINTERFACE(BlueprintType)
class MyApp_API UMyInterface: public UInterface
{
  // 要注意: ここでもこちらは U プリフィックス版を使う
  GENERATED_UINTERFACE_BODY()
};

// 手間暇(2): UE4のクラス定義マクロを付けずに先に定義した U プリフィックスのクラスの I プリフィックス版のクラスを定義する
class MyApp_API IMyInterface
{
  // 要注意: ここでもこちらは I プリフィックス版を使う
  GENERATED_IINTERFACE_BODY()
public:
  // 手間暇(3) Blueprint へ関数を公開するめんどくさいマクロをだらだら書く(めんどくさいでござる@w@)
  UFUNCTION( BlueprintNativeEvent, BlueprintCallable, Category = "MyInterface" ) float GetMyValue();
};
// === MyInterface.cpp ===
// 手間暇(4): GENERATED_UINTERFACE_BODY が宣言だけ自動的に生成する U プリフィックス版の方の ctor を定義する
UMyInterface::UMyInterface( const class FObjectInitializer& ObjectInitializer ): Super( ObjectInitializer ) { }

// 手間暇(5): ここでようやく C++ 的な virtual-override における virtual 宣言された基底クラスのメンバー関数を定義
// Note: どこで↓の宣言に相当する virtual retur_type FunctionName_Implementation(); が行われているのかは後述。
float GetMyValue_Implementation() { return 3.14159265358979f; }
// === MyImplementation.h
UCLASS()
class MyImplementation
  : public AActor
  , public IMyInterface
{
  GENERATED_BODY()
protected:
  // 手間暇(6): インターフェースを実装する UE4 オブジェクトクラスでの override 実装の宣言
  float GetMyValue_Implementation() override;
};
// === MyImplementation.cpp
// 手間暇(7): インターフェースを実装する UE4 オブジェクトクラスでの override 実装の定義
float MyImplementation::GetMyValue_Implementation() { return 1.41421356f; }

これで、例えば IMyUnitFunctions など適当なインターフェースで共通の何らかの Getter 群を継承、実装しつつ APawn から派生した AMyPawnACharacter から派生した AMyCharacter について、ブループリントで GetPlayerPawn から Cast To IMyUnitFunctions して IMyUnitFunctions インターフェースを持つオブジェクトを得て、共通の Getter で得た値を表示したり、あるいは何かに使えるようになる。

インターフェースを使わない場合は、 Tag で頑張ったり、 Get All Actors of XXXX から For EachBranch したり、あるいはそこまででなくとも Branch 地獄を書けば同じような事はたぶん表面上は実用可能な程度で実装できなくもないだろうけど、少々準備が面倒でも UE4 のインターフェースとして C++ コードを実装して綺麗に扱った方が良いだろう。

おまけ: xxxx_Implementation の宣言はいつのまにどこでされるのか?

xxxx_Implementation なる override 定義している関数の元の virtual な定義は、 xxxx.generated.h の中で、

#define MyApp_Source_MyApp_MyImplements_h_14_RPC_WRAPPERS \
   virtual float GetMyValue_Implementation(); \
 \
   DECLARE_FUNCTION(execGetMyValue) \
   { \
       P_FINISH; \
       P_NATIVE_BEGIN; \
       *(float*)Z_Param__Result=this->GetMyValue_Implementation(); \
       P_NATIVE_END; \
   }

などのように UE4 のヘッダープリプロセッサーと CPP 黒魔術によって生成されている。興味があればビルドプロセスのヘッダープリプロセッサーによって生成後の xxxx.generated.h を読んでみるとよい。

参考

  1. Interfaces in C++ - Epic Wiki