인터페이스(Interface) 란?

인터페이스란 추상적인 개념으로 서로 관계없는 것들에 대한 시스템이나 장치를 작동시키기 위한 규제 또는 규약이라고 생각하면 될 것 같다.

쉽게 예를들어 리모콘은 사람과 TV의 인터페이스이며, 언어는 사람과 사람사이의 인터페이스이다.

언리얼 엔진에서는 잠재적으로 무관한 클래스 세트가 공통의 함수 세트를 구현할 수 있도록 하는 경우에 쓰인다.

인터페이스가 없다면 유사성이 없었을 크고 복잡한 클래스들에게 어떤 게임 함수나 기능을 공유시키고자 하는 경우에 많이 쓰인다.

언리얼의 예로 트리거 볼륨에 들어가면 함정, 경보, 점수 등의 다양한 로직을 구현할 수 있다. 그런데 모두 트리거 볼륨에 들어갈 것이라는 인터페이스를 갖고 구현한다면, 함수를 부르기도, 구현을 나누기도 편해진다.

언리얼에서 인터페이스 선언

인터페이스 클래스 선언은 일반 언리얼 클래스 선언과 비슷하나, 2 가지 정도의 차이점이 있다.

  1. 인터페이스 클래스는 UCLASS 매크로가 아닌 uinterface 매크로를 사용한다. UObject를 직접 상속하는 대신 UInterface를 상속한다.
  2. UINTERFACE 클래스는 실제 인터페이스가 아니다. 언리얼 엔진의 리플렉션 시스템에 보이도록 하기 위해서만 존재하는 비어있는 클래스다. 다른 클래스에서 상속하게되는 실제 인터페이스는 같은 클래스 이름에 첫 글자만 “U” 에서 “I” 로 바뀐다.
#pragma once

#include "ReactToTriggerInterface.generated.h"

UINTERFACE(Blueprintable)
class UReactToTriggerInterface : public UInterface
{
    GENERATED_BODY()
};

class IReactToTriggerInterface
{    
    GENERATED_BODY()

public:
    /* 이 오브젝트를 활성화시키는 트리거 볼륨에 반응합니다. 반응에 성공하면 true 를 반환합니다. */
    UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="Trigger Reaction")
    bool ReactToTrigger() const;
};

이렇게 UINTERFACE 매크로가 선언되고 UInterface를 상속받는 UReactToTriggerInterface가 있고, 인터페이스에 들어갈 함수들을 선언한다.

예를 들어 트리거볼륨에 닿으면 호출될 ReactToTrigger() 함수를 선언했다고 치자.

#include "ReactToTriggerInterface.h"

bool IReactToTriggerInterface::ReactToTrigger() const
{
    return false;
}

이에 대한 구현부로 ReactToTriggerInterface의 cpp 파일에서는 그저 이렇게 적당한 구현을 남겨주면 된다.

C++로 작성중인 UObject 기반의 클래스들에서 인터페이스를 사용하려면 “I”로 시작하는 인터페이스 클래스를 상속하기만 하면 된다.

class ATrap : public AActor, public IReactToTriggerInterface
{
    GENERATED_BODY()

public:
    virtual bool ReactToTrigger() const override;
};

이렇게 다중 상속 시키면 된다. pure virtual (순수 가상함수)가 지원되기도 한다.

특정 클래스에 특정 인터페이스가 구현되어있는지 여부를 확인하려면 ImplementsInterface 함수를 쓰면 된다.

// UReactToTriggerInterface 인터페이스를 상속받았을 경우 true가 반환됨
bool bIsImplemented = OriginalObject->GetClass()->ImplementsInterface(UReactToTriggerInterface::StaticClass()); 

// OriginalObject가 UReactToTriggerInterface를 구현한다면, ReactingObject에 대한 Cast는 성공적일 것이다.
IReactToTriggerInterface* ReactingObject = Cast<IReactToTriggerInterface>(OriginalObject);