useTween

useTween provides property tweening, number interpolation, and timeline sequencing for game animations. It reads from the TweenManager which is automatically set up by Game.

tsx
import { useTween } from "@carverjs/core/hooks";

Quick Start #

tsx
import { useRef, useEffect } from "react";
import { Actor } from "@carverjs/core/components";
import { useTween } from "@carverjs/core/hooks";
import type { Group } from "@carverjs/core/types";

function BouncingBox() {
  const ref = useRef<Group>(null);
  const { tween } = useTween();

  useEffect(() => {
    if (!ref.current) return;

    const controls = tween({
      target: ref.current.position,
      to: { y: 3 },
      duration: 0.8,
      ease: "bounce.out",
      yoyo: true,
      repeat: -1,
    });

    return () => { controls.kill(); };
  }, [tween]);

  return <Actor ref={ref} type="primitive" shape="box" color="orange" />;
}

Return Value #

PropertyTypeDescription
tween(config)<T extends object>(TweenConfig<T>) => TweenControlsCreate and start a property tween
tweenNumber(config)(NumberTweenConfig) => TweenControlsCreate a target-less number tween
timeline(config?)(TimelineConfig?) => TimelineControlsCreate a timeline for sequenced animations
killAll()() => voidKill all tweens created by this hook instance
pauseAll()() => voidPause all active tweens
resumeAll()() => voidResume all paused tweens

Property Tweening #

Animate any numeric property on any object. Supports dot-notation for nested properties like position.x.

Basic #

tsx
const { tween } = useTween();

// Tween a Three.js position
tween({
  target: ref.current.position,
  to: { x: 5, y: 2 },
  duration: 1,
  ease: "quad.out",
});

With Explicit Start Values #

tsx
tween({
  target: ref.current.scale,
  from: { x: 0, y: 0, z: 0 },
  to: { x: 1, y: 1, z: 1 },
  duration: 0.5,
  ease: "back.out",
});

Callbacks #

tsx
tween({
  target: ref.current.position,
  to: { x: 10 },
  duration: 2,
  onStart: () => console.log("started"),
  onUpdate: (progress) => console.log("progress:", progress),
  onComplete: () => console.log("done"),
});

TweenConfig #

FieldTypeDefaultDescription
targetT extends objectRequired. Object whose properties to animate
toRecord<string, number>Required. End values (supports dot-notation keys)
fromRecord<string, number>Start values. If omitted, current values are captured
durationnumber0.3Duration in seconds
easeEasingInput"quad.out"Easing function or preset name
delaynumber0Delay before starting (seconds)
repeatnumber0Additional repeats. -1 for infinite
repeatDelaynumber0Delay between repeats (seconds)
yoyobooleanfalseReverse direction on each repeat
speednumber1Playback speed multiplier
groupstring""Named group for batch control
persistbooleanfalseKeep alive after completion (not auto-released to pool)
ignorePausebooleanfalseContinue updating when game is paused
onStart() => voidFired when the tween starts playing
onUpdate(progress: number) => voidFired every frame with eased progress (0–1)
onComplete() => voidFired when all repeats finish
onRepeat(count: number) => voidFired after each repeat
onYoyo(direction: TweenDirection) => voidFired when direction reverses
onKill() => voidFired when the tween is killed

TweenControls #

Every tween() call returns a TweenControls handle for imperative control:

tsx
const controls = tween({ target: obj, to: { x: 10 }, duration: 1 });

controls.pause();          // Pause
controls.resume();         // Resume
controls.kill();           // Stop and release
controls.restart();        // Replay from start
controls.seek(0.5);        // Jump to 50% progress
controls.setSpeed(2);      // Double speed
controls.chain({ ... });   // Chain another tween after completion
PropertyTypeDescription
idnumberUnique tween ID
stateTweenStateCurrent state: "playing", "completed", "killed", etc.
progressnumberCurrent eased progress (0–1)
pause()() => TweenControlsPause the tween
resume()() => TweenControlsResume a paused tween
kill()() => TweenControlsStop and release to pool
restart()() => TweenControlsRestart from the beginning
seek(progress)(number) => TweenControlsSeek to a progress value (0–1)
setSpeed(multiplier)(number) => TweenControlsSet playback speed
chain(config)(TweenConfig) => TweenControlsChain a tween to play after completion
finishedPromise<void>Resolves when the tween completes or is killed

All control methods return the same TweenControls for chaining:

tsx
tween({ target: pos, to: { x: 5 }, duration: 1 })
  .chain({ target: pos, to: { y: 5 }, duration: 1 });

Number Tweening #

Interpolate a single number without needing a target object. Useful for scores, timers, opacity, and any custom value.

tsx
const { tweenNumber } = useTween();

// Animate a score counter
tweenNumber({
  from: 0,
  to: 1000,
  duration: 2,
  ease: "expo.out",
  onUpdate: (value, progress) => {
    scoreElement.textContent = Math.round(value).toString();
  },
});
FieldTypeDefaultDescription
fromnumberRequired. Start value
tonumberRequired. End value
durationnumber0.3Duration in seconds
easeEasingInput"quad.out"Easing function
onUpdate(value: number, progress: number) => voidCalled every frame with the current interpolated value

All other fields are the same as TweenConfig (delay, repeat, yoyo, speed, etc.).


Timelines #

Sequence multiple tweens with precise timing control. Inspired by GSAP's timeline API.

Basic Sequence #

tsx
const { timeline } = useTween();

const tl = timeline()
  .add({
    target: ref.current.position,
    to: { x: 5 },
    duration: 1,
    ease: "sine.inOut",
  })
  .add({
    target: ref.current.position,
    to: { y: 3 },
    duration: 0.5,
  });

By default, each tween starts after the previous one finishes.

Overlapping Tweens #

tsx
timeline()
  .add({ target: pos, to: { x: 5 }, duration: 1 })
  .add({ target: pos, to: { y: 3 }, duration: 0.5 }, "-=0.3"); // overlap by 0.3s

Parallel Tweens #

tsx
timeline()
  .add({ target: pos, to: { x: 5 }, duration: 1 })
  .add({ target: scale, to: { x: 2, y: 2 }, duration: 1 }, "<"); // start at same time

Labels #

tsx
timeline()
  .addLabel("intro")
  .add({ target: pos, to: { x: 5 }, duration: 1 })
  .addLabel("middle")
  .add({ target: rot, to: { z: Math.PI }, duration: 0.5 }, "middle+=0.2");

Callbacks #

tsx
timeline()
  .add({ target: pos, to: { x: 5 }, duration: 1 })
  .addCallback(() => console.log("halfway!"), "+=0")
  .add({ target: pos, to: { y: 3 }, duration: 0.5 });

Position Syntax #

SyntaxMeaning
(omitted)After previous entry ends
0.5At absolute time 0.5s
"+=0.5"0.5s after the previous entry ends
"-=0.3"0.3s before the previous entry ends (overlap)
"<"At the same time the previous entry starts
"<+=0.5"0.5s after the previous entry starts
"label"At the time of a named label
"label+=0.3"0.3s after a named label

TimelineControls #

PropertyTypeDescription
idnumberUnique timeline ID
stateTweenStateCurrent state
progressnumberOverall progress (0–1)
durationnumberTotal timeline duration in seconds
add(config, position?)Adds a tween at a positionReturns self for chaining
addCallback(fn, position?)Adds a callback at a positionReturns self for chaining
addLabel(name, position?)Adds a named labelReturns self for chaining
play()Start playingReturns self
pause()PauseReturns self
resume()ResumeReturns self
kill()Stop and clean upReturns self
restart()Restart from beginningReturns self
seek(timeOrLabel)Seek to a time (seconds) or label nameReturns self
setSpeed(multiplier)Set playback speedReturns self
finishedPromise<void>Resolves on completion

Easing Functions #

33 Robert Penner easing presets plus spring. Pass as a string to the ease option.

Presets #

PresetTypical Use
"linear"Constant speed (timers, progress bars)
"quad.out"Default. Smooth deceleration (UI slide-in)
"quad.in"Accelerating start (projectile launch)
"cubic.inOut"Smooth start/stop (camera pan)
"quart.out"Snappy deceleration (menu open)
"sine.inOut"Gentle oscillation (breathing effect)
"expo.out"Sharp snap to end (impact response)
"elastic.out"Spring overshoot (UI popup)
"back.out"Overshoot and settle (button press)
"back.in"Pull-back before action (jump windup)
"bounce.out"Bouncing ball (landing, drop)
"spring"Damped spring (natural bounce)

Every equation comes in three variants: .in (accelerating), .out (decelerating), .inOut (both).

Custom Easing #

tsx
import { cubicBezier, steps } from "@carverjs/core/systems";

// CSS-compatible cubic bezier
tween({
  target: pos,
  to: { x: 10 },
  ease: cubicBezier(0.68, -0.55, 0.27, 1.55),
  duration: 1,
});

// Bezier as a tuple (shorthand)
tween({
  target: pos,
  to: { x: 10 },
  ease: [0.68, -0.55, 0.27, 1.55],
  duration: 1,
});

// Step easing (like CSS steps())
tween({
  target: pos,
  to: { x: 10 },
  ease: steps(5),
  duration: 1,
});

// Custom function
tween({
  target: pos,
  to: { x: 10 },
  ease: (t) => t * t * t,
  duration: 1,
});

Repeat & Yoyo #

tsx
// Repeat 3 more times (4 total plays)
tween({ target: pos, to: { y: 5 }, repeat: 3 });

// Infinite repeat with yoyo (ping-pong)
tween({ target: pos, to: { y: 5 }, repeat: -1, yoyo: true });

// Delay between repeats
tween({ target: pos, to: { y: 5 }, repeat: -1, yoyo: true, repeatDelay: 0.5 });

Chaining #

Chain tweens to play in sequence:

tsx
tween({
  target: ref.current.position,
  to: { x: 5 },
  duration: 1,
}).chain({
  target: ref.current.position,
  to: { y: 3 },
  duration: 0.5,
});

Promise-based Completion #

The .finished property returns a Promise that resolves when the tween completes:

tsx
const controls = tween({ target: pos, to: { x: 10 }, duration: 1 });
await controls.finished;
console.log("tween done!");

Pause-Immune Tweens #

Tweens pause automatically when the game phase is "paused" or "gameover". To keep a tween running during pause (e.g., pause menu animations):

tsx
tween({
  target: menuRef.current.position,
  to: { y: 0 },
  duration: 0.3,
  ignorePause: true,
});

Cleanup #

All tweens created via useTween() are automatically killed when the component unmounts. No manual cleanup needed for typical usage. For explicit control:

tsx
const { tween, killAll } = useTween();

// Kill all tweens from this hook
killAll();

// Or kill individually
const controls = tween({ ... });
controls.kill();

Game Phase Integration #

Tweens automatically respect the game phase:

PhaseBehavior
"loading"All tweens frozen (including ignorePause: true)
"playing"All tweens update normally
"paused"Normal tweens freeze. ignorePause: true tweens continue
"gameover"Same as paused

Type Definitions #

See Types for EasingFn, EasingPreset, EasingInput, TweenState, TweenDirection, TweenConfig, NumberTweenConfig, TweenControls, TweenGroupControls, TimelineConfig, TimelineControls, TimelinePosition, and UseTweenReturn.