BP 성능 최적화
블루프린트 성능 병목을 식별하고 최적화하는 실전 기법을 학습합니다
Tick 최적화
매 프레임 실행을 최소화하는 전략
블루프린트의 가장 큰 성능 비용은 매 프레임 실행(Tick)에서 발생합니다. 수백 개의 Actor가 Tick을 실행하면 프레임 레이트가 급락합니다.
전략 1: Tick 비활성화
// Class Defaults > Actor Tick
Start with Tick Enabled: False
Tick Interval: 0.0 (초, 0 = 매 프레임)
// 블루프린트에서 동적 제어
[Set Actor Tick Enabled] (True/False)
[Set Actor Tick Interval] (0.1) // 0.1초마다 Tick (초당 10회)
// 컴포넌트 Tick도 별도 제어
[Set Component Tick Enabled] (Component, False)
전략 2: Timer로 대체
// BAD: Event Tick에서 체력 재생 (60 FPS = 초당 60회)
[Event Tick] ──> [Heal] (Amount * Delta)
// GOOD: Timer로 1초마다 체력 재생 (초당 1회)
[Event BeginPlay] ──> [Set Timer by Event]
├─ Event: RegenTick
├─ Time: 1.0
└─ Looping: True
전략 3: 이벤트 기반 전환
| Tick 사용 | 이벤트 기반 대안 | 절약 비율 |
|---|---|---|
| 거리 확인 (매 프레임) | Timer (0.5초) + 근접 시 Tick 활성화 | ~97% |
| UI 갱신 (매 프레임) | Event Dispatcher (데이터 변경 시만) | ~99% |
| 입력 확인 (매 프레임) | Enhanced Input Event | ~99% |
| 상태 확인 (매 프레임) | 상태 변경 함수에서 직접 처리 | ~99% |
노드 비용 최적화
비용이 큰 노드를 식별하고 효율적으로 사용하기
비용이 큰 노드들
| 노드 | 비용 | 최적화 방법 |
|---|---|---|
| Get All Actors of Class | 매우 높음 | 결과를 캐싱하거나, 직접 참조 사용 |
| Line Trace | 높음 | 빈도 줄이기, Channel 최소화 |
| Overlap Actors | 높음 | 범위 최소화, 오브젝트 타입 필터 |
| Cast To | 중간 | Interface 사용, 결과 캐싱 |
| For Each Loop (대규모) | 규모 비례 | 병렬 처리 불가, 프레임 분산 |
| Pure 함수 (비용 큰) | 호출 횟수 비례 | 변수에 캐싱 후 재사용 |
| String 연산 | 중간 | FName으로 비교, 연산 최소화 |
// BAD: 매 프레임 모든 적을 검색
[Tick] ──> [Get All Actors of Class] (BP_Enemy) ──> ForEach...
// GOOD: BeginPlay에서 한 번 가져오고 캐싱
[BeginPlay] ──> [Get All Actors of Class] ──> [Set EnemyList]
// 적이 스폰/파괴될 때만 리스트 업데이트
// BAD: Pure 함수 여러 곳에서 호출
[GetExpensiveResult] ──> Node A
[GetExpensiveResult] ──> Node B // 2번 실행됨!
[GetExpensiveResult] ──> Node C // 3번 실행됨!
// GOOD: 한 번 계산하고 변수에 저장
[GetExpensiveResult] ──> [Set CachedResult]
[Get CachedResult] ──> Node A, B, C
Pure 노드(실행 핀 없는 노드)는 연결된 모든 곳에서 매번 재실행됩니다. 비용이 큰 Pure 함수를 3곳에 연결하면 3배 비용이 발생합니다. 핀을 우클릭 → Promote to Variable로 결과를 캐싱하세요.
메모리와 하드 참조
에셋 참조 관리와 메모리 최적화
하드 참조 vs 소프트 참조
// 하드 참조 (Hard Reference): 항상 메모리에 로드
UPROPERTY(EditAnywhere)
TSubclassOf<AActor> EnemyClass; // 이 BP가 로드되면 EnemyClass도 로드
// 소프트 참조 (Soft Reference): 필요 시에만 로드
UPROPERTY(EditAnywhere)
TSoftClassPtr<AActor> EnemyClass; // 명시적으로 로드할 때만 메모리에
// BP에서 Soft Object Reference:
// 변수 타입을 "Soft Object Reference" 또는 "Soft Class Reference"로 지정
// Async Load Asset 노드로 비동기 로드
Reference Viewer 활용
에셋을 우클릭 > Reference Viewer로 열면 해당 에셋의 모든 참조 관계를 그래프로 확인할 수 있습니다. 의도치 않은 하드 참조를 발견하고 제거하는 데 필수적입니다.
우클릭 > Size Map으로 에셋이 차지하는 메모리를 시각적으로 확인할 수 있습니다. 하드 참조로 인해 불필요하게 큰 에셋이 로드되는 문제를 발견하는 데 유용합니다.
Blueprint Nativization
블루프린트를 C++로 변환하여 성능 향상
Blueprint Nativization(BP를 C++로 자동 변환)은 UE 5.0에서 deprecated 되었습니다. 대신 UE5는 블루프린트 VM 자체의 성능을 개선하여, 대부분의 게임플레이 로직에서 BP 성능이 충분합니다. 성능이 크리티컬한 부분은 처음부터 C++로 작성하는 것이 UE5의 권장 접근법입니다.
UE5 블루프린트 성능 개선사항
| 버전 | 개선 내용 |
|---|---|
| UE 5.0+ | BP VM 최적화, 노드 실행 오버헤드 감소 |
| UE 5.1+ | Blueprint Debugger 성능 개선 |
| UE 5.3+ | Blueprint Compilation 속도 향상 |
| UE 5.4+ | 대규모 BP 프로젝트 로드 시간 최적화 |
최적화 체크리스트
- 불필요한 Tick을 비활성화했는가?
- Tick 대신 Timer 또는 이벤트로 대체할 수 있는가?
- Get All Actors of Class를 매 프레임 호출하고 있지 않은가?
- Pure 함수의 결과를 변수에 캐싱했는가?
- Cast 대신 Interface를 사용할 수 있는가?
- 소프트 참조로 불필요한 에셋 로드를 방지했는가?
- Reference Viewer로 의도치 않은 참조를 확인했는가?
핵심 요약
- Tick 최소화가 BP 최적화의 최우선 과제이다. Timer와 이벤트로 대체한다
- Get All Actors of Class는 결과를 캐싱하고, 스폰/파괴 시에만 갱신한다
- Pure 함수는 연결된 곳마다 재실행되므로, 비용이 큰 경우 변수에 캐싱한다
- 소프트 참조(Soft Reference)로 불필요한 에셋 메모리 로드를 방지한다
- Reference Viewer와 Size Map으로 참조 관계와 메모리 비용을 분석한다
- Blueprint Nativization은 UE5에서 deprecated, 성능 크리티컬 코드는 C++로 직접 작성한다
도전 과제
배운 내용을 직접 실습해보세요
매 Tick마다 Overlap 체크를 하는 비효율적인 BP를 만든 후, Tick Interval을 0.1초로 변경하세요. 이어서 Timer로 대체하고, 최종적으로 이벤트 기반(OnComponentBeginOverlap)으로 변환하여 세 가지 방식의 성능을 비교하세요.
수학 연산이 많은 블루프린트(100번 반복 루프 내 벡터 계산)를 만들고, Blueprint Nativization(프로젝트 설정)을 활성화하여 C++로 변환된 결과를 패키징하세요. Nativize 전후의 실행 시간을 비교해보세요.
1000개의 Actor를 스폰하는 시나리오에서 성능 최적화를 수행하세요. Tick 비활성화, Set Actor Hidden In Game으로 보이지 않는 Actor 비활성화, Get All Actors Of Class 대신 태그 기반 검색 사용, Soft Reference로 에셋 로딩을 지연시키는 종합 최적화를 적용하세요.