2026-04-23 TIL (43일차)
언리얼 코어 아키텍처
1. 블루프린트(BP)와 C++ 클래스의 관계
- 블루프린트는 C++ 클래스의 확장 버전입니다. * 프로그래머가 C++로 게임의 뼈대와 복잡한 로직을 클래스(Class)로 만들어두면, 아티스트나 기획자가 이를 상속받아 블루프린트(BP)로 확장합니다.
- 기획자는 코드를 몰라도 블루프린트 에디터에서 수치를 조정하고 에셋을 교체하며 게임을 조립할 수 있습니다.
2. “엔진은 도대체 클래스의 형태를 어떻게 알까?”
“클래스 A를 짠 뒤에, 그 안의 변수나 형태를 유추하는 건 쉽다. 하지만 게임이 실행되기도 전(메모리에 올라가기도 전)에, 언리얼 에디터는 어떻게 A 클래스의 형태를 미리 알고 디테일 패널에 변수들을 띄워줄 수 있을까?”
C++ 자체는 컴파일되고 나면 단순한 기계어(메모리 덩어리)가 되기 때문에, 프로그램 실행 중에는 자신의 구조(어떤 변수와 함수가 있는지)를 스스로 알 수 없습니다. 이를 해결하기 위해 언리얼 엔진이 도입한 마법이 바로 리플렉션(Reflection)입니다.
3. 리플렉션 시스템과 동작 원리
리플렉션이란 프로그램이 실행 시간(Runtime)에 자기 자신의 구조와 속성(클래스, 속성, 함수 등)을 읽고 조작할 수 있게 해주는 기능입니다.
🔹 UBT와 UHT의 역할
언리얼의 리플렉션은 UBT(Unreal Build Tool)와 UHT(Unreal Header Tool)라는 두 가지 툴의 합작으로 만들어집니다.
- 프로그래머가 코드에 매크로(
UCLASS(),UPROPERTY(),UFUNCTION())를 붙입니다. - 컴파일을 시작하면, UHT가 먼저 코드를 싹 훑으면서 이 매크로들을 찾아냅니다.
- 단순한 텍스트/메모리 집합에 불과했던 코드들을 쪼개고
FName등으로 이름을 붙여 엔진이 알아볼 수 있는 메모리 메타데이터(리플렉션 데이터)로 정리하여 별도의 자동 생성 코드(.generated.h)를 만듭니다. - 이후 UBT가 이 정보들을 묶어서 최종 컴파일을 수행합니다.
🔹 에디터와의 연결 (UObject)
이렇게 수집된 리플렉션 정보는 언리얼의 최상위 클래스인 UObject 시스템과 결합됩니다. 덕분에 우리는 C++로 짠 변수나 함수를 언리얼 에디터(디테일 패널, 블루프린트 노드)에서 눈으로 보고 직접 접근할 수 있게 되는 것입니다.
4. CDO (Class Default Object) - 클래스 디폴트 오브젝트
리플렉션을 통해 형태를 알았다면, 그 기본값을 관리하는 시스템이 CDO입니다.
- 개념: 언리얼 엔진은 엔진이 켜질 때(초기화 단계) 리플렉션 정보를 바탕으로 모든
UCLASS에 대해 딱 1개씩 ‘기본 템플릿(원형) 객체’를 미리 메모리에 만들어 둡니다. 이것이 CDO입니다. - 왜 쓸까? (카피 방식): 게임 중에 총알 1,000개를 스폰해야 한다고 가정해 봅시다. 매번 처음부터 총알의 생김새, 데미지, 속도를 세팅하면 엄청난 자원이 소모됩니다. 대신, 미리 구워둔 CDO(원본)의 메모리를 그대로 ‘복사(Copy)’해서 찍어내면 스폰 속도가 압도적으로 빨라집니다. 최적화를 위한 언리얼 엔진의 디자인 패턴입니다.
5. 지식: USTRUCT에는 왜 UFUNCTION을 못 넣을까?
언리얼 엔진에서 UCLASS 안에는 UFUNCTION을 넣을 수 있지만, USTRUCT(구조체) 안에는 UFUNCTION 매크로를 붙여 리플렉션 함수로 만들 수 없습니다. (일반 C++ 함수는 넣을 수 있습니다.)
- 이유:
UCLASS는UObject를 상속받아 엔진의 무거운 관리(가비지 컬렉션, 블루프린트 통신 등)를 완벽하게 지원받는 덩치 큰 객체입니다. 반면,USTRUCT는 오직 ‘가벼운 데이터의 묶음(값 타입)’을 빠르게 처리하기 위해 고안되었습니다. 구조체에 함수 리플렉션 기능까지 넣으면 처리 과정이 무거워져 구조체 본연의 존재 이유(가벼움과 빠름)가 사라지기 때문에, 엔진 차원에서 구조체 내 블루프린트 노드용 함수(UFUNCTION) 생성을 막아둔 것입니다.
리플렉션 매크로
1. UCLASS()
“나는 언리얼 객체다”
- 역할: 순수 C++ 클래스를 언리얼 엔진의 객체(Object)로 만들어 줍니다.
- 특징: 이 태그가 붙은 클래스(예:
Actor,Pawn,GameMode)는 언리얼의 가비지 컬렉터(메모리 자동 청소기)가 안전하게 생명주기를 관리해 줍니다. - 사용 예시: *
UCLASS(Blueprintable)👉 “이 C++ 클래스를 바탕으로 에디터에서 새 블루프린트 클래스를 만들 수 있게 해줘!”
2. USTRUCT()
“나는 가벼운 데이터 묶음이다”
- 역할: 여러 개의 데이터를 하나로 묶은 구조체(Struct)입니다.
- 특징:
UCLASS와 달리 메모리 자동 청소기의 관리를 직접 받지 않아 매우 가볍고 빠릅니다. 데이터 테이블 행(Row), 통신용 패킷 데이터, 아이템 정보 같은 단순 데이터 묶음을 만들 때 주로 사용합니다.
3. UPROPERTY()
“나는 에디터에서 만질 수 있는 변수다”
- 역할: C++ 변수에 태그를 달아 언리얼 에디터의 디테일 패널이나 블루프린트 노드에 노출시킵니다.
- 자주 쓰는 옵션:
EditAnywhere: 언리얼 에디터 디테일 패널에서 숫자를 마음대로 고칠 수 있습니다.BlueprintReadWrite: 블루프린트 노드에서 변수의Get/Set을 모두 할 수 있습니다.
- 사용 예시:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
4. UFUNCTION()
“나는 블루프린트에서 부를 수 있는 함수다”
- 역할: C++ 함수를 블루프린트 이벤트 그래프에서 ‘노드’ 형태로 꺼내 쓸 수 있게 연결해 줍니다.
- 자주 쓰는 옵션:
BlueprintCallable: 블루프린트에서 이 C++ 함수를 실행 핀(흰색 선)으로 연결해서 실행할 수 있습니다.BlueprintPure: 실행 핀 없이, 계산된 결괏값만 뽑아주는 순수 함수(예: 덧셈 노드, 데이터 Get 노드)로 만듭니다.
- 사용 예시:
UFUNCTION(BlueprintCallable, Category="Inventory")
5. UENUM()
“나는 깔끔한 선택지(드롭다운)다”
- 역할: C++의 열거형(enum) 데이터를 언리얼 리플렉션 시스템에 등록합니다.
- 특징: 캐릭터의 상태(Idle, Run)나 아이템 종류(Weapon, Potion)를 숫자가 아닌 ‘이름’으로 관리할 때 씁니다. 에디터에서 깔끔한 드롭다운 메뉴로 표시됩니다.
- 사용 예시:
1
2
3
4
5
6
7
UENUM(BlueprintType)
enum class EItemType : uint8
{
Weapon,
Armor,
Potion
};
6. UINTERFACE()
“나는 공통 규칙(계약서)이다”
- 역할: C++에서 블루프린트와 완벽하게 호환되는 ‘인터페이스’를 만들 때 사용합니다.
- 특징: 언리얼의 UObject는 다중 상속을 지원하지 않습니다. 대신 “상호작용 가능한가?”, “데미지를 입을 수 있는가?” 같은 공통 기능을 전혀 다른 클래스들(문, 보물상자, NPC)에 부여할 때 인터페이스를 붙여서 해결합니다.
- 사용 예시: UINTERFACE(MinimalAPI) 형태로 선언하여 공통 함수 규격을 정의합니다.
7. UMETA()
“나는 에디터용 친절한 설명서다”
- 역할: 에디터에서 개발자가 보기 편하게 데이터의 ‘디스플레이 이름’이나 툴팁을 덮어씌워 주는 메타데이터 조작기입니다.
- 특징: 주로 UENUM 안에서 각 항목의 이름을 예쁘게 꾸밀 때 씁니다. 코드에서는 영어로 작성하고, 에디터 화면에는 띄어쓰기나 한글을 띄울 수 있습니다.
- 사용 예시:
1
2
3
4
5
enum class EWeaponType : uint8
{
Sword UMETA(DisplayName = "Long Sword"),
Bow UMETA(DisplayName = "Hunter's Bow")
};
8. UPARAM()
“나는 블루프린트 핀(Pin) 조종기다”
- 역할: UFUNCTION() 안의 매개변수(Parameter)에 붙어서, 블루프린트 노드로 변환될 때 핀의 이름이나 입출력 방향을 제어합니다.
- 특징: C++에서 참조(&)로 변수를 넘기면 블루프린트는 보통 그것을 “결괏값을 내보내는 핀(출력)”으로 오해합니다. 이때 UPARAM(ref)를 붙이면 “입력 핀”으로 강제할 수 있습니다.
- 사용 예시:
1
2
3
UFUNCTION(BlueprintCallable)
void AddHealth(UPARAM(DisplayName = "Heal Amount") int32 Amount);
// => 블루프린트 노드에서 변수 이름이 'Amount' 대신 'Heal Amount'로 예쁘게 나옵니다.
보너스: DECLARE_DYNAMIC_MULTICAST_DELEGATE (바인딩과 차이)
“나는 이벤트 방송국(구독과 좋아요)이다”
- 특징: 매크로 이름이 ‘U’로 시작하지는 않지만, 언리얼 리플렉션 시스템이 낳은 아웃풋 중 하나입니다.
- 역할: 보스 몬스터가 죽었을 때 “나 죽었어!”라고 방송(Broadcast)을 때리면, UI 매니저나 사운드 매니저가 그 방송을 듣고 각자 알아서 작동하게 만드는 이벤트(Event) 델리게이트 노드를 만들어 줍니다. 결합도를 낮추는 기술입니다.