[이득우의 언리얼 프로그래밍 Part1 필기] 5-6. 언리얼 엔진 리플렉션 시스템
언리얼 엔진 게임 프레임워크는 리플렉션 시스템을 기반으로 짜여져 있기 때문에 이해하는 것이 중요하다.
리플렉션 시스템이란?
- 프로그램 실행 시간에 자기 자신을 조사하는 것.
- 다시 말하자면 프로그램이 실행 중에 동적으로 프로그램 내의 클래스의 데이터들에 접근하고 조작하는 것을 지원하는 시스템이다.
- C++ 에서는 이것을 지원하지 않아서 언리얼에서 자체적으로 만들었다.
우리가 컴파일 전 작성해놓은 매크로를 헤더 툴이 분석해서 리플리케이션을 구성하는 소스코드를 만들어준다.
UPROPERTY() , UFUNCTION() 과 같이 매크로를 적어놓고 선언을 해주면 엔진의 가비지 컬렉터가 관리 해주는데 그렇지 않을 떄는 직접 프로그래머가 관리 해주면 된다.
UField
->UStruct
->UClass
이렇게 상속 받고 UStruct에서 리플렉션이 시작된다.
StaticClass() 혹은 GetClass() 를 통해 리플리케이션 정보에 접근 할 수 있다. (iterator 사용 가능)
StaticClass() : 컴파일타임에 이미 정해져있는 클래스
GetClass() : 런타임엥서 실제 객체의 클래스를 조회할때 사용된다.
https://devshovelinglife.tistory.com/715
[Unreal] GetClass 와 StaticClass 의 차이
GetClass vs StaticClass StaticClass 는 컴파일 타임에서 UClass 타입의 정보를 얻어오는 것이며, GetClass 는 런타임에서 실제 객체의 클래스를 조회할때 사용된다. AMyActor* ActorPtr = NewObject(...); UObject* ObjPtr = A
devshovelinglife.tistory.com
언리얼 디폴트 오브젝트(CDO)
- 언리얼 객체가 가진 기본 값을 보관한다.
- 클래스 한개로부터 다수의 물체를 생성해 게임 콘텐츠에 배치할 때 일관성 있게 기본 값 조정 하는데 사용한다.
- 엔진 초기화 과정에서 생성된다(안심하고 사용 가능).
- 자동으로 프로퍼티를 초기화한다(0).
- CDO에는 값을변경해도 기본값 그대로 저장된다. 생성자에서 저장해놓은 기본값은 후에 변경을 해도 기본값 호출 가능
UCLASS()
class OBJECTREFLECTION_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UMyGameInstance();
virtual void Init() override;
private:
UPROPERTY()
FString SchoolName;
};
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("기본학교");
}
void UMyGameInstance::Init()
{
Super::Init();
UE_LOG(LogTemp, Log, TEXT("============================="));
UClass* ClassRuntime = GetClass();
UClass* ClassCompile = UMyGameInstance::StaticClass();
UE_LOG(LogTemp, Log, TEXT("학교를 담당하는 클래스 이름 : %s"), *ClassRuntime->GetName());
SchoolName = TEXT("청강문화산업대학교");
UE_LOG(LogTemp, Log, TEXT("학교 이름 : %s"), *SchoolName);
UE_LOG(LogTemp, Log, TEXT("학교 이름 기본값 : %s"), *GetClass()->GetDefaultObject<UMyGameInstance>()->SchoolName);
}
출력 결과
=======================
MyGameInstacne
청강문화산업대학교
기본학교
check() 사용은 중요하다. 게임 빌드 시에 check() 구문은 사라지기 때문에 괜찮다.
GPT의 설명
언리얼 엔진의 빌드 시에 Check() 구문은 코드의 런타임 상태를 확인하여 버그나 잠재적인 오류를 찾는 데 사용됩니다. 이러한 Check() 구문들은 디버그 모드에서는 실행되지만, 릴리스 모드에서는 실행되지 않습니다. 따라서 디버그 빌드에서는 잠재적인 문제를 조기에 발견할 수 있으며, 릴리스 빌드에서는 추가 오버헤드 없이 최적화된 코드를 실행할 수 있습니다.
Check() 구문은 프로그램의 상태를 검사하고, 조건이 참이 아니면 프로그램을 중단시키는 역할을 합니다. 이를 통해 개발자는 코드 실행 중에 예상치 못한 문제를 조기에 발견하고 디버깅할 수 있습니다.
일반적으로 언리얼 엔진 코드에서 Check() 구문은 코드의 무결성을 유지하고 예기치 않은 상황을 방지하기 위해 사용됩니다. 개발자는 자신의 코드에서 Check() 구문을 사용하여 프로그램이 예상대로 동작하는지 확인하고, 잠재적인 버그를 미리 발견할 수 있습니다.
ensure()
- 에디터 Crash 없이 크래시 요소를 확인 할 수 있다.
- ensureMsgf( 확인할 구문 혹은 변수 ,TEXT("에러 시 문자열")) <- 에러 시 에러 문자열 출력해준다.
GPT가 말하는 CDO 의 이점
1. 일관성 있는 초기화: 엔진이 시작될 때 클래스의 CDO가 생성되면, 모든 인스턴스는 이러한 초기화된 기본값을 기반으로 시작됩니다. 이는 게임 실행 시 예기치 못한 동작을 방지하고, 일관된 게임 플레이 경험을 제공합니다.
2. 데이터 접근의 안정성: CDO를 사용하여 클래스의 기본값을 설정하면, 게임의 다른 부분에서 이러한 값을 사용할 때 예상치 못한 오류를 방지할 수 있습니다. 이는 데이터 무결성을 유지하고 프로그램의 안정성을 향상시킵니다.
3. 성능 최적화: 게임 시작 시점에서 CDO를 생성하는 것은 런타임 동안 클래스의 인스턴스를 생성하는 것보다 효율적입니다. 클래스의 인스턴스가 필요할 때마다 CDO를 복제하거나 참조하여 사용할 수 있으므로, 객체 생성과 소멸에 따른 오버헤드를 줄일 수 있습니다.
UStudent* Student = NewObject<UStudent>();
UTeacher* Teacher = NewObject<UTeacher>(); # 새로운 오브젝트 생성 방법
Student->SetName(TEXT("학생1"));
UE_LOG(LogTemp, Log, TEXT("새로운 학생 이름 %s"), *Student->GetName());
FString CurrentTeacherName;
FString NewTeacherName(TEXT("이득우"));
FProperty* NameProp = UTeacher::StaticClass()->FindPropertyByName(TEXT("Name")); #CDO 를 활용해서 속성의 참조를 가져옴
if (NameProp)
{
NameProp->GetValue_InContainer(Teacher, &CurrentTeacherName); # CurrentTeacherName 에 Teacher 대입
UE_LOG(LogTemp, Log, TEXT("현재 선생님 이름 %s"), *CurrentTeacherName);
NameProp->SetValue_InContainer(Teacher, &NewTeacherName); # Set 도 가능
UE_LOG(LogTemp, Log, TEXT("새로운 선생님 이름 %s"), *Teacher->GetName());
}
UE_LOG(LogTemp, Log, TEXT("============================="));
Student->DoLesson();
UFunction* DoLessonFunc = Teacher->GetClass()->FindFunctionByName(TEXT("DoLesson")); # UFunction 포인터로 받아서 함수 참조 가져옴
if (DoLessonFunc)
{
Teacher->ProcessEvent(DoLessonFunc, nullptr); # ProcessEvent 로 함수 호출
}
UE_LOG(LogTemp, Log, TEXT("============================="));