Skip to content

Tweens

Tweens interpolate numeric component properties over time. Each tween spawns one Tween entity per animated property, which the TweenSystem processes in batch via iter().

import { AnimationTimeSource } from '@shell/animate';
const animate = resources.get('animate');
const handle = animate.tween(entity, Transform2D, {
x: 100,
y: 200,
duration: 1000, // ms
ease: 'outElastic',
delay: 0,
loop: 0,
alternate: false,
timeSource: AnimationTimeSource.Simulation,
});

Only numeric component fields can be tweened (Int8, Int16, Int32, Float32, Float64). String, Boolean, Object, and Instance fields are ignored.

PropertyTypeDefaultDescription
durationnumber300Duration in milliseconds
delaynumber0Delay before the tween starts in milliseconds
timeSourceAnimationTimeSourceAnimationTimeSource.SimulationClock used for delays, playback, and loops
easeEasingName'linear'Easing function name (see Easing)
loopnumber0Number of loops (0 = none, -1 = infinite)
alternatebooleanfalseAlternate direction on each loop (ping-pong)
onComplete() => void—Called when the tween completes all loops
onBegin() => void—Called when the tween starts (after delay)

Any additional keys matching the component schema’s numeric fields are treated as animation targets.

Animations use scaled simulation time by default, so changing ticker.timeScale changes their speed. Use wall time for UI feedback or for animations that control the time scale itself:

animate.tween(entity, Position, {
x: 48,
y: 48,
duration: 600,
timeSource: AnimationTimeSource.Wall,
});

AnimationTimeSource.Wall uses the ticker’s unscaled frame delta. It does not advance while the application ticker is paused or stopped.

You can animate to a single value or provide a [from, to] tuple:

// Animate from current value to 100
animate.tween(entity, Transform2D, { x: 100, duration: 500 });
// Animate from 0 to 100 explicitly
animate.tween(entity, Transform2D, { x: [0, 100], duration: 500 });

When animating from the current value, the system captures the in-progress interpolated position if an existing tween was mid-flight, enabling smooth redirection.

Calling animate.tween() on a property that is already being tweened updates the existing tween entity in-place rather than spawning a new one. The old handle becomes orphaned and will not receive completion callbacks.

animate.tween() returns a TweenHandle for controlling the animation:

handle.pause(); // Pause all tweens in this handle
handle.resume(); // Resume paused tweens
handle.cancel(); // Stop and despawn all tween entities
handle.onComplete(() => {});
handle.onBegin(() => {});
await handle.then(); // Promise that resolves on completion

Cancel all active tweens on an entity, optionally filtered by component:

animate.cancel(entity); // Cancel all tweens on entity
animate.cancel(entity, Transform2D); // Cancel only Transform2D tweens
animate.cancelAll(); // Cancel everything managed by this resource

Register a custom easing function at runtime:

const index = animate.registerEasing('myEase', (t) => t * t);
animate.tween(entity, Transform2D, {
x: 100,
ease: 'myEase',
});

Custom easing functions must map [0, 1] → [0, 1].

Each animated property spawns one Tween entity with these fields:

FieldTypeDescription
fromFloat64Start value
toFloat64End value
elapsedFloat64Milliseconds elapsed
durationFloat64Total duration in milliseconds
delayFloat64Delay before starting
timeSourceInt8Simulation=0, Wall=1
easingInt32Index into EasingTable
loopCountInt32Total loops (0 = none, -1 = infinite)
currentLoopInt32Current loop iteration (0-based)
alternateBooleanPing-pong direction each loop
stateInt32Pending=0, Running=1, Completed=2, Cancelled=3, Paused=4
groupIdInt32Links to TweenHandle for callbacks