Tweening

Animates numbers, entity positions and alpha over time, with easing curves, repeats and sequences.

Commands

Import blitz.tweening As T gives your program these, with T. in front. Each name leads to its part of the reference.

Functions

Types

Interfaces

Enums

Constants

Guide

Tweening makes things change smoothly. Instead of snapping a door open, or jumping a score straight from 0 to 500, you say where something should end up and how many seconds it should take, and the module works out every frame in between. Things can speed up and slow down gently, overshoot and settle, bounce, repeat, go there and back, or move one after another.

Seven balls racing across the screen, each with a different easing curve

Try it

Open race.bb and run it. Seven balls race to the right and back again, for ever, each with a different easing curve. Watch how each one sets off, slows down and arrives.

; Seven balls race across the screen and back, each with its own easing curve.
Dialect "modern"
Import blitz.tweening As T

Type Racer
    Field ball:Int
    Field name:Str
End Type

Graphics3D 800, 480, 0, 2
SetBuffer BackBuffer()
Local camera:Int = CreateCamera()
CameraClsColor camera, 18, 52, 79
CameraZoom camera, 1.6
PositionEntity camera, 0, 0, -13
Local light:Int = CreateLight()
RotateEntity light, 40, 30, 0

; One group runs all the tweens. The options say how each tween moves.
Local group:T.TweenGroup = T.CreateTweens()
Local options:T.TweenOptions = New T.TweenOptions
options.repeatCount = T.Infinite ; Keep going for ever...
options.pingPong = True          ; ...there and back again.

Local curve:Int
For curve = T.Ease.Linear To T.Ease.BounceOut
    Local racer:Racer = New Racer
    Local name:Str
    Read name
    racer.name = name
    racer.ball = CreateSphere(16)
    ScaleEntity racer.ball, 0.4, 0.4, 0.4
    EntityColor racer.ball, 255, 120 + curve * 20, 60
    PositionEntity racer.ball, -3, 3 - curve, 0
    ; Move to x = 6 in two seconds, with this curve.
    options.easing = curve
    T.TweenMove group, racer.ball, 6, 3 - curve, 0, 2D, options
Next
Delete options ; Each tween keeps its own copy.

Local previous:Int = MilliSecs()
While Not KeyDown(1)
    ; Tell the group how many seconds have passed, and it moves every ball.
    Local now:Int = MilliSecs()
    T.UpdateTweens group, Double(now - previous) / 1000D
    previous = now

    RenderWorld
    For racer = Each Racer
        CameraProject camera, -5.2, EntityY(racer.ball), 0
        Text ProjectedX(), ProjectedY(), racer.name, True, True
    Next
    Flip
Wend
Delete group
End

Data "Linear", "Smooth", "QuadIn", "QuadOut", "QuadInOut", "BackOut", "BounceOut"

The program makes one tween for each ball, and then, every frame, tells the tween group how much time has passed. The group does the rest. The module never looks at the clock itself: it only knows the seconds you give it, so you are in charge of pausing, slow motion and fast-forward.

How do I...

Move something, wait, then move it back?

Make both moves, then tell the second one to start after the first. AfterTween takes the tween that waits, the one it waits for, and how many seconds to pause in between.

Local opening:T.Tween = T.TweenMove(group, door, 2.4, 0, 0, 0.8D, Null)
Local closing:T.Tween = T.TweenMove(group, door, 0, 0, 0, 0.8D, Null)
T.AfterTween closing, opening, 0.5D

Null in place of options means "the usual way": a straight, steady move.

Start after a delay?

Set delay in the options. A tween copies its options, so you can delete them straight away, or change them and use them again.

Local options:T.TweenOptions = New T.TweenOptions
options.delay = 1.5D ; Wait a second and a half first.
options.easing = T.Ease.QuadOut
T.TweenMove group, ship, 0, 10, 0, 3D, options
Delete options

Pause everything?

Pause the group. Its tweens stay where they are until you carry on.

T.PauseTweens group, True  ; Pause.
T.PauseTweens group, False ; Carry on.

Give things that should keep moving while the game is paused, such as a menu, a group of their own.

Count up a score?

Tween a Value instead of an entity, and draw its value.

Local score:T.Value = New T.Value
T.TweenValue group, score, 500D, 1D, Null ; Count up to 500 in a second.
; Then, each frame, after T.UpdateTweens:
Text 10, 10, "Score: " + Int(score.value)

Fade something out?

T.TweenAlpha group, ghost, 1, 0, 2D, Null ; From solid to gone in two seconds.

You give the alpha to start from, because Blitz3D+ has no command to read an entity's alpha back.

Find out when a tween has finished?

Ask for its state.

If T.TweenState(opening) = T.State.Completed Then Print "The door is open"

Stop a tween early?

CancelTween stops it where it is. Add True to jump to the end instead.

T.CancelTween opening       ; Stop where it is.
T.CancelTween opening, True ; Jump to the end.

Blip's tip

Delete the tween group before you free the entities it moves. Deleting the group deletes all of its tweens too, so you never need to delete them one by one.

Good to know

More examples

Three more complete programs come with the module:

The reference below has every function, option and rule.

Reference

Needs
No other modules
Examples
door.bb, fade.bb, score.bb

Animate values, parent-local positions and alpha with explicit seconds, seven curves, repeats and branching sequences. This is an unpublished 0.1.0 preview for Extended with object-lifecycle-v1. It has no package dependencies, import side effects, binary assets or package scripts.

Blitz3D+ comes with this module, so any Extended program can Import blitz.tweening. Open an example and run it.

Start with a number

Dialect "modern"
Import blitz.tweening As T
Local group:T.TweenGroup = T.CreateTweens()
Local score:T.Value = New T.Value
Local tween:T.Tween = T.TweenValue(group, score, 100D, 1D, Null)
T.UpdateTweens group, 0.25D
Print score.value ; 25
T.UpdateTweens group, 0.75D
Print T.TweenState(tween) ; T.State.Completed
Local event:T.TweenEvent = T.NextEvent(group)
If event <> Null
    Print event.ID()
    Delete event
EndIf
Delete group ; Deletes tween; never deletes score.
Delete score
End

The caller supplies time; the library never polls clocks/input, sleeps, draws or updates the world. Use one group per independently paused/time-scaled system. Normal game order is input, decisions, UpdateTweens, event processing, world and UI drawing. All calls belong on the main thread. Worker execution is deferred.

Complete examples

FileWhat it doesControls
examples/score.bbQuick start: an eased numeric score and generated HUDSpace earns 100; P pauses; C cancels; F finishes; R resets; Escape exits
examples/door.bbOrdinary integration: a sliding, parented door opens, waits and closesSpace restarts; P pauses; C cancels either phase; Escape exits
examples/fade.bbAdvanced: interrupted fades coexist with ping-pong movement; IDs/state events and ownershipSpace reverses (also while paused); P pauses; C cancels at destination; R resets; Escape exits

Each example's --verify mode executes finite scripted interactions through the same control paths, checks results, draws and tears down. Normal runs use measured seconds, clamped to 0.1 per frame as a game policy; verification uses 1/30 s. Every mesh, colour, bar and label is generated in source.

API and options

CreateTweens():TweenGroup creates a caller-owned scheduler. Configuration is a caller-owned TweenOptions; factories copy it, and explicit Null means defaults. The compiler does not support object-valued Null default arguments.

Local options:T.TweenOptions = New T.TweenOptions
options.easing = T.Ease.Smooth
options.delay = 0.25D
options.repeatCount = 1
options.pingPong = True
Local tween:T.Tween = T.TweenValue(group, score, 100D, 1D, options)
Delete options ; The tween already copied it.
OperationContract
TweenValue(group, target, destination:Double, seconds:Double, options):TweenBorrows ValueTarget; captures when starting, after delay/dependency. Multiple scalar writers are allowed; cancel old ones explicitly.
TweenMove(group, entity:Int, x:Float, y:Float, z:Float, seconds:Double, options):TweenBorrows entity; captures parent-local XYZ when starting. Replaces this group's active movement on that entity.
TweenAlpha(group, entity:Int, initialAlpha:Float, alpha:Float, seconds:Double, options):TweenNo runtime alpha getter exists. Supply the starting alpha. A running fade replaced in this group supplies its last written alpha instead. Inputs and output clamp to 0..1.
AfterTween(child, predecessor, delay:Double=0D):IntReplaces a same-group dependency and its extra delay; adds that delay to the child's options delay. An already completed predecessor counts as finishing now, so the child still waits its delays and runs its full duration. Returns False for a cycle, a started/terminal child or an infinite predecessor. Wrong group/invalid seconds is an error.
UpdateTweens(group, seconds:Double)Samples one fixed horizon in dependency order, with the bounded-work rules below.
PauseTweens(group, paused:Int=True)Paused updates discard their incoming delta. Existing work/time remains retained.
CancelTween(tween, finish:Int=False)Terminal cancellation event; leaves current value, or writes the literal destination once. Does not delete the handle.
TweenState(tween):IntState.Waiting, Running, Completed or Cancelled; no event consumption.
tween.ID():LongMonotonically increasing, non-reused identity within its group.
TweenEvents(group, enabled:Int=True)False stops queuing terminal events and discards queued ones, so fire-and-forget games that never poll cannot reach the event capacity. Not allowed inside callbacks.
NextEvent(group):TweenEventTransfers the oldest event, or Null. event.ID() / event.State() are immutable. Caller deletes the event.

Procedure-style calls and ValueTarget.SetValue have the compiler's Int result convention; ignore that result. Only AfterTween's result is a success status. GetValue replaces the plan's illegal Read spelling (Read is reserved).

Value is a simple caller-filled record with value:Double. For game-specific values, implement the small interface; there is no arbitrary callback dispatcher:

Type DisplayScore Implements T.ValueTarget
    Field shown:Double
    Method GetValue:Double()
        Return Self.shown
    End Method
    Method SetValue:Int(value:Double)
        Self.shown = value
    End Method
End Type

Options default to Ease.Linear, zero delay/repeats and no ping-pong. repeatCount counts extra legs, and Infinite=-1 repeats forever. Ping-pong reverses odd legs: one repeat finishes at the captured initial value. Finite zero-duration repetition collapses to one destination write and completion, even with ping-pong. Infinite zero-duration repetition is invalid. Non-ping-pong repeats reset to their starting value at each leg boundary; ping-pong is continuous.

Curves are Linear, Smooth (cubic smoothstep), QuadIn, QuadOut, QuadInOut, BackOut and BounceOut. Progress is bounded to 0..1 and endpoints are exact. BackOut intentionally overshoots; alpha still clamps. BackOut interpolation that exceeds finite Double or engine Float range raises an error before that write.

Time, capture and property replacement

All times/deltas must be finite, non-negative Double seconds. Zero delta is useful for starting a ready tween or continuing budgeted work. Values/coordinates must be finite. A deleted borrowed target is rejected; engine handles have no general validity query, so entities must outlive their tweens.

Progress is derived from captured endpoints and elapsed time, never from an incrementally interpolated value. Repeats use floating remainder to avoid an infinite quotient for tiny durations and a huge elapsed time. Exact endpoints, equal extremes and convex interpolation of opposite extremes stay finite. Overflowing schedules and positive times too small to advance their base clock are errors, not silently lost time. Ordinary binary64 rounding still applies.

Dependencies carry excess time across finite durations and delays, including branches and zero-duration chains. Capture happens when a ready tween is first visited. Ordering is deterministic: registration order with each ancestor visited before its descendants. This is update-resolution capture, not chronological substepping of every property transition inside a large delta.

For example, an X move 0→10 over 2 s and a second X move to 20 over 1 s delayed 0.5 s give X=7.8125 after a single 0.75 s update. Updating 0.5 then 0.25 s gives X=6.875, because the second tween captured earlier. Both are intentional and tested. Fixed-capture independent tweens are partition-invariant within rounding; overlapping property writes and game/callback changes depend on update boundaries.

Movement and alpha are separate properties. Replacement coordinates only the same property in the same group, at the instant its new tween starts. Completed fades no longer supply alpha; pass the known current alpha after completion, cancellation, deletion or game-owned changes. Other groups/game writes are uncoordinated. Cancelling with finish writes the literal destination even for a waiting tween or a ping-pong tween. Repeating a terminal cancellation is a no-op.

Callback phases and bounded work

A round snapshots the group and a time horizon. Each UpdateTweens evaluates at most 65,536 records. If the round needs another call, it retains its cursor and horizon; incoming deltas accumulate for the next round. Use UpdateTweens(group,0D) to continue without adding time. New tweens created during a round use its horizon as their epoch and start no earlier than the next round. Snapshot/preflight and cancellation propagation still scan the group: the budget bounds evaluated target records, not all CPU operations. Terminal handles also occupy snapshot entries; delete them when the game no longer needs them.

During GetValue or SetValue:

One terminal-event slot is reserved for every nonterminal tween at creation. queued events + staged events + nonterminal tweens <= 262144 is maintained even when callbacks create or cancel work. Creation fails before registration when no slot remains. This prevents capacity errors halfway through completion writes. Drain events regularly and delete unused active handles, or switch events off with TweenEvents group, False when the game never polls them. The preflight covers capacity, not system allocation failure or arbitrary callback exceptions; runtime errors terminate the program and do not promise rollback of earlier writes.

Sorting a changed dependency forest is O(n + edges), using an explicit stack. AfterTween checks O(ancestor depth); ordinary rounds and unregister are O(n). Completed or cancelled handles that you keep are skipped by rounds, so they do not use the 65,536 evaluations each update allows, though they still hold memory until deleted.

Ownership and explicit actions

ResourceOwner and deletion rule
TweenGroupCaller; delete before targets/entities. Owns all remaining handles and undelivered events/containers.
TweenGroup; caller may delete a child outside callbacks. Unregisters itself. Terminal handles remain queryable until deletion.
Value/custom target/entityBorrowed, never deleted by group/tween. Caller releases it after its groups.
TweenOptionsCaller; copied immediately, may be deleted after creation.
NextEvent resultOwnership transfers to caller. Contains IDs/state, no references to the group/handle. Survives both.

Delete tween leaves the current value, makes no target calls and emits no event. Deleting an unfinished predecessor silently cancels all waiting descendants, including branches/pending edges. Deleting a completed predecessor preserves its completion time for waiting dependants. Delete group detaches then deletes remaining children and all owned containers/events without callbacks. No global Delete Each or shutdown sweep is required. Typed Null/already deleted aliases are harmless to Delete; other API use rejects deleted handles.

Pause, cancellation and disposal are distinct actions. There is no separate Stop alias: use pause to resume later, cancellation to end and notify, deletion to release ownership. There is no persistent state transaction, so Save/Commit are not APIs here; destruction never saves or commits game work implicitly.

Examples of programming errors:

; T.UpdateTweens group, -1D       ; invalid seconds
; Local x:T.Tween = New T.Tween  ; private constructor; use a factory
; Print tween.duration          ; private representation
; Delete group inside SetValue  ; callback deletion is forbidden

Separate timelines, a parallel-group DSL, more easing families, skeletal animation and worker adapters remain explicitly deferred.