UE4 インターフェイスについてのメモ


概要

UnrealEngine のインターフェイスについてのメモ書きです。

環境

Windows10
Visual Studio 2017
UnrealEngine 4.25

参考

以下を参考にさせて頂きました、ありがとうございます。

Unreal Engine : 公式
【Unreal C++】⑥Interface【UE4】
Interfaces In C++ | UE4 Community Wiki
【UE4】InterfaceとDispatcherの使い分け方-自分流

インターフェイスについて

インターフェイスは実装を縛る仕組みでクラスの依存度を下げるのに有用です。
C++にはインターフェイスがないため継承や純粋仮想クラスを使ったインターフェイスクラスで実現していますが、Unreal C++ も似たような感じのようです。

BPにてアクセスしたい相手BPクラスにキャストするケースがありますが、こういう場合にインターフェイスを使うことが望ましいようです。これはBPの参照が増えることによるビルド時間が延びてしまうことを抑えることができるためだと思われます。

Unreal C++ でのインターフェイス作成1

インターフェイス定義

UInterface を継承したクラスを宣言し、UプリフィックスをIに変えたクラスを宣言し、実装を縛るメソッドを宣言します。この時、GENERATED_***_BODY() が UI で違うことに注意。

またメタ情報の CannotImplementInterfaceInBlueprint はC++での実装に制限する指定です。

MyInterface.h
#pragma once

#include "CoreMinimal.h"
#include "MyIntarface.generated.h"

UINTERFACE(BlueprintType, meta = (CannotImplementInterfaceInBlueprint))
class MYPROJECT2_API UPerson : public UInterface
{
    GENERATED_UINTERFACE_BODY()
};

class MYPROJECT_API IPerson
{
    GENERATED_IINTERFACE_BODY()

public:
    // 実装したいメソッド
    virtual FString     Greeting();
};

Uプリフィックスのコンストラクタと、Iプリフィックスの実装メソッドデフォルト実装を書きます。この時点でインターフェイスぽくないです。。

MyInterface.cpp
#include "MyInterface.h"

UPerson::UPerson(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
}

FString IPerson::Greeting()
{
    return( FString(TEXT("....")) );
}

C++でインターフェイスを実装する

あとはインターフェイスクラスを継承してクラス宣言します。

MyClass.h

UCLASS()
class MYPROJECT_API UJapanese : public UObject, public IPerson
{
    GENERATED_BODY()

public:
    // オーバーライドする
    virtual FString     Greeting() override { return( FString::Printf(TEXT("こんにちは!")) ); }
};

デフォルト実装を持っているので、そちらを呼び出すこともできます。

MyClass.h
UCLASS()
class MYPROJECT_API UAmerican : public UObject, public IPerson
{
    GENERATED_BODY()

public:
    // 元の実装を呼び出す
    virtual FString     Greeting() override { return(IPerson::Greeting()); }
};

Unreal C++ でのインターフェイス作成2

インターフェイス定義

BlueprintImplementableEvent BlueprintNativeEvent をつけたインターフェイスを定義します。

MySampleInterface.h
#pragma once
#include "MySampleInterface.generated.h"

UINTERFACE(Blueprintable)
class UMySampleInterface : public UInterface
{
    GENERATED_UINTERFACE_BODY()
};

class IMySampleInterface
{
    GENERATED_IINTERFACE_BODY()

public:

    UFUNCTION(Category="Test", BlueprintImplementableEvent, BlueprintCallable)
    void Func0();
    UFUNCTION(Category="Test", BlueprintImplementableEvent, BlueprintCallable)
    void Func0Arg(int32 Value);
    UFUNCTION(Category="Test", BlueprintNativeEvent, BlueprintCallable)
    int32 Func1();
    UFUNCTION(Category="Test", BlueprintNativeEvent, BlueprintCallable)
    int32 Func1Arg(int32 Value);
};
MySampleInterface.cpp
#include "MySampleInterface.h"

UMySampleInterface::UMySampleInterface(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
}

実装インターフェイス

実装インターフェイス で実装してみます。

関数は以下の様になります。

Func0Func0Arg はイベントで実装します。

Func1Func1Arg は関数で実装します。

呼び出しテスト

以下のコードを使ってインターフェイスの呼び出しを行います。

.cpp
#include "Kismet/GameplayStatics.h"

TArray<AActor*> _TargetActors;
// インターフェース取得
UGameplayStatics::GetAllActorsWithInterface(this->GetWorld(), UMySampleInterface::StaticClass(), _TargetActors);
// インターフェースを実行
for (auto _Actor : _TargetActors)
{
    IMySampleInterface::Execute_Func0(_Actor);
    IMySampleInterface::Execute_Func0Arg(_Actor, 100);
    IMySampleInterface::Execute_Func1(_Actor);
    IMySampleInterface::Execute_Func1Arg(_Actor, 200);
}

BPで呼び出す場合は以下の様になります。

以下の様になりました。問題なく呼び出せました。

継承インターフェイス

継承インターフェイス で実装してみます。

関数は以下の様になります。

実装インターフェイスの場合と同様に実装でき、同様の結果になります。

BPでインターフェイスを実装する

BPエディタで開いて
[クラス設定] -> [詳細] -> [インターフェイス] で追加できます。

CannotImplementInterfaceInBlueprint を付与したインターフェイスは実装インターフェイスでは追加できません。

継承インターフェイス はBPが継承したクラスが実装しているインターフェイスが表示されます。

BP でのインターフェイス作成

インターフェイス定義

コンテンツフォルダから右クリックで「ブループリントインターフェイス」で作成できます。

BPインターフェイスにて関数名や引き数、返り値が設定できます。

BPクラスでの実装

このBPインターフェイスを実装したいBPクラスに対し実装インターフェイスで追加して使用します。[クラス設定] -> [詳細] -> [インターフェイス]

追加するとインターフェイスイベントを実装できるようになります。

まとめ

UEのインターフェイスはイベントメッセージ的な面が強い気がします。
クラスの依存度を下げるためにも積極的に使っていきたいところですが、定義の記述方法はもう少しどうにかなりませんかね。。