Show a spawned object's first animation frame right away
An object that appears during a frame and plays an animation in its On start was drawn once in its original state. A composition item showed frame 0 one frame late. An object from the Spawn node even ran its On start only on the next frame, after it had been drawn. - BehaviorSystem: at the end of the behavior pass, objects that came to be in this frame hear On start and On enable. This covers spawned objects and behaviors switched on for the first time. Their On update starts with the next frame. - AnimationSystem.showStarted: right after that, every animation started in this frame shows its frame 0. The clock runs from the next frame, and an end is told no earlier than then. A paused frame skips this, its animations stand still anyway. - What such a late On start spawns in turn starts in the next frame. Otherwise a spawn chain could stretch one frame without end. - Scene start is unchanged. There On start already ran before the first frame. - Wiki Behaviors, Lifecycle: the new timing. Tests: gate green on stein2 (melektaus-atx went offline mid-run), 305 files, 4716 tests (2 new). e2e 68 on melektaus-atx green, 10 checks. Without the late pass the Spawn test reads 7 instead of 0, without showStarted both new tests are red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
33891acfb3
commit
64d0d472e5
5 files changed
+95
-3
No files matched your search
@@ -1515,6 +1515,46 @@ describe('the animation nodes', () => {
|
||||
expect(startersOf(holder.animations![1]!)).toEqual([]);
|
||||
});
|
||||
|
||||
it('shows frame 0 in the frame a spawned behavior plays it from On start, before it is drawn', () => {
|
||||
const f = makeSpriteScene();
|
||||
const box = place(f, f.spriteId, { x: 7, y: 0 });
|
||||
const walk = addAnimation(f, f.spriteId, { fps: 10 });
|
||||
addRow(f, walk, 'x', [
|
||||
[0, 0],
|
||||
[9, 90],
|
||||
]);
|
||||
const logic = makeBehavior(f, 'Logic', []);
|
||||
const edit = graphEditor(f, logic);
|
||||
const start = edit.addNode('OnStart');
|
||||
const owner = edit.addNode('Owner');
|
||||
const play = edit.addNode('PlayAnimation', { animation: walk.id });
|
||||
edit.wire([
|
||||
[start, 'exec', play, 'exec'],
|
||||
[owner, 'object', play, 'object'],
|
||||
]);
|
||||
// A spawner below the box puts Logic below the box too on a key.
|
||||
const spawner = makeBehavior(f, 'Spawner', []);
|
||||
place(f, spawner.id, { x: 0, y: 0 }, box);
|
||||
const spawning = graphEditor(f, spawner);
|
||||
const key = spawning.addNode('OnKeyDown', { key: 'x' });
|
||||
const here = spawning.addNode('Owner');
|
||||
const spawn = spawning.addNode('Spawn', { asset: logic.id });
|
||||
spawning.wire([
|
||||
[key, 'exec', spawn, 'exec'],
|
||||
[here, 'object', spawn, 'parent'],
|
||||
]);
|
||||
const { world, input, frame } = startWorld(f, { animations: true, behaviors: true });
|
||||
const sprite = world.findByObjectId(box)!;
|
||||
frame(0.1);
|
||||
expect(sprite.x).toBe(7);
|
||||
input.press('x');
|
||||
frame(0.1);
|
||||
expect(sprite.animations).toHaveLength(1);
|
||||
expect(sprite.x).toBe(0);
|
||||
frame(0.1);
|
||||
expect(sprite.x).toBeCloseTo(10);
|
||||
});
|
||||
|
||||
it('passes Blend on to the player it starts (#106)', () => {
|
||||
const f = makeSpriteScene();
|
||||
const box = place(f, f.spriteId, { x: 0, y: 0 });
|
||||
|
||||
@@ -205,6 +205,40 @@ describe('a composition played in a scene', () => {
|
||||
expect(armOf?.props.rotation).toBe(50);
|
||||
});
|
||||
|
||||
it('shows frame 0 of what an item’s own On start plays in the frame the item appears', () => {
|
||||
const stage = makeStage();
|
||||
director(stage);
|
||||
const rig = new CreateAssetCommand('container', 'Rig');
|
||||
const arm = new CreateAssetCommand('sprite', 'Arm');
|
||||
stage.stack.execute(rig);
|
||||
stage.stack.execute(arm);
|
||||
const wave = stage.move(arm.asset.id);
|
||||
const logic = makeBehavior(stage, 'Wave');
|
||||
const edit = graphEditor(stage, logic);
|
||||
const start = edit.addNode('OnStart');
|
||||
const owner = edit.addNode('Owner');
|
||||
const playIt = edit.addNode('PlayAnimation', { animation: wave });
|
||||
edit.connect(start, 'exec', playIt, 'exec');
|
||||
edit.connect(owner, 'object', playIt, 'object');
|
||||
(findAssetOf(stage.project, rig.asset.id, 'container') as ContainerAsset).objects.push(
|
||||
createSceneObject('arm', arm.asset.id, { x: 5, y: 0 }),
|
||||
createSceneObject('wave', logic.id, { x: 0, y: 0 }, 'arm'),
|
||||
);
|
||||
const item = stage.item(first(stage), rig.asset.id, 2, 30);
|
||||
const run = play(stage);
|
||||
const armOf = () => run.world.findByObjectId(contentId(contentId(stage.placement, item), 'arm'));
|
||||
let ticks = 0;
|
||||
while (!armOf() && ticks < 10) {
|
||||
run.frame();
|
||||
ticks++;
|
||||
}
|
||||
// Begun mid-run, on the third tick: drawn with frame 0 of its animation, not where it stood.
|
||||
expect(ticks).toBe(3);
|
||||
expect(armOf()!.x).toBe(0);
|
||||
run.frame();
|
||||
expect(armOf()!.x).toBeCloseTo(10);
|
||||
});
|
||||
|
||||
it('drives the video an item wears at its video time', () => {
|
||||
const stage = makeStage();
|
||||
director(stage);
|
||||
|
||||
Reference in new issue
Block a user