Arknights EP - [Confront]
Categories
Tags
1199 words
6 minutes
[UE5/Part1] 6. UObject Reflection 2
“이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해” 학습 내용을 정리한 강의 노트입니다.
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.
강의 목표
- 리플렉션 시스템을 이용해 언리얼 오브젝트를 다루는 방법
- 속성/함수 검색 및 호출/변경
- 접근 지시자 우회
1. FProperty로 속성 접근/변경
MyGameInstance.cpp
Super::Init();
// 언리얼 오브젝트 생성 - NewObject<T>()
UStudent* Student = NewObject<UStudent>(this);
UTeacher* Teacher = NewObject<UTeacher>(this);
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());
}Person.h
protected:
UPROPERTY()
FString Name; // 선생(학생) 이름Output Log
LogTemp: 현재 선생님 이름 이선생
LogTemp: 현재 선생님 이름 새이름FProperty의 API를 이용해 프로퍼티의 접근 및 변경이 가능하다.GetValue_InContainer()SetValue_InContainer()
- 이때, Protected로 보호받고 있는 프로퍼티 값인 선생님 이름을 Reflection을 이용해 그대로 출력 가능
2. UFunction으로 함수 사용
MyGameInstance.cpp
Student->DoLesson();
UFunction* DoLessonFunc = Teacher->GetClass()->FindFunctionByName(TEXT("DoLesson"));
if (DoLessonFunc)
{
Teacher->ProcessEvent(DoLessonFunc, nullptr); // 인자가 없는 함수라 null 전달
}Output Log
LogTemp: 새이름님이 수업에 참여합니다. // Person.cpp
LogTemp: 3년차 선생님 새이름님이 수업을 강의합니다. // Teacher.cppUFunction의 API를 이용해 함수 사용이 가능하다.
정리
- Reflection System 활용
- 언리얼 오브젝트의 특정 속성과 함수를 이름으로 검색 가능
- 접근 지시자와 무관하게 값을 설정
- 언리얼 오브젝트의 함수 호출
- 언리얼 엔진의 프레임워크는 리플렉션을 활용해서 구축되어 있으므로, 이해가 필요
- (주의) 컴파일러의 타입 체크 지원을 받지 못하므로, 런타임때 검증 로직을 직접 작성해야 함
번외
타입 안정성 개선 : CastField<T>()
문제
FindPropertyByName은 단순히 메모리 오프셋 정보만 가져온다.- 타입 일치 여부를 보장하지 않아, 메모리 오염(Memory Corruption) 위험이 존재
개선
CastField<T>를 사용해 런타임 타입 검사(RTTI)를 수행
FProperty* NameProp = UTeacher::StaticClass()->FindPropertyByName(TEXT("Name"));
if (NameProp)
{
// FStrProperty인지 확인 (int, float 등 다른 타입일 경우 크래시 방지를 위해서)
FStrProperty* StrProp = CastField<FStrProperty>(NameProp);
if (StrProp)
{
StrProp->SetValue_InContainer(Teacher, &NewTeacherName);
}
}상속시 주의점 : generated.h
#include "Student.generated.h"
#include "Person.h" // generated.h 아래에 있으면 빌드 오류 발생- 언리얼에서 다른 헤더를 상속 받을때 항상
generated.h파일이 가장 밑에 include 되도록 주의하자. - UHT가
Student.h와 같은 C++ 헤더를 파싱하여, 리플렉션 정보가 담긴.generated.h를 만들기 때문에, 당연히 이 파일이 마지막에 있어야GENERATED_BODY매크로가 정상 작동한다.
GetValue_InContainer vs C++ 직접 접근
문제
- 리플렉션은 유연하지만 느리다. 매 프레임 호출되는
Tick같은 곳에서는 지양해야한다.FindPropertyByName검색은 당연히 직접 접근하는Teacher->SetName()보다 느리다.
써야 한다면 캐싱해서 쓰자
BeginPlay에서FindPropertyByName으로FProperty*를 찾아 변수에 저장(캐싱)해두고Tick에서는 저장된 포인터로SetValue_InContainer만 수행
실습 코드 구조
- Student와 Teacher는 Person을 상속받음
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:
UPROPERTY() // 이래야 UE가 관리해줌(GC, 직렬화, 리플렉션 등)
FString SchoolName;
};MyGameInstance.cpp
#include "MyGameInstance.h" // 현재 언리얼 오브젝트 헤더가 가장 위에 있어야 함 (UHT 규칙)
#include "Student.h"
#include "Teacher.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("기본 학교"); // 이 기본값은 CDO 템플릿 객체에 저장
}
void UMyGameInstance::Init()
{
Super::Init();
// 언리얼 오브젝트 생성 - NewObject<T>()
UStudent* Student = NewObject<UStudent>();
UTeacher* Teacher = NewObject<UTeacher>();
Student->SetName(TEXT("학생1"));
UE_LOG(LogTemp, Log, TEXT("새로운 학생 이름 %s"), *Student->GetName());
// reflection을 이용해 속성 접근/변경
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);
// FProperty API는 주소(void*)를 요구하며, 대상 객체 메모리에 값을 복사한다.
// 단, FString은 내부적으로 버퍼 공유 + 참조 카운트 증가 방식이라
// 실제 문자열 데이터는 즉시 복제되지 않는다 (Copy-On-Write 발생 X → O(1), 발생 시 → O(n))
NameProp->SetValue_InContainer(Teacher, &NewTeacherName);
UE_LOG(LogTemp, Log, TEXT("현재 선생님 이름 %s"), *Teacher->GetName());
}
UE_LOG(LogTemp, Log, TEXT("===================="));
// reflection을 이용해 함수 사용
Student->DoLesson();
UFunction* DoLessonFunc = Teacher->GetClass()->FindFunctionByName(TEXT("DoLesson"));
if (DoLessonFunc)
{
Teacher->ProcessEvent(DoLessonFunc, nullptr); // 인자가 없는 함수라 null 전달
}
}Person.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.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:
};
