언리얼 엔진은 C++를 기반으로 하지만, 순수한 C++만으로는 엔진의 강력한 리플렉션(Reflection), 직렬화(Serialization), 가비지 컬렉션(Garbage Collection), 블루프린트(Blueprint) 통합 등의 기능을 구현하기 어렵다. 이를 가능하게 하는 핵심적인 요소가 바로 UCLASS(), USTRUCT()와 같은 특수한 매크로다. 이 매크로들은 언리얼 헤더 툴(Unreal Header Tool, UHT)이라는 전처리기에 의해 처리되어, 런타임에 필요한 메타데이터와 보일러플레이트(boilerplate) 코드를 자동으로 생성해준다..
1. 언리얼 헤더 툴 (Unreal Header Tool, UHT)의 역할
언리얼 엔진의 빌드 시스템에서 UHT는 C++ 컴파일러가 코드를 처리하기 전에 먼저 실행되는 특별한 전처리 단계다. UHT의 주요 역할은 다음과 같다.
- 매크로 파싱:
UCLASS(),USTRUCT(),UPROPERTY(),UFUNCTION()등U접두사가 붙은 언리얼 특정 매크로들을 파싱한다. - 메타데이터 추출: 파싱된 매크로들로부터 클래스, 구조체, 프로퍼티, 함수의 이름, 타입, 접근 지정자, 블루프린트 노출 여부 등 다양한 메타데이터를 추출한다.
- 코드 생성: 추출된 메타데이터를 기반으로
*.generated.h및*.generated.cpp파일을 생성한다. 이 파일들에는 리플렉션 시스템이 런타임에 클래스 정보를 조회하고 조작할 수 있도록 하는 보일러플레이트 코드가 포함된다.
이 과정 덕분에 개발자는 C++ 표준 문법을 크게 벗어나지 않으면서도 언리얼 엔진의 강력한 기능을 활용할 수 있게 된다. UHT가 생성하는 코드는 C++ 컴파일러가 이해할 수 있는 형태로, 런타임에 리플렉션 데이터를 구축하는 데 사용된다.
2. UCLASS() 매크로의 특성과 역할
UCLASS() 매크로는 UObject를 상속받는 C++ 클래스에 적용된다. 이 매크로가 붙은 클래스는 언리얼 엔진의 객체 시스템에 완전히 통합되어 다음과 같은 특성을 가진다.
2.1. 내부 구조 및 UHT에 의한 코드 생성
UCLASS() 매크로가 붙은 클래스 내부에는 반드시 GENERATED_BODY() 매크로가 포함되어야 한다. UHT는 이 GENERATED_BODY() 위치에 해당 클래스에 대한 방대한 양의 코드를 삽입한다.
생성되는 주요 코드 요소:
- 리플렉션 메타데이터: 클래스의 이름, 부모 클래스, 모든
UPROPERTY()및UFUNCTION()에 대한 정보 등을 담는 정적UClass객체를 생성하고 초기화하는 코드. - 가비지 컬렉션 지원:
AddReferencedObjects()와 같은 함수를 생성하여, 해당 클래스가 참조하는 다른UObject들을 가비지 컬렉터에게 알림. - 블루프린트 통합: 블루프린트에서 클래스를 생성하고, 프로퍼티에 접근하며, 함수를 호출할 수 있도록 하는 인터페이스 코드.
- 직렬화: 객체의 상태를 저장하고 로드할 수 있도록 하는 코드.
- 네트워킹:
AActor와 같은 특정UCLASS에서는 네트워크 리플리케이션을 위한 코드가 생성됨. - 생성자 및 소멸자 래퍼: 엔진이 객체를 생성하고 파괴하는 과정에서 필요한 추가 로직을 수행하는 래퍼 함수.
2.2. 계층 구조에서의 동작
- 개발자 코드:
UCLASS()매크로를 사용하여AMyActor와 같은 클래스를 정의한다.UCLASS() class AMyActor : public AActor { GENERATED_BODY() // ... }; - UHT 전처리: 빌드 시 UHT가
AMyActor클래스를 스캔하고,AMyActor.generated.h및AMyActor.generated.cpp파일을 생성한다. 이 파일들에는AMyActor의 리플렉션 정보와 엔진 통합을 위한 코드가 포함된다. - C++ 컴파일러: UHT가 생성한 파일들과 개발자의 원본 C++ 파일이 함께 컴파일된다. 이 과정에서
GENERATED_BODY()는 UHT가 생성한 코드로 대체된다. - 엔진 초기화: 엔진이 시작될 때, 컴파일된 코드에 포함된 정적 초기화 루틴들이 실행되어 모든
UCLASS에 대한UClass객체(메타데이터)가 생성되고 전역 리플렉션 시스템에 등록된다. - 런타임 사용:
NewObject<AMyActor>()나SpawnActor<AMyActor>()와 같은 함수를 통해AMyActor의 인스턴스가 생성되면, 이 인스턴스는 자신의UClass객체를 통해 런타임에 자신의 타입 정보, 프로퍼티, 함수 등에 접근할 수 있게 된다. 가비지 컬렉터는 이 인스턴스의 생명주기를 관리한다.
3. USTRUCT() 매크로의 특성과 역할
USTRUCT() 매크로는 C++의 struct에 적용되어 언리얼 엔진의 리플렉션 시스템에 통합되도록 한다. USTRUCT는 UObject를 상속받지 않으므로, UCLASS와는 다른 특성을 가진다.
3.1. 내부 구조 및 UHT에 의한 코드 생성
USTRUCT() 매크로가 붙은 구조체 내부에도 GENERATED_BODY() 매크로가 포함되어야 한다. UHT는 이 위치에 해당 구조체에 대한 코드를 삽입한다.
생성되는 주요 코드 요소:
- 리플렉션 메타데이터: 구조체의 이름, 모든
UPROPERTY()에 대한 정보 등을 담는 정적UScriptStruct객체를 생성하고 초기화하는 코드. - 블루프린트 통합: 블루프린트에서 구조체를 변수 타입으로 사용하고, 프로퍼티에 접근할 수 있도록 하는 인터페이스 코드.
- 직렬화: 구조체의 상태를 저장하고 로드할 수 있도록 하는 코드.
- 복사 및 비교 연산자: 필요에 따라 구조체의 복사 및 비교를 위한 헬퍼 함수가 생성될 수 있음.
3.2. 계층 구조에서의 동작
- 개발자 코드:
USTRUCT()매크로를 사용하여FMyStruct와 같은 구조체를 정의한다.USTRUCT(BlueprintType) struct FMyStruct { GENERATED_BODY() // ... }; - UHT 전처리: 빌드 시 UHT가
FMyStruct구조체를 스캔하고,FMyStruct.generated.h및FMyStruct.generated.cpp파일을 생성한다. 이 파일들에는FMyStruct의 리플렉션 정보와 엔진 통합을 위한 코드가 포함된다. - C++ 컴파일러: UHT가 생성한 파일들과 개발자의 원본 C++ 파일이 함께 컴파일된다.
GENERATED_BODY()는 UHT가 생성한 코드로 대체된다. - 엔진 초기화: 엔진이 시작될 때, 컴파일된 코드에 포함된 정적 초기화 루틴들이 실행되어 모든
USTRUCT에 대한UScriptStruct객체(메타데이터)가 생성되고 전역 리플렉션 시스템에 등록된다. - 런타임 사용:
FMyStruct MyInstance;와 같이FMyStruct의 인스턴스가 생성되면, 이는 일반적인 C++ 구조체처럼 값 타입으로 동작한다. 하지만UPROPERTY()로 선언된UObject의 멤버로 포함되거나, 블루프린트에서 사용될 때, 엔진은UScriptStruct객체를 통해 런타임에 해당 구조체의 프로퍼티 정보에 접근하여 직렬화, 블루프린트 노출 등의 작업을 수행할 수 있다.
4. UCLASS() vs. USTRUCT() 매크로: 핵심 비교
| 특징 (Feature) | UCLASS() 매크로 | USTRUCT() 매크로 |
|---|---|---|
| 적용 대상 | UObject를 상속받는 C++ 클래스 |
C++ struct |
| UHT 생성 코드 | UClass 객체, GC 지원, 블루프린트 통합, 직렬화, 네트워킹 등 UObject 시스템 전반에 걸친 코드 |
UScriptStruct 객체, 블루프린트 통합, 직렬화 등 데이터 구조 관련 코드 |
GENERATED_BODY() 역할 |
UObject의 리플렉션, GC, 블루프린트 통합을 위한 핵심 코드 삽입 |
UStruct의 리플렉션, 블루프린트 통합, 직렬화를 위한 핵심 코드 삽입 |
| 리플렉션 객체 | UClass (클래스 메타데이터) |
UScriptStruct (구조체 메타데이터) |
| 가비지 컬렉션 | 지원 (UObject 인스턴스 관리) | 미지원 (소유 객체에 의해 관리) |
| 상속 | 지원 (클래스 계층 구조 형성) | 미지원 (단일 데이터 묶음) |
| 함수 (UFUNCTION) | 지원 (블루프린트 노출 및 RPC 등) | 미지원 (순수 데이터 구조) |
| 주요 사용 사례 | 독립적인 생명주기, 복잡한 기능, 다형성, 네트워킹이 필요한 객체 | 순수 데이터 묶음, 값 타입, 다른 UObject의 멤버로 포함 |
4.1. 장단점 및 사용 전략
UCLASS() 매크로 사용 시
- 장점: 언리얼 엔진의 모든 핵심 기능(GC, 리플렉션, 블루프린트, 네트워킹, 직렬화)을 활용할 수 있다. 복잡한 게임 로직과 상태를 가진 객체를 구현하는 데 필수적이다.
- 단점: UHT가 생성하는 코드의 양이 많고, 런타임 오버헤드(GC, 포인터 역참조)가
USTRUCT보다 크다. 메모리 파편화의 가능성도 있다. - 사용 시기: 게임 월드의 액터, 컴포넌트, 게임 모드, 게임 인스턴스, 위젯 등 독립적인 생명주기와 복잡한 상호작용이 필요한 모든 객체에 사용한다.
USTRUCT() 매크로 사용 시
- 장점: 가볍고 빠르며, GC 오버헤드가 없다. 데이터가 연속적으로 배치되어 캐시 효율성이 좋다. 관련된 데이터를 하나의 단위로 묶어 관리하기 용이하다.
- 단점: 상속, 다형성,
UFUNCTION을 통한 복잡한 로직 구현이 불가능하다. 값 타입이므로 큰 구조체를 값으로 전달할 때 복사 비용이 발생할 수 있다. - 사용 시기:
FVector,FRotator와 같은 수학적 타입, 아이템 데이터(FItemData), 플레이어 스탯(FPlayerStats) 등 순수 데이터 묶음이 필요할 때 사용한다. 주로UCLASS의 멤버 변수로 포함되거나,TArray와 같은 컨테이너에 저장되어 사용된다.
5. 요약
UCLASS()와 USTRUCT() 매크로는 언리얼 엔진의 리플렉션 시스템을 활성화하는 핵심 도구입니다. 이 매크로들은 언리얼 헤더 툴(UHT)이라는 특별한 전처리기에 의해 처리되어, 런타임에 필요한 메타데이터와 보일러플레이트 코드를 자동으로 생성해줍니다.
UCLASS() 매크로는 UObject를 상속받는 클래스에 붙어, 해당 클래스를 언리얼 엔진의 객체 시스템에 완전히 통합시킵니다. 이를 통해 가비지 컬렉션, 블루프린트 통합, 직렬화, 네트워킹, 그리고 UFUNCTION()을 통한 함수 노출 등 UObject가 가진 모든 강력한 기능을 사용할 수 있게 됩니다. GENERATED_BODY() 매크로 위치에 이 모든 기능을 위한 코드가 삽입되는 것이죠.
반면에 USTRUCT() 매크로는 C++의 struct에 적용되어, 해당 구조체를 리플렉션 시스템에 노출시킵니다. USTRUCT는 UObject를 상속받지 않기 때문에 가비지 컬렉션의 대상이 아니며, UFUNCTION()을 가질 수도 없습니다. 주로 FVector나 FItemData처럼 순수한 데이터 묶음을 정의하고, 이를 블루프린트에서 사용하거나 직렬화할 수 있도록 할 때 사용됩니다. GENERATED_BODY() 매크로 위치에는 구조체의 리플렉션 및 직렬화를 위한 코드가 삽입됩니다.
결론적으로, UCLASS()는 독립적인 생명주기와 복잡한 기능을 가진 객체를 만들 때, USTRUCT()는 가볍고 효율적인 데이터 묶음을 정의할 때 사용하며, 두 매크로 모두 UHT를 통해 언리얼 엔진의 강력한 기능을 C++ 코드에 부여하는 역할을 합니다.
'Unreal' 카테고리의 다른 글
| 언리얼 엔진 리플렉션 시스템 Unreal Engine Reflection System (0) | 2025.10.23 |
|---|---|
| UE5 Cast의 내부 동작 원리 (0) | 2025.10.22 |
| CDO (Class Default Object) (0) | 2025.10.21 |
| UObject (0) | 2025.10.21 |
| UPROPERTY와 UFUNCTION의 차이 (0) | 2025.10.16 |
