Arknights EP - [Confront]
Categories
Tags
1061 words
5 minutes
[UE5/Part1] 8. Composition
“이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해” 학습 내용을 정리한 강의 노트입니다.
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.
강의 목표
- 언리얼 C++의 컴포지션 기법을 사용해 오브젝트의 포함 관계를 설계한다.
- 언리얼 C++이 제공하는 열거형(Enum) 타입의 선언과 메타데이터 활용 방법을 학습한다.
컴포지션 (Composition)
객체 지향 설계에서 상속이 가진 Is-A 관계에만 의존해서는 복잡한 기능을 설계하고 유지 보수하기 어렵다.

- 컴포지션은
Has-A관계를 구현하는 설계 방법이다. 복합적인 기능을 가진 거대한 클래스를 상속으로만 처리하지 않고, 필요한 기능을 가진 부품(오브젝트)을 조립해서 만든다.

하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트인
CDO(Class Default Object)가 존재한다.언리얼 오브젝트에 다른 언리얼 오브젝트를 조합(Composition)할 때, CDO 생성 시점에 따라 두 가지 선택지가 존재한다.
생성자CDO 생성 시점에 미리 산하 오브젝트를 생성해 조합한다. (CreateDefaultSubobjectAPI 활용)런타임CDO에는 빈 포인터만 두고, 런타임(게임 실행 중)에 필요할 때 오브젝트를 생성해 조합한다. (NewObjectAPI 활용)
언리얼 오브젝트 간의 컴포지션 관계 용어
SubObject내가 소유한 산하 언리얼 오브젝트 (그림의 보라 상자 → 초록 상자)Outer나를 소유한 상위 언리얼 오브젝트 (그림의 초록 상자 → 보라 상자)
실습
1. Card 클래스 생성 (Enum 활용)
언리얼 엔진의 리플렉션 기능을 활용하기 위해 UENUM 매크로와 UMETA를 사용한다. Card.h
UENUM()
enum class ECardType : uint8
{
Student = 1 UMETA(DisplayName = "For Student"),
Teacher UMETA(DisplayName = "For Teacher"),
Staff UMETA(DisplayName = "For Staff"),
Invalid
};UCard클래스 생성 및ECardType멤버 변수 추가- CardType에 대한 Getter/Setter 구현
2. Person has-a Card (컴포지션 구현)
Person 클래스가 Card 객체를 소유하도록 구현한다.
Person.h
UCLASS()
class UNREALCOMPOSITION_API UPerson : public UObject
{
GENERATED_BODY()
public:
UCard* GetCard() const { return Card; }
void SetCard(UCard* InCard) { Card = InCard; }
protected:
// Person has-a Card
// 언리얼 5부터는 원시 포인터 대신 TObjectPtr<T> 사용 권장
UPROPERTY()
TObjectPtr<UCard> Card;
// class UCard* Card; // 언리얼 4 방식
};TObjectPtr언리얼 5로 넘어오면서UPROPERTY로 지정된 멤버 변수는 원시 포인터 대신TObjectPtr래퍼 클래스를 사용하는 것으로 변경되었다. (에디터 빌드 시 동적 로딩 등 이점 제공, 런타임에서는 원시 포인터와 동일)
3. GameInstance에서 활용
컴포지션 객체 접근
MyGameInstance.cpp
const UCard* OwnCard = Person->GetCard();
if (OwnCard)
{
ECardType CardType = OwnCard->GetCardType();
UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %d"), *Person->GetName(), (int32)CardType);
}Enum 메타데이터 접근 (리플렉션)
FindObject를 사용해 열거형의 메타 정보(UMETA로 지정한 DisplayName)를 가져올 수 있다.
MyGameInstance.cpp
const UEnum* CardEnumType = FindObject<UEnum>(nullptr, TEXT("/Script/UnrealComposition.ECardType"));
if (CardEnumType)
{
// Enum의 Value를 통해 DisplayName(MetaData)을 문자열로 추출
FString CardMetaData = CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();
UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류: %s"), *Person->GetName(), *CardMetaData);
}출력 결과
LogTemp: ===============================
LogTemp: 학생님이 소유한 카드 종류 1
LogTemp: 선생님님이 소유한 카드 종류 For Teacher
LogTemp: 직원님이 소유한 카드 종류 For Staff정리
- 언리얼 C++는 객체 간의 포함 관계(Has-A)를 구현하는 컴포지션 패턴을 지원한다.
- 생성자 단계에서
CreateDefaultSubobject를 사용해 필수적인 SubObject를 미리 생성하고 조립할 수 있다.SubObject내가 소유한 하위 오브젝트Outer나를 소유한 상위 오브젝트
TObjectPtr는 UE5부터 도입된 표준 객체 포인터 래퍼 클래스이다. (헤더에서TObjectPtr<UCard> Card;형태로 선언하여 컴포지션 구현)enum class와UMETA매크로를 조합하면 열거형의 메타 정보를 런타임에 활용 가능하다.

