Reflection
간단히 말해, 리플렉션 시스템을 사용해주면, 언리얼 엔진에서 제공하는 다양한 이점을 활용 할 수있거나 자체적으로 언리얼에서 관리를 해주기에 편리하다.
사용하지않으면, 사용하지않은 변수에 대한 메모리 관리를 자체적으로 해줘야 한다.
예제를 통해, 리플렉션에 대해 알아보자.
헤더파일을 고칠 때는 에디터와 라이브코딩창을 모두 꺼야한다.
더이상 헤더파일을 고치는 경우없이 cpp파일에서 할때는 간편한 라이브코딩 방식을 써보자.
MyGameInstance.h 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"
UCLASS()
class OBJECTREFLECTION_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UMyGameInstance();
virtual void Init() override;
private:
//이렇게 리플렉션 시스템 (프로퍼티)를 사용해주면 지금 선언한 SchoolName에 대한 정보를 런타임, 컴파일타임에서 얻어 올 수 있다
UPROPERTY()
FString SchoolName;
};
MyGameInstance.cpp
#include "MyGameInstance.h"
UMyGameInstance::UMyGameInstance()
{
}
void UMyGameInstance::Init()
{
Super::Init();
UE_LOG(LogTemp, Log, TEXT("=========================="));
UClass* ClassRuntime = GetClass();
UClass* ClassCompile = UMyGameInstance::StaticClass();
check(ClassRuntime == ClassCompile);
UE_LOG(LogTemp, Log, TEXT("학교를 담당하는 클래스 이름 : %s"), *ClassRuntime->GetName());
UE_LOG(LogTemp, Log, TEXT("=========================="));
}
실행 결과 : 학교를 담당하는 클래스 이름 : MyGameInstance
즉 ClassRuntime 과 ClassCompile은 같은 값이라는걸 알 수 있다.
그리고 check를 통해 코드의 안정성을 높일 수 있다.
check에서 일부러 ClassRuntime != ClassCompile 을 하면 에디터 자체가 뻗어버릴 것이다.
이럴 때 사용 할 수 있는 함수가 ensure 이다.
ensure(ClassRuntime != ClassCompile);
//메시지 출력도 가능
ensureMsgf(ClassRuntime != ClassCompile,TEXT("일부러 에러 코드를 발생));
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"
UCLASS()
class OBJECTREFLECTION_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UMyGameInstance();
virtual void Init() override;
private:
//이렇게 리플렉션 시스템 (프로퍼티)를 사용해주면 지금 선언한 SchoolName에 대한 정보를 런타임, 컴파일타임에서 얻어 올 수 있다
UPROPERTY()
FString SchoolName;
};
위에서 선언한 SchoolName은 기본값을 지정하지 않으면 빈 문자열이 되는데,
기본값을 만들고 싶다하면 cpp에 생성자를 통해 기본값을 지정해주면 된다.
#include "MyGameInstance.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("기본학교");
}
void UMyGameInstance::Init()
{
Super::Init();
UClass* ClassRuntime = GetClass();
UClass* ClassCompile = UMyGameInstance::StaticClass();
check(ClassRuntime == ClassCompile);
UE_LOG(LogTemp, Log, TEXT("=========================="));
SchoolName = TEXT("따로 지정한 학교");
UE_LOG(LogTemp, Log,TEXT("학교 이름: %s"),*SchoolName);
UE_LOG(LogTemp, Log,TEXT("학교 기본 이름 : %s"), *GetClass()->GetDefaultObject<UMyGameInstance>()->SchoolName);
UE_LOG(LogTemp, Log, TEXT("=========================="));
}
결과 값 : LogTemp: 학교 이름 : 따로 지정한 학교
LogTemp: 학교 기본 이름 :
기본값과 무관하게 실제 클레스로부터 만들어지는 인스턴스에서 따로 지정한 값을 일부러 지정하면 기본값이 없어질까?
답은 아니다.
기본값은 클래스 디폴트 오브젝트라고 하는 템플릿 객체에 저장되어 있고
기본 객체와 무관하게 생성된 MyGameInstance에는 SchoolName이 따로 지정한 학교 라고 저장되어지는 형태이다.
여기서 주의할 점은, 에디터를 끄지않고 컴파일하면 CDO같은 경우 에디터가 활성화 되기 이전에 초기화되는 순서를 가지고 있기 때문에 생성자에서 초기화 코드를 진행해도 에디터에서 인지를 못하는 경우가 종종 있다. 그래서, 이런 경우(CDO 기본값을 고쳐주는 생성자 코드를 변경하게 되는 경우)에는 헤더파일 고칠 때랑 마찬가지로 에디터를 끄고 빌드 후 실행하면 인식이 될 것이다.
에디터 종료 후 다시 실행하면,
결과값 : LogTemp: 학교 기본 이름 : 기본학교
제대로 출력이 될 것이다.
그리고 CDO가 언제 생성되는지 알아보기 위해 생성자 SchoolName = TEXT("기본학교"); 에 브레이크 포인트를 잡고 에디터를 끄고 디버그 모드로 실행해보면, 에디터가 초기화되는 과정 75%정도에 잡히는데
즉, 이 때 CDO 나 UClass 정보들이 만들어지는 것이다.
이러한 것들이 만들어진 이후에 에디터, 게임 , 어플리케이션이 가동 된다고 생각하자.
결론:
헤더 파일에 리플렉션 정보 구조 변경
생성자 코드에서 CDO 기본값을 변경하는 경우
둘 다 에디터를 끄고 컴파일하여 실행하는 것을 인지하자.