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++] 객체 지향 프로그래밍 - 클래스 전방 선언
velog.io