futurelabunseen / B-JeonganLee

UNSEEN 2nd Term Learning and Project Repo.
5 stars 0 forks source link

8강: 언리얼 C++ 설계 II - 컴포지션 #34

Closed fkdl0048 closed 6 months ago

fkdl0048 commented 6 months ago

8강: 언리얼 C++ 설계 II - 컴포지션

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

컴포지션(Composition)

컴포지션은 객체지향 프로그래밍에서 객체의 포함 관계를 나타내는 방법으로 합성 또는 구성이라고도 한다. 상속의 is-a관계만으로는 설계와 유지보수가 어렵기에 has-a관계를 나타내는 것이다.

모던 객체 설계 기법과 컴포지션

좋은 객체지향 설계 패턴을 제작하기 위한 모던 객체 설계 기법 (SOLID)라고 한다.

사실 이런 SOLID를 제대로 이해하기 위해선 객체지향에 대한 공부가 먼저 선행되어야 한다고 생각한다. 그냥 법칙을 외우고 사용하는 것에서 대부분 끝난다.

컴포지션 설계 예시

전 강의에서 활용한 학교 구성원 시스템을 그대로 사용하되, 출입증으로 만들어 Person에게 구현하여 상속할 것인지, 합성인 컴포지션으로 가져갈 것인지를 고민해보자. 정답은 컴포지션으로 상속의 고질적인 문제점을 조금만 생각해보면 답이 나온다.

언러얼 엔진에서의 컴포지션 구현 방법

두 가지 방법을 유니티의 관점에서 바라본다면, Awake에서 실질적으로 정적인 할당이 이뤄지는 것과 참조 변수만 두고 런타임 도중에 할당하는 것과 같다고 볼 수 있다. 이 메서드를 CreateDefaultSubobject라는 API로 생성자에서 구현하거나 런타임에 NewObject로 생성할 수 있다.

실습

#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 NewCardType) { CardType = NewCardType; }

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;} // const 지시자
 FORCEINLINE void SetName (const FString& NewName) {Name = NewName;}

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

protected:
 UPROPERTY()
 FString Name;

 UPROPERTY()
 TObjectPtr<class UCard> Card; // 전방선언 (언리얼 5기준)
 //class UCard* Card; // 전방선언 (언리얼 4기준)
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "person.h"
#include "Card.h"

Uperson::Uperson()
{
 Name = TEXT("이정안");
 Card = CreateDefaultSubobject<UCard>(TEXT("NAME_Card"));
}
// 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::DoLessson()
{
 //ILessonInterface::DoLessson(); // 인터페이스에 구현된 베이스 함수 호출
 UE_LOG(LogTemp, Log, TEXT("%s님은 가르칩니다."), *Name);
}
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyGameInstance.h"

#include "Card.h"
#include "Staff.h"
#include "Student.h"
#include "Teacher.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("======================"));
}

정리