The frame: Tick, timers, and timelines¶
A frame is one pass of the running game. Tick, timers, and timelines all run inside that pass, but they solve different problems.
The one-minute version¶
- Event Tick runs every frame while ticking is enabled. Use it for work that genuinely needs continuous per-frame updates.
- Delta Seconds is the game time since the previous frame. Multiply per-second rates by Delta Seconds so movement and countdowns do not change speed with frame rate.
- Timers schedule callbacks for later or on a repeating interval. They are not background threads; the timer manager checks and fires them during game frames on the game thread.
- Timelines play authored curves over time. While a timeline is playing, its Update output fires across frames with the current curve values.
- Avoid Tick when you are just asking "has something happened yet?" Use an event, overlap, dispatcher, or timer instead.
- Avoid timers for smooth movement and animation. Use Tick with Delta Seconds, a Timeline, or a movement/animation system that already ticks.
What a frame actually is¶
When Unreal is playing your level, it repeats a loop: advance game time, process input and gameplay, update actors/components that tick, step physics in the right phase, update animation and rendering work, then present the next image. That loop is a frame.
Frame length is not fixed:
60 frames per second -> about 0.0167 seconds per frame
30 frames per second -> about 0.0333 seconds per frame
120 frames per second -> about 0.0083 seconds per frame
That is why Delta Seconds exists. If you move 600 Unreal units per second,
you do not add 600 every frame. You add:
movement this frame = direction * 600 * DeltaSeconds
At 60 frames per second that is about 10 units per frame. At 30 frames per second it is about 20. The per-frame number changes so the per-second result stays the same.
Event Tick¶
Event Tick is the blunt instrument: "call this graph once per frame for this
actor, if this actor is allowed to tick."
It is appropriate when the answer can change every frame and you need the new value every frame:
- rotate a preview object while play is running;
- trace from the camera while the player is holding an aim button;
- update a custom movement calculation;
- interpolate toward a target when no Timeline or component already owns that interpolation.
It is the wrong tool when you are polling for something that could tell you directly:
- do not Tick every frame asking "am I overlapping the trigger?" Use overlap events;
- do not Tick every frame asking "is the door open request true?" Call an event when the request happens;
- do not Tick every frame asking "is one second over?" Use a timer;
- do not Tick every widget binding just to copy health every frame. Fire an update when health changes.
Tick cost is multiplied by actors and frame rate:
50 ticking actors * 60 frames per second = 3,000 graph executions per second
50 ticking actors * 120 frames per second = 6,000 graph executions per second
The cost is not the red Tick node by itself. The cost is everything you hang off it: casts, searches, traces, widget updates, loops, spawning, and logging.
Timers¶
A timer says: "call this event/function later, or repeatedly, using a handle I can pause, clear, or replace."
Use timers for:
- damage over time every 1.0 second;
- weapon cooldowns;
- retrying setup after a short delay;
- clearing a temporary buff;
- doing a cheap scan every 0.25 seconds instead of every frame.
Blueprint shape:
BeginPlay
-> Set Timer by Event
Time = 1.0
Looping = true
-> store Return Value as PoisonTimerHandle
Custom Event ApplyPoisonTick
-> Apply Damage
EndOverlap or EndPlay
-> Clear and Invalidate Timer by Handle
Important details:
- a timer fires on a later frame; it is not a separate thread;
- a looping timer can drift by frame boundaries because it can only fire when the game is ticking;
- setting a timer with the same handle replaces the old timer;
- a Time value less than or equal to zero clears that timer instead of starting a useful countdown;
- timers bound to a destroyed actor/object are canceled before they call into that destroyed object.
Timers are excellent for "do this every N seconds." They are poor for "move smoothly every visible frame."
Timelines¶
A Timeline is a small curve player inside a Blueprint. You add float, vector, color, or event tracks, keyframe those tracks, then call Play, Reverse, Stop, or Set New Time.
While the Timeline is playing, its Update pin runs over frames and outputs the current track values. That makes it good for authored, finite changes:
- open a door over 0.7 seconds;
- fade a light from 0 to 8 intensity;
- move a platform along a curve;
- animate a UI value with a custom easing shape.
Blueprint shape:
Interact event
-> Timeline Play from Start
Timeline Update (Alpha 0..1)
-> Lerp ClosedRotation to OpenRotation by Alpha
-> SetRelativeRotation on DoorMesh
Timeline Finished
-> mark door open
The Timeline owns the time curve. You should not also add a Delay loop or a Timer loop around it. Start it, let its Update pin drive the changing value, and respond to Finished when the motion is done.
When they do nothing¶
These tools usually fail silently because they are scheduling or update hooks, not "try/catch" operations.
| Tool | Common reason nothing happens | What to check |
|---|---|---|
| Event Tick | Tick is disabled, the actor is not spawned in the play world, or the graph is on a different instance than the one you are watching. | Class Defaults tick settings, Set Actor Tick Enabled, actor instance, and whether play has begun. |
| Timer | You never set it, you cleared/replaced its handle, Time was <= 0, the bound object was destroyed, or the world is not ticking. |
Store the handle, print when setting/clearing, and clear it in EndPlay. |
| Timeline | You never called Play, it is already stopped/finished, it has no useful track values, or the owning actor is gone. | Call Play/Play from Start, watch the Update pin, and inspect the timeline length/tracks. |
In multiplayer, each machine has its own frames. A client ticking locally does not mean the server ran the same logic. Put authoritative gameplay changes on the server, and use client Tick/Timeline work for local presentation unless the state is intentionally replicated.
Lookalikes - which one do I want?¶
| Tool | Runs | Use when |
|---|---|---|
| Event Tick | Every frame while enabled | Continuous per-frame gameplay or presentation that cannot be event-driven. |
| Timer | Later, or repeatedly at an interval | Cooldowns, delayed calls, periodic damage, polling that does not need every frame. |
| Timeline | Every frame while the timeline is playing | Authored curves for doors, fades, moving platforms, and short scripted motion. |
| Delay | Resumes one execution path later | A simple one-off pause in a Blueprint flow where you do not need a reusable handle. |
| Event Blueprint Update Animation | Animation Blueprint update | Animation state gathering, not general actor gameplay. |
| Async Physics Tick | Physics-thread oriented update | Advanced physics work; not a replacement for beginner Event Tick. |
Rule of thumb: event when possible, timer when occasional, timeline when curved over time, Tick only when the answer truly changes every frame.
Going deeper¶
- Construction Script vs BeginPlay - where timers, gameplay lookups, and runtime starts belong.
- Spawn Actor from Class - why gameplay spawning usually belongs to events or BeginPlay, not editor construction.
- Event Blueprint Update Animation - the animation-specific per-update event.
- Get Velocity - a common
value read each animation update, and why
Delta Secondsmatters for motion. - Official docs: Event Tick, Actor Ticking, AActor::Tick, Gameplay Timers, Set Timer by Event, Add Timeline, and Editing Timelines.
- Engine source (requires engine access - we do not reproduce it here):
AActor::Tick,AActor::TickActor, andAActor::ReceiveTickinEngine/Source/Runtime/Engine/Private/Actor.cpp;FTimerManager::TickandFTimerManager::SetTimerinEngine/Source/Runtime/Engine/Private/TimerManager.cpp;FTimelineandUTimelineComponentbehavior inEngine/Source/Runtime/Engine/Private/Components/TimelineComponent.cpp.