포스트

유니티 개발자를 위한 언리얼 C++ #7

언리얼 매크로의 마법 - UCLASS, UPROPERTY, UFUNCTION

유니티 개발자를 위한 언리얼 C++ #7
TL;DR — 핵심 요약
  • UCLASS, UPROPERTY, UFUNCTION은 C++ 표준이 아닌 언리얼 전용 매크로다 - 리플렉션 시스템에 클래스/변수/함수를 등록하는 역할이다
  • UPROPERTY()가 없는 UObject* 포인터는 GC에 의해 수거될 수 있다 - UPROPERTY()를 붙여야 GC가 참조를 추적한다
  • EditAnywhere vs VisibleAnywhere: Edit는 값 편집 가능, Visible은 보기만 가능이다 - 컴포넌트 포인터에는 VisibleAnywhere를 쓴다
  • GENERATED_BODY()는 Super typedef, 리플렉션 코드, 직렬화 코드를 자동 생성한다 - .generated.h가 반드시 마지막 #include여야 한다
Visitors

이 코드, 읽을 수 있나요?

언리얼 프로젝트에서 무기 클래스를 열면 이런 게 나옵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Weapon.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"

UCLASS(Blueprintable)
class MYGAME_API AWeapon : public AActor
{
    GENERATED_BODY()

public:
    AWeapon();

    UFUNCTION(BlueprintCallable, Category = "Combat")
    void Fire();

    UFUNCTION(BlueprintPure, Category = "Combat")
    int32 GetCurrentAmmo() const;

    UFUNCTION(BlueprintImplementableEvent, Category = "Effects")
    void PlayFireEffect();

protected:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    USkeletalMeshComponent* WeaponMesh;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
    float Damage = 25.f;

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config", meta = (ClampMin = "1"))
    int32 MaxAmmo = 30;

    UPROPERTY(BlueprintReadOnly, Category = "State")
    int32 CurrentAmmo;
};

유니티 개발자라면 이런 의문이 듭니다:

  • UCLASS(Blueprintable) — 이게 뭐지? C++ 문법이 아닌데?
  • GENERATED_BODY() — 이 매크로는 뭘 하는 거지?
  • Weapon.generated.h — 이 헤더는 어디서 오는 거지?
  • UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") — 이 긴 괄호 안의 것들은 뭐지?
  • UFUNCTION(BlueprintImplementableEvent) — 함수인데 구현이 없어도 되나?

이번 강에서 언리얼 매크로 시스템을 완전히 정리합니다.


서론 - 왜 매크로가 필요한가

C++은 C#과 달리 리플렉션(Reflection)이 없습니다. C#에서는 런타임에 클래스의 필드, 메서드 정보를 읽고 쓸 수 있지만, 순정 C++에서는 불가능합니다.

그런데 언리얼 엔진은 이런 기능이 필요합니다:

  • 에디터: 변수를 인스펙터에 노출해야 함
  • 블루프린트: C++ 함수를 블루프린트에서 호출해야 함
  • GC: 어떤 UObject*가 참조되고 있는지 추적해야 함
  • 직렬화: 객체를 저장/로드해야 함
  • 네트워크: 변수를 네트워크로 복제해야 함

이 모든 것을 가능하게 하는 것이 UHT(Unreal Header Tool)리플렉션 매크로입니다.

flowchart TB
    subgraph CSharp["C# (Unity)"]
        direction TB
        CS1["리플렉션 내장"]
        CS2["[SerializeField] 어트리뷰트"]
        CS3["런타임에 타입 정보 접근"]
        CS4["에디터 연동 자동"]
    end

    subgraph CPP["C++ (Unreal)"]
        direction TB
        CP1["리플렉션 없음 (순정 C++)"]
        CP2["UHT가 빌드 전에 코드 분석"]
        CP3["UCLASS/UPROPERTY/UFUNCTION 매크로"]
        CP4[".generated.h 코드 자동 생성"]
        CP5["리플렉션 시스템 구축!"]
        CP1 --> CP2 --> CP3 --> CP4 --> CP5
    end
C# (Unity)C++ (Unreal)역할
[SerializeField]UPROPERTY()에디터/직렬화에 노출
[Header("Stats")]Category = "Stats"카테고리 분류
[Range(0, 100)]meta = (ClampMin = "0", ClampMax = "100")값 범위 제한
public 필드UPROPERTY(BlueprintReadWrite)외부에서 읽기/쓰기
리플렉션 내장UCLASS() + GENERATED_BODY()타입 정보 등록

1. GENERATED_BODY()와 .generated.h

1-1. GENERATED_BODY()가 하는 일

GENERATED_BODY()는 UHT가 자동 생성한 코드를 삽입하는 매크로입니다. 내부적으로 다음을 포함합니다:

1
2
3
4
5
6
// GENERATED_BODY()가 대략 이런 것들을 생성
typedef ACharacter Super;            // Super:: 사용 가능하게
typedef AMyCharacter ThisClass;      // ThisClass 타입 정의
static UClass* StaticClass();        // 런타임 타입 정보
virtual UClass* GetClass() const;    // 실제 타입 반환
// + 직렬화, 리플렉션, GC 관련 코드들...

규칙:

  • 모든 UCLASS, USTRUCT 클래스에 반드시 있어야 합니다
  • 클래스 본문의 첫 번째 줄에 위치해야 합니다
  • public/private/protected 위에 옵니다

1-2. .generated.h 헤더

1
2
3
4
5
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"  // ← 반드시 마지막 #include!

Weapon.generated.h는 UHT가 자동 생성하는 헤더입니다. 빌드 전에 생성되며, 직접 편집하면 안 됩니다.

규칙: .generated.h는 항상 마지막 #include여야 합니다. 순서가 틀리면 컴파일 에러가 발생합니다.

💬 잠깐, 이건 알고 가자

Q. Unity에서 이런 매크로 같은 건 왜 없나요?

C#에는 리플렉션이 언어 자체에 내장되어 있기 때문입니다. typeof(MyClass).GetFields()로 필드 정보를 읽을 수 있죠. C++에는 이런 기능이 없어서 언리얼이 빌드 도구(UHT)를 만들어서 흉내 내는 것입니다.

Q. GENERATED_BODY() 대신 GENERATED_UCLASS_BODY()를 볼 때가 있는데요?

GENERATED_UCLASS_BODY()는 구 버전 매크로입니다. 생성자가 자동으로 public이 되는 등 차이가 있습니다. UE4 후기부터 GENERATED_BODY()가 권장되며, 새 코드에서는 GENERATED_BODY()만 사용하세요.


2. UCLASS - 클래스를 엔진에 등록

2-1. 기본 사용법

1
2
3
4
5
6
// 가장 기본적인 UCLASS
UCLASS()
class MYGAME_API AMyActor : public AActor
{
    GENERATED_BODY()
};

MYGAME_API는 모듈 내보내기 매크로입니다. 다른 모듈에서 이 클래스에 접근할 수 있게 합니다. 프로젝트 이름에 따라 달라집니다 (MYGAME은 프로젝트 이름).

2-2. 자주 쓰는 UCLASS 지정자

1
2
3
4
5
6
UCLASS(Blueprintable)                    // 블루프린트 상속 가능
UCLASS(BlueprintType)                    // 블루프린트 변수 타입으로 사용 가능
UCLASS(Abstract)                         // 직접 인스턴스화 불가 (자식만 가능)
UCLASS(NotBlueprintable)                 // 블루프린트 상속 불가
UCLASS(Transient)                        // 디스크에 저장 안 됨
UCLASS(Config=Game)                      // .ini 설정 파일과 연동
UCLASS 지정자Unity 대응의미
BlueprintableMonoBehaviour (기본 동작)BP 자식 클래스 생성 가능
BlueprintType직렬화 가능 클래스BP 변수 타입으로 사용
Abstractabstract class인스턴스 생성 불가
NotBlueprintable-BP 상속 차단

실무에서 가장 흔한 조합:

1
2
3
4
5
6
7
8
9
10
11
// Actor 계열 — 보통 지정자 없이 사용 (기본으로 Blueprintable)
UCLASS()
class AMyCharacter : public ACharacter { ... };

// 데이터 에셋 — BlueprintType 필수
UCLASS(BlueprintType)
class UWeaponData : public UDataAsset { ... };

// 추상 베이스 — 직접 사용 금지
UCLASS(Abstract)
class ABaseProjectile : public AActor { ... };

3. UPROPERTY - 변수를 에디터/GC에 등록

UPROPERTY는 언리얼에서 가장 자주 쓰는 매크로입니다. 두 가지 핵심 역할이 있습니다:

  1. GC 추적 — UObject* 포인터가 수거되지 않도록 보호
  2. 에디터/블루프린트 노출 — 인스펙터에서 편집하거나 BP에서 접근

3-1. GC 추적 — 가장 중요한 역할

1
2
3
4
5
6
7
8
9
10
11
12
13
UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()

private:
    // ❌ UPROPERTY 없음 → GC가 이 참조를 모름 → 대상이 수거될 수 있음!
    UWeaponComponent* BadWeapon;

    // ✅ UPROPERTY 있음 → GC가 참조 추적 → 안전!
    UPROPERTY()
    UWeaponComponent* GoodWeapon;
};

규칙: UObject* 포인터 멤버 변수에는 반드시 UPROPERTY()를 붙이세요. 지정자가 없어도 괄호만 있으면 GC 추적이 됩니다.

C#에서는 이런 걱정이 없습니다. GC가 알아서 모든 참조를 추적하니까요. C++에서는 GC에게 “이 포인터를 추적해줘”라고 명시적으로 알려야 합니다.

3-2. 에디터 노출 — Edit vs Visible

1
2
3
4
5
6
7
8
9
// Edit 계열 — 값을 편집할 수 있음
UPROPERTY(EditAnywhere)        // 클래스 기본값 + 인스턴스 모두 편집 가능
UPROPERTY(EditDefaultsOnly)    // 클래스 기본값에서만 편집 (인스턴스에서는 읽기 전용)
UPROPERTY(EditInstanceOnly)    // 인스턴스에서만 편집 (기본값에서는 안 보임)

// Visible 계열 — 보기만 가능 (편집 불가)
UPROPERTY(VisibleAnywhere)     // 어디서든 보기만 가능
UPROPERTY(VisibleDefaultsOnly) // 기본값에서만 보기
UPROPERTY(VisibleInstanceOnly) // 인스턴스에서만 보기
flowchart TB
    subgraph Edit["Edit 계열 (편집 가능)"]
        E1["EditAnywhere\n기본값 ✏️ + 인스턴스 ✏️"]
        E2["EditDefaultsOnly\n기본값 ✏️ + 인스턴스 👁️"]
        E3["EditInstanceOnly\n기본값 ❌ + 인스턴스 ✏️"]
    end

    subgraph Visible["Visible 계열 (보기만)"]
        V1["VisibleAnywhere\n기본값 👁️ + 인스턴스 👁️"]
        V2["VisibleDefaultsOnly\n기본값 👁️ + 인스턴스 ❌"]
        V3["VisibleInstanceOnly\n기본값 ❌ + 인스턴스 👁️"]
    end

실무 패턴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 디자이너가 조정하는 밸런스 값 → EditDefaultsOnly
UPROPERTY(EditDefaultsOnly, Category = "Stats")
float MaxHealth = 100.f;

// 디자이너가 인스턴스별로 다르게 설정할 값 → EditAnywhere
UPROPERTY(EditAnywhere, Category = "Config")
FName SpawnTag;

// 생성자에서 만드는 컴포넌트 → VisibleAnywhere
UPROPERTY(VisibleAnywhere, Category = "Components")
UStaticMeshComponent* MeshComp;

// 런타임에 바뀌는 상태 값 → 에디터 노출 불필요
UPROPERTY()
float CurrentHealth;
상황추천 지정자이유
밸런스 수치 (HP, 데미지 등)EditDefaultsOnlyBP 기본값에서 한 번 설정
인스턴스별 다른 설정EditAnywhere레벨에 배치된 각 인스턴스마다 다른 값
컴포넌트 포인터VisibleAnywhere생성자에서 만들었으므로 편집 X, 보기만
런타임 상태UPROPERTY() or BlueprintReadOnlyGC 추적 + 디버깅용 노출

3-3. 블루프린트 노출

1
2
UPROPERTY(BlueprintReadWrite)   // BP에서 읽기 + 쓰기
UPROPERTY(BlueprintReadOnly)    // BP에서 읽기만 가능

에디터 노출과 블루프린트 노출은 독립적입니다. 둘 다 필요하면 조합합니다:

1
2
3
4
5
6
7
8
9
// 가장 흔한 조합들
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Stats")
float MaxHealth = 100.f;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
float MoveSpeed = 600.f;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UCameraComponent* Camera;

3-4. meta 지정자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 값 범위 제한 (에디터 슬라이더 + 코드에서도 클램핑)
UPROPERTY(EditAnywhere, meta = (ClampMin = "0", ClampMax = "100"))
int32 Percentage;

// 에디터 UI에서만 범위 제한 (코드에서는 범위 밖도 가능)
UPROPERTY(EditAnywhere, meta = (UIMin = "0", UIMax = "1000"))
float Damage;

// 에디터에서 3D 위젯으로 편집
UPROPERTY(EditAnywhere, meta = (MakeEditWidget = true))
FVector TargetLocation;

// 툴팁 추가
UPROPERTY(EditAnywhere, meta = (ToolTip = "초당 회복량"))
float RegenRate = 1.f;

Unity 대응:

Unity 어트리뷰트Unreal UPROPERTY meta의미
[Range(0, 100)]meta = (ClampMin = "0", ClampMax = "100")값 범위 제한
[Tooltip("설명")]meta = (ToolTip = "설명")인스펙터 툴팁
[Header("섹션")]Category = "섹션"카테고리 분류
[HideInInspector]에디터 지정자 없이 UPROPERTY()인스펙터에서 숨김

💬 잠깐, 이건 알고 가자

Q. 컴포넌트에 EditAnywhere를 쓰면 안 되나요?

컴포넌트 포인터EditAnywhere를 쓰면, 에디터에서 포인터 자체를 다른 컴포넌트로 바꿀 수 있게 됩니다. 이건 거의 항상 의도한 동작이 아닙니다. 컴포넌트 포인터에는 VisibleAnywhere를, 컴포넌트의 속성(마테리얼, 크기 등)은 에디터에서 컴포넌트를 선택해서 편집합니다.

Q. Category가 없으면 어떻게 되나요?

에디터에 표시될 때 “Default” 카테고리에 들어갑니다. Category를 쓰면 인스펙터에서 변수가 섹션별로 정리됩니다. 팀 프로젝트에서는 Category를 반드시 쓰는 것이 좋습니다.


4. UFUNCTION - 함수를 블루프린트/엔진에 등록

4-1. BlueprintCallable — 가장 기본

1
2
3
4
5
6
// C++에서 구현하고, 블루프린트에서 호출 가능
UFUNCTION(BlueprintCallable, Category = "Combat")
void Fire();

UFUNCTION(BlueprintCallable, Category = "Combat")
void Reload();

Unity에서 public 메서드를 UnityEvent나 에디터 버튼에서 호출하는 것과 비슷합니다.

4-2. BlueprintPure — 출력만 있는 함수

1
2
3
4
5
6
7
8
9
// 블루프린트에서 "실행 핀" 없이 값만 반환
UFUNCTION(BlueprintPure, Category = "Stats")
float GetHealthPercent() const;

UFUNCTION(BlueprintPure, Category = "Stats")
bool IsDead() const;

UFUNCTION(BlueprintPure, Category = "Stats")
int32 GetCurrentAmmo() const { return CurrentAmmo; }

BlueprintPure부작용이 없는 getter 함수에 사용합니다. 블루프린트에서 실행 핀 없이 데이터 핀만으로 연결됩니다.

지정자블루프린트에서용도
BlueprintCallable실행 핀 있음 (노드 왼쪽/오른쪽)동작을 수행하는 함수
BlueprintPure실행 핀 없음 (데이터 핀만)값을 반환하는 getter

4-3. BlueprintImplementableEvent — BP에서만 구현

1
2
3
4
5
6
// C++에서 선언만, 구현은 블루프린트에서
UFUNCTION(BlueprintImplementableEvent, Category = "Events")
void OnLevelUp();

UFUNCTION(BlueprintImplementableEvent, Category = "Effects")
void PlayHitEffect(FVector HitLocation);

C++에서 .cpp에 구현을 쓰면 안 됩니다! 이 함수의 구현은 블루프린트 에디터에서 합니다.

1
2
3
4
5
6
7
8
9
10
// C++에서 호출은 가능
void AMyCharacter::GainExp(int32 Amount)
{
    Exp += Amount;
    if (Exp >= ExpToNextLevel)
    {
        Level++;
        OnLevelUp();  // ← BP에서 구현한 함수 호출
    }
}

4-4. BlueprintNativeEvent — C++ 기본 구현 + BP 오버라이드

1
2
3
// C++에서 기본 구현 제공, BP에서 오버라이드 가능
UFUNCTION(BlueprintNativeEvent, Category = "Combat")
float CalculateDamage(float BaseDamage);

주의: C++ 구현은 _Implementation 접미사가 붙은 함수에 작성합니다.

1
2
3
4
5
6
// .cpp — _Implementation 접미사!
float AMyCharacter::CalculateDamage_Implementation(float BaseDamage)
{
    // 기본 구현: 방어력 적용
    return FMath::Max(0.f, BaseDamage - Defense);
}

블루프린트에서 이 함수를 오버라이드하지 않으면 C++ 구현이 사용되고, 오버라이드하면 블루프린트 구현이 사용됩니다.

지정자C++ 구현BP 구현용도
BlueprintCallable✅ 필수❌ 불가C++ 전용 로직
BlueprintImplementableEvent❌ 불가✅ 필수BP 전용 이벤트
BlueprintNativeEvent✅ (_Implementation)✅ (오버라이드 가능)C++ 기본 + BP 커스터마이즈
1
2
3
4
// Unity에서 비슷한 패턴
// BlueprintCallable ≈ public 메서드
// BlueprintImplementableEvent ≈ UnityEvent / SendMessage
// BlueprintNativeEvent ≈ virtual 메서드 (자식 클래스에서 override)

💬 잠깐, 이건 알고 가자

Q. 왜 _Implementation 접미사가 필요한가요?

BlueprintNativeEvent는 UHT가 래퍼 함수를 자동 생성합니다. CalculateDamage()를 호출하면 래퍼가 “BP 오버라이드가 있으면 BP를, 없으면 C++ _Implementation을” 선택합니다. 이 메커니즘 때문에 직접 구현은 _Implementation에 해야 합니다.

Q. C#의 [ContextMenu("Fire")]처럼 에디터에서 함수를 호출할 수 있나요?

네! CallInEditor 지정자를 사용합니다:

1
2
UFUNCTION(CallInEditor, Category = "Debug")
void DebugSpawnEnemy();

에디터 디테일 패널에 버튼이 생기고, 클릭하면 함수가 실행됩니다.


5. USTRUCT — 구조체를 엔진에 등록

UCLASS만큼 자주 쓰이는 것이 USTRUCT입니다. 데이터 묶음을 블루프린트에서 사용할 때 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
USTRUCT(BlueprintType)
struct FWeaponStats
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Damage = 10.f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float FireRate = 0.1f;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 MaxAmmo = 30;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float ReloadTime = 2.f;
};

// 사용
UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()

protected:
    UPROPERTY(EditDefaultsOnly, Category = "Config")
    FWeaponStats WeaponStats;  // 구조체를 변수로 사용
};
1
2
3
4
5
6
7
8
9
// Unity 대응
[System.Serializable]
public struct WeaponStats
{
    public float damage;
    public float fireRate;
    public int maxAmmo;
    public float reloadTime;
}
매크로대상필수 조건GC 관리
UCLASS()클래스UObject 상속 필요
USTRUCT()구조체UObject 상속 불필요❌ (값 타입)
UENUM()열거형-

6. UENUM — 열거형을 블루프린트에 등록

1
2
3
4
5
6
7
8
9
10
11
12
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
    Rifle      UMETA(DisplayName = "라이플"),
    Shotgun    UMETA(DisplayName = "샷건"),
    Pistol     UMETA(DisplayName = "권총"),
    Sniper     UMETA(DisplayName = "저격총")
};

// 사용
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
EWeaponType WeaponType = EWeaponType::Rifle;
1
2
3
4
5
6
7
8
// Unity에서는 enum을 그냥 public으로 선언하면 자동 노출
public enum WeaponType
{
    Rifle,
    Shotgun,
    Pistol,
    Sniper
}

언리얼 규칙:

  • 열거형은 uint8 기반으로 선언 (블루프린트 호환)
  • E 접두사 사용
  • UMETA(DisplayName = "표시이름")으로 에디터 표시 이름 설정

7. 전체 요약 — 매크로 조합 치트시트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ═══════════════════════════════════════════
// 언리얼 매크로 실전 치트시트
// ═══════════════════════════════════════════

// ── 클래스 ──
UCLASS()                          // 기본
UCLASS(Blueprintable)             // BP 상속 허용
UCLASS(Abstract)                  // 인스턴스 생성 금지

// ── 변수: 에디터 노출 ──
UPROPERTY()                       // GC 추적만 (에디터 안 보임)
UPROPERTY(EditAnywhere)           // 어디서든 편집
UPROPERTY(EditDefaultsOnly)       // 기본값만 편집 ← 밸런스 수치
UPROPERTY(VisibleAnywhere)        // 보기만 ← 컴포넌트 포인터

// ── 변수: 블루프린트 노출 ──
UPROPERTY(BlueprintReadWrite)     // BP에서 읽기/쓰기
UPROPERTY(BlueprintReadOnly)      // BP에서 읽기만

// ── 변수: 실전 조합 ──
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Stats")    // 밸런스 값
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")      // 설정 값
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") // 컴포넌트

// ── 함수 ──
UFUNCTION(BlueprintCallable)              // BP에서 호출 가능
UFUNCTION(BlueprintPure)                  // getter (실행 핀 없음)
UFUNCTION(BlueprintImplementableEvent)    // BP에서만 구현
UFUNCTION(BlueprintNativeEvent)           // C++ 기본 + BP 오버라이드

// ── 구조체/열거형 ──
USTRUCT(BlueprintType)            // BP에서 사용 가능한 구조체
UENUM(BlueprintType)              // BP에서 사용 가능한 열거형

8. 흔한 실수 & 주의사항

실수 1: UPROPERTY 없는 UObject* 포인터

1
2
3
4
5
6
// ❌ GC에 의해 수거될 수 있음!
UMyComponent* Weapon;

// ✅ UPROPERTY로 GC 추적
UPROPERTY()
UMyComponent* Weapon;

이것이 가장 흔하고 치명적인 실수입니다. 크래시가 바로 나지 않고 간헐적으로 발생해서 디버깅이 어렵습니다.

실수 2: .generated.h 순서 오류

1
2
3
4
5
6
7
// ❌ .generated.h가 마지막이 아님
#include "Weapon.generated.h"
#include "Components/StaticMeshComponent.h"  // 에러!

// ✅ .generated.h는 항상 마지막
#include "Components/StaticMeshComponent.h"
#include "Weapon.generated.h"

실수 3: 컴포넌트에 EditAnywhere 사용

1
2
3
4
5
6
7
// ❌ 컴포넌트 포인터를 편집 가능하게 하면 위험
UPROPERTY(EditAnywhere)
UStaticMeshComponent* Mesh;  // 에디터에서 다른 컴포넌트로 바꿀 수 있음!

// ✅ 컴포넌트 포인터는 보기만
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* Mesh;

실수 4: BlueprintNativeEvent에서 _Implementation 누락

1
2
3
4
5
6
7
8
9
// 선언
UFUNCTION(BlueprintNativeEvent)
void OnHit(float Damage);

// ❌ 일반 이름으로 구현
void AMyActor::OnHit(float Damage) { ... }  // 컴파일 에러!

// ✅ _Implementation 접미사
void AMyActor::OnHit_Implementation(float Damage) { ... }

정리 - 7강 체크리스트

이 강을 마치면 언리얼 코드에서 다음을 읽을 수 있어야 합니다:

  • UCLASS(), UPROPERTY(), UFUNCTION()이 리플렉션 매크로라는 것을 안다
  • GENERATED_BODY()가 Super typedef, 리플렉션 코드를 생성함을 안다
  • .generated.h가 반드시 마지막 #include여야 함을 안다
  • UObject* 포인터에 UPROPERTY()가 없으면 GC 위험이 있음을 안다
  • EditAnywhere / EditDefaultsOnly / VisibleAnywhere의 차이를 안다
  • BlueprintReadWrite vs BlueprintReadOnly의 차이를 안다
  • BlueprintCallable / BlueprintPure / BlueprintImplementableEvent / BlueprintNativeEvent의 차이를 안다
  • BlueprintNativeEvent_Implementation 패턴을 안다
  • USTRUCT(BlueprintType)UENUM(BlueprintType)을 읽을 수 있다
  • Category, meta = (ClampMin/ClampMax) 등 지정자를 읽을 수 있다

다음 강 미리보기

8강: 언리얼 클래스 계층과 게임플레이 프레임워크

UObject → AActor → APawn → ACharacter로 이어지는 클래스 트리가 왜 이렇게 설계되었는지, GameMode, PlayerController, GameState는 각각 무슨 역할인지, BeginPlay() → Tick() → EndPlay() 라이프사이클은 어떤 순서인지 다룹니다. 언리얼의 컴포넌트 아키텍처가 Unity의 그것과 어떻게 다른지도 비교합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.