Timeline
The Timeline system drives multi-track animations that coordinate property changes, Spine playback, sprite animations, audio events, and entity activation over time. Timelines are created in the editor’s Timeline panel and played back at runtime via the TimelinePlayer component and TimelineControl API.
Tutorial: Animate a Sprite
Let’s create a timeline that moves a sprite from left to right and fades it out.
Step 1: Select the Entity
Click on the entity you want to animate in the Hierarchy.
Step 2: Open the Timeline Panel
Open View → Timeline (or find it in the panel menu). If the entity doesn’t have a TimelinePlayer component yet, the panel shows a Create button — click it to add one automatically.
Step 3: Add a Property Track
- Click the + button in the track list
- Select Property → Transform → position.x
- A new track appears, targeting the selected entity’s
Transform.position.x
Step 4: Add Keyframes
- Move the playhead to time 0.0 by clicking the ruler at the top
- Click the diamond icon (or press
K) to add a keyframe — it captures the current value - Move the playhead to 1.0 seconds
- In the Inspector, change position.x to 200
- Press
Kagain — a second keyframe is added with the new value
Step 5: Add a Second Track
- Click + → Property → Sprite → color.a
- At time 0.0, add a keyframe (value: 1.0)
- At time 1.0, add a keyframe (value: 0.0)
Step 6: Preview
Press Space to play in the editor. The sprite moves right while fading out over 1 second.
Step 7: Configure Playback
In the Inspector, set the TimelinePlayer properties:
- wrapMode:
loopto make it repeat - speed:
0.5to play at half speed
Track Targeting
Every track has a target entity. By default, it’s the entity that owns the TimelinePlayer component.
Animating Child Entities
To animate a child entity, set the track’s Child Path — a /-separated name hierarchy:
Arm/Hand → find child "Arm", then its child "Hand"Body → find direct child "Body"(empty) → target the root entity itselfIn the editor, when you add a track while a child entity is selected, the child path is set automatically.
Track Types
Property
Animate any numeric component property with keyframe interpolation.
Built-in properties (optimized, computed in C++):
| Component | Properties |
|---|---|
| Transform | position.x/y/z, scale.x/y/z, rotation |
| Sprite | color.r/g/b/a, opacity, size.x/y |
| UIRect | offsetMin.x/y, offsetMax.x/y, anchorMin.x/y, anchorMax.x/y, pivot.x/y |
| Camera | orthoSize |
Custom properties: Any other numeric component field can be animated using nested paths (e.g., myComponent.health).
Keyframe Interpolation
Right-click a keyframe to choose an interpolation mode:
| Mode | Behavior |
|---|---|
| Hermite | Smooth cubic curve using tangent handles (default) |
| Linear | Straight line between keyframes |
| Step | Hold value until next keyframe (no interpolation) |
| EaseIn | Slow start, accelerating toward the next keyframe |
| EaseOut | Fast start, decelerating toward the next keyframe |
| EaseInOut | Smooth acceleration and deceleration |
For Hermite mode, drag the tangent handles in the curve editor to shape the interpolation.
Spine
Trigger Spine animation clips on a SpineAnimation component. Each keyframe specifies an animation name to play at that time.
Sprite Animation
Play sprite animation clips. Each keyframe specifies a clip name to start at that time.
AnimFrames
A visual frame track for sprite-sheet-style animation. Unlike sprite animation clips, each frame is displayed as a colored, resizable block on the timeline:
- Each block references a texture and has its own duration (default: 1/12 second)
- Drag block edges to adjust frame timing
- The target entity must have a
Spritecomponent — the texture is swapped each frame
Use AnimFrames when you need per-frame timing control without creating a separate animation clip.
Audio
Trigger audio playback at specific times. Each keyframe specifies an audio asset to play.
Activation
Toggle entity visibility over time ranges. The entity is enabled within the active range and disabled outside it.
Marker
Place named markers at specific times. Markers don’t affect playback — they serve as reference points for the editor and for seeking.
Custom Event
Trigger named events at specific times. Listen for these events in your systems to run custom logic at precise moments during the animation.
Editor Controls
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Space | Play / Pause |
, / . | Step backward / forward one frame |
K | Add keyframe at playhead position |
Delete | Delete selected keyframes |
Ctrl+Click | Toggle keyframe selection |
Shift+Click | Range select keyframes |
| Box drag | Rubber-band select multiple keyframes |
Toolbar
- Record: Toggle auto-keyframe mode — property changes in the Inspector are automatically recorded as keyframes at the current playhead position
- Speed: Cycle playback speed (0.25x, 0.5x, 1x, 2x, 4x)
- Wrap Mode: Cycle between Once, Loop, and PingPong
- Snap: Toggle grid snapping during keyframe drag
- Duration: Double-click the time display to edit total timeline duration
Multi-Select & Batch Operations
Select multiple keyframes (Ctrl+Click, Shift+Click, or box drag), then:
- Drag to move them all together
- Delete to remove them as a single undo step
Track Management
- Drag tracks to reorder in the list
- Right-click a track for rename / delete
- Each track type has a distinct color indicator in the sidebar
Live Playhead Sync
When entering Play Mode, the timeline automatically switches to LIVE mode. The playhead tracks the runtime animation position in real-time (polled at 60fps), and a red blinking “LIVE” indicator appears in the toolbar. Editor playback controls are disabled during live mode and restore when exiting Play Mode.
TimelinePlayer Component
Add TimelinePlayer to an entity to play a timeline asset at runtime.
| Property | Type | Default | Description |
|---|---|---|---|
timeline | string | '' | Path to the .estl timeline asset |
playing | boolean | false | Set to true to start playback |
speed | number | 1.0 | Playback speed multiplier |
wrapMode | string | 'once' | 'once', 'loop', or 'pingPong' |
When playing is set to true, the timeline starts. When it finishes (in once mode), playing is automatically set back to false.
WrapMode
| Value | Description |
|---|---|
once | Play once and stop at the end |
loop | Restart from the beginning when finished |
pingPong | Alternate between forward and reverse playback |
TimelineControl API
Use TimelineControl for runtime playback control from systems:
import { defineSystem, addSystem, Query, TimelinePlayer, TimelineControl } from 'esengine';import { Res, Input } from 'esengine';
addSystem(defineSystem( [Res(Input), Query(TimelinePlayer)], (input, query) => { for (const [entity] of query) { if (input.isKeyPressed('Space')) { TimelineControl.play(entity); } if (input.isKeyPressed('KeyP')) { TimelineControl.pause(entity); } } }));Methods
| Method | Returns | Description |
|---|---|---|
TimelineControl.play(entity) | void | Start or resume playback |
TimelineControl.pause(entity) | void | Pause playback |
TimelineControl.stop(entity) | void | Stop and reset to the beginning |
TimelineControl.setTime(entity, time) | void | Seek to a specific time (seconds) |
TimelineControl.isPlaying(entity) | boolean | Check if timeline is playing |
TimelineControl.getCurrentTime(entity) | number | Get current playback time (seconds) |
Registering Timeline Assets
When loading timelines outside the editor (e.g., in a custom asset pipeline), use the registration API:
import { registerTimelineAsset, getTimelineHandle, parseTimelineAsset } from 'esengine';
const asset = parseTimelineAsset(jsonData);registerTimelineAsset('timelines/intro.estl', asset);
const tl = getTimelineHandle('timelines/intro.estl');Example: Intro Sequence
import { defineSystem, addStartupSystem, addSystem, Query, Mut, TimelinePlayer, TimelineControl, Res, Input} from 'esengine';
// Play the intro timeline on startupaddStartupSystem(defineSystem( [Query(Mut(TimelinePlayer))], (query) => { for (const [entity, player] of query) { if (player.timeline === 'timelines/intro.estl') { TimelineControl.play(entity); } } }));
// Allow skipping with SpaceaddSystem(defineSystem( [Res(Input), Query(TimelinePlayer)], (input, query) => { if (input.isKeyPressed('Space')) { for (const [entity] of query) { if (TimelineControl.isPlaying(entity)) { TimelineControl.stop(entity); } } } }));Resource Cleanup
Timeline and tween resources attached to an entity are automatically cleaned up when the entity is despawned. The engine calls _tl_destroy to free C++ timeline objects and Tween.cancelAll() to cancel active tweens via onDespawn. You do not need to manually stop timelines or cancel tweens when destroying entities.
Next Steps
- State Machine — drive timelines through state transitions
- Tween Animation — code-driven property animation
- Spine Animation — skeletal animation
- Sprite Animation — frame-by-frame sprite animation