Interfac(인터페이스)
인터페이스란?
클래스가 언리얼 인터페이스(Unreal Interface) 클래스에서 상속하면 인터페이스는 새 클래스가 공통의 함수를 구현하도록 합니다. 이는 특정 함수 기능을 서로 다른 규모의 복잡한 클래스에서 공유할 수 있을 때 유용합니다. 즉, 인터페이스 자체는 아무런 기능을 가지지 않지만, 이를 상속받은 클래스가 인터페이스에 정의된 함수들을 구현해야 합니다. 이 방식은 객체 간 결합도를 낮추고 유지보수를 용이하게 하며, 다양한 클래스에서 동일한 기능을 일관되게 구현할 수 있도록 합니다.
- 객체가 반드시 구현해야 할 행동을 지정하는데 활용되는 타입
- 다형성(Polymorphism)의 구현, 의존성이 분리(Decouple)된 설계에 유용하게 활용
인터페이스는 특히 게임 게발에서 다음과 같은 역할을 수행할 수 있습니다.
- 여러 오브젝트에 공통된 기능을 부여: 예를 들어 상호작용 가능한 오브젝트를 정의하고 싶다면, 문, 상자, NPC 등 다양한 액터들이 이 인터페이스를 구현하면 됩니다.
- 유지보수성과 확장성을 향상: 인터페이스를 사용하면 특정 기능을 추가하거나 변경할 때도 기존 코드의 수정 없이 새로운 클래스에 쉽게 적용할 수 있습니다.
- 코드의 유연성을 증가: 특정 클래스의 구현에 의존하지 않고도, 인터페이스를 통해 다양한 오브젝트를 동일한 방식으로 다룰 수 있습니다.
✅ 인터페이스의 장점
- 유연성과 확장성
- 인터페이스를 사용하면 기존 클래스를 수정하지 않고도 새로운 기능을 쉽게 추가할 수 있습니다.
- 클래스 간의 결합도를 낮추어 코드 확장이 용이해집니다.
- 특히 다중 상속이 불가능한 언어(예: C++, C#에서는 가능하지만, Java, Unreal Engine의 블루프린트에서는 불가능)에서 대체재로 유용하게 쓰입니다.
- 코드 재사용성 증가
- 여러 개의 클래스가 같은 인터페이스를 구현하면, 동일한 방식으로 객체를 처리할 수 있어 코드 중복이 줄어듭니다.
- 예를 들어, IDamageable 인터페이스를 구현한 객체(적, 플레이어, 오브젝트 등)는 하나의 TakeDamage() 함수로 동일하게 다룰 수 있습니다.
- 유지보수 용이
- 특정 기능이 변경될 때 인터페이스를 구현한 클래스들만 수정하면 되므로 유지보수가 편리해집니다.
- 예를 들어, IInteractable 인터페이스를 가진 모든 객체에 새로운 인터랙션 로직을 추가할 때, 해당 인터페이스를 구현한 클래스들만 수정하면 됩니다.
❌ 인터페이스의 단점
- 구현의 강제성
- 인터페이스를 상속받은 클래스는 반드시 해당 인터페이스의 모든 함수를 구현해야 합니다.
- 필요하지 않은 기능도 구현해야 하는 경우가 생길 수 있습니다.
- 이 단점은 인터페이스를 여러 개로 나누어 설계하거나, 추상 클래스와 인터페이스를 조합하여 해결할 수 있습니다.
- 코드의 복잡성 증가
- 인터페이스를 과도하게 사용하면 구조가 복잡해질 수 있습니다.
- 작은 기능을 너무 세분화해서 여러 인터페이스로 분리하면 코드가 난잡해지고 가독성이 떨어질 수 있습니다.
- 간단한 상속 관계라면, 인터페이스보다 **일반적인 상속(inheritance)이나 조합(composition)**을 활용하는 것이 더 나을 수도 있습니다.
언리얼 C++ 인터페이스 특징
- `U`로 시작하는 타입 클래스
- 리플렉션 시스템에서 사용되며, 런타임에 객체가 해당 인터페이스를 구현했는지 여부를 확인하는 용도로 사용됩니다.
- 직접 작업에 사용할 일은 없습니다.
- `I`로 시작하는 인터페이스 클래스
- 인터페이스의 기능 정의 및 구현을 담당합니다.
- 객체 설계 시, 이 클래스를 기반으로 인터페이스를 구현하며, 순수 가상 함수를 재정의 합니다.
C++에서 인터페이스 선언하기
C++에서의 인터페이스 선언은 보통의 언리얼 클래스 선언과 비슷합니다. 하지만 다음과 같은 몇 가지 기본적인 차이가 있습니다.
- 인터페이스 클래스는 `UCLASS`매크로 대신 `UINTERFACE`매크로를 사용합니다.
- 인터페이스 클래스는 `UObject`대신 `UInterface`에서 상속합니다.
📌`UINTERFACE`는 언리얼의 리플렉션 시스템에 인터페이스의 타입 정보를 노출하기 위한 클래스입니다. 이 클래스는 함수 구현을 포함하지 않으며, 실제 인터페이스 동작은 `I`로 시작하는 클래스에서 정의됩니다.
인터페이스 지정자
인터페이스 지정자를 사용하여 클래스를 언리얼 리플렉션 시스템에 노출시킵니다. 다음 표에 관련 인터페이스 지정자가 포함되어 있습니다.
인터페이스 지정자 | 설명 |
`Blueprintalbe` | 블루프린트에서 구현할 수 있도록 이 인터페이스를 노출시킵니다. 블루프린트에서 인터페이스를 구현하려면, 안터페이스에 포함된 함수들이 반드시 `BlueprintImpementableEvent` 또는 `BlueprintNativeEvent`로 선언되어야 합니다. 블루프린트 구현이 적합하지 않다면 `NotBlueprintable` 또는 `meta=(CannotImplementInterfaceInBlueprint)`로 명시해야 합니다. |
`BlueprintTyep` | 이 클래스를 블루프린트의 변수에 사용할 수 있는 타입으로 노출합니다. |
`DepedsOn=(ClassName1, ClassName2, ...) | 빌드 시스템에서 이 클래스를 컴파일하기 전에 이 지정자로 나열된 모든 클래스를 컴파일 합니다. 컴파일 순서를 명확히 하여 빌드 에러를 방지할 수 있으며, 같은 패키지나 이전 패키지의 클래스를 대상으로 지정해야 하며, 쉼표로 여러 클래스를 한 줄에 지정하거나, 각 클래스에 대해 별도로 지정할 수 있습니다. |
MinimalAPI | 다른 모듈에서 사용하도록 클래스의 타입 정보만 익스포트합니다. 클래스는 외부에서 캐스팅되거나 타입으로 참조될 수 있지만, 인라인 함수 외의 대부분의 함수는 호출할 수 없습니다. 외부에서 호출해야 하는 함수는 반드시 명시적으로 익스포트 매크로를 추가해야 하며 다른 모듈에서 액세스할 수 있는 모든 함수가 필요하지 않는 클래스에 대해 모든 것을 익스포트할 필요가 없어 컴파일 시간이 단축됩니다. |
인터페이스 활용한 설계
인터페이스 활용 예시로 각각의 미사일과 레이저가 나가는 터렛을 만드는 것을 가정하고 작성해 보겠습니다.
터렛 인터페이스 정의
1. ITurretInterface.h
// TurretInterface.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "TurretInterface.generated.h"
// UInterface로 선언 (리플렉션 시스템에서 사용)
UINTERFACE(Blueprintable)
class YOURGAME_API UTurretInterface : public UInterface
{
GENERATED_BODY()
};
// ITurretInterface로 구현
class YOURGAME_API ITurretInterface
{
GENERATED_BODY()
public:
// 모든 터렛이 구현해야 하는 Fire 함수
virtual void Fire() = 0;
// 터렛의 타입 반환
virtual FString GetTurretType() const = 0;
};
📌 블루프린트 예시
더보기
// TurretInterface.h
UINTERFACE(BlueprintType)
class UTurretInterface : public UInterface
{
GENERATED_BODY()
};
class ITurretInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void Fire();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
FString GetTurretType() const;
};
// 블루프린트에서 LaserTurret과 MissileTurret의 동작을 각각 정의할 수 있습니다.
// 예: 블루프린트에서 레이저 터렛은 적을 따라다니는 이펙트 추가, 미사일 터렛은 폭발 이펙트를 추가.
2. ALaserTurret.h
// LaserTurret.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TurretInterface.h"
#include "LaserTurret.generated.h"
UCLASS()
class YOURGAME_API ALaserTurret : public AActor, public ITurretInterface
{
GENERATED_BODY()
public:
// ITurretInterface의 Fire 함수 구현
virtual void Fire() override
{
UE_LOG(LogTemp, Warning, TEXT("레이저 발사!"));
}
// ITurretInterface의 GetTurretType 함수 구현
virtual FString GetTurretType() const override
{
return TEXT("Laser");
}
};
3. AMissileTurret.h
// MissileTurret.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TurretInterface.h"
#include "MissileTurret.generated.h"
UCLASS()
class YOURGAME_API AMissileTurret : public AActor, public ITurretInterface
{
GENERATED_BODY()
public:
// ITurretInterface의 Fire 함수 구현
virtual void Fire() override
{
UE_LOG(LogTemp, Warning, TEXT("미사일 발사!"));
}
// ITurretInterface의 GetTurretType 함수 구현
virtual FString GetTurretType() const override
{
return TEXT("Missile");
}
};
4. UTurretManager.h
// TurretManager.cpp
#include "MissileTurret.h"
#include "LaserTurret.h"
#include "TurretInterface.h"
void HandleTurretFire(UObject* TurretObject)
{
// TurretObject가 ITurretInterface를 구현했는지 확인
ITurretInterface* Turret = Cast<ITurretInterface>(TurretObject);
if (Turret)
{
// 터렛 타입 가져오기
FString TurretType = Turret->GetTurretType();
// 터렛 타입에 따른 로직 처리
if (TurretType == TEXT("Missile"))
{
UE_LOG(LogTemp, Warning, TEXT("미사일 터렛이 선택되었습니다."));
}
else if (TurretType == TEXT("Laser"))
{
UE_LOG(LogTemp, Warning, TEXT("레이저 터렛이 선택되었습니다."));
}
// 공통 Fire 함수 호출
Turret->Fire();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("터렛이 ITurretInterface를 구현하지 않았습니다."));
}
}
5. 호출 테스트
void TestTurretFiring(UWorld* WorldContext)
{
if (!WorldContext)
{
UE_LOG(LogTemp, Error, TEXT("유효하지 않은 월드 컨텍스트입니다."));
return;
}
// 미사일 터렛 스폰
AActor* MissileTurret = WorldContext->SpawnActor<AMissileTurret>(AMissileTurret::StaticClass(), FTransform());
if (MissileTurret)
{
HandleTurretFire(MissileTurret);
}
// 레이저 터렛 스폰
AActor* LaserTurret = WorldContext->SpawnActor<ALaserTurret>(ALaserTurret::StaticClass(), FTransform());
if (LaserTurret)
{
HandleTurretFire(LaserTurret);
}
}
// AGameModeBase.cpp
void AMyGameModeBase::BeginPlay()
{
Super::BeginPlay();
// 현재 월드를 매개변수로 전달
TestTurretFiring(GetWorld());
}
코드 요약
- 인터페이스 정의: `Fire`와 `GetTurretType`메서드로 모든 터렛이 공통적으로 가져야 할 기능을 선언
- 미사일 터렛과 레이저 터렛 구현: 각각 `ITurretInterface`를 구현하여 개별적으로 동작 정의
- 핸들링 함수: 모든 터렛을 `ITurretInterface`로 묶어 관리하며, 타입에 따라 다르게 처리
- 테스트 호출: 미사일 터렛과 레이저 터렛을 각각 테스트
- 이 인터페이스는 블루프린트에서 구현 가능하며, 블루프린트 상에서 터렛의 `Fire`기능을 추가적으로 정의하거나 확장할 수 있습니다.
'Unreal > Core Concepts' 카테고리의 다른 글
Reflection System (0) | 2025.02.08 |
---|---|
Actor Lifecycle (0) | 2025.02.04 |
LEVEL (0) | 2025.01.24 |
Actor (0) | 2025.01.15 |
Component (0) | 2025.01.15 |