“이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해” 학습 내용을 정리한 강의 노트입니다. 옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.
문서 내용 정리 -> 강의 요약 -> 실습 & 분석 -> 번외 순으로 정리되어 있습니다.
복습할때는 실습&분석 파트를 읽는걸 권장
강의 목표
- 언리얼 오브젝트의 특징과 리플렉션 시스템의 설명
- 언리얼 오브젝트의 처리 방식의 이해
(문서) Unreal Property System
링크 Reflection은 반사를 의미하는 그래픽 용어와 혼동되어 Unreal Property System이라고도 부른다.
Reflection은 프로그램이 실행시간에 자기 자신을 조사하는 기능- but) C++은 지원하지 않아, 언리얼 자체적으로 구축되어 있음
include "FileName.generated.h"-
UENUM(), UCLASS(), USTRUCT(), UFUNCTION(), UPROPERTY() 등의 매크로를 추가하면 언리얼 헤더 툴이 적절한 코드들을 자동으로 Intermediate 폴더 내부에 생성
-
모든 멤버 변수가 반드시
UPROPERTY()로 설정될 필요는 없음 -
리플렉션된 프로퍼티가 아닌 것들은 UE시스템에서 관리를 받지 않음
- GC가 레퍼런스를 확인 못함 (위험)
UPROPERTY()붙이면 아넌
-
UHT는 실제 C++ 파서가 아님 (한계)
- 너무 족잡한 유형은 UHT이 읽어드리지 못함
- 프로퍼티 시스템의 계층 구조
- 추후 강의에서 자세히 다룸
UField
UStruct - 기본적인 종합 구조체
UClass (C++ class) - 자손으로 함수나 프로퍼티 포함 가능
UScriptStruct (C++ struct) - 자손은 프로퍼티로만 제한
UFunction (C++ function) - 자손은 프로퍼티로만 제한
UEnum (C++ enumeration)
UProperty (C++ member variable or function parameter)
(Many subclasses for different types)이렇게 구축된 UnrealObject 클래스는 Static 클래스 함수나 Get 클래스 함수로 접근 가능
- 이 함수를 호출하면 이러한 Reflection, UHT이 분석해서 만들어 놓은 Reflection 정보들을 보관한 특정 객체에 접근 가능
for (TFieldIterator<UProperty> PropIt(GetClass()); PropIt; ++PropIt)
{
UProperty* Property = *PropIt;
// Do something with the property
}UProperty라는 UnrealObject를 사용해서 순회하면, UnrealObject의 속성들을 우리가 조회하면서 거기에 대한 값이나 정보를 빼올 수 있다.
- 다음 강의 실습 예제로 확인
(이론 강의)
언리얼 오브젝트의 구성
- 언리얼 오브젝트에는 특별한 프로퍼티와 함수 지정 가능
- UPROPERTY : 클래스 멤버 변수
- UFUNCTION : 클래스 멤버 함수
- 모든 언리얼 오브젝트는 클래스 정보와 함께 함
- 모든 프로퍼티 & 함수 정보를 언제든 조회 가능
- 컴파일 타임 :
UMyGameInstance::StaticClass(); - 런타임 :
GetClass();
- 컴파일 타임 :
- 모든 프로퍼티 & 함수 정보를 언제든 조회 가능
- 이런 기능을 제공하는 언리얼 오브젝트는 NewObject API를 사용해 생성해야 함.

Class Default Object; CDO
- 언리얼 클래스 정보에는 클래스 기본 오브젝트
CDO가 포함됨 CDO는 언리얼 객체가 가진 기본 값을 보관하는 템플릿 객체- 가정) 한 클래스로부터 다수의 물체를 생성해 게임 콘텐츠에 배치하는 상황
- 일관성 있게 기본 값을 조정하는데 유영하게 사용
- 클래스 정보
UClass->GetDefaultObject()=CDO추출 가능 UClass,CDO는 엔진 초기화 과정에서 생성- 직접 디버그 걸어서 확인 가능 (엔진 로드 75%쯤에서 브레이크 걸림)

- 직접 디버그 걸어서 확인 가능 (엔진 로드 75%쯤에서 브레이크 걸림)
UClass()와 CDO가 어떻게 동작하는지 실습을 통해 알아보자
0. 실습용 코드 구조
- 헤더 구조는 그대로 가져가고, 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:
UPROPERTY() // 이래야 UE가 관리해줌(GC, 직렬화, 리플렉션 등)
FString SchoolName;
};MyGameInstance.cpp
#include "MyGameInstance.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("기본 학교"); // 이 기본값은 CDO 템플릿 객체에 저장
}
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);
UE_LOG(LogTemp, Log, TEXT("===================="));
}1. 검증 코드 (Assertion 함수)
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("===================="));
}로그
LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: ====================- 정상적으로
check()Assertion 함수를 통과하고, 로그에 MyGameInstance가 찍히는걸 볼 수 있다.
1-1. check()
MyGameInstance.cpp
check(ClassRuntime != ClassCompile);- 이번에는, check를 부정이 뜨도록 바꿔보자.
Unreal Crash Reporter
LoginId:ec4d00644b8bfc8993dcb989a922f693
EpicAccountId:4d1d3208fcc147eba2964ad944fd2b63
Assertion failed: ClassRuntime != ClassCompile [File:F:\UE5\UE5Part1\ObjectReflection\Source\ObjectReflection\MyGameInstance.cpp] [Line: 17] - 컴파일 후, 언리얼을 실행해보면, 위와 같이 Crash Reporter가 뜨며 에디터가 다운된다.
Assertion failed: ClassRuntime != ClassCompile에서 Assertion 실패
1-2. ensure()
MyGameInstance.cpp
ensure(ClassRuntime != ClassCompile);Output Log ![[Pasted image 20260204093829.png]]
- 실행하면, 에디터는 돌아가나 로그에 빨간 에러가 잡힌다.
1-3. ensureMsgf()
MyGameInstance.cpp
ensureMsgf(ClassRuntime != ClassCompile, TEXT("에러 발생 !!!"));Output Log ![[Pasted image 20260204094409.png]]
- 로그에, 우리가 입력한 TEXT도 함께 잡힌다.
정리
check()와 같은 함수를 계속 넣어주며 검증하기- 실제 게임으로 빌드할때는 모두 사라지니 안심하고 사용하자
2. Class Default Object; CDO
정의
- UE엔진은 모든
UClass타입에 대해 기본 객체 (Default Object)를 하나 자동으로 생성.- 이 기본 객체를
Class Default Object; CDO라고 부른다.
- 이 기본 객체를
역할
- 해당 클래스의 기본값(Default Propery Values)을 저장하는 템플릿 역할
- 새 인스터스를 만들 때, 이 CDO에 저장된 값들을 복사해서 초기화
- 블루프린트 에디터에서 Default Values 창에 보이는 값들이 사실상 CDO에 들어있는 값
특징
- 엔진이 자동으로 생성
- 개발자가
new로 만드는 게 아님.
- 개발자가
StaticClass()->GetDefaultObject()로 접근 가능
실습
2-1. CDO를 덮어주는 값 출력
MyGameInstance.cpp
#include "MyGameInstance.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("기본 학교"); // 이 기본값은 CDO 템플릿 객체에 저장
}
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("임의의 학교"); // 현재 인스턴스의 멤버 변수를 덮는다. (CDO로 설정된 초기값이 덮어짐)
UE_LOG(LogTemp, Log, TEXT("학교 이름 : %s"), *SchoolName);
UE_LOG(LogTemp, Log, TEXT("===================="));
}- 위와 같이 SchoolName = TEXT()로 CDO에 저장된 값을 덮어씌우면, 정상적으로 변경된다.
LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: 학교 이름 : 임의의 학교
LogTemp: ====================2-2. CDO 기본값 출력
MyGameInstance.cpp
SchoolName = TEXT("임의의 학교");
UE_LOG(LogTemp, Log, TEXT("학교 이름 : %s"), *SchoolName);
UE_LOG(LogTemp, Log, TEXT("학교 이름 기본값 : %s"), *GetClass()->GetDefaultObject<UMyGameInstance>()->SchoolName);- 이번에는,
*GetClass()->GetDefaultObject<UMyGameInstance>()->SchoolName로 CDO 기본값을 출력해보자
LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: 학교 이름 : 임의의 학교
LogTemp: 학교 이름 기본값 :
LogTemp: ====================- 위와 같이 기본값이 안나오는데…
- Class Default Object를 고쳐주는 생성자 코드를 변경하는 경우에도 헤더 파일을 고치는 것과 똑같이 에디터를 꺼줘야한다.
- 헤더 파일의 리플렉션 정보에 구조를 변경
- 생성자 코드에서 CDO의 기본값을 변경
LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: 학교 이름 : 임의의 학교
LogTemp: 학교 이름 기본값 : 기본 학교
LogTemp: ====================- 에디터를 다시 실행하면 정상적으로 로그에 찍히는걸 볼 수 있다.
3. 정리
UObject <-> UClass 매칭
- 모든
UObject기반 클래스는 자신을 설명하는UClass객체를 하나 가진다. StaticClass()로 해당 클래스의UClass객체에 접근이 가능하다.UClass()aozmfhsms UHT가 해당 클래스를 리플렉션 시스테멩 등록하도록 표시한다.
UClass의 역할
UClass는 다음 2가지의 요소를 관리한다.
1. 클래스 메타데이터
- 클래스 이름, 함수 목록, 프로퍼티 목록 등
- 클래스 자체의 정보
2. CDO (Class Default Object)
- 기본값을 담은 템플릿 객체
- 인스턴스 생성 시 초기화 기준으로 사용
UClass는 CDO의 포인터를 가지고 있어 런타임에 접근 가능
생성 및 반영
- 클래스 정보와 CDO는 엔진 초기화 과정에서 자동으로 생성되므로 안전하게 사용 가능
- 헤더 변경 or 생정자에서 CDO 기본값 수정시 -> 에디터 종료 후 다시 컴파일해야 정상 반영
4. 번외
UPROPERTY()
매크로 분석
// These macros wrap metadata parsed by the Unreal Header Tool, and are otherwise
// ignored when code containing them is compiled by the C++ compiler
#define UPROPERTY(...)
#define UFUNCTION(...)
#define USTRUCT(...)
#define UMETA(...)
#define UPARAM(...)
#define UENUM(...)
#define UDELEGATE(...)
#define RIGVM_METHOD(...)
#define VMODULE(...)- 주석 부분
- 이 매크로들은 Unreal Header Tool (UHT)가 파싱하는 메타데이터를 감싸는 역할을 한다.
- C++ 컴파일러가 코드를 컴파일할 때는 무시된다.
즉, UPROPERTY()는 C++ 컴파일러 입장에서는 아무 의미 없는 빈 매크로이고, UHT가 소스 코드를 읽을 때만 의미를 가진다.
- 매크로 정의 부분
- #define UPROPERTY(…)라는 이름의 매크로를 정의했는데, 인자를 받아도 아무 코드로 치환되지 않는다.
- C++ 컴파일러가 볼 때는 그냥 ‘없음!’ 으로 처리된다.
정리
UPROPERTY()는 C++ 컴파일러에는 무시되는 깡통 매크로- 하지만, UHT (Unreal Header Tool)이 소스 코드를 파싱할 때는 중요한 메타데이터로 인식
- GC
- 직렬화
- 리플렉션
- 블루프린트 등에 필요한 정보를 생성
- C++ 컴파일러는 위에서 생성된
.generated.h에 메타데이터 코드를 포함해서 컴파일
Assertion (어설션) 함수
정의
- 프로그램이 실행될 때 특정 조건이 참인지 검사하는 함수
목적
- 잘못된 상태가 발생했을 때 즉시 알려주어 디버깅을 쉽게하고, 치명적인 오류를 방지
종류
assert()표준 C/C++<cassert헤더에 정의됨- 조건이 거짓이면 프로그램을 강제 종료하고 오류 메시지를 출력
- Unreal Engine의
check()/ensure()check(): 조건이 거짓이면 즉시 크래시 발생 (개발 중 치명적 오류 확인용)ensure(): 조건이 거짓이면 경고 메시지 출력 후 계속 실행 (비치명적 오류 확인용)
예시
void UMyGameInstance::Init()
{
Super::Init();
// 반드시 SchoolName이 비어있지 않아야 한다고 가정
check(!SchoolName.IsEmpty()); // 조건이 거짓이면 크래시 발생
// 혹은 덜 치명적인 경우
ensure(!SchoolName.IsEmpty()); // 조건이 거짓이면 경고만 띄우고 실행은 계속
}