Part (a) of the animation rework: model, commands, saving, runtime and nodes. - AnimationAsset holds a root (sprite, text, container or scene) and rows; a row keys one property of the root or of an object inside it, keys sit on whole frames. - The last key decides the length (its frame counts as a frame of its own). The playhead runs between frames, so numbers and colors blend every tick even at 10 fps; everything else holds. - Playback gains `once-reset`, which puts back what was there before the start. A row can be relative, adding to where its target stood. - Play / Pause / Stop animation act on an object; each instance plays on its own, the later start wins on a shared property, and a behavior writing the same property still writes last. - A sprite no longer wears an animation through `texture`. "Create animation from texture" builds a sprite plus a texture row keyed frame by frame instead. - The Animation tab shows a placeholder until the timeline editor (part b) lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
43 lines
1.6 KiB
JavaScript
43 lines
1.6 KiB
JavaScript
import { openTab } from './lib/scaffold.mjs';
|
||
|
||
/**
|
||
* Animations (#97): the timeline replaced the picture strip. Until the timeline editor lands this
|
||
* step only holds the tab to the new model – an animation over a sprite opens, and the readout
|
||
* counts its rows and frames. It removes everything it made.
|
||
*/
|
||
export async function animation(t) {
|
||
const { check, json, mutate, shot } = t;
|
||
|
||
await mutate(`
|
||
__gm.state.project.assets.push(
|
||
{ id: 'an-sprite', type: 'sprite', name: 'Dancer', props: { width: 40, height: 40 } },
|
||
{
|
||
id: 'an-walk', type: 'animation', name: 'Walk', props: { fps: 10, playback: 'loop' }, root: 'an-sprite',
|
||
tracks: [{ id: 'an-row', target: '', property: 'x', keys: [{ frame: 0, value: 0 }, { frame: 9, value: 90 }] }],
|
||
},
|
||
);
|
||
__gm.state.select({ kind: 'asset', assetId: 'an-walk' });
|
||
__gm.state.preview();
|
||
`);
|
||
await openTab(t, 'animation');
|
||
await shot('01-tab');
|
||
check('the animation tab shows the animation', (await json('__gm.animation.current()')) === 'an-walk');
|
||
const info = await json('__gm.animation.info()');
|
||
check(
|
||
'its readout counts one row over ten frames, one second at 10 fps',
|
||
info === '1 row · 10 frames · 10 fps · 1.0 s',
|
||
info,
|
||
);
|
||
|
||
await mutate(`
|
||
const p = __gm.state.project;
|
||
p.assets = p.assets.filter((a) => a.id !== 'an-walk' && a.id !== 'an-sprite');
|
||
__gm.state.select(null);
|
||
__gm.state.preview();
|
||
`);
|
||
const left = await json(
|
||
"__gm.state.project.assets.filter((a) => a.id === 'an-walk' || a.id === 'an-sprite').length",
|
||
);
|
||
check('the step leaves the fixture as it found it', left === 0, String(left));
|
||
}
|