Tick 최적화
CPU 성능의 핵심, 불필요한 Tick을 제거하고 효율적으로 관리하기
Tick이란?
매 프레임 실행되는 함수의 비용
Tick 함수는 매 프레임 실행됩니다. 60 FPS 게임에서는 초당 60번, 수백 개의 액터가 있다면 그만큼 Tick 호출이 발생합니다.
대규모 오픈월드에서 수천 개의 액터가 Tick을 실행하면 CPU 성능에 심각한 영향을 미칩니다. dumpticks 명령어로 현재 틱킹 중인 객체를 확인하세요.
// 틱킹 객체 목록 확인
dumpticks // 모든 틱킹 액터/컴포넌트
dumpticks grouped // 그룹별로 정렬
listtimers // 활성화된 타이머 목록
Tick 비활성화 전략
필요할 때만 Tick을 활성화
Level 1: 완전 비활성화
정적 오브젝트, 이벤트 기반 로직만 있는 액터
// 생성자에서 Tick 완전 비활성화
AMyStaticActor::AMyStaticActor()
{
// Tick 기능 자체를 사용하지 않음
PrimaryActorTick.bCanEverTick = false;
}
// 컴포넌트도 마찬가지
UMyComponent::UMyComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
Level 2: 조건부 활성화
애니메이션 재생 중, 이동 중, 상호작용 중일 때만
AMyActor::AMyActor()
{
// Tick 가능하지만, 시작 시 비활성화
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = false;
}
// 필요할 때 활성화
void AMyActor::StartMoving()
{
SetActorTickEnabled(true);
bIsMoving = true;
}
// 완료 후 비활성화
void AMyActor::StopMoving()
{
SetActorTickEnabled(false);
bIsMoving = false;
}
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 이동 로직 수행
UpdateMovement(DeltaTime);
// 목적지 도착 시 Tick 비활성화
if (HasReachedDestination())
{
StopMoving();
}
}
Level 3: Tick Interval 조절
AI 의사결정, 거리 체크 등 빈번하지 않아도 되는 로직
AMyAIActor::AMyAIActor()
{
PrimaryActorTick.bCanEverTick = true;
// 0.1초마다 Tick (10 FPS)
PrimaryActorTick.TickInterval = 0.1f;
}
// 또는 런타임에 동적으로 변경
void AMyAIActor::SetTickFrequency(float Interval)
{
PrimaryActorTick.TickInterval = Interval;
}
// 거리에 따라 Tick Interval 조절
void AMyAIActor::AdjustTickByDistance(float Distance)
{
if (Distance < 1000.f)
{
// 가까우면 자주 업데이트
PrimaryActorTick.TickInterval = 0.033f; // 30 FPS
}
else if (Distance < 3000.f)
{
PrimaryActorTick.TickInterval = 0.1f; // 10 FPS
}
else
{
PrimaryActorTick.TickInterval = 0.5f; // 2 FPS
}
}
컴포넌트 Tick 최적화
액터뿐 아니라 컴포넌트도 최적화
UMyAnimationComponent::UMyAnimationComponent()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = false;
}
void UMyAnimationComponent::PlayAnimation()
{
// 애니메이션 시작 시 Tick 활성화
SetComponentTickEnabled(true);
ResetAnimationState();
}
void UMyAnimationComponent::TickComponent(float DeltaTime,
ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// 애니메이션 업데이트
UpdateAnimation(DeltaTime);
// 완료되면 Tick 비활성화
if (IsAnimationComplete())
{
SetComponentTickEnabled(false);
OnAnimationFinished.Broadcast();
}
}
Timer 활용
Tick 대신 Timer 사용하기
void Tick(float DeltaTime)
{
Timer += DeltaTime;
if (Timer >= 1.0f)
{
DoSomething();
Timer = 0.f;
}
}
void BeginPlay()
{
GetWorldTimerManager().SetTimer(
TimerHandle,
this,
&AMyActor::DoSomething,
1.0f, // 1초마다
true // 반복
);
}
Timer 활용 예제
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor()
{
// Tick 완전 비활성화
PrimaryActorTick.bCanEverTick = false;
}
virtual void BeginPlay() override
{
Super::BeginPlay();
// 1초마다 반복 실행
GetWorldTimerManager().SetTimer(
HealthRegenHandle,
this,
&AMyActor::RegenerateHealth,
1.0f,
true
);
// 5초 후 한 번만 실행
GetWorldTimerManager().SetTimer(
SpawnHandle,
this,
&AMyActor::SpawnReinforcements,
5.0f,
false
);
}
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
{
// Timer 정리
GetWorldTimerManager().ClearTimer(HealthRegenHandle);
GetWorldTimerManager().ClearTimer(SpawnHandle);
Super::EndPlay(EndPlayReason);
}
private:
FTimerHandle HealthRegenHandle;
FTimerHandle SpawnHandle;
void RegenerateHealth();
void SpawnReinforcements();
};
Timer는 내부적으로 최적화되어 있어 Tick보다 효율적입니다. 특히 1초 이상의 간격이나 일회성 작업에 적합합니다. listtimers 명령으로 활성화된 타이머를 확인할 수 있습니다.
이벤트 기반 설계
폴링 대신 이벤트 사용
void Tick(float DeltaTime)
{
// 매 프레임 체력 확인
if (Health <= 0)
{
Die();
}
}
void TakeDamage(float Damage)
{
Health -= Damage;
// 변경 시점에만 체크
if (Health <= 0)
{
Die();
}
}
// 이벤트 정의
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
UCLASS()
class UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
UHealthComponent()
{
// Tick 불필요
PrimaryComponentTick.bCanEverTick = false;
}
UPROPERTY(BlueprintAssignable)
FOnHealthChanged OnHealthChanged;
void TakeDamage(float Damage)
{
float OldHealth = Health;
Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
if (Health != OldHealth)
{
// 변경 시에만 이벤트 발생
OnHealthChanged.Broadcast(Health);
}
}
private:
float Health = 100.f;
float MaxHealth = 100.f;
};
핵심 요약
- bCanEverTick = false - Tick이 필요 없는 액터는 완전히 비활성화
- bStartWithTickEnabled = false - 필요할 때만 Tick 활성화
- TickInterval - 매 프레임 대신 간격을 두고 실행
- Timer 사용 - 주기적 작업은 Tick 대신 Timer 활용
- 이벤트 기반 설계 - 폴링 대신 상태 변경 시 이벤트 발생
- dumpticks - 현재 틱킹 객체 목록 확인
1. Tick 완전 비활성화 > 2. 조건부 활성화 > 3. Tick Interval 조절 > 4. Timer 대체
도전 과제
배운 내용을 직접 실습해보세요
PrimaryActorTick.bCanEverTick = false로 틱이 필요 없는 RPG Actor(정적 소품, 환경 오브젝트)의 틱을 비활성화하세요. stat DumpTicks로 틱 중인 Actor 수를 확인하세요.
GetWorldTimerManager().SetTimer()로 주기적 체크(몬스터 어그로 범위, 버프 지속시간)를 대체하세요. 델리게이트 기반 이벤트로 상태 변경 시에만 로직을 실행하도록 리팩터링하세요.
USignificanceManager를 활용하여 카메라 거리에 따라 NPC의 틱 빈도를 동적으로 조절하세요. 가까운 NPC는 매 프레임, 먼 NPC는 0.5초마다 틱하도록 설정하세요.