로그 출력 활용법과 액터 생명주기의 중요성
1. UE_LOG를 활용한 디버깅
로그는 코드의 실행 흐름을 파악하고 오류를 잡는 데 필수적
기본 사용법
UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"));
- 로그 카테고리: LogTemp는 특정 카테고리가 지정되지 않았을 때 쓰는 임시 카테고리
- 로그 수준(Verbosity):
- Display: 일반적인 정보 확인용 (흰색)
- Warning: 잠재적 문제 경고 (노란색)
- Error: 심각한 오류 발생 (빨간색)
- TEXT("메시지"): 유니코드 문자열 형식을 사용합니다.
고유 카테고리 정의 (심화)
프로젝트가 커지면 LogTemp 대신 전용 카테고리를 만들어 로그를 필터링하는 것이 좋다.
- 헤더 파일(.h): 카테고리 선언
-
C++
DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All); - 소스 파일(.cpp): 카테고리 정의
-
C++
DEFINE_LOG_CATEGORY(LogSparta); - 사용: UE_LOG(LogSparta, Display, TEXT("Sparta Category Log!"));
2. 액터 라이프 사이클 이해가 중요한 이유
단순히 함수 순서를 외우는 것보다 왜 이 시점에 이 코드를 넣어야 하는가?를 아는 것이 중요
① 초기화 시점 결정 (Initialization Timing)
액터의 상태에 따라 할 수 있는 일이 단계별로 다름.
- 생성자: 컴포넌트 생성(CreateDefaultSubobject)과 기본 변수 세팅만 수행. (월드 접근 불가)
- BeginPlay: 월드가 준비된 상태이므로 다른 액터를 참조하거나 게임 플레이 로직 시작.
② 성능 관리 (Performance Optimization)
- Tick 함수는 매 프레임 호출되므로 CPU 비용이 많이 발생
- 반드시 실시간 업데이트가 필요한 액터만 bCanEverTick = true를 설정하고, 나머지는 **이벤트 기반(Event-driven)**으로 설계하여 최적화해야 함
③ 리소스 정리 (Resource Management)
액터가 파괴되거나 레벨이 넘어갈 때 정리를 제대로 하지 않으면 **메모리 누수(Memory Leak)**가 발생
- EndPlay나 Destroyed에서 동적 메모리 해제, 타이머 중지, 이벤트 바인딩 해제 등을 처리하여 안정성을 확보
주요 함수들
1단계: 생성 및 초기화 (Spawn & Initialization)
1. 생성자 (Constructor)
- 호출 시점: C++ 클래스 객체가 메모리에 생성될 때 단 한 번 호출
- 특징: 액터가 아직 월드(World)에 완전히 등록되지 않은 상태.
- 월드 관련 기능이나 다른 액터를 참조하는 로직은 안전하지 않다.
- 주요 작업: CreateDefaultSubobject를 이용한 컴포넌트 생성.
- 초기 변수 및 기본값 세팅.
2. PostInitializeComponents()
- 호출 시점: 액터에 포함된 모든 컴포넌트가 생성 및 초기화된 직후에 호출
- 특징: 생성자에서는 단순 '할당'만 했다면, 여기서는 컴포넌트들이 준비되었으므로 서로를 참조하거나 상호작용할 수 있다.
- 주요 작업: 컴포넌트 간 의존성 설정 및 초기화 로직.
2단계: 게임 시작 및 업데이트 (Gameplay)
3. BeginPlay()
- 호출 시점: 게임이 시작될 때(PIE 포함) 또는 게임 도중 액터가 SpawnActor로 월드에 배치될 때 한 번 호출
- 특징: * 액터가 월드에 완전히 안착한 상태입니다
- 다른 액터, 게임 모드, 플레이어 컨트롤러 등 외부 시스템과 자유롭게 소통할 수 있다.
- 주요 작업: 타이머 설정, 델리게이트(이벤트) 바인딩, 게임 로직의 실질적인 시작.
4. Tick(float DeltaTime)
- 호출 시점: 매 프레임마다 호출
- 활성화 조건: PrimaryActorTick.bCanEverTick = true; 설정이 필요
- 특징: 실시간 연산(이동, 물리 계산 등)이 필요할 때 사용하지만, 너무 많은 액터가 Tick을 쓰면 성능이 저하
- Tip: 가능하다면 이벤트 기반(Event-driven) 로직으로 대체하는 것이 성능 최적화에 유리
3단계: 소멸 및 정리 (Destruction & Cleanup)
5. Destroyed()
- 호출 시점: Destroy() 함수를 호출하여 액터를 수동으로 제거할 때 직전에 호출
- 주의사항: 레벨 전환이나 게임 종료 시에는 호출되지 않을 수 있으므로, 모든 정리 로직을 여기에만 의존해서는 안 됨.
- 주요 작업: 수동 할당 메모리 해제, 자식 액터 제거, 사운드/파티클 중지.
6. EndPlay(const EEndPlayReason::Type EndPlayReason)
- 호출 시점: 액터가 월드에서 사라지는 모든 순간에 호출 (액터 파괴, 레벨 전환, 게임 종료 등)
- 특징: EEndPlayReason 파라미터를 통해 왜 종료되는지 이유를 구분할 수 있어 매우 유연
- 주요 작업: (가장 권장되는 정리 장소)
- 타이머 해제 (ClearTimer)
- 데이터 저장 (점수, 상태 등)
- 남아있는 동적 리소스 정리
| 단계 | 함수명 | 주요 역할 | 비고 |
| 생성 | Constructor | 컴포넌트 생성, 기본값 설정 | 월드 참조 불가 |
| 초기화 | PostInitializeComponents | 컴포넌트 간 상호작용 설정 | 컴포넌트 준비 완료 |
| 시작 | BeginPlay | 실질적인 게임 로직 시작 | 월드/액터 참조 가능 |
| 실행 | Tick | 매 프레임 업데이트 | 성능 유의 |
| 제거 | Destroyed | 수동 제거 시 호출 | 종료/레벨 전환 시 미호출 가능 |
| 종료 | EndPlay | 최종 자원 정리, 타이머 해제 | 가장 안전한 정리 시점 |
코드
void AItem::PostInitializeComponents() {
Super::PostInitializeComponents();
UE_LOG(LogSparta, Warning, TEXT("%s PostInitializeComponents"), *GetName());
}
void AItem::BeginPlay() {
Super::BeginPlay();
UE_LOG(LogSparta, Warning, TEXT("%s BeginPlay"), *GetName());
}
void AItem::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
}
void AItem::Destroyed() {
UE_LOG(LogSparta, Warning, TEXT("%s Destroyed"), *GetName());
Super::Destroyed();
}
void AItem::EndPlay(const EEndPlayReason::Type EndPlayReason) {
UE_LOG(LogSparta, Warning, TEXT("%s EndPlay"), *GetName());
Super::EndPlay(EndPlayReason);
}
Transform 조작과 프레임 독립적인 움직임
1. Transform(트랜스폼)의 이해
액터가 월드에 존재하기 위한 3가지 필수 데이터
- 위치 (Location): 월드 내의 X, Y, Z 좌표 (FVector 사용)
- 회전 (Rotation): 액터가 바라보는 방향 (FRotator 사용)
- Roll: X축 기준 회전 (좌우 기울기)
- Pitch: Y축 기준 회전 (위아래 기울기)
- Yaw: Z축 기준 회전 (좌우 회전)
- 스케일 (Scale): 액터의 크기 비율 (1.0이 기본 크기)
2. 좌표계: 월드(World) vs 로컬(Local)
- 월드 좌표계 (World Space): 게임 전체 세계의 절대적인 중심(0,0,0)을 기준으로 함
- 로컬 좌표계 (Local Space): 액터 자신이나 부모 컴포넌트를 기준으로 한 상대적 좌표
- 부모-자식 관계: 부모가 움직이면 자식은 자신의 '상대적 위치'를 유지하며 함께 움직인다.
- SetRelativeLocation() 등을 통해 부모 기준 위치를 조정할 수 있다.
3. C++로 Transform 조작하기
주요 함수
- GetActorLocation() / SetActorLocation(): 위치 가져오기 및 설정
- SetActorRotation(): 회전 설정
- SetActorScale3D(): 크기 설정 (FVector(2.0f) 처럼 단일 값 입력 시 모든 축 적용)
적용 예시 (BeginPlay)
게임이 시작될 때 액터의 초기 위치와 크기를 강제로 설정하는 코드
void AItem::BeginPlay()
{
Super::BeginPlay();
SetActorLocation(FVector(300.f, 200.f, 100.f)); // 특정 위치로 이동
SetActorRotation(FRotator(0.f, 45.f, 0.f)); // 45도 회전
SetActorScale3D(FVector(2.0f)); // 2배 크기로 확대
}
4. Tick 함수와 DeltaTime
Tick(float DeltaTime)이란?
액터가 매 프레임마다 실행하는 함수입니다. 실시간으로 변하는 로직(회전하는 아이템, 움직이는 캐릭터 등)을 구현할 때 사용
- 활성화 방법: 생성자에서 PrimaryActorTick.bCanEverTick = true; 설정 필수.
왜 DeltaTime을 곱해야 할까? (프레임 독립성)
컴퓨터마다 성능(FPS)이 다름
- 60 FPS: 1초에 Tick이 60번 호출
- 120 FPS: 1초에 Tick이 120번 호출
단순히 위치 += 1을 하면 고사양 컴퓨터에서 캐릭터가 2배 더 빨리 움직이게 됨 이를 방지하기 위해 마지막 프레임에서 현재 프레임까지 걸린 시간인 DeltaTime을 곱해줌
공식: 이동 거리 = 속도 * DeltaTime
코드
// 생성자에서 Tick 활성화
AItem::AItem() {
PrimaryActorTick.bCanEverTick = true;
RotationSpeed = 90.0f; // 초당 90도 회전 설정
}
void AItem::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
// DeltaTime을 곱해 사양에 관계없이 초당 RotationSpeed만큼 회전
if (!FMath::IsNearlyZero(RotationSpeed)) {
AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
}
}
- AddActorLocalRotation: 현재 회전 값에 특정 값을 더하는 함수.
- FMath::IsNearlyZero: 부동소수점 오차를 고려해 안전하게 0인지 확인
주의: 회전 시 gizmo를 기준으로 해서 원하는 회전 형식이 아닐 수 있음. 따라서 Modeling Mode에 들어가 edit pivot을 눌러 조정 가능

리플렉션 시스템과 C++ & 블루프린트
1. 블루프린트 vs C++ : 어떤 것을 써야 할까?
실무에서는 두 방식을 섞어서 사용하는 하이브리드 워크플로우가 일반적
| 구분 | 블루프린트 (Blueprint) | C++ (Native Code) |
| 특징 | 노드 기반 시각적 스크립팅 | 텍스트 기반 네이티브 프로그래밍 |
| 장점 | 빠른 프로토타이핑, 직관적, 쉬운 수정 | 최고의 성능, 복잡한 연산 최적화, 외부 라이브러리 확장 |
| 단점 | 복잡해지면 가독성 저하(스파게티 코드), 성능 병목 | 컴파일 대기 시간, 높은 진입 장벽 |
| 용도 | UI 로직, 간단한 이벤트 연출, 기획자 수정용 변수 | 게임 엔진 확장, 물리/AI 연산, 핵심 프레임워크 설계 |
2. 리플렉션 시스템(Reflection System)이란?
C++는 원래 자신이 어떤 변수와 함수를 가졌는지 스스로 알지 못함(런타임 기준). 하지만 언리얼 엔진은 리플렉션 시스템을 통해 C++ 클래스 정보를 메타데이터로 저장하여, 에디터와 블루프린트가 이 정보를 읽고 조작할 수 있게 만듦
필수 구성 요소
- UCLASS(): 클래스를 리플렉션 시스템에 등록.
- GENERATED_BODY(): 리플렉션에 필요한 필수 코드를 자동 생성하여 삽입.
- #include "FileName.generated.h": 반드시 헤더 파일의 가장 마지막에 위치해야 하는 자동 생성 파일.
3. 변수를 에디터에 노출하기: UPROPERTY()
변수 위에 매크로를 붙여 에디터에서의 노출 범위와 권한을 설정.
주요 지정자
- 편집 권한
- EditAnywhere: 클래스 기본값과 배치된 인스턴스 모두 수정 가능.
- VisibleAnywhere: 보기만 가능하고 수정 불가(주로 컴포넌트 참조 변수에 사용).
- EditDefaultsOnly: 원본 블루프린트(클래스 기본값)에서만 수정 가능.
- 블루프린트 접근
- BlueprintReadWrite: 블루프린트 노드에서 읽기/쓰기 가능.
- BlueprintReadOnly: 읽기 전용 Getter만 생성.
- 기타
- Category="이름": 디테일 패널에서 변수를 그룹화하여 정리.
- meta=(AllowPrivateAccess="true"): private 변수여도 에디터 노출 허용.
4. 함수를 블루프린트에서 호출하기: UFUNCTION()
함수 또한 리플렉션에 등록하여 블루프린트 노드로 사용할 수 있다.
주요 지정자
- BlueprintCallable: 블루프린트에서 호출할 수 있는 일반적인 실행 노드로 생성.
- BlueprintPure: 실행 핀(하얀색 선) 없이 값만 반환하는 노드(Getter 등).
- BlueprintImplementableEvent: C++에서 선언만 하고, 실제 구현은 블루프린트에서 담당. (C++에서 특정 시점에 이벤트를 발생시킬 때 유용)



5. 실전 코드 예시
// Item.h
UCLASS(Blueprintable)
class AItem : public AActor {
GENERATED_BODY()
protected:
// 에디터에서 메시를 선택할 수 있게 노출
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Item|Components")
UStaticMeshComponent* StaticMeshComp;
// 회전 속도 (기본값만 수정 가능)
UPROPERTY(EditDefaultsOnly, Category="Item|Properties")
float RotationSpeed;
// C++에서 호출하지만 동작은 블루프린트에서 정의
UFUNCTION(BlueprintImplementableEvent, Category="Item|Events")
void OnItemPickedUp();
// 블루프린트에서 호출 가능한 함수
UFUNCTION(BlueprintCallable, Category="Item|Actions")
void ResetPosition();
};
'언리얼' 카테고리의 다른 글
| 레인보우 식스 시즈 코어 루프 분석 및 MDA 적용 (0) | 2026.04.10 |
|---|---|
| 리플렉션/UHT/UBT/CDO (feat. Garbage Collection) (1) | 2026.04.10 |
| 언리얼C++) C++로 엑터 생성 (1) | 2026.04.07 |
| 언리얼C++) 초기 세팅 (0) | 2026.04.06 |
| C++) TRPG 최종 (0) | 2026.04.01 |