1. 언리얼 오브젝트와 리플렉션
언리얼 엔진의 핵심에는 C++ 코드를 엔진의 다양한 시스템(블루프린트, 에디터, 네트워크 등)과 연결하는 강력한 리플렉션(Reflection) 시스템이 있다. 리플렉션은 프로그램이 런타임에 자신의 구조(클래스, 변수, 함수 등)를 검사하고 수정할 수 있게 해주는 기능이다.
UPROPERTY()와 UFUNCTION()은 이 리플렉션 시스템에 C++ 변수와 함수를 각각 등록하는 매크로다. 이 매크로들은 단순한 주석이 아니라, 언리얼 헤더 툴(Unreal Header Tool, UHT)에 의해 파싱되어 추가적인 코드를 생성하고, 이를 통해 가비지 컬렉션, 직렬화, 에디터 연동, 블루프린트 통신, 네트워크 복제와 같은 핵심 기능을 활성화한다.
2. 계층 구조
- 사용자 코드 (C++): 개발자는
.h헤더 파일에UCLASS(),UPROPERTY(),UFUNCTION()등의 매크로를 사용하여 클래스, 변수, 함수를 선언한다.
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
float MyVariable;
UFUNCTION(BlueprintCallable)
void MyFunction();
};
- 빌드 프로세스 (Unreal Header Tool): 프로젝트를 빌드하면, 언리얼 빌드 툴(UBT)이 언리얼 헤더 툴(UHT)을 실행합니다. UHT는 모든 헤더 파일을 스캔하여 언리얼 매크로를 찾고, 각 매크로에 대한 리플렉션 데이터를 담은 C++ 코드(주로
*.generated.h와*.gen.cpp파일)를 자동으로 생성합니다. 이 생성된 코드가 C++ 코드와 엔진 시스템을 잇는 다리 역할을 합니다. - 엔진 런타임 (리플렉션 시스템): 게임이 실행되면, UHT가 생성한 리플렉션 데이터는
UClass라는 특별한 객체에 로드됩니다. 엔진의 각 시스템은 이UClass객체를 통해 런타임에 특정 클래스의 변수(FProperty)나 함수(UFunction) 정보에 접근할 수 있습니다.- 에디터: 디테일 패널은
UClass를 조회하여UPROPERTY로 노출된 변수들을 찾아 UI에 표시하고 값을 편집할 수 있게 합니다. - 블루프린트: 블루프린트 에디터는
UClass를 통해UPROPERTY와UFUNCTION으로 노출된 요소들을 노드로 시각화합니다. - 가비지 컬렉터:
UPROPERTY로 지정된UObject포인터들을 추적하여, 더 이상 참조되지 않는 객체의 메모리를 안전하게 해제합니다.
- 에디터: 디테일 패널은
3. UPROPERTY(): 변수를 위한 리플렉션
UPROPERTY()는 멤버 변수를 리플렉션 시스템에 등록하여, 단순한 C++ 변수를 넘어 엔진이 관리하는 '프로퍼티'로 만든다.
주요 기능 및 지정자
- 가비지 컬렉션 (GC):
UPROPERTY()의 가장 중요한 역할 중 하나다.UObject를 상속받는 객체에 대한 포인터를UPROPERTY()로 감싸면, 가비지 컬렉터가 해당 참조를 인지하고 객체가 사용 중임을 파악하여 메모리를 임의로 해제하지 않는다. 만약UPROPERTY()없이 일반 C++ 포인터를 사용하면, 해당 객체는 아무도 참조하지 않는다고 판단되어 GC에 의해 메모리가 수거될 수 있고, 이는 댕글링 포인터 문제와 크래시로 이어진다. - 에디터 연동:
EditAnywhere: 에디터의 디테일 패널에서 값을 편집할 수 있다.VisibleAnywhere: 디테일 패널에서 보이지만 편집은 불가능하다. 디버깅 용도로 유용하다.Category = "MyCategory": 디테일 패널에서 프로퍼티들을 특정 카테고리 하에 묶어 정리할 수 있다.
- 블루프린트 노출:
BlueprintReadOnly: 블루프린트에서 값을 읽을 수만 있다.BlueprintReadWrite: 블루프린트에서 값을 읽고 쓸 수 있다.
- 직렬화 (Serialization):
UPROPERTY()로 지정된 변수는 레벨을 저장하거나, 에셋을 저장하거나, 게임 상태를 저장(SaveGame)할 때 그 값이 디스크에 자동으로 저장되고 로드될 수 있다. - 네트워크 복제 (Replication):
Replicated: 멀티플레이어 게임에서 서버의 변수 값이 변경될 때 클라이언트로 자동으로 복제되도록 한다.ReplicatedUsing = OnRep_MyVariable: 변수가 복제될 때 특정 함수(OnRep_MyVariable)를 클라이언트에서 호출하도록 한다. 주로 값 변경에 따른 시각적 효과를 처리할 때 사용된다.
내부 동작 방식
UHT는 UPROPERTY() 선언을 만나면, 해당 변수에 대한 모든 메타데이터(타입, 이름, 플래그 등)를 포함하는 FProperty의 자식 클래스(예: FNumericProperty, FObjectProperty) 객체를 생성하는 코드를 *.gen.cpp 파일에 만든다. 이 FProperty 객체들은 UClass 내의 연결 리스트에 저장되어, 엔진이 런타임에 "이 클래스는 'Health'라는 이름의 float 변수를 가지고 있고, 에디터에서 수정 가능하며, 네트워크로 복제된다"와 같은 정보를 알 수 있게 해준다.
4. UFUNCTION(): 함수를 위한 리플렉션
UFUNCTION()은 멤버 함수를 리플렉션 시스템에 등록하여, C++ 함수에 추가적인 능력을 부여한다.
주요 기능 및 지정자
- 블루프린트 연동:
BlueprintCallable: 블루프린트에서 실행 핀(하얀색 선)이 있는 노드로 호출할 수 있게 한다. 객체의 상태를 변경하는 동작에 주로 사용된다.BlueprintPure: 실행 핀이 없는 순수 함수로 만든다. 객체의 상태를 변경하지 않고 값을 반환하는 'Getter' 함수에 이상적이다. (예:GetHealth())BlueprintImplementableEvent: C++에서는 선언만 하고, 실제 구현은 블루프린트에서 하도록 위임하는 함수다. C++에서 이 함수를 호출하면 블루프린트 그래프에 정의된 로직이 실행된다.BlueprintNativeEvent: C++에 기본 구현을 제공하면서, 블루프린트에서 이를 오버라이드(재정의)할 수 있게 한다. C++에서는FunctionName_Implementation()형태의 함수에 기본 동작을 구현해야 한다.
- 네트워크 (RPC - 원격 프로시저 호출):
Server: 클라이언트에서 호출하면 서버에서 실행되는 함수를 만든다. (예: 플레이어가 총을 쏘는 입력)Client: 서버에서 호출하면 특정 클라이언트에서만 실행되는 함수를 만든다. (예: 특정 플레이어에게만 보이는 UI 업데이트)NetMulticast: 서버에서 호출하면 서버 자신과 모든 클라이언트에서 실행되는 함수를 만든다. (예: 모든 플레이어에게 보이는 폭발 효과)
- 에디터 기능:
CallInEditor: 에디터의 디테일 패널에 이 함수를 호출하는 버튼을 생성한다. 테스트나 디버깅에 유용하다.
내부 동작 방식
UHT는 UFUNCTION()을 발견하면, 해당 함수를 감싸는 썽크(Thunk) 함수를 *.gen.cpp 파일에 생성한다. 이 썽크 함수는 블루프린트 가상 머신(VM)이나 네트워크 시스템과 실제 C++ 함수 구현 사이의 중간 다리 역할을 한다.
예를 들어, 블루프린트에서 BlueprintCallable 함수를 호출하면, VM은 직접 C++ 함수를 호출하는 대신 UObject::ProcessEvent라는 함수를 통해 해당 UFunction을 실행한다. ProcessEvent는 UHT가 생성한 썽크 함수를 호출하고, 이 썽크 함수가 마침내 우리가 작성한 C++ 코드를 실행하는 구조다. RPC의 경우, 썽크 함수는 함수 호출과 파라미터를 네트워크를 통해 전송하는 역할을 담당한다.
5. UPROPERTY() vs UFUNCTION() 비교
| 구분 | UPROPERTY() | UFUNCTION() |
|---|---|---|
| 대상 | 멤버 변수 (데이터, 상태) | 멤버 함수 (동작, 이벤트) |
| 주요 목적 | 데이터의 저장, 관리, 노출 | 로직의 실행, 이벤트 처리 |
| 가비지 컬렉션 | UObject 포인터의 생명주기 관리에 필수적 |
직접적인 관련 없음 |
| 블루프린트 | 변수 값을 읽거나 쓰는 노드 제공 | 함수를 호출하는 실행 노드 제공 |
| 네트워크 | 상태 복제(State Replication): 변수의 '값'을 동기화 | 원격 프로시저 호출(RPC): 특정 머신에서 '함수'를 실행 |
| 에디터 | 디테일 패널에 변수 UI 표시 | 디테일 패널에 함수 호출 버튼 생성 |
언제 무엇을 사용해야 할까?
- 객체가 가져야 할 데이터나 상태(체력, 속도, 이름, 장착 아이템 등)를 정의할 때는
UPROPERTY()를 사용한다. - 객체가 수행해야 할 동작이나 이벤트(피해를 입는다, 아이템을 줍는다, 죽었을 때 처리 등)를 정의할 때는
UFUNCTION()을 사용한다.
간단히 말해, UPROPERTY()는 '명사'에, UFUNCTION()은 '동사'에 해당한다고 생각할 수 있다.
6. 구술형 요약
`UPROPERTY`와 `UFUNCTION`은 언리얼 엔진의 리플렉션 시스템에 C++ 클래스의 멤버 변수와 멤버 함수를 각각 등록하는 매크로입니다.
`UPROPERTY`는 변수를 위한 것으로, 가장 중요한 역할은 UObject 포인터를 가비지 컬렉터가 추적하게 해서 메모리 누수를 막는 것입니다. 그 외에도 변수를 에디터 디테일 패널에 노출시켜 값을 쉽게 바꾸게 하거나, 블루프린트에서 읽고 쓸 수 있게 하며, 멀티플레이어 게임에서 서버의 변수 값을 클라이언트로 복제하는 기능 등을 담당합니다. 즉, 데이터의 '상태'를 관리하고 노출하는 데 사용됩니다.
반면에 `UFUNCTION`은 함수를 위한 것입니다. C++로 만든 함수를 블루프린트에서 노드로 불러다 쓸 수 있게 하거나, 반대로 블루프린트에서 구현할 이벤트를 C++에 선언할 수도 있습니다. 또한, 네트워크 통신을 위한 RPC, 즉 '서버에서 실행', '클라이언트에서 실행' 같은 함수를 만드는 데에도 필수적입니다. 즉, 객체의 '동작'이나 '이벤트'를 정의하는 데 사용됩니다.
핵심적인 차이는 `UPROPERTY`가 '데이터의 상태'를 다루는 반면, `UFUNCTION`은 '코드의 실행'을 다룬다는 점입니다. 이 둘은 언리얼 헤더 툴을 통해 추가 코드를 생성하며, 이를 통해 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 |
| UClass와 UStruct가 매크로로 사용될 때의 특성과 차이 (0) | 2025.10.15 |
