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
+31
-10
@@ -18,7 +18,8 @@ import {
|
||||
} from '../graph';
|
||||
import { isParticleNodeDef } from '../particles/nodes';
|
||||
import { isShaderNodeDef } from '../shader/nodes';
|
||||
import type { Asset, StateMachineAsset, TextureFrame } from '../model/Asset';
|
||||
import { normalizeKeys } from '../model/animation';
|
||||
import type { AnimationKey, AnimationTrack, Asset, StateMachineAsset, TextureFrame } from '../model/Asset';
|
||||
import { isAssetType } from '../model/AssetTypes';
|
||||
import { isFontFile } from '../model/font';
|
||||
import { isSoundFile } from '../model/sound';
|
||||
@@ -168,15 +169,9 @@ export function deserializeProject(raw: unknown, fallbackId = ''): Project {
|
||||
const file = typeof a.file === 'string' && isSoundFile(a.file) ? a.file : '';
|
||||
assets.push({ id, type, name, props, file });
|
||||
} else if (type === 'animation') {
|
||||
// Kept even when they dangle, like a sprite's `texture`: a frame reads as no picture.
|
||||
const frames = Array.isArray(a.frames) ? a.frames : [];
|
||||
assets.push({
|
||||
id,
|
||||
type,
|
||||
name,
|
||||
props,
|
||||
frames: frames.filter((f): f is string => typeof f === 'string' && f !== ''),
|
||||
});
|
||||
// A root or row aiming at something gone is kept: it reads as missing and keeps its keys.
|
||||
const root = typeof a.root === 'string' ? a.root : '';
|
||||
assets.push({ id, type, name, props, root, tracks: normalizeTracks(a.tracks) });
|
||||
} else {
|
||||
assets.push({ id, type, name, props });
|
||||
}
|
||||
@@ -461,6 +456,32 @@ function normalizeTexture(
|
||||
return { file, width, height, frames };
|
||||
}
|
||||
|
||||
/**
|
||||
* The rows of an animation. Keys hold flat values only – a row animates one property – and are
|
||||
* sorted onto whole frames; a second row on the same target and property, or with an id already
|
||||
* taken, is dropped like a doubled node.
|
||||
*/
|
||||
function normalizeTracks(raw: unknown): AnimationTrack[] {
|
||||
const tracks: AnimationTrack[] = [];
|
||||
for (const item of Array.isArray(raw) ? raw : []) {
|
||||
const t = asRecord(item);
|
||||
if (typeof t.id !== 'string' || !t.id || typeof t.property !== 'string' || !t.property) continue;
|
||||
const target = typeof t.target === 'string' ? t.target : '';
|
||||
if (tracks.some((o) => o.id === t.id || (o.target === target && o.property === t.property))) continue;
|
||||
const keys: AnimationKey[] = [];
|
||||
for (const k of Array.isArray(t.keys) ? t.keys : []) {
|
||||
const key = asRecord(k);
|
||||
const frame = Number(key.frame);
|
||||
if (!Number.isFinite(frame) || !isPrimitive(key.value)) continue;
|
||||
keys.push({ frame, value: key.value });
|
||||
}
|
||||
const track: AnimationTrack = { id: t.id, target, property: t.property, keys: normalizeKeys(keys) };
|
||||
if (t.relative === true) track.relative = true;
|
||||
tracks.push(track);
|
||||
}
|
||||
return tracks;
|
||||
}
|
||||
|
||||
function normalizeBag(raw: unknown, schema: PropertySchema, sparse: boolean): PropertyBag {
|
||||
const r = asRecord(raw);
|
||||
const bag: PropertyBag = {};
|
||||
|
||||
Reference in new issue
Block a user