본문 바로가기
언리얼_엔진_게임개발_공부/언리얼 C++

언리얼 인터페이스 레퍼런스 - TScriptInterface

by jaboy 2025. 5. 3.

적 캐릭터를 정의하는 IEnemyInterface 인터페이스 가 있다고 가정하자.

이 적 액터에 대한 레퍼런스를 인터페이스 타입으로 변수화하고 싶을 때, TScriptInterface 를 사용할 수 있다.

TScriptInterface<InInterfaceType>

TScriptInterface 는 FScriptInterface 구조체를 템플릿화한 래퍼 클래스이다.

template <typename InInterfaceType>
class TScriptInterface : public FScriptInterface

 

FScriptInterface 는 오브젝트를 가리키는 포인터와 인터페이스를 가리키는 포인터를 담고 있다.

class FScriptInterface
{
private:
    /**
     * A pointer to a UObject that implements an interface.
     */
    TObjectPtr<UObject>    ObjectPointer = nullptr;

    /**
     * For native interfaces, pointer to the location of the interface object within the UObject referenced by ObjectPointer.
     */
    void*     InterfacePointer = nullptr;

 

즉, TScriptInterface 를 통해 특정 오브젝트와 그 오브젝트가 구현하는 인터페이스에 대한 레퍼런스를 저장할 수 있다.

 

예를 들어, 플레이어 컨트롤러에서 IEnemyInterface 를 구현한 적 캐릭터 위로 마우스 호버를 판단하기 위해서는 아래와 같은 로직을 사용할 수 있다.

TScriptInterface<IEnemyInterface> ThisActor;

FHitResult CursorHit;
GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
if (!CursorHit.bBlockingHit) return;

ThisActor = CursorHit.GetActor();

if (ThisActor)
{
	// ThisActor 는 IEnemyInterface 를 구현함
}

 

TScriptInterface 에서 operator= 와 bool() 연산자를 어떻게 오버로딩하는지 살펴보면 다음과 같다.

 

operator=

여러 오버로딩 함수 중 TObjectPtr 를 받는 경우를 살펴보았다.

template <typename ObjectType>
TScriptInterface& operator=(TObjectPtr<ObjectType> SourceObject)
{
    *this = TScriptInterface(SourceObject);
    return *this;
}

 

이 경우 SourceObject 를 인자로 생성자에 넘긴다.

 

TScriptInterface 복사 생성자

template <typename ObjectType>
TScriptInterface(TObjectPtr<ObjectType> SourceObject)
{
    // Always set the object
    SetObject(SourceObject);

    if constexpr (std::is_base_of<InInterfaceType, ObjectType>::value)
    {
       // If we know at compile time that we got passed some subclass of InInterfaceType, set it
       // without a cast (avoiding the cast also allows us to not require linking to its module)
       SetInterface(SourceObject.Get());
    }
    else
    {
       // Tries to set the native interface instance, this will set it to null for BP-implemented interfaces
       InInterfaceType* SourceInterface = Cast<InInterfaceType>(ToRawPtr(SourceObject));
       SetInterface(SourceInterface);
    }
}

 

1. SetObject 는 무조건 호출되어 인터페이스를 구현하는지 여부와 관련 없이 객체에 대한 레퍼런스를 저장한다.

* 아래와 같이 구현된 SetObject 는 FScriptInterface 구조체 내 ObjectPointer 에 SourceObject 를 대입한다.

// TScriptInterface::SetObject
FORCEINLINE void SetObject( UObjectType* InObjectPointer )
{
    FScriptInterface::SetObject(const_cast<UObject*>(InObjectPointer));
}


// FScriptInterface::SetObject
FORCEINLINE void SetObject( UObject* InObjectPointer )
{
    ObjectPointer = InObjectPointer;
    if ( ObjectPointer == nullptr )
    {
        SetInterface(nullptr);
    }
}

 

2. if constexpr (is_base_of ...) 체크를 통해 컴파일 타임에 지정된 인터페이스를 구현하는지 확인한다.

2-1. true 인 경우, SetInterface 를 호출한다.

* 아래와 같이 구현된 SetInterface 는 구조체 내 InterfacePointer 에 InInterfaceType* 로 변환된 raw pointer 를 대입한다.

// TScriptInterface::SetInterface
FORCEINLINE void SetInterface(InInterfaceType* InInterfacePointer)
{
    FScriptInterface::SetInterface((void*)InInterfacePointer);
}

// FScriptInterface::SetInterface
FORCEINLINE void SetInterface( void* InInterfacePointer )
{
    InterfacePointer = InInterfacePointer;
}

 

2-2. false 인 경우, Cast<InInterfaceType> 하여 SetInterface 를 통해 InterfacePointer 를 초기화한다.

따라서 Cast 가 실패하는 경우, 즉 InInterfaceType 을 구현하지 않는 경우, ObjectPointer 는 객체를 가리키나 InterfacePointer는 nullptr 이다.

 

operator bool()

FORCEINLINE explicit operator bool() const
{
    return GetInterface() != nullptr;
}

 

위의 예시처럼 if (ThisActor) 와 같이 호출하면 GetInterface() 를 체크하여 인터페이스 포인터가 유효한지 검사한다.

따라서, 맨 위의 예시를 주석과 함께 다시 살펴보자면

// 오브젝트 포인터와 인터페이스 포인터를 갖고 있는 구조체 템플릿 wrapper
TScriptInterface<IEnemyInterface> ThisActor;

FHitResult CursorHit;
GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);
if (!CursorHit.bBlockingHit) return;

// 내부적으로 객체를 인터페이스 타입으로 변환 시도하여 인터페이스 포인터에 저장
// 변환 실패 시 인터페이스 포인터는 nullptr
ThisActor = CursorHit.GetActor();

if (ThisActor) // 내부적으로 인터페이스 포인터 != nullptr 의 결과값 bool 반환
{
	// ThisActor는 IEnemyInterface 를 구현함
}

로 이해할 수 있다.