본문 바로가기
Unreal/[서적] 언리얼5 이득우님 인프런1

Ch 08. 컴포지션

by GameStudy 2023. 5. 11.

8.1 컴포지션

8.1-1 컴포지션 개요

  - 개체 지향 설계에서 상속이 가진 Is-A 관계만 의존해서는 설계와 유지보수가 어려움.

    상속의 깊이가 길고, 최상위 부모 클래스의 코드가 변경되면 그 하위 모든 자식 클래스가 영향을 받기 때문.

  - 컴포지션은 개체 지향 설계에서 Has-A 관계를 구현하는 설계 방법.

    복잡한 기능을 가져야 하는 클래스를 효과적으로 설계하는데 유용함.

  - 모던 개체 설계 기법의 핵심은 상속을 단순화하고,

    단순한 기능을 가진 개체를 조합해서 복잡한 개체를 구성하는 것.

    그래서 컴포지션은 아주 중요함.

 

8.1-2 언리얼 엔진에서의 컴포지션 구현 방법

  - 하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트가 있음.

  - 언리얼 오브젝트 간의 컴포지션은 어떻게 구현할 것인가?

    CDO에 미리 다른 언리얼 오브젝트를 생성 후 조합.(필수적 포함)

    CDO에는 빈 포인터만 넣고, 런타임에서 언리얼 오브젝트를 생성 후 조합.(선택적 포함)

  - 언리얼 오브젝트를 생성할 때 컴포지션 정보를 구축할 수 있음.

    내가 소유한 언리얼 오브젝트를 Subobject라고 함.

    나를 소유한 언리얼 오브젝트를 Outer라고 함.

 

8.1-3 컴포지션 실습 

  - 학교 구성원을 위해 출입증을 만들고자 함.

    출입증을 Person에서 구현 후 상속? 아니면 컴포지션으로 분리?

  - 상속 시키는 경우의 문제점

    새로운 구성원이 필요해짐. 근데 그 구성원은 출입증이 필요 없는 구성원.

    그럼 Person을 수정해야함. 하위 모든 자식 클래스에 영향을 줌.

  - 새 프로젝트 "UnrealComposition" 생성

  - 새 C++ 클래스 > UObject 부모 클래스 > "Card" 클래스 생성

<hide/>

// Card.h

#pragma once

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

UENUM()
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()
	uint32 Id;	
};
<hide/>

// Card.cpp

#include "Card.h"

UCard::UCard()
{
	CardType = ECardType::Invalid;
	Id = 0;
}
<hide/>

// Person.h

#pragma once

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

UCLASS()
class UNREALCOMPOSITION_API UPerson : public UObject
{
	GENERATED_BODY()
	
public:
	...

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

protected:
	...

	UPROPERTY()
	TObjectPtr<class UCard> Card; 
    	// UE5부터 로우 포인터를 TObjectPtr<>로 사용할 것이 권장됨.
        // header에서는 tobjectptr, cpp에서는 로우포인터 써도됨.
};
<hide/>

// Person.cpp

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

UPerson::UPerson()
{
	...
	Card = CreateDefaultSubobject<UCard>(TEXT("NAME_Card"));
}
<hide/>

// Teacher.cpp

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

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

...
<hide/>

// Student.cpp

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

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

...
<hide/>

// Staff.cpp

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

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

// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Student.h"
#include "Teacher.h"
#include "Staff.h"
#include "Card.h"

UMyGameInstance::UMyGameInstance()
{
	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);
		ECardType CardType = OwnCard->GetCardType();
		//UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %d"), *Person->GetName(), CardType);

		const UEnum* CardEnumType = FindObject<UEnum>(nullptr, TEXT("/Script/UnrealComposition.ECardType"));
		if (CardEnumType)
		{
			FString CardMetaData = CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();
			UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %s"), *Person->GetName(), *CardMetaData);
		}
	}

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

 

 

'Unreal > [서적] 언리얼5 이득우님 인프런1' 카테고리의 다른 글

Ch 10. Array and Set  (0) 2023.05.12
Ch 09. 델리게이트  (0) 2023.05.12
Ch 07. 언리얼 인터페이스  (0) 2023.05.11
Ch 06. 언리얼 리플렉션 2  (0) 2023.05.10
Ch 05. 언리얼 리플렉션  (0) 2023.05.10

댓글