적 캐릭터를 정의하는 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 를 구현함
}
로 이해할 수 있다.
'언리얼_엔진_게임개발_공부 > 언리얼 C++' 카테고리의 다른 글
[언리얼/C++] 굴러다니며 벽에 부딪힐 때 방향 바뀌는 바위 (반사 벡터) (0) | 2025.02.14 |
---|---|
[언리얼/C++] 디버프 - 코드로 UTextBlock 동적 생성 및 추가 (0) | 2025.02.13 |
[언리얼/C++] UUserWidget 인스턴스에 RemoveFromParent 호출 시 메모리 해제? (0) | 2025.02.10 |
[언리얼/C++] ApplyDamage / TakeDamage 파헤쳐보자 (1) | 2025.02.07 |
[언리얼/C++] TSubclassOf 와 TSoftClassPtr - Get() 함수 비교하기 (0) | 2025.02.06 |