3 분 소요

인터페이스 이해하기

인터페이스란?

  • 인터페이스 (Interface) 란 클래스 (또는 오브젝트)가 반드시 구현해야 할 함수 목록만을 미리 정의하고, 실제 동작(구현 내용)은 해당 클래스를 상속받거나 구현하는 쪽에서 자유롭게 작성할 수 있도록 하는 일종의 계약서
  • C++에서는 UInterface를 상속받아 IItemInterface 같은 인터페이스를 제작 가능하고, 언리얼 블루프린트에서도 “블루프린트 인터페이스”를 통해 비슷한 개념을 구현 가능

상속 (Inheritance)과의 차이점?

  • 상속
    • 부모 클래스의 모든 속성과 기능을 자식 클래스가 물려받는 구조
    • 부모 클래스에 구현된 로직을 자식 클래스가 그대로 사용 가능, 필요하다면 자식 클래스에서 재정의(오버라이딩)
  • 인터페이스
    • 인터페이스는 “이 함수를 반드시 만들어야 한다”라는 함수 원형 (함수 시그니처) 만을 정의
    • 실제 함수가 어떻게 동작할지는 각 자식 (또는 구현 클래스)에서 자유롭게 작성
  • 상속은 부모의 실제 구현을 가져다 쓰는 반면, 인터페이스는 “함수의 틀”만 빌려 쓰고, 그 안에 담길 코드는 직접 작성

인터페이스를 사용하면 좋은 점?

  • 결합도 (Coupling) 감소
    • 클래스 간 구체적인 구현 내용을 공유 x, 필요한 함수 목록만 약속하므로 클래스 간 의존도가 감소
    • 즉, 다른 클래스 내부가 어떻게 돌아가는지 몰라도, “이 함수를 이렇게 호출하면 된다” 정도만 알면 됨
  • 확장성 (Extensibility) 향상
    • 새로운 아이템 클래스를 만들 때, 이미 정의된 인터페이스를 구현하기만 하면 기존 시스템에 쉽게 편입 가능
  • 다형성 (Polymorphism) 극대화
    • TArray<IItemInterface*> Items;와 같은 인터페이스 포인터 배열로 관리 시, 아이템 종류가 무엇이든 같은 함수를 호출하여 다룰 수 있음

언리얼의 인터페이스 형태 (C++)

ItemInterface 인터페이스 정의

  • 공통적으로 필요한 함수를 인터페이스로 묶어두면, 다른 아이템들도 쉽게 동일한 규칙 (함수 시그니처)을 갖출 수 있음
#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "IItemInterface.generated.h"

// 인터페이스를 UObject 시스템에서 사용하기 위한 기본 매크로
UINTERFACE(MinimalAPI)
class UItemInterface : public UInterface
{
    GENERATED_BODY()
};

// 실제 C++ 레벨에서 사용할 함수 원형(시그니처)를 정의
class SPARTAPROJECT_API IItemInterface
{
    GENERATED_BODY()

public:
    // 플레이어가 이 아이템의 범위에 들어왔을 때 호출
    virtual void OnItemOverlap(AActor* OverlapActor) = 0;
    // 플레이어가 이 아이템의 범위를 벗어났을 때 호출
    virtual void OnItemEndOverlap(AActor* OverlapActor) = 0;
    // 아이템이 사용되었을 때 호출
    virtual void ActivateItem(AActor* Activator) = 0;
    // 이 아이템의 유형(타입)을 반환 (예: "Coin", "Mine" 등)
    virtual FName GetItemType() const = 0;
};

구조

  • UINTERFACE(MinimalAPI)
    • 언리얼 엔진의 리플렉션 시스템 (Reflection)을 위해 사용하는 매크로
    • 이렇게 선언해야 블루프린트나 다른 모듈에서도 해당 인터페이스를 인식하고 사용 가능
  • class UItemInterface : public UInterface
    • 실제 객체(클래스) 관리를 위한 언리얼 측 클래스, C++의 IItemInterface와 구분해 사용
  • class SPARTAPROJECT_API IItemInterface
    • 우리가 직접 구현해서 사용할 인터페이스 함수들을 정의
    • = 0;으로 끝나는 순수 가상 함수(Pure Virtual Function) 형태로 지정하므로, 반드시 이를 구현(Override)할 것
  • 이와 같이 언리얼에서 C++로 ‘인터페이스’를 구현할 때는
    UInterface + IInterface(C++)‘한 쌍’ 으로 만들어짐
구분 무엇인가 주 역할 멤버 변수 가능 UFUNCTION/UPROPERTY
UItemInterface : UInterface UObject 기반의 “타입/메타 정보” 리플렉션, “이 타입의 인터페이스를 구현함”을 엔진/블루프린트가 알아차리게 함 (의미상) X 여기서 함수는 선언하지 않음 (보통 비워둠)
IItemInterface 순수 C++ 인터페이스 본체 실제로 호출할 함수 시그니처를 선언 X 여기에 UFUNCTION을 단다 (인터페이스 함수 노출은 여기서 함). UPROPERTY는 불가

Unreal Interface 에 대한 설명

  • U와 I 한쌍으로 ‘나누는 이유?’
    BP와 C++ 에서 Interface를 안전하게 접합하기 위하여 사용
    • UInterface 는 UClass로서 리플렉션을 통해 ‘타입 표지자, 메타’ 등을 제공
    • IInterface 가 ‘호출 표면’ (UFUNRCION)을 제공
  • 이 두 클래스들이 서로 ‘엮일 수 있는’ 이유?
    GENERATE_BODY() 를 통해
    UHT(Unreal Header Tool)가
    UInterface::StaticClass() 같은 리플렉션 정보를 만들고
    이후, IInterface 에 선언된 UFUNCTION 들을 ‘인터페이스 메시지’로 등록
    이를 통해
    • obj->GetClass()->ImplementsInterface(UYourInterface::StaticClass())로 인터페이스 상속 여부 체크
      (리플렉션 정보 존재로 인하여 파악 가능)
    • IYourInterface::Execute_YourFunc(obj, args...) 로 C++/블루프린트의 구현을 통일된 경로로 호출 가능
  • 기본적으로 ‘상태’를 표현하는 멤버 변수는
    ‘인터페이스’가 아니라 실제 ‘구현 클래스’에서
    선언하는 것이 정석

C++ 에서 다루는 방식

  • BP / C++ 양쪽을 고려 호출 코드
void TryInteract(UObject* Target, AActor* InstigatorActor)
{
    if (Target && Target->GetClass()->ImplementsInterface(UInteractable::StaticClass()))
    {
        IInteractable::Execute_Interact(Target, InstigatorActor); // BP/C++ 둘 다 커버
    }
}

C++이란 것이 확실한 경우는 C++ Interface로 Cast 하여 사용도 가능하다
(다만 이때는 BP면 nullptr을 return하므로 Execute_ 방식이 안전)

  • TScriptInterface 타입
// UPROPERTY 참조를 통해서
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TScriptInterface<UInteractable> InteractableRef; // U* 타입 사용 (UHT가 요구)

// 일반 C++ (파라미터 / 리턴)
TScriptInterface<IInteractable> InteractIt;

인터페이스와 관련된 팁들

  • ‘상태’는 ‘구현 클래스’에 ‘행위’는 ‘인터페이스’에
    인터페이스에 ‘데이터/로직’이 들어가지 말 것
  • Execute_ 를 통한 호출이 안전
    (BP를 커버)

  • 단순히 ‘Tag’만 필요하다면
    ‘빈 인터페이스’ 대신에 Gameplay Tag 나 마킹용 컴포넌트를 고려해볼 것

  • 인터페이스는 ‘결합도’와 연관
    상호참조/의존이 심해질수록
    인터페이스를 통한 DI(Dependency Injection, 의존성 주입) 패턴을 추천
    (외부 클래스와 직접 연결되지 않는 방식)

댓글남기기