An animation is a timeline of rows over one asset, played on an object rather than worn as a picture (#97)
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>
This commit is contained in:
1 parent
d3563897d8
commit
193378e6a9
29 files changed
+1194
-1499
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import type { AnimationPlayer } from '../model/animation';
|
||||
import type { Mat } from '../model/matrix';
|
||||
import type { Position, PropertyBag } from '../model/Properties';
|
||||
import { type ChainTransform, composeAlongChain } from '../model/walk';
|
||||
@@ -28,9 +29,11 @@ export interface EntityHandle {
|
||||
parent: EntityHandle | null;
|
||||
/**
|
||||
* Counts the Emit pulses a particle source has been given. Kept off `props` for the reason the
|
||||
* animation clock is: what the world writes back to the model must not grow an override per frame.
|
||||
* animation players are: what the world writes back to the model must not grow an override per frame.
|
||||
*/
|
||||
emits?: number;
|
||||
/** What `Play animation` started on this object (see `model/animation.ts`). */
|
||||
animations?: AnimationPlayer[];
|
||||
}
|
||||
|
||||
/** Position, rotation, scale and mirror of every ancestor applied, nearest last. */
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { pauseAnimation, playAnimation, stopAnimation } from '../../../model/animation';
|
||||
import { textOf } from '../../../model/Properties';
|
||||
import type { PropertyDef } from '../../../properties/PropertySchema';
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { asEntity } from '../../values';
|
||||
import { exec, obj } from '../shared';
|
||||
|
||||
const animationParam = (): PropertyDef => ({
|
||||
key: 'animation',
|
||||
label: 'Animation',
|
||||
kind: 'asset',
|
||||
default: '',
|
||||
assetTypes: ['animation'],
|
||||
});
|
||||
|
||||
export const PlayAnimation: NodeDef = {
|
||||
type: 'PlayAnimation',
|
||||
label: 'Play animation',
|
||||
category: 'action',
|
||||
description:
|
||||
'Plays an animation on an object: its rows reach the object and what is placed inside it, so every ' +
|
||||
'instance plays on its own. A paused one runs on from where it stands; one already running is left alone.',
|
||||
inputs: [exec(), obj('object')],
|
||||
outputs: [exec()],
|
||||
params: [animationParam()],
|
||||
run: (_ctx, inputs, params) => {
|
||||
const target = asEntity(inputs.object);
|
||||
const animation = textOf(params.animation ?? '');
|
||||
if (target && animation) playAnimation(target, animation);
|
||||
return { next: 'exec' };
|
||||
},
|
||||
};
|
||||
|
||||
export const StopAnimation: NodeDef = {
|
||||
type: 'StopAnimation',
|
||||
label: 'Stop animation',
|
||||
category: 'action',
|
||||
description:
|
||||
'Ends an animation on an object where it stands – the values it set stay. Leave Animation at None to end every one.',
|
||||
inputs: [exec(), obj('object')],
|
||||
outputs: [exec()],
|
||||
params: [animationParam()],
|
||||
run: (_ctx, inputs, params) => {
|
||||
const target = asEntity(inputs.object);
|
||||
if (target) stopAnimation(target, textOf(params.animation ?? ''));
|
||||
return { next: 'exec' };
|
||||
},
|
||||
};
|
||||
|
||||
export const PauseAnimation: NodeDef = {
|
||||
type: 'PauseAnimation',
|
||||
label: 'Pause animation',
|
||||
category: 'action',
|
||||
description:
|
||||
'Holds an animation on an object at its frame until Play animation lets it run on. Leave Animation at None to hold every one.',
|
||||
inputs: [exec(), obj('object')],
|
||||
outputs: [exec()],
|
||||
params: [animationParam()],
|
||||
run: (_ctx, inputs, params) => {
|
||||
const target = asEntity(inputs.object);
|
||||
if (target) pauseAnimation(target, textOf(params.animation ?? ''));
|
||||
return { next: 'exec' };
|
||||
},
|
||||
};
|
||||
@@ -28,7 +28,7 @@ export const StringLiteral = literal('String', 'text', 'A constant text.');
|
||||
export const TextureLiteral = literal(
|
||||
'Texture',
|
||||
'texture',
|
||||
'A constant picture: a texture, one of its frames or an animation, as a sprite wears it.',
|
||||
'A constant picture: a texture or one of its frames, as a sprite wears it.',
|
||||
);
|
||||
export const StateMachineLiteral = literal(
|
||||
'StateMachine',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type NodeDef, registerNode } from '../NodeRegistry';
|
||||
import { Destroy } from './action/Destroy';
|
||||
import { PauseAnimation, PlayAnimation, StopAnimation } from './action/animationNodes';
|
||||
import { Emit } from './action/Emit';
|
||||
import { LoadScene } from './action/LoadScene';
|
||||
import { Log } from './action/Log';
|
||||
@@ -204,6 +205,9 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
Spawn,
|
||||
Destroy,
|
||||
Emit,
|
||||
PlayAnimation,
|
||||
PauseAnimation,
|
||||
StopAnimation,
|
||||
PlaySound,
|
||||
StopSound,
|
||||
LoadScene,
|
||||
|
||||
Reference in new issue
Block a user