Animation
Back to the reference

Animation

What moves over time: sprite frames, skeletons and their clips, and tweens.

16 symbols

getSpriteAnimationfunctionnacatamalon

getSpriteAnimation(sprite: TSprite) => TSpriteAnimation | null

The animator a sprite already has, or null when nothing animates it.

A sprite from a scene file can arrive already moving: the file says which run it starts on, and the scene started it. This is how a behaviour reaches that same animator to change the run, instead of making a second one that fights the first for the same sprite every frame. If more than one was made for a sprite, it is the last.

Parameters

spriteTSprite

The sprite to ask about.

Example

const punch = (self: TGameObject) => {
    const sprite = self.drawables.find((drawable) => drawable.type === 'sprite');
    const anim = sprite === undefined ? null : getSpriteAnimation(sprite);
    const keys = useKeyboard();

    useUpdate(() => {
        if (keys.justPressed('f')) anim?.play('punch');
    });
};
View source · hooks/animation/get_sprite_animation.ts:47

useSkeletalAnimationfunctionhooknacatamalon

useSkeletalAnimation(model: TGltfModel, options: TSkeletalAnimationOptions) => TSkeletalAnimation

Makes a loaded model move: plays the movements it came with, and eases between them.

A rigged model is a skin stretched over bones. A movement turns the bones, and the skin follows, which is why one model can walk, run and fall over without three copies of it existing.

It can be asked for a movement before the file has arrived, which is the ordinary case: the model comes back still loading, so play is remembered and honoured the moment it shows up.

fade is what makes it look like a character rather than a demo. Asked to change movement over half a second, it turns the bones from where one has them to where the other does; cutting straight across snaps a leg from behind to in front in a single frame.

Parameters

modelTGltfModel

The loaded model, from useLoadGltf.

optionsTSkeletalAnimationOptions

Which movement to start on, and how it plays.

Example

export const Level: TSceneFn = () => {
    useCamera3d({ projection: 'perspective', z: 200 });
    useLight({ intensity: 1 });

    const fox = useLoadGltf({ src: '/models/fox.glb' });
    createModel({ model: fox });
    const moves = useSkeletalAnimation(fox, { play: 'Walk', fade: 0.25 });

    const keys = useKeyboard();
    useUpdate(() => {
        if (keys.justPressed('Space')) moves.play('Run');
    });

    return createScene();
};
View source · hooks/animation/use_skeletal_animation.ts:54

useSpriteAnimationfunctionhooknacatamalon

useSpriteAnimation(sprite: TSprite, options: TSpriteAnimationOptions) => TSpriteAnimation

Plays a sprite's pictures in order, so it walks, attacks or spins.

A sheet already holds every picture and a sprite already shows one of them: animating is deciding which one, and when. That is all this does, once per frame, so the renderer never learns that animation exists.

The sprite has to come from a sheet (createSprite({ atlas })), since that is where the frames are. Call it while the scene is being built, like every hook, and keep what it returns to change clips later.

Parameters

spriteTSprite

The sprite to animate.

optionsTSpriteAnimationOptions

The runs it knows, which one to start with, and how fast.

Example

declare const walk: TSpriteAtlas;
declare let hurt: boolean;

const enemy = createSprite({ atlas: walk, frame: 0, transform: { x: 80, y: 120 } });

const anim = useSpriteAnimation(enemy, {
    clips: {
        walk: { frames: [0, 1, 2, 3, 4, 5], fps: 12 },
        hit: { frames: [6, 7], fps: 20, loop: false },
    },
    play: 'walk',
});

useUpdate(() => {
    if (hurt) anim.play('hit');
});
View source · hooks/animation/use_sprite_animation.ts:58

useTweenfunctionhooknacatamalon

useTween() => TTweenStarter

Moves a number from one value to another over time, so something can slide in, grow, fade or bounce without you writing the arithmetic for it every frame.

Calling useTween() animates nothing by itself: it gives you back a function that starts tweens. Call that function whenever you want one, while the scene is being built or later from a key press, and it hands you the controls to that particular tween: pause it, resume it, throw it away or send it straight to the end.

What is animated is a plain number. The tween gives it to you every frame in onUpdate and you put it wherever it belongs, which is why the same hook moves a sprite, widens a loading bar or mixes two colours.

Tweens belong to the scene that started them: they freeze while it is paused, they run in slow motion when the game does, and they are gone when it leaves. There is nothing to clean up.

Example

export const Level: TSceneFn = () => {
    const hero = createSprite({ key: 'hero', transform: { x: 40, y: 112 } });
    const tween = useTween();

    // There and back for ever, landing with a bounce on each end
    tween({
        from: 40,
        to: 420,
        duration: 1.5,
        ease: easeOutBounce,
        yoyo: true,
        repeat: -1,
        onUpdate: (x) => { hero.transform.x = x; },
    });

    return createScene();
};
View source · hooks/tween/use_tween.ts:127

TAnimationCliptypenacatamalon

One named run of frames: which pictures, how fast, and whether it starts again at the end.

Properties

framestypeOperator

The frames of the sheet to show, in order. They may repeat and go backwards.

fpsoptionalnumber

Frames per second. Default 12, which is the usual pace for hand-drawn movement.

loopoptionalboolean

Whether it starts again at the end. Default true.

eventsoptionaltypeOperator

Moments inside the run worth being told about: the picture a blow lands on, a footstep.

at is the place in this run, counting from 0, not the number of the picture on the sheet. A run is allowed to show the same picture twice ([4, 5, 6, 5, 4]), and the two showings are not the same moment: one is the swing going out and the other is it coming back. Counting places is the only way to tell them apart.

nextoptionalstring

The run to play when this one ends, for one that does not loop: a punch that drops back to standing. A name there is no run for is said once, and the run rests on its last picture.

View source · hooks/animation/types/t_sprite_animation.ts:10

TClipEventtypenacatamalon

Something that happens at a point inside a movement, rather than at its end: the moment a sword becomes dangerous, a foot meets the ground, a spell leaves the hand.

It is a cue, not a consequence: the movement says when, and the game decides what that means. That split is what lets the same swing be a hit, a parry or a miss without the animation knowing which, and it is why nothing here names a function.

Used by both halves of the engine. What at counts differs, because the two kinds of movement are measured differently, and each says so where it is taken.

Properties

atnumber

Where in the movement it happens. Its unit is the one the movement is measured in.

namestring

What it is called, which is what the listener is handed.

View source · animation/types/t_clip_event.ts:16

TJointPosetypenacatamalon

Where one bone sits relative to the bone above it: a move, a turn and a size.

Kept as three separate things rather than one matrix because that is how animation writes it: a clip turns a shoulder without touching where it is, and two poses can only be mixed sensibly one part at a time.

Properties

t[number, number, number]
s[number, number, number]
View source · animation/types/t_skeleton.ts:14

TSkeletalAnimationOptionstypenacatamalon

What to ask a model's bones to do.

Properties

playoptionalstring

Which movement to start on. Left out, it takes the first the file has.

loopoptionalboolean

Whether it goes round for ever. Default true.

speedoptionalnumber

How fast, as a multiple. Default 1.

fadeoptionalnumber

How long to take easing into a new movement, in seconds. Default 0, which is a cut.

eventsoptionalReadonly<Record<string, typeOperator>>

Moments inside a movement worth being told about, by the name of the movement they are in: the instant a blow lands, a foot meets the ground, a spell leaves the hand.

at is in seconds from the start of that movement, because a movement off a file is a continuous thing with no pictures in it to count.

They are asked for here rather than written on the movement because a glTF file has nowhere to put them: the format says what moves and when, and nothing at all about what any of it is supposed to mean. Somebody has to decide, and this is where.

View source · hooks/animation/types/t_skeletal_animation.ts:10

TSkeletalCliptypenacatamalon

One named movement of a skeleton: walking, running, being hit.

Named apart from a sprite's TAnimationClip, which is a run of pictures. The two are the same idea in the two halves of the engine and share nothing at all.

It is plain data and holds nothing that is running. Two characters playing the same clip at different moments share this and keep their own time, which is what makes a crowd cheap.

Properties

type'clip'
namestring
durationnumber

How long it lasts, in seconds: the latest key any of its runs reaches.

channelsTSkeletalChannel[]
samplersTSkeletalSampler[]
View source · animation/types/t_animation_clip.ts:63

TSkeletontypenacatamalon

The bones of a model, and how they are standing right now.

It comes out of a file that was rigged: the bones themselves, which bone hangs from which, how they stand at rest, and what it takes to undo that rest position. Animation writes pose and nothing else; the graphics card reads jointMatrices and nothing else. That line down the middle is what keeps playing an animation, posing a skeleton and drawing it three separate problems.

Bones are a flat list, parents before their children, so working out where each one ended up is one pass from the front with nothing to recurse into.

Properties

type'skeleton'
keystring

What it is kept under: the model's name, plus which of its skeletons this is.

parentIndexInt32Array

The bone each one hangs from, -1 for one that hangs from nothing.

bindPoseTJointPose[]

How it stands when nothing is playing.

poseTJointPose[]

How it stands now. The only thing an animation writes.

inverseBindMatricesFloat32Array

What undoes the rest position, 16 numbers a bone.

jointMatricesFloat32Array

What the graphics card reads, 16 numbers a bone. Worked out from pose each frame.

rootMatricesFloat32Array | null

What sits above the bones that hang from nothing, 16 numbers a bone, or null when nothing does.

A rig is usually kept inside something: a file exported from Blender puts everything under one node that turns the whole scene upright. That turn is part of where a bone is, and the numbers undoing the rest position were written knowing it. Left out, the model is drawn a quarter turn away from where it belongs, and only on files that have such a node, which is why it is easy to ship without noticing.

posedOnnumber

Which frame jointMatrices was last worked out on, so several models sharing one skeleton do the work once between them. -1 before it has ever been done.

View source · animation/types/t_skeleton.ts:35

TSpriteAnimationtypenacatamalon

The controls for one animated sprite, returned by useSpriteAnimation.

Properties

play
stop
setSpeed
playingboolean

Whether a run is going right now. False once a clip that does not loop has finished.

clipstring | null

Which run is on, or null if none ever started.

framenumber

Which frame of the sheet is showing.

onEnd
onEvent
View source · hooks/animation/types/t_sprite_animation.ts:68

TSpriteAnimationOptionstypenacatamalon

What useSpriteAnimation is asked for.

Properties

clipsReadonly<Record<string, TAnimationClip>>

The runs this sprite knows, by name: walking, attacking, dying.

playoptionalstring

Which one to start with. Left out, the sprite stays on the frame it was created with.

speedoptionalnumber

A multiplier on every clip's speed: 2 twice as fast, 0.5 half. Default 1.

View source · hooks/animation/types/t_sprite_animation.ts:46

TTweenHandletypenacatamalon

The controls of one tween that is already running, handed back when you start it.

Properties

pause
resume
cancel
finish
doneboolean

Whether it is over, whichever way it ended: by arriving, by finish or by cancel.

View source · hooks/tween/types/t_tween.ts:64

TTweenOptionstypenacatamalon

One trip of a number: where it starts, where it ends, how long it takes and what to do with the value along the way.

A tween animates a number, not an object, and that is on purpose: onUpdate hands you the value and you decide where it goes. Into an x, into a width, into the amount of one colour mixed into another. Anything made of numbers can be animated without the tween knowing it exists.

Properties

fromnumber

The value it starts at.

tonumber

The value it ends at.

durationnumber

How many seconds one trip takes.

easeoptionalTEaseFn

The shape of the movement: a function that takes how far along the trip is, from 0 to 1, and answers how far along the value should be. Default linear, which is the same speed all the way. easeOutBounce lands and bounces, easeOutElastic overshoots and wobbles back.

delayoptionalnumber

Seconds to wait before it starts moving. Default 0.

repeatoptionalnumber

How many extra times it runs after the first trip, so 1 means it goes twice. A negative number means for ever. Default 0.

yoyooptionalboolean

Whether each repeat comes back the other way, there and back. Default false.

onUpdate(value: number) => void

Called every frame with the value right now. This is where you put it to use.

onCompleteoptional() => void

Called once when the last trip ends. Never called if it repeats for ever.

View source · hooks/tween/types/t_tween.ts:15