언리얼 리플렉션 시스템
다른 말로는 언리얼 프로퍼티 시스템이라고도 한다. Reflection 시스템은 프로그램을 실행하면서 자기 상태를 바라보는 것 이라고 할 수 있는데 이 기능 자체가 언리얼의 근간을 이룬다.
코드가 돌아가면서 뭔가 문제고~~ 이런거를 파악하는 것을 리플렉션이라고 하는 것이다.
C++에서는 이런 기능이 없고 언리얼에서 자체적으로 구성 한 것이다.
그렇다면 리플렉션은 어떻게 보장받을 수 있을까???
언리얼에서는 리플렉션을 하고자 하는 유형 또는 프로퍼티에 특정 매크로를 설정해 Unreal Header Tool에 의해서 프로젝트를 컴파일 할 때 해당 정보를 수집하게 된다.
#include "FileName.generated.h" // 이게 바로 리플렉션을 보장하는 시스템이구나.
UENUM(), UCLASS(), USTRUCT(), UFUNCTION(), UPROPERTY()
이러한 매크로를 사용한다.
그렇다면 UPROPERTY가 없어도 그냥 RAW 하게 선언해도 무방할까??
그렇다. 상관은 없다. 사용하지 않는 경우는 내가 스스로 직접 관리만 해줄 수 있다며 된다.
언리얼 오브젝트는 이러한 기능을 가지고 있다.(UCLASS)
- 언리얼 오브젝트에는 특별한 프로퍼티와 함수를 지정가능!
- UPROPERTY = 멤버 변수
- UFUNCTION = 멤버 함수
- 에디터와 연동되는 메타 데이터를 심을 수 있다.
- 모든 언리얼 오브젝트는 클래스 정보와 함께 한다.
- UCLASS를 사용해서 자신이 가진 프로퍼티와 함수 정보를 컴파일 타임과 런타임에서 조회가 가능하다.
따라서 언리얼 오브젝트를 생성할 때에는 New 키워드가 아닌 NewObject()함수를 통해 생성해주어야 한다.
CDO
컴파일 할 때 클래스마다 기본 오브젝트가 정해진다. 이는 클래스 오브젝트라고 하는데 CDO는 언리얼 객체가 가진 기본 값을 보관하는 템플릿 객체이다.
만약 Enemy 클래스의 Health를 100으로 설정 두었다면 Enemy CDO의 Health는 100일 것이다. 그리고 내가 레벨 안에 5마리의 Enemy를 배치하고 그 중 두 Enemy만 Health를 300으로 수정한 상태라고 해보자. 이때 내가 CDO의 Health를 150으로 수정하면 Health가 300인 Enemy를 제외한 나머지는 전부 150으로 수정이 될 것이다.
언리얼 오브젝트 처리
- UPROPERTY로 속성이 되어있는 경우에는 자동으로 초기화가 된다!
- 레퍼런스 자동 업데이트 ⇒ 언리얼이 제공하는 메모리 관리 시스템을 보장 받을 수 있다는 것!
- 직렬화 작업 : 객체를 지정된 포맷에 맞게 불러오는 것! 단, UProperty로 선언한 경우에 한함.
- 프로퍼티 값 업데이트 하기 : CDO를 이용해서 게임 제작시 게임 월드에 여러가지 배치했을 시, CDO를 이용해서 효과적으로 관리할 수 있다는 것.
- 에디터 통합 : UProperty와 매크로 안에 메타 데이터를 넣어주면 에디터와 통합되면서 유용한 기능을 사용할 수 있음.
- 런타임 유형 정보 및 형변환 : 런타임에서 정보를 얻고 안전한 캐스팅 보장하는데, 함수 오버라이딩 시, Super 같은 것도 리플렉션이 있어서 설정이 가능한 것임.
- 가비지 컬렉션 : 더 이상 안쓴다면 ( 미사용 ) 자동으로 메모리에서 회수하는 것.
- 네트워크 리플리케이션 : 직렬화와 유사기능이기는 한데, 네트워크 통신을 통해서 UPROPERTY를 전송하고 받을 수 있게 설계 되어있음
이러한 기능들을 자동으로 제공하는 것이다.
예제 작성하기
여기서 알아두어야하는 점은 리플렉션을 적용한 Class의 생성자의 실행 시점은 언제일까??
CDO 때문에 실행을 언리얼이 실행되는 그 순간 보다 한발 더 빠르게 동작한다. 즉, 엔진을 초기화 하는 과정에서 실행된다는 것이다.
따라서 언리얼 에디터를 꺼줘야하는 경우는 헤더파일의 수정이 있을 때 뿐만 아니라
리플렉션을 적용중인 기본 생성자의 초기화 코드를 고쳐버리는 경우가 있다.
리플렉션을 연습하기 위해 이러한 상속 관계를 가지는 Class를 만들어보자.
Person.h
// 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 OBJECTREFLECTION_API UPerson : public UObject
{
GENERATED_BODY()
public :
UPerson();
//언리얼에서 해당 함수를 관리할 수 있도록 매크로 설정.
UFUNCTION()
virtual void DoLesson();
//비 공개 멤버들에 접근하여 값을 가져오거나, 변경하는 함수.
const FString& GetName() const;
void SetName(const FString& InName);
protected:
//언리얼에서 해당 멤버 변수를 관리할 수 있도록 매크로 설정.
UPROPERTY()
FString Name;
UPROPERTY()
int32 Year;
private:
};
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Person.h"
#include "Student.generated.h"
/**
*
*/
UCLASS()
class OBJECTREFLECTION_API UStudent : public UPerson
{
GENERATED_BODY()
public:
UStudent();
virtual void DoLesson() override;
private:
UPROPERTY()
int32 Id;
};
Teacher는 Student와 같은 구성을 가진다.
이러한 구성을 가지고 있을 때 UMyGameInstance에서 Student와 Teacher를 생성해보자.
UStudent* Student = NewObject<UStudent>();
UTeacher* Teacher = NewObject<UTeacher>();
클래스 안에 있는 내용을 우리가 아는 방식인 Getter와 Setter를 이용해 수정하면 다음과 같다.
Student->SetName(TEXT("김민석"));
UE_LOG(LogTemp, Log, TEXT("새로운 학생 이름 %s"), *Student->GetName());
리플렉션을 이용하면 해당 클래스의 프로퍼티를 보호수준과 상관 없이 바로 가져올 수 있다.
FString CurrentTeacherName;
FString NewTeacherName(TEXT("이교수"));
FProperty* NameProp = UTeacher::StaticClass()->FindPropertyByName(TEXT("Name"));
if (NameProp)
{
NameProp->GetValue_InContainer(Teacher, &CurrentTeacherName);
UE_LOG(LogTemp, Log, TEXT("현재 선생님 이름 %s"), *CurrentTeacherName);
NameProp->SetValue_InContainer(Teacher, &NewTeacherName);
UE_LOG(LogTemp, Log, TEXT("새로운 이름 %s"), *Teacher->GetName());
}
함수는 어떻게 할까
Student->DoLesson();
//런티임 클래스 가져오기
UFunction* DoLessonFunc = Teacher->GetClass()->FindFunctionByName(TEXT("DoLesson"));
if (DoLessonFunc) {
Teacher->ProcessEvent(DoLessonFunc, nullptr);
}
첫번째 줄은 일반 클래스에서 함수를 바로 가져오는 거고
두번째는 리플렉션을 이용하는데, 런타임에서 동작 중인 객체의 UFUCTION 프로퍼티 중에서 Dolesson의 함수 포인터를 가져와 만약 존재한다면 이를 실행하게 하는 방법으로 동작한다.
여기서 Teacher->GetClass()를 써도 되고 UTeacher::StaticClass()를 써도 상관 없다.
언리얼 엔진의 기본 프레임워크는 리플렉션을 활용해서 구축이 되어있으므로, 언리얼 엔진을 이해하기 위해서는 리플렉션 시스템을 이해하는 것이 필요하다.
내가 실제로 어떤 클래스를 동작시킬 때 리플렉션만 이용하지 않아도 되나, 기본적인 언리얼의 동작구조는 리플렉션을 기반으로 두기에, 이를 이해하는 것이 바람직하다는 것.
'Unreal Engine 5 > 강의' 카테고리의 다른 글
[UE5] 언리얼 컨테이너 라이브러리 - Struct와 TMap, 언리얼 메모리 관리 (0) | 2024.11.25 |
---|---|
[UE5] 언리얼 컨테이너 라이브러리 - TArray, TSet (0) | 2024.11.22 |
[UE5] 언리얼 C++ 설계 - 인터페이스, 컴포지션, 델리게이트 (0) | 2024.11.21 |
[UE5] 언리얼 오브젝트의 이해 - 코딩 표준, 기본 타입과 문자열, 언리얼 오브젝트란? (0) | 2024.11.19 |
[UE5] 언리얼 오브젝트의 이해 - 프로젝트 세팅 및 로그 찍기 (0) | 2024.11.18 |