概要
UnrealEngine のインターフェイスについてのメモ書きです。
環境
Windows10
Visual Studio 2017
UnrealEngine 4.25, 5.3
更新履歴
日付 | 内容 |
---|---|
2020/11/09 | 初版 |
2024/08/07 | インターフェイスの実装確認について追記 |
2024/10/21 | インターフェイスの参照保持について追記 |
参考
以下を参考にさせて頂きました、ありがとうございます。
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() が U
と I
で違うことに注意。
またメタ情報の CannotImplementInterfaceInBlueprint
はC++での実装に制限する指定です。
#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プリフィックスの実装メソッドデフォルト実装を書きます。この時点でインターフェイスぽくないです。。
#include "MyInterface.h"
UPerson::UPerson(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
FString IPerson::Greeting()
{
return( FString(TEXT("....")) );
}
C++でインターフェイスを実装する
あとはインターフェイスクラスを継承してクラス宣言します。
UCLASS()
class MYPROJECT_API UJapanese : public UObject, public IPerson
{
GENERATED_BODY()
public:
// オーバーライドする
virtual FString Greeting() override { return( FString::Printf(TEXT("こんにちは!")) ); }
};
デフォルト実装を持っているので、そちらを呼び出すこともできます。
UCLASS()
class MYPROJECT_API UAmerican : public UObject, public IPerson
{
GENERATED_BODY()
public:
// 元の実装を呼び出す
virtual FString Greeting() override { return(IPerson::Greeting()); }
};
Unreal C++ でのインターフェイス作成2
インターフェイス定義
BlueprintImplementableEvent
BlueprintNativeEvent
をつけたインターフェイスを定義します。
#pragma once
#include "MySampleInterface.generated.h"
UINTERFACE(Blueprintable)
class UMySampleInterface : public UInterface
{
GENERATED_BODY()
};
class IMySampleInterface
{
GENERATED_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);
};
GENERATED_BODY()
マクロで作成した場合は、デフォルト実装のコンストラクタが不要で、GENERATED_UINTERFACE_BODY()
マクロで作成した場合はデフォルト実装のコンストラクタが必要の様です。
実装インターフェイス
呼び出しテスト
以下のコードを使ってインターフェイスの呼び出しを行います。
#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エディタで開いて
[クラス設定] -> [詳細] -> [インターフェイス] で追加できます。
CannotImplementInterfaceInBlueprint
を付与したインターフェイスは実装インターフェイスでは追加できません。
継承インターフェイス
はBPが継承したクラスが実装しているインターフェイスが表示されます。
BP でのインターフェイス作成
インターフェイス定義
コンテンツフォルダから右クリックで「ブループリントインターフェイス」で作成できます。
BPインターフェイスにて関数名や引き数、返り値が設定できます。
BPクラスでの実装
このBPインターフェイスを実装したいBPクラスに対し実装インターフェイスで追加して使用します。[クラス設定] -> [詳細] -> [インターフェイス]
追加するとインターフェイスイベントを実装できるようになります。
インターフェイス実装確認
インターフェイスの実装を確認するには Implements
を使うか、キャストして確認することができます。以下サンプルコード。
// インターフェイス実装の確認(`I` ではなく `U` のほうで指定する)
if( _Object->Implements<UMySampleInterface>() ){
// 実装されている
}
// キャストしてインターフェイスの実装を確認する
auto _CastObject = Cast<IMySampleInterface>(_Object);
if( _CastObject){
// 実装されている
}
インターフェイスの参照保持
UPROPERTY()
をつけてインターフェイスの参照を保持するには TScriptInterface
を使う必要があります。以下サンプルコード。
UPROPERTY()
TScriptInterface<IMyInterface> MyInterface;
オブジェクトとインターフェイスを両方設定します。
// MyObject からインターフェイス参照を保持する
MyInterface.SetObject(MyObject);
MyInterface.SetInterface(Cast<IMyInterface>(MyObject));
// インターフェイスに定義されている SampleProc() メソッドを使う
MyInterface.GetInterface()->SampleProc();
まとめ
UEのインターフェイスはイベントメッセージ的な面が強い気がします。
クラスの依存度を下げるためにも積極的に使っていきたいところですが、定義の記述方法はもう少しどうにかなりませんかね。。