프로퍼티 시스템이란? (리플렉션)
**리플렉션(Reflection)**은 프로그램이 실행 시간에 자기 자신을 조사하는 기능입니다. C++는 기본적으로 리플렉션을 지원하지 않지만, 언리얼 엔진은 자체 리플렉션 시스템을 구축하여 다음과 같은 핵심 기능들을 제공합니다:
- 에디터의 디테일 패널
- Serialization (저장/로드)
- 가비지 콜렉션
- 네트워크 리플리케이션
- 블루프린트/C++ 커뮤니케이션
프로퍼티 시스템의 주요 기능
1. 자동 프로퍼티 초기화
// 예시: AEnemy 클래스
UPROPERTY(EditAnywhere, Category="Stats")
int32 Health = 100;
// 레벨에 배치하고 Health를 500으로 설정 후 저장하면
// 별도 코드 없이 자동으로 로드됨
UObject는 생성자 호출 전에 자동으로 0으로 초기화됩니다. 이후 생성자에서 커스텀 값으로 초기화할 수 있습니다.
2. 레퍼런스 자동 업데이트
AActor 또는 UActorComponent가 소멸되면, UPROPERTY로 마킹된 포인터나 언리얼 컨테이너(TArray 등)에 저장된 레퍼런스는 자동으로 null이 됩니다.
// UPROPERTY로 마킹된 포인터는 자동으로 null 처리됨
UPROPERTY()
AActor* MyActor;
// Raw 포인터는 자동 null 처리 안 됨 (위험!)
AActor* RawPointer; // 사용 지양
// 가비지 컬렉션은 막지 않지만 유효성 검사가 가능한 약 포인터
TWeakObjectPtr<AActor> WeakPointer; // 대안으로 사용 가능
3. 자동 Serialization
UObject가 저장될 때, 모든 UPROPERTY 값은 자동으로 읽고 쓰기가 가능합니다 (transient로 마킹되지 않은 경우).
// 예시: AEnemy 클래스
UPROPERTY(EditAnywhere, Category="Stats")
int32 Health = 100;
// 레벨에 배치하고 Health를 500으로 설정 후 저장하면
// 별도 코드 없이 자동으로 로드됨
4. 프로퍼티 값 자동 업데이트
CDO(Class Default Object)가 변경되면, 기존 인스턴스들의 값이 자동으로 업데이트됩니다.
작동 원리
- 인스턴스의 값이 이전 CDO 기본값과 같으면 -> 새 CDO 값으로 업데이트
- 인스턴스의 값이 다르면 -> 의도적으로 설정된 것으로 간주하여 보존
// 예시
// 초기 CDO Health = 100, Enemy_1 Health = 100, Enemy_2 Health = 500
// CDO Health를 150으로 변경하면
// Enemy_1 Health = 150 (자동 업데이트)
// Enemy_2 Health = 500 (보존됨)
5. 에디터 통합
UPROPERTY와 UFUNCTION은 별도 코드 없이 에디터에 자동으로 노출되며, 블루프린트와의 통합도 가능합니다.
6. 런타임 타입 정보 및 형변환
모든 UObject는 자신의 타입을 알고 있으며, 안전한 형변환이 가능합니다.
// Cast를 사용한 안전한 형변환
AMegaBoss* MegaBoss = Cast<AMegaBoss>(Enemy);
if (MegaBoss)
{
// 형변환 성공
MegaBoss->SpecialAttack();
}
// IsA를 사용한 타입 체크
if (Enemy->IsA(AMegaBoss::StaticClass()))
{
// Enemy가 AMegaBoss 타입임
}
// Super를 사용한 부모 함수 호출
class AMegaBoss : public AEnemy
{
virtual void Speak()
{
Say("Powering up! ");
Super::Speak(); // 부모 클래스의 Speak 호출
}
};
리플렉션 시스템 사용하기
1. 헤더 파일 상단에 generated.h 포함
#include "StrategyChar.generated.h" // 반드시 마지막에 include
2. 매크로 사용
/** 전략 게임의 기본 캐릭터 클래스입니다. */
UCLASS(Abstract)
class AStrategyChar : public ACharacter
{
GENERATED_BODY() // 또는 GENERATED_UCLASS_BODY()
/** 죽었을 때 얻을 수 있는 자원량입니다. */
UPROPERTY(EditAnywhere, Category="변수")
int32 ResourcesToGather;
/** 무기를 장착하는 함수입니다. */
UFUNCTION(BlueprintCallable, Category="Attachment")
void SetWeaponAttachment(class UStrategyAttachment* Weapon);
protected:
/** 근접 공격 애니메이션입니다. */
UPROPERTY(EditDefaultsOnly, Category="변수")
UAnimMontage* MeleeAnim;
private:
// UPROPERTY가 없는 멤버 변수도 사용 가능
// 단, 리플렉션 시스템에서는 보이지 않음
uint8 MyTeamNum;
};
```
### 리플렉션 타입 계층구조
```
UField
├── UStruct
│ ├── UClass (C++ class)
│ ├── UScriptStruct (C++ struct)
│ └── UFunction (C++ function)
├── UEnum (C++ enumeration)
└── UProperty (C++ member variable)
클래스 정보 가져오기
// 컴파일 타임에 클래스 정보 가져오기
UClass* ClassCompile = UMyGameInstance::StaticClass();
UScriptStruct* StructInfo = FMyStruct::StaticStruct();
// 런타임에 인스턴스로부터 클래스 정보 가져오기
UClass* ClassRuntime = MyInstance->GetClass();
// 두 방식으로 얻은 클래스 정보는 정확히 동일한 객체입니다
// ClassCompile == ClassRuntime
// 클래스 이름 가져오기
FString ClassName = ClassRuntime->GetName();
Class Default Object (CDO)
Class Default Object는 언리얼 객체가 가진 기본값을 보관하는 템플릿 객체입니다.
CDO의 특징
- UClass 정보에 함께 포함되어 있음
- 엔진 초기화 과정에서 생성됨 (프로그램 75% 정도 로드되었을 타이밍)
- 클래스당 하나만 존재
CDO 접근 방법
// 방법 1: GetClass()를 통한 접근
UMyGameInstance* DefaultObject = GetClass()->GetDefaultObject<UMyGameInstance>();
FString DefaultSchoolName = DefaultObject->SchoolName;
// 방법 2: StaticClass()를 통한 접근
UMyGameInstance* CDO = UMyGameInstance::StaticClass()->GetDefaultObject<UMyGameInstance>();
// CDO의 값 읽기
int32 DefaultHealth = CDO->Health;
리플렉션 데이터 활용
프로퍼티 반복 처리
/** 클래스의 모든 프로퍼티를 순회하는 함수입니다. */
void IterateProperties()
{
// 모든 UPROPERTY 순회
for (TFieldIterator<UProperty> PropIt(GetClass()); PropIt; ++PropIt)
{
UProperty* Property = *PropIt;
// 프로퍼티 이름 출력
UE_LOG(LogTemp, Log, TEXT("Property: %s"), *Property->GetName());
}
}
/** 특정 클래스의 함수만 순회하는 함수입니다. */
void IterateFunctions()
{
// 상속받은 함수 제외, 현재 클래스의 함수만 순회
for (TFieldIterator<UFunction> FuncIt(GetClass(), EFieldIteratorFlags::ExcludeSuper); FuncIt; ++FuncIt)
{
UFunction* Function = *FuncIt;
// 함수 처리
}
}
언리얼 객체 생성
NewObject를 사용한 올바른 생성 방법
// ❌ 잘못된 방법 - 일반 C++ new 사용
UMyObject* WrongObject = new UMyObject(); // 사용 금지!
// ✅ 올바른 방법 - NewObject API 사용
UMyObject* CorrectObject = NewObject<UMyObject>(this);
// 외부 객체를 소유자로 지정
UMyObject* MyObject = NewObject<UMyObject>(Outer);
// 클래스 정보로부터 생성
UMyObject* DynamicObject = NewObject<UMyObject>(this, MyClass);
중요: 언리얼 오브젝트는 반드시 NewObject()API로 생성해야 합니다. 일반 C++ new 연산자를 사용하면 가비지 컬렉션, 리플렉션 등의 기능이 작동하지 않습니다.
UHT (Unreal Header Tool)의 한계
1. 조건부 컴파일 제한
// ❌ UPROPERTY에 조건부 컴파일 사용 지양
#if PLATFORM_WINDOWS
UPROPERTY()
int32 WindowsOnlyValue; // 위험!
#endif
// ✅ WITH_EDITOR와 WITH_EDITORONLY_DATA는 예외적으로 허용
#if WITH_EDITOR
UPROPERTY()
int32 EditorOnlyValue; // 안전
#endif
2. 제한된 템플릿 지원
// ✅ 지원되는 템플릿 타입
UPROPERTY()
TArray<AActor*> Actors;
UPROPERTY()
TSubclassOf<AActor> ActorClass;
// ❌ 중첩 템플릿은 지원 안 됨
UPROPERTY()
TArray<TArray<int32>> NestedArray; // 컴파일 에러!
3. generated.h 위치
#include "OtherHeader.h"
#include "MyClass.generated.h" // 반드시 마지막에 include
실전 예제
예제 1 : 동적 프로퍼티 값 변경
/** 프로퍼티 시스템을 활용한 동적 값 변경 예제입니다. */
void SetPropertyByName(UObject* Object, FName PropertyName, int32 NewValue)
{
// 프로퍼티 찾기
UProperty* Property = Object->GetClass()->FindPropertyByName(PropertyName);
if (Property)
{
// int32 타입 프로퍼티인지 확인
UIntProperty* IntProperty = Cast<UIntProperty>(Property);
if (IntProperty)
{
// 값 설정
IntProperty->SetPropertyValue_InContainer(Object, NewValue);
}
}
}
예제 2 : 리플렉션을 활용한 디버그 출력
/** 객체의 모든 프로퍼티 값을 로그로 출력하는 함수입니다. */
void DebugPrintProperties(UObject* Object)
{
if (!Object) return;
UE_LOG(LogTemp, Log, TEXT("=== %s Properties ==="), *Object->GetName());
for (TFieldIterator<UProperty> PropIt(Object->GetClass()); PropIt; ++PropIt)
{
UProperty* Property = *PropIt;
FString PropertyName = Property->GetName();
FString PropertyValue;
// 프로퍼티 값을 문자열로 변환
Property->ExportTextItem(PropertyValue, Property->ContainerPtrToValuePtr<void>(Object), nullptr, Object, PPF_None);
UE_LOG(LogTemp, Log, TEXT("%s = %s"), *PropertyName, *PropertyValue);
}
}'기타 > [강의] 이득우의 언리얼 프로그래밍 Part1' 카테고리의 다른 글
| [이득우의 언리얼] 8. 언리얼의 C++ 설계 - 컴포지션 (0) | 2026.02.06 |
|---|---|
| [이득우의 언리얼] 7. 언리얼 C++ 설계 - 인터페이스 (0) | 2026.02.04 |
| [이득우의 언리얼] 6.언리얼 오브젝트 리플렉션 시스템 2 (0) | 2026.02.04 |
| [이득우의 언리얼] 4. 언리얼 오브젝트 시스템 (0) | 2026.01.21 |
| [이득우의 언리얼] 3. FString의 구조와 활용 (0) | 2026.01.20 |