Unreal engine/Unreal C++

1_6_언리얼 오브젝트의 이해_언리얼 오브젝트 리플렉션 시스템2

devRiripong 2023. 9. 18.
반응형

언리얼 오브젝트 리플렉션 시스템의 활용

강의 목표

언리얼 오브젝트 리플렉션 시스템을 사용해 언리얼 오브젝트를 다루는 방법의 학습

 

정리

1. 리플렉션 시스템을 사용해 언리얼 오브젝트의 특정 속성과 함수를 이름으로 검색할 수 있다.

2. 리플렉션 시스템을 사용해 접근 지시자와 무관하게 값을 설정할 수 있다.

3. 리플렉션 시스템을 사용해 언리얼 오브젝트의 함수를 호출할 수 있다.

 

언리얼 엔진의 기본 프레임웍은 리플렉션을 활용해 구축되어 있으므로 언리얼 엔진을 이해하기 위해서는 리플렉션 시스템을 이해하는 것이 필요함.

 

 

 

Person 클래스와 이를 상속받은 Student, Teacher 클래스를 만든다. 

 

일단 Person.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: 
	
};

이렇게 변수와 함수를 선언 해주고 alt + enter키를 눌러 Person.cpp에 구현부를 만들어 준다.

UPerson::UPerson()
{
	Name = TEXT("홍길동"); 
	Year = 1; 
}

void UPerson::DoLesson()
{
	UE_LOG(LogTemp, Log, TEXT("%s님이 수업에 참여합니다."), *Name); 
}

const FString& UPerson::GetName() const
{
	return Name;
}

void UPerson::SetName(const FString& InName)
{
	Name = InName; 
}

컴파일을 해서 잘 되나 확인한다.

이제 이걸 상속받은 Student클래스를 구현해보자.

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Person.h"
#include "Student.generated.h"

언리얼 Object 를 선언할 떄는 generated.h가 가장 밑에 오게 해야 한다.

UCLASS()
class OBJECTREFLECTION_API UStudent : public UPerson
{
	GENERATED_BODY()
public:

private:
	
};

이렇게 하고 빌드를 한다.

UCLASS()
class OBJECTREFLECTION_API UStudent : public UPerson
{
	GENERATED_BODY()
public:
	UStudent(); 

	virtual void DoLesson() override; 

private:
	UPROPERTY()
	int32 Id; 
};

이렇게 하고, 함수들의 구현부를 alt + enter를 이용해 만들어준다.

컴파일을 진행한다.

#include "Student.h"

UStudent::UStudent()
{
	Name = TEXT("이학생"); 
	Year = 1; 
	Id = 1; 
}

void UStudent::DoLesson()
{
	Super::DoLesson(); 

	UE_LOG(LogTemp, Log, TEXT("%d학년 %d번 %s님이 수업을 듣습니다."), Year, Id, *Name); 
}

이렇게 구현부를 작성하고, 디폴트 오브젝트가 다시 생성해줘야 하기 때문에 컴파일 해준다.

Teacher도 동일하게 구현을 해준다.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Person.h"
#include "Teacher.generated.h"

/**
 * 
 */
UCLASS()
class OBJECTREFLECTION_API UTeacher : public UPerson
{
	GENERATED_BODY()
public:
	UTeacher(); 

	virtual void DoLesson() override; 

private:
	UPROPERTY()
	int32 Id; 	
};

이렇게 하고, alt+enter로 함수의 구현부를 만든 후 컴파일을 하기 전에 기본값을 지정한다.

가끔 Super:: 다음 것이 자동 검색이 안된다면 헤더 파일에서 한칸 엔터를 치거나 하면 .generated.h이 재생성 된다.

#include "Teacher.h"

UTeacher::UTeacher()
{
	Name = TEXT("이선생"); 
	Year = 3; 
	Id = 1; 
}

void UTeacher::DoLesson()
{
	Super::DoLesson(); 

	UE_LOG(LogTemp, Log, TEXT("%d년차 선생님 %s님이 수업을 강의합니다"), Year, *Name);
}

이렇게 작성하고, 한글이 들어가 있기 때문에 새 이름으로 저장해서 인코딩을 유니코드로 바꿔준다. Student.cpp, Person.cpp도 마찬가지로 해준다.

빌드를 한다.

이 세가지 클래스들은 MyGameInstance의 Init함수에서 실행하면서 리플렉션 기능들을 테스트 해보자.

MyGameInstnace.cpp에 헤더를 선언해준다.

.cpp에 선언할 헤더도 해당 오브젝트의 헤더가 가장 위쪽에 위치해 있어야 한다. 이 규칙을 지키지 않으면 컴파일러에서 에러를 띄운다.

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyGameInstance.h"
#include "Student.h"
#include "Teacher.h"

UMyGameInstance::UMyGameInstance()
{
	SchoolName = TEXT("기본학교");
}

void UMyGameInstance::Init()
{
	Super::Init();   

	UE_LOG(LogTemp, Log, TEXT("==============================")); 
	UClass* ClassRuntime = GetClass(); 
	UClass* ClassCompile = UMyGameInstance::StaticClass(); 

	//check(ClassRuntime != ClassCompile); 
	//ensure(ClassRuntime != ClassCompile); 
	//ensureMsgf(ClassRuntime != ClassCompile, TEXT("일부러 에러를 발생시킨 코드"));

	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); 

	UE_LOG(LogTemp, Log, TEXT("==============================")); 

	UStudent* Student = NewObject<UStudent>(); 
	UTeacher* Teacher = NewObject<UTeacher>(); 
	Student->SetName(TEXT("학생1")); 
	UE_LOG(LogTemp, Log, TEXT("새로운 학생 이름 %s"), *Student->GetName()); 

	FString CurrentTeacherName; 
	FProperty* NameProp = UTeacher::StaticClass()->FindPropertyByName(TEXT("Name")); 
	if (NameProp)
	{
		NameProp->GetValue_InContainer(Teacher, &CurrentTeacherName); 
		UE_LOG(LogTemp, Log, TEXT("현재 선생님 이름 %s"), *CurrentTeacherName);
	}
}

이렇게 하고 빌드 하고, 언리얼에서 확인해보면

`

새로운 학생 이름을 GetName으로 잘 가져온 거 볼 수 있고,

이름과 선생 이름에 대해서는 언리얼 리플렉션 시스템을 사용해서 protected 지시자로 보호 받고 있는 프로퍼티 값을 가져와서 출력하게 된거. 이런 기능도 가능하다 정도로 이해하면 된다.

이번엔 선생님 이름 바꿔보자.

    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());
	}

이 코드를 추가하고 언리얼에서 alt ctrl f11을 누르고 실행해 보면

잘 변경된 것을 볼 수 있다.

이번엔 리플렉션 기능으로 함수를 출력해보는 기능을 구현해보자.

Student->DoLesson();

이 함수를 실행하면 로그가 나오는지 보자

선생님과 관련된 함수는 리플렉션을 써보자.

 

        UE_LOG(LogTemp, Log, TEXT("=============================="));

	Student->DoLesson(); 
	UFunction* DoLessonFunc = Teacher->GetClass()->FindFunctionByName(TEXT("DoLesson"));
	if (DoLessonFunc)
	{
		Teacher->ProcessEvent(DoLessonFunc, nullptr); 
	}

	UE_LOG(LogTemp, Log, TEXT("=============================="));

잘 찍히는 걸 볼 수 있다.

반응형

댓글