다양한 기록

[UE] Composition (컴포지션) 본문

언리얼 엔진/Unreal C++

[UE] Composition (컴포지션)

라구넹 2024. 12. 22. 21:44

Has-a 관계

(상속은 Is-a 관계)

 

SOLID

Single Responsibility Principle - 하나의 객체는 하나의 의무만 가지도록

Open-Closed Principle - 기존에 구현된 코드를 변경하지 않으면서 새로운 기능 추가 가능하도록

Liskov Substitution Principle - 자식 객체를 부모 객체로 변경해도 작동에 문제 없을 정도로 상속을 단순히

Interface Segregation Design - 객체가 구현해야 할 기능이 많다면 이들을 여러 개의 단순한 인터페이스로 분리하여 설계
Dependency Inversion Principle - 구현된 실물보다 구축해야 할 추상적 개념에 의존


하나의 언리얼 오브젝트는 항상 CDO를 가짐

언리얼 오브젝트 간의 컴포지션?

- CDO에 미리 언리얼 오브젝트를 생성해 조합 (필수적 포함)

    - CreateDefaultSubObject()

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

    - NewObject()

 

내가 소유한 언리얼 오브젝트 -> 서브오브젝트

나를 소유한 "" -> 아우터


// 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"

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();
	FORCEINLINE ECardType GetCardType() const { return CardType; }
	FORCEINLINE void SetCardType(ECardType InCardType) { CardType = InCardType; }

private:
	UPROPERTY()
	ECardType CardType;

	UPROPERTY()
	uint32 Id;
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "Card.h"

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

열거형, 메타데이터까지 지정 가능함

 

// 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();
	FORCEINLINE const FString& GetName() const { return Name; }
	FORCEINLINE void SetName(const FString& InName) { Name = InName; }

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

protected:
	UPROPERTY()
	FString Name;
	// UE 4까지의 표준 
	/*UPROPERTY()
	class UCard* Card;*/
	// UE5 표준, 구현에서는 포인터 써도 됨
	TObjectPtr<class UCard> Card;
private:
	
};

Person.h 헤더 파일, 객체 선언 시 원시 포인터 형태로 선언은 언리얼4 때까지의 표준

선언은 이제 TObjectPtr로 선언해주는게 좋음

구현에서는 그냥 포인터 써도 됨

위 예제에서는 독립성을 높이기 위해 전방 선언되었음

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


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

UPerson::UPerson()
{
	Card = CreateDefaultSubobject<UCard>(TEXT("NAME_Card"));
	Name = TEXT("홍길동");
}

Person.cpp 파일, CreateDefaultSubObject로 필수 포함 시킴

인자에 FNAME을 넣어서 유니크한 값을 설정해줘야 하는데, 이때 TEXT의 시작을 NAME으로 시작해서

이게 FNAME인지 분간하기 쉽게 함

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


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

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

Staff, Student, Teacher는 각각 카드를 포함함

Person의 생성자에서 이미 카드 오브젝트를 만들긴 하기 때문에 또 만들 필요는 없음

 

// 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()
{
	SchoolName = TEXT("기본학교");
}

void UMyGameInstance::Init()
{
	Super:: Init();
	
	TArray<UPerson*> Persons = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>() };

	UE_LOG(LogTemp, Log, TEXT("=================================="));
	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("=================================="));

}

게임인스턴스

복잡할만한 건 메타데이터 가져오기

절대 경로를 지정해서 오브젝트를 뽑아아와야 함

/Script/UnrealComposition.ECardType

/Script 밑에 모듈 안에 존재함

 

CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();

이걸 통해 DisplayName을 뽑아올 수 있음

기본 리턴은 FText인데, FString으로 처리해야 로그 출력됨