Skip to main content

14 / 16

Ticking and per-frame work

Leave tick disabled by default. Turn it on only for work that genuinely runs every frame.

Ticking is the default answer to "when should this run" and it is usually the wrong one. A ticking actor costs a call and a cache miss every frame whether or not it has anything to do.

Rules

  1. 01Set PrimaryActorTick.bCanEverTick = false in the constructor unless the class needs a per-frame update. The same applies to PrimaryComponentTick on a component.
  2. 02Use bStartWithTickEnabled = false for a class that can tick but should stay idle until something turns it on.
  3. 03Toggle ticking with SetActorTickEnabled or SetComponentTickEnabled rather than returning early inside Tick. An early return still pays for the call.
  4. 04Set TickInterval when work runs regularly but not every frame. Epic documents the field as "the time in seconds between executions of this tick function. If <= 0 then it will tick every frame."
  5. 05Choose the tick group deliberately. TG_PrePhysics runs before the physics step and TG_PostPhysics after it, so reading a transform from the wrong group gives you last frame data.
  6. 06Prefer a timer for delayed or repeating work and a delegate for anything reacting to an event. Checking for a change every frame costs far more than being told about it once.
  7. 07Never allocate inside Tick. A container rebuilt each frame reallocates each frame, and the cost scales with frame rate rather than with work done.

Scheduling the work

Every-frame tick

The result has to change with the frame, such as steering, interpolation, or a camera update. Runs in a known order relative to physics.

CostsCosts something every frame for every instance, and the cost scales with instance count rather than with work.

TickInterval

Work runs on a set schedule rather than every frame, such as a periodic scan. Keeps the tick setup while cutting how often it runs.

CostsThe interval is not frame aligned, so the gap between runs is not exact.

Timer

A one-shot delay or a fixed repeat, such as a cooldown or respawn. Costs nothing between firings.

CostsIt can only fire as often as the frame allows, and the timer handle has to be cleared when the owner leaves play.

Delegate or event

The work reacts to a change rather than to time. Runs exactly as often as the thing it responds to.

CostsNeeds something to broadcast, and the binding has to understand the owner lifetime.

Start with tick disabled and opt in

The projectile never ticks. The missile turns ticking on when it actually needs a per-frame update.

Projectile.cppcpp
AProjectile::AProjectile()
{
    PrimaryActorTick.bCanEverTick = false;
}

void AProjectile::BeginPlay()
{
    Super::BeginPlay();

    GetWorld()->GetTimerManager().SetTimer(
        LifetimeTimer,
        this,
        &AProjectile::HandleLifetimeExpired,
        MaxLifetimeSeconds,
        false);
}

AHomingMissile::AHomingMissile()
{
    // Steering needs a per-frame update, and it has to run before physics.
    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.bStartWithTickEnabled = false;
    PrimaryActorTick.TickGroup = TG_PrePhysics;
}

void AHomingMissile::BeginPlay()
{
    Super::BeginPlay();
    SetActorTickEnabled(IsValid(CurrentTarget));
}
Why
Every ticking actor costs a function call and a cache miss each frame, whether or not it does work. Disabling tick in the constructor makes the cost opt-in rather than the default.
Costs
Setup now lives in two places, and a subclass that needs ticking has to remember to enable it. Enabling in BeginPlay keeps that decision next to the runtime state it depends on.
Instead
Use TickInterval when work needs to run regularly but not every frame. Use a timer for a one-shot delay, and a delegate for anything that reacts to an event rather than to time.

All 16 rules