본문 바로가기

언리얼 + cpp

언리얼C++ 7일차(인터페이스)

인터페이스

인터페이스 (Interface)란 클래스 (또는 오브젝트)가 반드시 구현해야 할 함수 목록만을 미리 정의해 두고, 실제 함수의 동작(구현 내용)은 해당 클래스를 상속받거나 구현하는 쪽에서 자유롭게 작성할 수 있도록 하는 것이다.

C++에서는 UInterface를 상속받아 IItemInterface 같은 인터페이스를 만들 수 있으며, 언리얼 블루프린트에서도 “블루프린트 인터페이스”를 통해 비슷한 개념을 구현할 수 있습니다.

인터페이스의 장점

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

인터페이스 정의

  • 작은 코인, 큰 코인, 지뢰, 힐링 아이템 등 다양한 아이템은 모두 “플레이어와 오버랩되거나”, “사용 (Activate)될 때” 특정 동작을 수행합니다.
  • 이처럼 공통적으로 필요한 함수를 인터페이스로 묶어두면, 다른 아이템들도 쉽게 동일한 규칙 (함수 시그니처)을 갖출 수 있습니다.
// ItemInterface.h
#pragma once

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

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

// 실제 C++ 레벨에서 사용할 함수 원형(시그니처)를 정의
class UNREAL_CPP_STUDY_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 UNREAL_CPP_STUDY_API IItemInterface
    • 직접 구현해서 사용할 인터페이스 함수들을 정의합니다.
    • = 0;으로 끝나는 순수 가상 함수(Pure Virtual Function) 형태이므로, 이를 상속받는 파생클래스는 반드시 이를 구현(Override)해야 합니다.
인터페이스는 순수 가상 함수만 선언하고 구현을 하지 않기에 cpp가 필요 없지만 삭제는 하지 않고 그대로 두는것이 좋다.

BaseItem

Actor와 ItemInterface를 상속받은 BaseItem 클래스를 만들고 인터페이스에서 선언한 함수를 구현한다.

// BaseItem.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemInterface.h"  // 만들어둔 인터페이스 헤더 포함
#include "BaseItem.generated.h"

UCLASS()
class UNREAL_CPP_STUDY_API ABaseItem : public AActor, public IItemInterface
{
    GENERATED_BODY()

public:    
    ABaseItem();
    
protected:
	// 아이템 유형(타입)을 편집 가능하게 지정
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	FName ItemType;

	// IItemInterface에서 요구하는 함수들을 반드시 구현
	virtual void OnItemOverlap(AActor* OverlapActor) override;
	virtual void OnItemEndOverlap(AActor* OverlapActor) override;
	virtual void ActivateItem(AActor* Activator) override;
	virtual FName GetItemType() const override;
		
	// 아이템을 제거하는 공통 함수 (추가 이펙트나 로직을 넣을 수 있음)
	virtual void DestroyItem();
};

이렇게 AActor와 IItemInterface를 둘 다 상속받게 되면 AActor의 기능을 가지고있고 IItemInterface에서 선언한 함수를 반드시 구현해야 하는 클래스가 된다.

-> ABaseItem은 AActor의 모든 특성을 가진 액터이자, IItemInterface가 약속한 매서드들을 구현한 "아이템" 역할을 하는 클래스

// BaseItem.cpp
#include "BaseItem.h"

ABaseItem::ABaseItem()
{
	// 틱(매 프레임 실행)은 필요 없으므로 끕니다.
	PrimaryActorTick.bCanEverTick = false;
}

// 플레이어가 아이템 범위에 들어왔을 때 동작
void ABaseItem::OnItemOverlap(AActor* OverlapActor)
{
	// 기본은 빈 함수 - 각 자식 클래스에서 구현
}

// 플레이어가 아이템 범위를 벗어났을 때 동작
void ABaseItem::OnItemEndOverlap(AActor* OverlapActor)
{
	// 기본은 빈 함수 - 필요하다면 자식 클래스에서 활용
}

// 아이템이 사용(Activate)되었을 때 동작
void ABaseItem::ActivateItem(AActor* Activator)
{
	// 기본은 빈 함수 - 자식 클래스에서 구현
}

// 아이템 유형을 반환
FName ABaseItem::GetItemType() const
{
	return ItemType;
}

// 아이템을 파괴(제거)하는 함수
void ABaseItem::DestroyItem()
{
	// AActor에서 제공하는 Destroy() 함수로 객체 제거
	Destroy();
}
  • ABaseItem
    • 모든 아이템에 공통적으로 적용될 기능을 담는 부모 클래스입니다.
    • 인터페이스의 함수들을 빈 함수로 구현해두어, 필요한 아이템 (자식 클래스)에서 오버라이드(Override) 하여 실제 로직을 작성하게끔 합니다.
  • DestroyItem()
    • 아이템을 제거하는 함수입니다.
    • 단순히 Destroy()로 끝낼 수도 있고, 사운드나 파티클 이펙트 등을 추가하여 연출을 강화할 수도 있습니다.
  • ItemType
    • 에디터에서 “Coin”, “Mine”, “Potion” 같은 식으로 입력하여, 각 아이템의 타입을 구분할 수 있습니다.
    • GetItemType()를 통해 간단히 아이템 종류를 확인할 수 있습니다.

이제 실제 아이템들을 하나씩 구현해볼 차례다.

아이템 종류는 작은코인(10점), 큰코인(50점), 지뢰(5초 후 폭발), 힐링(체력 회복) 등이 있다.

모든 아이템은 BaseItem을 상속받으며 이미 정의해 둔 인터페이스 함수를 각각 다르게 재정의(오버라이드) 해야한다.

CoinItem

코인 계열 아이템들의 공통 기능을 담을 CoinItem 클래스를 만들어서 Coin 아이템이 가져야 할 공통 함수와 변수(점수)를 선언하고 파생된 코인 아이템들은 이를 상속받아 필요한 부분만 재정의 한다.

//CoinItem.h
#pragma once

#include "CoreMinimal.h"
#include "BaseItem.h"
#include "CoinItem.generated.h"

UCLASS()
class UNREAL_CPP_STUDY_API ACoinItem : public ABaseItem
{
	GENERATED_BODY()
	
public:
	ACoinItem();

protected:
	// 코인 획득 시 얻을 점수 (자식 클래스에서 상속받아 값만 바꿔줄 예정)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	int32 PointValue;
};
// CoinItem.cpp
#include "CoinItem.h"

ACoinItem::ACoinItem()
{
		// 부모 생성자 로직 (필요 시)
}
  • PointValue는 UPROPERTY로 선언했기 때문에, 에디터에서 값을 수정하거나 블루프린트로 접근이 가능합니다.
  • CoinItem 자체는 직접 사용하기보다는, 구체적인 코인 아이템 (BigCoin, SmallCoin)의 부모 클래스로 활용됩니다.

BigCoinItem은 50점짜리 코인이고 ItemType을 BigCoin으로 설정한다.

// BigCoinItem.h
#pragma once

#include "CoreMinimal.h"
#include "CoinItem.h"
#include "BigCoinItem.generated.h"

UCLASS()
class UNREAL_CPP_STUDY_API ABigCoinItem : public ACoinItem
{
	GENERATED_BODY()
	
public:
	ABigCoinItem();

	virtual void ActivateItem(AActor* Activator) override;
};
// BigCoinItem.cpp
#include "BigCoinItem.h"

ABigCoinItem::ABigCoinItem()
{
	PointValue = 50;
	ItemType = "BigCoin";
}

void ABigCoinItem::ActivateItem(AActor* activator)
{
	DestroyItem();
}
  • virtual void ActivateItem(AActor* Activator) override;는 코인 아이템이 ‘사용’되었을 때(획득)의 동작을 재정의하는 함수입니다. 여기서는 단순히 DestroyItem() 함수를 통해 아이템을 제거하고 있으나, 실제로는 플레이어 점수를 증가시키는 로직을 추가할 수 있습니다.

SmallCoinItem은 10점짜리 코인이고 ItemType을 SmallCoin으로 설정한다.

// SmallCoinItem.h
#pragma once

#include "CoreMinimal.h"
#include "CoinItem.h"
#include "SmallCoinItem.generated.h"

UCLASS()
class UNREAL_CPP_STUDY_API ASmallCoinItem : public ACoinItem
{
	GENERATED_BODY()
	
public:
	ASmallCoinItem();

	virtual void ActivateItem(AActor* Activator) override;
};
// SmallCoinItem.cpp
#include "SmallCoinItem.h"

ASmallCoinItem::ASmallCoinItem()
{
	PointValue = 10;
	ItemType = "SmallCoin";
}

void ASmallCoinItem::ActivateItem(AActor* activator)
{
	DestroyItem();
}

MineItem

지뢰 아이템은 설치된 후 일정 시간이 지나면 폭발하여 주변에 피해를 준다.

폭발 범위와 데미지 등은 변수로 관리한다.

// MineItem.h
#pragma once

#include "CoreMinimal.h"
#include "BaseItem.h"
#include "MineItem.generated.h"

UCLASS()
class UNREAL_CPP_STUDY_API AMineItem : public ABaseItem
{
	GENERATED_BODY()
	
public:
	AMineItem();

	virtual void ActivateItem(AActor* Activator) override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionDelay;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionRadius;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float ExplosionDamage;
};
// MineItem.cpp
#include "MineItem.h"

AMineItem::AMineItem()
{
	ExplosionDelay = 5.0f;
	ExplosionRadius = 300.0f;
	ExplosionDamage = 30.0f;
	ItemType = "Mine";
}

void AMineItem::ActivateItem(AActor* activator)
{
	DestroyItem();
}

여기서는 간단히 소멸시키지만 실제로는 지연 시간 후 폭발 로직을 구현하거나 폭발 이펙트, 데미지 계산 등을 추가할 수 있다.

HealingItem

플레이어의 체력을 회복시키는 아이템이다.

// HealingItem.h
#pragma once

#include "CoreMinimal.h"
#include "BaseItem.h"
#include "HealingItem.generated.h"

UCLASS()
class UNREAL_CPP_STUDY_API AHealingItem : public ABaseItem
{
	GENERATED_BODY()
	
public:
	AHealingItem();

	virtual void ActivateItem(AActor* Activator) override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	float HealAmount;
};
#include "HealingItem.h"

AHealingItem::AHealingItem()
{
	HealAmount = 20.0f;
	ItemType = "Healing";
}

void AHealingItem::ActivateItem(AActor* activator)
{
	DestroyItem();
}
  • MineItem, HealingItem처럼 전혀 다른 동작을 하는 아이템들도 같은 함수 이름 (ActivateItem)으로 제어 가능합니다.
  • 이러한 인터페이스 기반 설계는 한 번에 여러 종류의 아이템을 다형성으로 처리할 수 있어, 새로운 아이템을 추가하는 작업이 훨씬 쉬워집니다.