An animation reaches the scene's own background and objects inside placed containers (#109)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
353b348c3c
commit
f68941fa3a
7 files changed
+334
-128
No files matched your search
@@ -21,12 +21,14 @@ export interface AnimationFinish {
|
||||
animationId: string;
|
||||
}
|
||||
|
||||
/** A row that reaches something this tick: the entity it writes and the kind it blends as. */
|
||||
/** A row that reaches something this tick: how to read and write it, and the kind it blends as. */
|
||||
interface Binding {
|
||||
track: AnimationTrack;
|
||||
entity: Entity;
|
||||
kind: string;
|
||||
/** Where the player keeps the starting value: `<entity id> <property>`, or `scene <property>`. */
|
||||
base: string;
|
||||
read(): PropertyValue;
|
||||
write(value: PropertyValue): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,18 +55,18 @@ export class AnimationSystem implements System {
|
||||
if (!player.bases) {
|
||||
// The first tick only takes the starting values and shows frame 0; the clock runs from the next.
|
||||
player.bases = {};
|
||||
for (const b of bindings) player.bases[b.base] = read(b.entity, b.track.property);
|
||||
for (const b of bindings) player.bases[b.base] = b.read();
|
||||
} else if (player.playing && isHandleEnabled(entity)) player.time += dt;
|
||||
const head = animationPlayheadAt(asset, player.time);
|
||||
for (const b of bindings) {
|
||||
const value = trackWriteAt(b.track, b.kind, head.frame, player.bases[b.base]);
|
||||
if (value !== undefined) write(b.entity, b.track.property, value);
|
||||
if (value !== undefined) b.write(value);
|
||||
}
|
||||
if (!head.finished) continue;
|
||||
if (playbackOf(asset) === 'once-reset')
|
||||
for (const b of bindings) {
|
||||
const base = player.bases[b.base];
|
||||
if (base !== undefined) write(b.entity, b.track.property, base);
|
||||
if (base !== undefined) b.write(base);
|
||||
}
|
||||
drop(entity, player);
|
||||
this.finished.push({ entity, animationId: asset.id });
|
||||
@@ -81,32 +83,58 @@ function drop(entity: Entity, player: AnimationPlayer): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* The rows that reach something on this object. A scene's rows aim at its objects by id, and only
|
||||
* while that scene runs; everything else aims at the object itself or at what its template placed
|
||||
* inside it. A row whose target is gone, or lacks the property, is skipped.
|
||||
* The rows that reach something on this object. A scene's rows aim at the scene itself or at its
|
||||
* objects by id, and only while that scene runs; everything else aims at the object itself or at
|
||||
* what its template placed inside it, however deep. A row whose target is gone, or lacks the
|
||||
* property, is skipped.
|
||||
*/
|
||||
function bind(world: World, asset: AnimationAsset, owner: Entity): Binding[] {
|
||||
const root = world.project.assets.find((a) => a.id === asset.root);
|
||||
if (!root) return [];
|
||||
const bindings: Binding[] = [];
|
||||
for (const track of asset.tracks) {
|
||||
const key = track.property;
|
||||
if (root.type === 'scene' && track.target === '') {
|
||||
const kind = world.sceneId === root.id ? animatedKind('scene', key) : undefined;
|
||||
if (kind)
|
||||
bindings.push({
|
||||
track,
|
||||
kind,
|
||||
base: `scene ${key}`,
|
||||
read: () => world.sceneProps[key] ?? 0,
|
||||
write: (value) => {
|
||||
world.sceneProps[key] = value;
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
let entity: Entity | undefined;
|
||||
if (root.type === 'scene')
|
||||
entity = world.sceneId === root.id && track.target ? world.findByObjectId(track.target) : undefined;
|
||||
entity = world.sceneId === root.id ? world.findByObjectId(track.target) : undefined;
|
||||
else if (track.target === '') entity = owner;
|
||||
else entity = world.findByObjectId(childId(owner.objectId, track.target));
|
||||
const kind = entity && animatedKind(entity.type, track.property);
|
||||
if (entity && kind) bindings.push({ track, entity, kind, base: `${entity.id} ${track.property}` });
|
||||
const kind = entity && animatedKind(entity.type, key);
|
||||
if (entity && kind) {
|
||||
const aimed = entity;
|
||||
bindings.push({
|
||||
track,
|
||||
kind,
|
||||
base: `${aimed.id} ${key}`,
|
||||
read: () => read(aimed, key),
|
||||
write: (value) => write(aimed, key, value),
|
||||
});
|
||||
}
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/** The id a template object gets below a placed container – nested one deeper when the container is content itself. */
|
||||
function childId(ownerId: string, templateId: string): string {
|
||||
/**
|
||||
* The id a template object gets below a placed container – nested one deeper when the container is
|
||||
* content itself. `path` may run through containers placed inside the template (`a/b`).
|
||||
*/
|
||||
function childId(ownerId: string, path: string): string {
|
||||
const split = splitInstanceId(ownerId);
|
||||
return split
|
||||
? instanceChildId(split.instanceId, `${split.path}/${templateId}`)
|
||||
: instanceChildId(ownerId, templateId);
|
||||
return split ? instanceChildId(split.instanceId, `${split.path}/${path}`) : instanceChildId(ownerId, path);
|
||||
}
|
||||
|
||||
function read(entity: Entity, key: string): PropertyValue {
|
||||
|
||||
Reference in new issue
Block a user