UE5 C++ 핵심 개념 입문 가이드
개요 — 언리얼 C++이 일반 C++과 다른 이유
Section titled “개요 — 언리얼 C++이 일반 C++과 다른 이유”언리얼 엔진 C++(이하 UE C++)은 표준 C++을 기반으로 하지만, 엔진 고유의 리플렉션 시스템, 가비지 컬렉션, 직렬화, 에디터 통합 레이어가 얹혀 있습니다. 이 때문에 일반 C++ 경험만으로 처음 UE C++ 코드를 보면 낯선 매크로와 접두사들로 인해 당황하기 쉽습니다.
핵심 차이점을 한 줄로 요약하면 이렇습니다.
일반 C++에서는 객체의 생성과 소멸을 개발자가 직접 관리하지만, UE C++에서는
UObject를 상속한 객체는 엔진의 가비지 컬렉터가 관리합니다. 매크로는 엔진에게 객체 정보를 알려주는 “신고서” 역할을 합니다.
이 가이드에서는 UE C++ 입문자가 가장 먼저 이해해야 할 네 가지 축을 다룹니다.
| 축 | 핵심 내용 |
|---|---|
| UObject 시스템 | 모든 UE 객체의 뿌리, 계층 구조 |
| 매크로 시스템 | UCLASS, UPROPERTY, UFUNCTION의 역할 |
| 가비지 컬렉션 | 메모리 안전을 위한 포인터 규칙 |
| AActor 생명주기 | 스폰부터 소멸까지 이벤트 흐름 |
1. UObject 시스템
Section titled “1. UObject 시스템”1.1 UObject란 무엇인가
Section titled “1.1 UObject란 무엇인가”UObject는 언리얼 엔진의 모든 엔진 관리 객체의 기반 클래스입니다. UObject를 상속하면 다음 기능을 자동으로 얻습니다.
- 리플렉션: 런타임에 클래스·프로퍼티·함수 정보 조회 가능
- 직렬화: 에디터 저장/로드, 네트워크 복제에 활용
- 가비지 컬렉션: 참조가 없어지면 엔진이 자동으로 메모리 해제
- 에디터 통합: Blueprint에서 접근, 디테일 패널 노출
UObject를 상속하지 않는 일반 C++ 구조체(예: FVector, FTransform)는 이 기능을 갖지 않으며, GC의 관리 대상도 아닙니다.
1.2 UObject 계층 구조
Section titled “1.2 UObject 계층 구조”언리얼의 주요 클래스들은 모두 UObject에서 파생됩니다. 게임플레이 프로그래밍에서 자주 만나는 계층 구조는 다음과 같습니다.
UObject └─ UActorComponent (컴포넌트 기반 클래스) └─ USceneComponent (트랜스폼을 가진 컴포넌트) └─ AActor (월드에 배치 가능한 객체) └─ APawn (빙의 가능한 Actor) └─ ACharacter (이동 컴포넌트 포함 캐릭터) └─ UGameInstance (세션 전체 지속) └─ USubsystem (엔진 서브시스템)접두사 규칙이 계층을 바로 알려줍니다.
| 접두사 | 의미 | 예시 |
|---|---|---|
U | UObject 파생 클래스 | UActorComponent, UTexture |
A | AActor 파생 클래스 | ACharacter, APlayerController |
F | 일반 C++ 구조체 (UObject 아님) | FVector, FHitResult |
T | 템플릿 클래스 | TArray<T>, TObjectPtr<T> |
I | 인터페이스 클래스 | IAbilitySystemInterface |
E | 열거형 | ECollisionChannel |
1.3 UObject 생성과 파괴
Section titled “1.3 UObject 생성과 파괴”UObject 파생 클래스는 new 연산자 대신 엔진 전용 생성 함수를 사용해야 합니다. new로 생성하면 GC가 해당 객체를 추적하지 않아 메모리 문제가 발생합니다.
// ---- 생성 방법 세 가지 ----
// 1. NewObject<T>() — 런타임에 UObject 생성 (Actor가 아닌 컴포넌트, 에셋 등)UMyComponent* Comp = NewObject<UMyComponent>(this, UMyComponent::StaticClass());
// 2. CreateDefaultSubobject<T>() — 생성자(Constructor) 안에서만 사용// 컴포넌트를 Actor의 기본 서브오브젝트로 등록할 때 사용USkeletalMeshComponent* MeshComp = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComp"));
// 3. SpawnActor<T>() — 월드에 AActor를 스폰FActorSpawnParameters SpawnParams;AMyActor* NewActor = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass(), Location, Rotation, SpawnParams);파괴는 직접 호출하지 않습니다. Actor의 경우 Destroy()를 호출하면 다음 GC 사이클에서 메모리가 해제됩니다. 일반 UObject는 참조가 없어지면 GC가 자동 처리합니다.
// Actor 제거 요청 (실제 메모리 해제는 GC가 수행)MyActor->Destroy();
// 절대 하지 말아야 할 것// delete MyActor; // UObject에 delete는 크래시 또는 undefined behavior2. 핵심 매크로 시스템
Section titled “2. 핵심 매크로 시스템”언리얼 헤더 툴(UHT, Unreal Header Tool)은 빌드 시 .h 파일의 매크로를 분석해 리플렉션 코드를 자동 생성합니다. 매크로는 “엔진에게 이 클래스/프로퍼티/함수를 등록해달라”는 선언입니다.
2.1 UCLASS — 클래스 등록
Section titled “2.1 UCLASS — 클래스 등록”UCLASS 매크로는 해당 C++ 클래스를 언리얼 리플렉션 시스템에 등록합니다. 매크로 없이는 엔진이 이 클래스의 존재를 알 수 없습니다.
#pragma once
#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "MyActor.generated.h" // 반드시 마지막 include — UHT 생성 코드 포함
UCLASS(BlueprintType, Blueprintable) // 지정자(Specifier)로 동작 제어class MYGAME_API AMyActor : public AActor{ GENERATED_BODY() // UHT가 채워넣을 자동 생성 코드 위치
public: AMyActor();};주요 UCLASS 지정자:
| 지정자 | 의미 |
|---|---|
BlueprintType | 이 클래스를 Blueprint 변수로 사용 가능 |
Blueprintable | 이 클래스를 Blueprint에서 상속 가능 |
Abstract | 인스턴스 직접 생성 불가 (순수 기반 클래스) |
NotBlueprintable | Blueprint 상속 불가 |
MinimalAPI | 다른 모듈에 최소한의 API만 노출 |
2.2 UPROPERTY — 프로퍼티 리플렉션 및 GC 연동
Section titled “2.2 UPROPERTY — 프로퍼티 리플렉션 및 GC 연동”UPROPERTY는 멤버 변수를 엔진에 등록합니다. 이를 통해 에디터 노출, 직렬화, 네트워크 복제, 그리고 GC 추적이 가능해집니다.
UCLASS()class MYGAME_API AMyActor : public AActor{ GENERATED_BODY()
public: // EditAnywhere: 에디터 모든 위치에서 편집 가능 // BlueprintReadWrite: Blueprint에서 읽기/쓰기 가능 // Category: 디테일 패널 카테고리 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats") float Health = 100.f;
// VisibleAnywhere: 에디터에서 보기만 가능 (수정 불가) // BlueprintReadOnly: Blueprint에서 읽기만 가능 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") TObjectPtr<UStaticMeshComponent> MeshComponent;
// Replicated: 네트워크 복제 대상 UPROPERTY(Replicated, BlueprintReadOnly, Category = "Stats") int32 Score = 0;
// EditDefaultsOnly: Blueprint 클래스 기본값에서만 편집 (인스턴스 편집 불가) // TSubclassOf<T>: 특정 클래스 또는 그 서브클래스만 지정 가능 UPROPERTY(EditDefaultsOnly, Category = "Config") TSubclassOf<AActor> ProjectileClass;};주요 UPROPERTY 접근 지정자:
| 지정자 | 에디터 접근 | Blueprint 접근 |
|---|---|---|
EditAnywhere | 기본값 + 인스턴스 모두 편집 | — |
EditDefaultsOnly | 기본값만 편집 | — |
EditInstanceOnly | 인스턴스만 편집 | — |
VisibleAnywhere | 보기만 | — |
BlueprintReadWrite | — | 읽기 + 쓰기 |
BlueprintReadOnly | — | 읽기만 |
중요:
UPROPERTY가 없는UObject*원시 포인터는 GC가 추적하지 않습니다. 해당 포인터가 참조하는 객체가 GC에 의해 수집되면 포인터는 **댕글링 포인터(dangling pointer)**가 됩니다.
2.3 UFUNCTION — 함수 리플렉션 및 Blueprint 노출
Section titled “2.3 UFUNCTION — 함수 리플렉션 및 Blueprint 노출”UFUNCTION은 멤버 함수를 엔진에 등록합니다. Blueprint 호출, 네트워크 RPC, 이벤트 바인딩 등에 필요합니다.
UCLASS()class MYGAME_API AMyActor : public AActor{ GENERATED_BODY()
public: // BlueprintCallable: Blueprint에서 호출 가능한 노드로 노출 UFUNCTION(BlueprintCallable, Category = "Combat") void TakeDamage_Custom(float DamageAmount);
// BlueprintPure: 실행 핀 없는 순수 함수 (값 반환만) UFUNCTION(BlueprintPure, Category = "Stats") float GetHealthPercent() const;
// BlueprintImplementableEvent: C++에서 선언, Blueprint에서 구현 UFUNCTION(BlueprintImplementableEvent, Category = "Events") void OnHealthChanged(float NewHealth);
// BlueprintNativeEvent: C++에 기본 구현 있음, Blueprint에서 오버라이드 가능 // 구현부는 함수명_Implementation으로 정의 UFUNCTION(BlueprintNativeEvent, Category = "Events") void OnDeath(); virtual void OnDeath_Implementation();
// Server: 클라이언트에서 호출 → 서버에서 실행되는 RPC // Reliable: 패킷 유실 없이 보장 UFUNCTION(Server, Reliable, WithValidation, Category = "Network") void ServerRequestJump();
// NetMulticast: 서버에서 호출 → 모든 클라이언트에서 실행 UFUNCTION(NetMulticast, Unreliable, Category = "Network") void MulticastPlayEffect();};2.4 GENERATED_BODY — 자동 생성 코드 자리표시자
Section titled “2.4 GENERATED_BODY — 자동 생성 코드 자리표시자”GENERATED_BODY()는 UHT가 리플렉션 코드를 삽입할 위치를 지정합니다. UCLASS, USTRUCT, UENUM 매크로를 사용하는 모든 타입의 선언부에 반드시 포함해야 합니다.
UCLASS()class AMyActor : public AActor{ GENERATED_BODY() // 이 자리에 UHT가 생성자, 리플렉션 테이블 등을 자동 삽입 // ...};
USTRUCT(BlueprintType)struct FMyData{ GENERATED_BODY() // 구조체도 동일하게 필요
UPROPERTY(EditAnywhere) FString Name;
UPROPERTY(EditAnywhere) int32 Value = 0;};3. 가비지 컬렉션 (Garbage Collection)
Section titled “3. 가비지 컬렉션 (Garbage Collection)”3.1 UE5 GC 동작 원리
Section titled “3.1 UE5 GC 동작 원리”언리얼의 GC는 표시-정리(Mark-and-Sweep) 방식으로 동작합니다.
- 루트셋(Root Set) 탐색:
GUObjectArray에 등록된 모든UObject를 순회합니다. - 도달성 분석: 루트셋에서 참조를 따라가며 도달 가능한 객체에 마킹합니다.
- 정리: 마킹되지 않은(참조 없는) 객체의 메모리를 해제합니다.
GC는 기본적으로 60초마다 실행되며, 프레임 사이에 분산 처리(Incremental GC)됩니다. 개발자는 ForceGarbageCollection(true)로 즉시 실행을 요청할 수 있지만 일반 게임플레이 코드에서는 불필요합니다.
3.2 UPROPERTY가 GC에서 중요한 이유
Section titled “3.2 UPROPERTY가 GC에서 중요한 이유”GC가 참조를 추적하는 유일한 방법은 UPROPERTY로 등록된 포인터를 통해서입니다.
UCLASS()class AMyActor : public AActor{ GENERATED_BODY()
// 올바른 방식: UPROPERTY로 등록 → GC가 추적 → 안전 UPROPERTY() TObjectPtr<UStaticMeshComponent> SafeComponent;
// 위험한 방식: UPROPERTY 없음 → GC가 추적 불가 → 댕글링 포인터 위험 UStaticMeshComponent* DangerousComponent; // 하지 마세요};UPROPERTY가 없는 원시 포인터에 할당된 UObject가 다른 곳에서 참조가 끊기면, GC가 해당 객체를 수집할 수 있습니다. 이후 해당 포인터를 역참조하면 크래시가 발생합니다.
3.3 안전한 포인터 타입
Section titled “3.3 안전한 포인터 타입”UE5는 UObject 참조를 위한 전용 포인터 타입을 제공합니다.
// TObjectPtr<T> — UE5에서 TObjectPtr<T>이 UObject 멤버 포인터의 표준// UPROPERTY와 함께 사용하면 GC 추적 + 에디터 직렬화 모두 지원UPROPERTY(VisibleAnywhere)TObjectPtr<UStaticMeshComponent> MeshComp;
// TWeakObjectPtr<T> — 약한 참조 (GC 수집을 막지 않음)// 참조 대상이 삭제될 수 있는 경우 사용 (캐시, 관찰자 패턴)TWeakObjectPtr<AActor> TargetActor;
void CheckTarget(){ // IsValid() / Get()으로 안전하게 접근 if (TargetActor.IsValid()) { TargetActor->DoSomething(); }}
// TStrongObjectPtr<T> — 명시적으로 GC 수집 방지// 테스트 코드나 특수 케이스에서만 사용TStrongObjectPtr<UMyObject> PinnedObject;비 UObject 타입(일반 C++ 클래스)을 힙에 할당해야 한다면 표준 스마트 포인터를 사용합니다.
// TSharedPtr<T> — 공유 소유권 (레퍼런스 카운팅)TSharedPtr<FMyStruct> SharedData = MakeShared<FMyStruct>();
// TUniquePtr<T> — 단독 소유권TUniquePtr<FMyStruct> UniqueData = MakeUnique<FMyStruct>();
// TSharedRef<T> — null이 될 수 없는 TSharedPtrTSharedRef<FMyStruct> RefData = MakeShared<FMyStruct>();3.4 흔한 실수와 메모리 안전 패턴
Section titled “3.4 흔한 실수와 메모리 안전 패턴”// 실수 1: UPROPERTY 없는 원시 포인터 멤버// 해결: 항상 UPROPERTY + TObjectPtr 사용UPROPERTY()TObjectPtr<UMyComponent> MyComp; // 올바름
// 실수 2: 지역 변수에 NewObject로 생성 후 저장하지 않음void BadFunction(){ UMyObject* TempObj = NewObject<UMyObject>(this); // TempObj를 UPROPERTY 멤버에 저장하지 않으면 다음 GC에서 수집됨}
// 해결: UPROPERTY 멤버에 저장void GoodFunction(){ MyStoredObject = NewObject<UMyObject>(this); // UPROPERTY 멤버에 저장}
// 실수 3: IsValid() 없이 포인터 역참조void BadDereference(){ TargetActor->DoSomething(); // TargetActor가 Destroy() 호출 후면 크래시}
// 해결: IsValid() 또는 IsValidLowLevel() 체크void SafeDereference(){ if (IsValid(TargetActor)) { TargetActor->DoSomething(); }}팁:
IsValid(Obj)는 포인터가 null인지, Pending Kill(소멸 대기)인지 모두 체크합니다. 단순Obj != nullptr는 Pending Kill 상태를 잡지 못합니다.
4. AActor 생명주기
Section titled “4. AActor 생명주기”4.1 생명주기 단계 전체 흐름
Section titled “4.1 생명주기 단계 전체 흐름”AActor는 월드에 스폰되는 순간부터 소멸될 때까지 정해진 순서로 이벤트 함수가 호출됩니다. 이 순서를 모르면 초기화 코드를 잘못된 타이밍에 넣어 버그를 만들기 쉽습니다.
[스폰/로드] 1. 생성자(Constructor) — 기본값 설정, 서브오브젝트 생성 2. PostInitProperties() — 생성자 이후 프로퍼티 초기화 완료 3. PostLoad() (로드된 경우) — 에셋/레벨에서 로드 시 호출
[초기화] 4. PostActorCreated() — 에디터/게임에서 새로 생성 시 5. PostInitializeComponents() — 모든 컴포넌트 InitializeComponent() 완료 후
[플레이 시작] 6. BeginPlay() — 게임플레이 로직 시작 (가장 많이 사용) 7. Tick(float DeltaSeconds) — 매 프레임 호출 (활성화된 경우)
[소멸] 8. EndPlay(EEndPlayReason) — BeginPlay의 쌍대, 정리 코드 작성 9. BeginDestroy() — GC 수집 직전 (직접 오버라이드는 드묾) 10. IsReadyForFinishDestroy() — 비동기 소멸 대기 여부 11. FinishDestroy() — 메모리 해제 직전 마지막 콜백4.2 핵심 이벤트 구현 예시
Section titled “4.2 핵심 이벤트 구현 예시”#pragma once
#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "MyActor.generated.h"
UCLASS()class MYGAME_API AMyActor : public AActor{ GENERATED_BODY()
public: AMyActor();
protected: // 컴포넌트 초기화 완료 후 — 컴포넌트 간 의존성 설정에 적합 virtual void PostInitializeComponents() override;
// 게임플레이 로직 시작 지점 — 대부분의 초기화 코드는 여기에 virtual void BeginPlay() override;
// 매 프레임 호출 virtual void Tick(float DeltaTime) override;
// 소멸 또는 레벨 언로드 시 — 리소스 정리, 이벤트 언바인드 virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) TObjectPtr<UStaticMeshComponent> MeshComponent;
UPROPERTY(EditDefaultsOnly, Category = "Config") float RotationSpeed = 45.f;
// 타이머 핸들 — 타이머 취소에 필요 FTimerHandle EffectTimerHandle;};#include "MyActor.h"#include "Components/StaticMeshComponent.h"
AMyActor::AMyActor(){ // Tick 활성화 여부 — 불필요하면 false로 성능 절약 PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.TickInterval = 0.f; // 0 = 매 프레임, 0.1 = 초당 10회
// 생성자에서 컴포넌트 생성 — CreateDefaultSubobject는 생성자에서만 유효 MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent")); SetRootComponent(MeshComponent);}
void AMyActor::PostInitializeComponents(){ Super::PostInitializeComponents();
// 컴포넌트가 모두 초기화된 이후 시점 // 예: 컴포넌트의 이벤트에 델리게이트 바인딩 if (MeshComponent) { MeshComponent->OnComponentHit.AddDynamic(this, &AMyActor::OnHit); }}
void AMyActor::BeginPlay(){ Super::BeginPlay(); // 부모 구현 호출 필수
// 게임 시작 시 1회 초기화 로직 UE_LOG(LogTemp, Log, TEXT("AMyActor BeginPlay: %s"), *GetName());
// 타이머 예시: 3초 후 Effect 호출 GetWorldTimerManager().SetTimer( EffectTimerHandle, this, &AMyActor::PlayEffect, 3.f, false // 반복 여부 );}
void AMyActor::Tick(float DeltaTime){ Super::Tick(DeltaTime);
// 매 프레임 회전 AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaTime, 0.f));}
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason){ // EndPlayReason으로 소멸 원인 파악 가능 switch (EndPlayReason) { case EEndPlayReason::Destroyed: UE_LOG(LogTemp, Log, TEXT("Actor destroyed: %s"), *GetName()); break; case EEndPlayReason::LevelTransition: UE_LOG(LogTemp, Log, TEXT("Level transition: %s"), *GetName()); break; default: break; }
// 타이머 정리 GetWorldTimerManager().ClearTimer(EffectTimerHandle);
// 부모 구현 호출 — 반드시 마지막에 Super::EndPlay(EndPlayReason);}
void AMyActor::PlayEffect(){ // 타이머 콜백 예시 UE_LOG(LogTemp, Log, TEXT("Effect played!"));}
void AMyActor::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit){ if (OtherActor && OtherActor != this) { UE_LOG(LogTemp, Log, TEXT("Hit by: %s"), *OtherActor->GetName()); }}4.3 PrimaryActorTick 설정과 성능 고려
Section titled “4.3 PrimaryActorTick 설정과 성능 고려”Tick은 강력하지만 비용이 높습니다. 불필요한 Tick은 성능에 악영향을 줍니다.
AMyActor::AMyActor(){ // 기본: Tick 비활성화 (필요할 때만 켜는 것이 권장) PrimaryActorTick.bCanEverTick = false;}
// 런타임에 Tick 동적 활성화/비활성화void AMyActor::EnableTick(bool bEnable){ SetActorTickEnabled(bEnable);}
// Tick 간격 조정 — 모든 오브젝트가 매 프레임 업데이트될 필요는 없음// 예: AI 판단 로직은 0.1~0.5초 간격으로도 충분PrimaryActorTick.TickInterval = 0.1f; // 초당 10회Tick 대신 타이머를 쓸 수 있는 경우: 일정 주기로 한 번씩 실행되는 로직은 SetTimer가 Tick + 카운터보다 효율적입니다.
5. 실습 예시 — 미니 아이템 컴포넌트 구현
Section titled “5. 실습 예시 — 미니 아이템 컴포넌트 구현”앞서 배운 내용을 종합한 간단한 아이템 Actor를 만들어 봅니다. 플레이어가 가까이 오면 자동으로 획득되고, 일정 시간 후 리스폰되는 아이템입니다.
#pragma once
#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "PickupItem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnItemPickedUp, APickupItem*, Item);
UCLASS(BlueprintType, Blueprintable)class MYGAME_API APickupItem : public AActor{ GENERATED_BODY()
public: APickupItem();
// Blueprint에서 구독 가능한 이벤트 UPROPERTY(BlueprintAssignable, Category = "Pickup") FOnItemPickedUp OnItemPickedUp;
// Blueprint에서 호출 가능 UFUNCTION(BlueprintCallable, Category = "Pickup") void PickUp(AActor* Picker);
protected: virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) TObjectPtr<UStaticMeshComponent> MeshComp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true")) TObjectPtr<class USphereComponent> OverlapComp;
// 아이템 이름 — 에디터에서 설정 UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item", meta = (AllowPrivateAccess = "true")) FText ItemName;
// 리스폰 대기 시간 (초) UPROPERTY(EditDefaultsOnly, Category = "Item") float RespawnDelay = 5.f;
// 이미 획득된 상태인지 bool bIsPickedUp = false;
FTimerHandle RespawnTimerHandle;
UFUNCTION() void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
void Respawn();};#include "PickupItem.h"#include "Components/SphereComponent.h"#include "Components/StaticMeshComponent.h"
APickupItem::APickupItem(){ PrimaryActorTick.bCanEverTick = false; // Tick 불필요
// 루트: 충돌 구체 OverlapComp = CreateDefaultSubobject<USphereComponent>(TEXT("OverlapComp")); OverlapComp->SetSphereRadius(100.f); OverlapComp->SetCollisionProfileName(TEXT("Trigger")); SetRootComponent(OverlapComp);
// 비주얼 메시 (루트에 부착) MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp")); MeshComp->SetupAttachment(OverlapComp); MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);}
void APickupItem::BeginPlay(){ Super::BeginPlay();
// 오버랩 이벤트 바인딩 OverlapComp->OnComponentBeginOverlap.AddDynamic(this, &APickupItem::OnOverlapBegin);}
void APickupItem::EndPlay(const EEndPlayReason::Type EndPlayReason){ GetWorldTimerManager().ClearTimer(RespawnTimerHandle); Super::EndPlay(EndPlayReason);}
void APickupItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult){ if (bIsPickedUp || !OtherActor) { return; }
// 플레이어인지 확인 (프로젝트에 맞게 캐스트 타입 변경) if (OtherActor->ActorHasTag(FName("Player"))) { PickUp(OtherActor); }}
void APickupItem::PickUp(AActor* Picker){ if (bIsPickedUp) { return; }
bIsPickedUp = true;
UE_LOG(LogTemp, Log, TEXT("%s picked up %s"), *Picker->GetName(), *ItemName.ToString());
// Blueprint 및 C++ 구독자에게 이벤트 전파 OnItemPickedUp.Broadcast(this);
// 비주얼 숨기기, 충돌 비활성화 SetActorHiddenInGame(true); SetActorEnableCollision(false);
// 리스폰 타이머 GetWorldTimerManager().SetTimer( RespawnTimerHandle, this, &APickupItem::Respawn, RespawnDelay, false );}
void APickupItem::Respawn(){ bIsPickedUp = false; SetActorHiddenInGame(false); SetActorEnableCollision(true);
UE_LOG(LogTemp, Log, TEXT("%s respawned"), *ItemName.ToString());}6. 정리 및 다음 단계
Section titled “6. 정리 및 다음 단계”이 가이드에서 다룬 UE C++ 입문 핵심 4가지를 정리합니다.
| 개념 | 핵심 요약 |
|---|---|
| UObject 시스템 | 모든 UE 관리 객체의 기반. NewObject, CreateDefaultSubobject, SpawnActor로 생성 |
| 매크로 시스템 | UCLASS·UPROPERTY·UFUNCTION은 엔진 리플렉션 등록용 “신고서” |
| 가비지 컬렉션 | UPROPERTY 없는 원시 포인터는 위험. TObjectPtr/TWeakObjectPtr 사용 |
| AActor 생명주기 | 생성자 → BeginPlay → Tick → EndPlay 순서를 지켜 코드를 배치 |
다음 단계로 학습할 주제
Section titled “다음 단계로 학습할 주제”- UActorComponent vs USceneComponent: 컴포넌트 설계 패턴과 부착(Attach) 계층 구조
- 델리게이트(Delegate):
DECLARE_DYNAMIC_MULTICAST_DELEGATE를 활용한 이벤트 시스템 - 네트워크 복제(Replication):
Replicated,RepNotify, RPC 기초 - GameplayAbilitySystem(GAS): 스킬·버프 시스템 구현 프레임워크
- Enhanced Input System: 입력 액션과 매핑 컨텍스트 C++ 연동