Когда мы разрабатываем на C++, Java или любом другом объектно-ориентированном языке программирования, мы часто используем концепцию интерфейсов ООП. В C++ они обычно реализуются с использованием полностью абстрактного класса без членов, т.е. класса, содержащего только чистые виртуальные функции.

Однако при разработке классов чертежей C++ с помощью Unreal Engine напрямую использовать такой синтаксис невозможно. Действительно, Unreal Engine имеет особый синтаксис для интерфейсов. Это подробно описано в вики Unreal Engine, здесь, но то, что мы предлагаем в этой статье, — это краткий код шагов объявление интерфейса, реализация интерфейса, использование интерфейса, а также некоторые подробности о TScriptInterface.

Объявление интерфейса.

Допустим, нам нужен интерфейс BroadcastListener с единственным методом OnBroadcastReceived(const FString& Message). Вот как это объявить (в файле BroadcastListener.h):

UINTERFACE(BlueprintType)
class BROADCAST_API UBroadcastListener : public UInterface { 
  GENERATED_BODY() 
}; 
class BROADCAST_API IBroadcastListener {
  GENERATED_BODY()
public:
  UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Broadcast")
  int32 OnBroadcastReceived(const FString& Message);
};

Мы можем отметить, что необходимо определить как UBroadcastListener, так и IBroadcastListener, используя представленный синтаксис. Кроме того, чтобы использовать UInterface, нам нужно включить UObject/Interface.h, и функция должна что-то вернуть, даже если мы проигнорируем результат после.

Реализация интерфейса.

Теперь, когда мы определили интерфейс, мы хотим определить реализацию этого интерфейса. Опять же, мы не можем изобрести синтаксис для использования, мы должны использовать следующий. Для заголовочного файла:

UCLASS(BlueprintType)
class BROADCAST_API ULogBroadcastListener : public UObject, public IBroadcastListener // We inherit both UObject and IBroadcastListener {
  GENERATED_BODY()
public:
  UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Broadcast")
  int32 OnBroadcastReceived(const FString& Message); // This is the prototype declared in the interface
  virtual int32 OnBroadcastReceived_Implementation(const FString& Message) override; // This is the declaration of the implementation 
};

Здесь можно отметить, что имя функции реализации должно соответствовать именно этой схеме: InterfaceFunctionName_Implementation.

Теперь для исходного файла:

int32 ULogBroadcastListener::OnBroadcastReceived_Implementation(const FString& Message) {
  UE_LOG(BroadcastLog,Warning,TEXT("Message: %s"), *Message);
}

Конечно, мы можем реализовать здесь любую логику. Этот ULogBroadcastListener только регистрирует сообщение.

Использование интерфейса.

Теперь мы увидим, как можно использовать этот интерфейс, т. е. как можно хранить ссылку на интерфейс и как мы можем вызывать методы, определенные интерфейсом.

Сохранение ссылки на интерфейс

Допустим, мы хотим иметь IBroadcastListener как свойство класса, чтобы пользователь мог привязать прослушиватель по своему выбору. Вот как это будет определено в UCLASS:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Broadcast") TScriptInterface<IBroadcastListener> BroadcastListener

Ключевым моментом здесь является использование TScriptInterface, обязательного для хранения ссылки на интерфейс.

Установка эталона программно

Допустим, мы хотим программно установить значение свойства BroadcastListener. Вот как:

ULogBroadcastListener* LogBroadcastListener = NewObject<ULogBroadcastListener>(); // Instantiating the ULogBroadcastListener BroadcastListener.SetObject(LogBroadcastListener); // BroadcastListener is of type TScriptInterface 
BroadcastListener.SetInterface(Cast<IBroadcastListener>(LogBroadcastListener));

Вызов метода интерфейса

Теперь, когда мы хотим вызвать метод, определенный интерфейсом (например, для запуска события получения широковещательной рассылки), мы будем использовать следующий синтаксис:

UObject* BroadcastListenerObject = BroadcastListener.GetObject(); IBroadcastListener::Execute_OnBroadcastReceived(BroadcastListenerObject, Message); // Message is of type FString

Ключевым моментом здесь является вызов метода OnBroadcastReceived: нам нужно сделать статический вызов функции интерфейса с именем Execute_NameOfTheInterfaceMethod. Первым параметром этой функции является объект, для которого мы хотим вызвать функцию, а затем у нас есть все параметры, которые мы хотим передать этой функции.

Первоначально опубликовано на isaratech.com 16 ноября 2017 г.