언리얼 C++ 프로그래밍

[언리얼 C++] 인터페이스 클래스

언린이 2021. 7. 28. 21:54

인터페이스 클래스는 관련이 없는 클래스 세트가 공통의 함수 세트를 구현할 수 있도록 해줍니다.

그대로라면 유사성이 없었을 크고 복잡한 클래스들에 어떤 게임 함수 기능을 공유시키고자 하는 경우 매우 좋습니다.

 

 

1. 인터페이스 선언

인터페이스 클래스 선언은 일반 언리얼 클래스 선언과 비슷하지만 크게 두가지 차이점이 있습니다.

 

UINTERFACE([specifier, specifier, ...], [meta(key=value, key=value, ...)])
class UClassName : public UInterface
{
    GENERATED_BODY()
};

첫째, 인터페이스 클래스는 UCLASS 매크로 대신 UINTERFACE 매크로를 사용하며, UObject 대신 UInterface를 상속합니다.

둘째, 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;
};

함수를 헤더 파일에 직접 작성할 수 있는데, 기본적으로 아무것도 하지 않는 함수이거나 false, 0, 비어 있는 스트링 반환처럼 사소한 동작을 하는 함수의 경우 종종 그렇게 합니다. 보다 복잡한 함수는 .cpp 파일에 작성하여 컴파일 시간을 줄일 수 있습니다.

그리고 순수 가상 함수도 지원됩니다. 위의 샘플 함수는 헤더 파일에 작성해도 될 만큼 간단하지만 .cpp 파일에 작성하도록 하겠습니다.

 

#include "ReactToTriggerInterface.h"

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

접두사가 'U'인 클래스는 생성자나 기타 다른 함수가 필요치 않은 반면, 'I'인 클래스는 모든 인터페이스 함수를 포함하며, 다른 클래스에 실제로 상속되는 클래스이기도 합니다.

 

 

2. 인터페이스 지정자

인터페이스 지정자 효과
BlueprintType 이 클래스를 블루프린트에서 변수로 사용할 수 있는 유형으로 노출합니다.
DependsOn=(ClassName1, ClassName2, ...) 나열된 모든 클래스는 이 클래스에 앞서 컴파일됩니다. ClassName 은 같은 (또는 기존) 패키지의 클래스를 지정해야 합니다. 쉼표로 구분되는 단일 DependsOn 줄, 또는 각 클래스마다 별도의 DependsOn 줄을 사용하여 다중 종속 클래스를 지정할 수 있습니다. 컴파일러는 이미 컴파일된 클래스에 있는 것만 알기때문에, 다른 클래스에 선언된 구조체 또는 열거형을 사용할 때는 중요합니다.
MinimalAPI 클래스의 형 정보만 다른 모듈에서 사용할 수 있도록 익스포트하게 만듭니다. 클래스는 형변환 가능하지만, 그 클래스의 함수는 (인라인 메서드를 제외하고) 호출할 수 없습니다. 그러면 다른 모듈에서 접근할 수 없는 함수가 모두 필요치 않은 클래스에 대해 모든 것을 익스포트하지 않아 컴파일 시간이 빨라집니다.

 

 

3. C++로 인터페이스 구현

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

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

새 클래스에서 인터페이스를 사용하려면, 단순히 접두사가 'I'인 인터페이스 클래스를 상속하기만 하면 됩니다.

 

 

4. 인터페이스 구현 여부 확인

인터페이스를 구현하는 두 C++와 블루프린트 클래스 사이의 호환을 위해서는 다음 함수 중 하나를 사용하면 됩니다.

bool bIsImplemented = OriginalObject->GetClass()->ImplementsInterface(UReactToTriggerInterface::StaticClass()); // OriginalObject 가 UReactToTriggerInterface 를 구현한다면 bIsImplemented 는 true 가 됩니다.
IReactToTriggerInterface* ReactingObject = Cast<IReactToTriggerInterface>(OriginalObject); // OriginalObject 가 UReactToTriggerInterface 를 구현한다면 ReactingObject 는 null 이외의 값이 됩니다.

 

 

5. 다른 언리얼 유형으로의 형변환

언리얼 엔진의 형변환 시스템은 한 인터페이스에서 다른 인터페이스로, 또는 인터페이스에서 적합한 경우 언리얼 유형으로의 형변환을 지원합니다.

IReactToTriggerInterface* ReactingObject = Cast<IReactToTriggerInterface>(OriginalObject); // 인터페이스가 구현된 경우 ReactingObject 는 null 이외의 값이 됩니다.
ISomeOtherInterface* DifferentInterface = Cast<ISomeOtherInterface>(ReactingObject); // ReactingObject 가 null 이외의 값이고 ISomeOtherInterface 를 구현하는 경우 DifferentInterface 는 null 이외의 값이 됩니다.
AActor* Actor = Cast<AActor>(ReactingObject); // ReactingObject 가 null 이외의 값이고 OriginalObject 는 AActor 또는 AActor 파생 클래스인 경우, Actor 는 null 이외의 값이 됩니다.

 

 

6. 블루프린트에서의 구현

블루프린트가 인터페이스를 구현할 수 있도록 하려면, Blueprintable 메타데이터 지정자를 사용해야 합니다.

그리고 블루프린트 클래스가 덮어쓰려는 모든 인터페이스 함수는 BlueprintNativeEvent 또는 BlueprintImplementableEvent 여야 합니다. BlueprintCallable로 마킹된 함수는 호출은 가능하지만 덮어쓰기는 불가능합니다.

이 외에 다른 모든 함수들은 블루프린트에서 접근할 수 없습니다.