언리얼 공부/이득우의 언리얼 프로그래밍

[공부 내용 정리] 8강 언리얼 C++ 설계 II - 컴포지션

dubu0721 2025. 3. 17. 00:03

0. 강의 내용 요약

더보기

컴포지션을 활용한 언리얼 오브젝트 설계

  • 언리얼 C++ 은 컴포지션을 구현하는 독특한 패턴이 있다.
  • 클래스 기본 객체를 생성하는 생성자 코드를 사용해 복잡한 언리얼 오브젝트를 생성할 수 있음.
  • 언리얼 C++ 컴포지션의 Has-A 관계에 사용되는 용어
    • 내가 소유한 하위 오브젝트 : Subobject
    • 나를 소유한 상위 오브젝트 : Outer
  • 언리얼 C++ 이 제공하는 확장 열거형을 사용해 다양한 메타 정보를 넣고 활용할 수 있다.

언리얼 C++ 의 컴포지션 기법은 게임의 복잡한 객체를 설계하고 생성할 때 유용하게 사용된다.

 

 


 

 

1. 실습 코드

1.1 MyGameInstance

더보기

1. 헤더 파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class UNREALCOMPOSITION_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
public:
	UMyGameInstance();

	virtual void Init() override;

private:
	UPROPERTY() // 언리얼 엔진이 관리할 수 있도록 매크로 지정
	FString SchoolName; // 이제 언리얼 엔진이 파아갛고 관리할 수 있음, 리플렉션 시스템을 사용해서 이 정보를 런타임이든 컴파일 타임이든 언제든지 가져올 수 있게됨
	// 기본값을 주고 싶다면 생성자에 기본값을 지정해주면 됨
};

 

 

2. cpp 파일

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyGameInstance.h" // 이 헤더는 가장 위에 있어야함.
#include "Student.h"
#include "Teacher.h"
#include "Staff.h"
#include "Card.h"

UMyGameInstance::UMyGameInstance()
{
	// CDO 의 생성 시점은 대체 언제인가?
	// 클래스 정보와 CDO 엔진 초기화 과정에서 생성되므로 게임 개발에서 안전하게 사용 가능
	// 생성자 코드에서 Class Default Object 의 기본값을 변경하는 경우에는 에디터를 끄고 컴파일해서 다시 실행해주기~
	SchoolName = TEXT("기본학교"); // 기본값 지정
}

void UMyGameInstance::Init() {
	Super::Init();

	UE_LOG(LogTemp, Log, TEXT("========================"));
	TArray<UPerson*> Persons = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>() };
	for (const auto Person : Persons) 
	{
		const UCard* OwnCard = Person->GetCard();
		check(OwnCard); // OwnCard 있는지 없는지 확인..
		ECardType CardType = OwnCard->GetCardType();		
		//UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %s"), *Person->GetName(), CardType);

		// 이렇게 하면 우리가 선언한 열거형에 대한 정보를 얻을 수 있음
		const UEnum* CardEnumType = FindObject<UEnum>(nullptr, TEXT("/Script/UnrealComposition.ECardType"));
		if (CardEnumType)
		{
			// Student = 1 UMETA(DisplayName = "For Student") 이것처럼 DisplayName 으로 설정했던 메타정보를 아래 방식으로 가져올 수 있음
			FString CardMetaData = CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();
			UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %s"), *Person->GetName(), *CardMetaData);
		}
	}

	UE_LOG(LogTemp, Log, TEXT("========================"));
}

 

1.2 Card

더보기

1. 헤더 파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Card.generated.h"

// 8bit, byte 형태
UENUM() // UCLASS() 매크로랑 비슷하게 이제 이 객체에 대한 정보를 언리얼 엔진이 파악해서 우리가 유용한 정보들을 가져올 수 있음, 필드마다 유용한 메타정보를 집어넣을 수 있음
enum class ECardType : uint8 
{
	Student = 1 UMETA(DisplayName = "For Student"),
	Teacher UMETA(DisplayName = "For Teacher"),
	Staff UMETA(DisplayName = "For Staff"),
	Invalid
};

/**
 * 
 */
UCLASS()
class UNREALCOMPOSITION_API UCard : public UObject
{
	GENERATED_BODY()
	
public:
	UCard();

	// 게터, 세터
	ECardType GetCardType() const { return CardType; }
	void SetCardType(ECardType InCardType) { CardType = InCardType; }

private:
	// 카드 타입 지정
	UPROPERTY()
	ECardType CardType;

	UPROPERTY()
	int32 Id;
};

 

 

2. cpp 파일

// Fill out your copyright notice in the Description page of Project Settings.


#include "Card.h"

UCard::UCard()
{
	CardType = ECardType::Invalid;
	Id = 0;
}

 

1.3 Person

더보기

1. 헤더 파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Person.generated.h"

/**
 * 
 */
UCLASS()
class UNREALCOMPOSITION_API UPerson : public UObject
{
	GENERATED_BODY()
	
public:
	UPerson();

	// forchinline 을 통해서 최대한 인라인이 되도록 선언..
	FORCEINLINE const FString& GetName() const { return Name; }
	FORCEINLINE void SetName(const FString& InName) { Name = InName; }

	FORCEINLINE class UCard* GetCard() const { return Card; }
	FORCEINLINE void SetCard(class UCard* InCard) { Card = InCard; }

protected:
	UPROPERTY() // 언리얼이 관리할 수 있는 속성으로 발전시키기위해 매크로 추가
	FString Name;

	//// 언리얼 엔진 4버전까지 정석이었음
	//// 컴포지션은 헤더로 포함하지 않고 전방선언해서 사용할수도 있음(의존성 최대한 없앨 수 있음)
	//// 보통 오브젝트는 포인터로 관리
	//UPROPERTY()
	//class UCard* Card;

	// 언리얼 엔진 5 
	// 원시포인터를 TObjectPtr 로 감싸줭
	// 얘는 기본 자체가 포인터라 * 연산자 안 써
	// 전방 선언은 그대로
	TObjectPtr<class UCard> Card;
};

 

 

2. cpp 파일

// Fill out your copyright notice in the Description page of Project Settings.


#include "Person.h"
#include "Card.h"

UPerson::UPerson()
{
	Name = TEXT("홍길동");

	// 이 서브오브젝트의 내부 식별자(Name)를 지정하기 위해서 인자로 TEXT 를 받음(FName 타입)
	Card = CreateDefaultSubobject<UCard>(TEXT("NAME_Card")); // NAME_ 이라는 접두사를 붙이면 코드를 보는 사람이 이것은 String 이 아니고 FName 이구나라고 알 수 있음
}

 

1.4 Teacher

더보기

1. 헤더 파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "LessonInterface.h"
#include "Teacher.generated.h"

/**
 * 
 */
UCLASS()
class UNREALCOMPOSITION_API UTeacher : public UPerson, public ILessonInterface
{
	GENERATED_BODY()
	
public:
	UTeacher();

	virtual void DoLesson() override;
};

 

 

2. cpp 파일

// Fill out your copyright notice in the Description page of Project Settings.


#include "Teacher.h"
#include "Card.h"

UTeacher::UTeacher()
{
	Name = TEXT("이선생");
	Card->SetCardType(ECardType::Teacher);
}

void UTeacher::DoLesson()
{
	ILessonInterface::DoLesson(); // 어쩔 수 없이 직접 ILessonInterface 써줘야함

	UE_LOG(LogTemp, Log, TEXT("%s님은 가르칩니다."), *Name);
}

 

1.5 Student

더보기

1. 헤더 파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "LessonInterface.h"
#include "Student.generated.h"

/**
 * 
 */
UCLASS()
class UNREALCOMPOSITION_API UStudent : public UPerson, public ILessonInterface
{
	GENERATED_BODY()
	
public:
	UStudent();

	// 인터페이스 상속했으니까 해당 인터페이스의 가상 함수를 무조건 구현해야함
	virtual void DoLesson() override;
};

 

 

2. cpp 파일

// Fill out your copyright notice in the Description page of Project Settings.


#include "Student.h"
#include "Card.h"

UStudent::UStudent()
{
	Name = TEXT("이학생");
	Card->SetCardType(ECardType::Student);
}

void UStudent::DoLesson()
{
	// LessonInterface 의 DoLesson 함수는 Super 로 가져올 수 없음
	// 얘의 Supter 는 Person 이기 때문.. 

	ILessonInterface::DoLesson(); // 어쩔 수 없이 직접 ILessonInterface 써줘야함

	UE_LOG(LogTemp, Log, TEXT("%s님은 공부합니다."), *Name);
}

 

1.6 Staff

더보기

1. 헤더 파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Person.h"
#include "Staff.generated.h"

/**
 * 
 */
UCLASS()
class UNREALCOMPOSITION_API UStaff : public UPerson
{
	GENERATED_BODY()
	
public:
	UStaff();
};

 

 

2. cpp 파일

#include "Staff.h"
// Fill out your copyright notice in the Description page of Project Settings.


#include "Staff.h"
#include "Card.h"

UStaff::UStaff()
{
	Name = TEXT("이직원");
	Card->SetCardType(ECardType::Staff);
}

 

 


 

 

2. 참고자료

[C++] 객체 지향 프로그래밍 - 클래스 전방 선언

 

[C++] 객체 지향 프로그래밍 - 클래스 전방 선언

[C++] 객체 지향 프로그래밍 - 클래스 전방 선언

velog.io