Files
feuerwerk/tests/viewport/active-camera.test.ts
root c74037b6e1 Show the camera's picture in the game tab without playing
The game tab held a "Press Play" hint where the picture belongs. It now
previews the scene through the active camera, and says "No active camera"
when none is switched on.

Camera choice and its world position moved into activeCamera.ts so both
are testable without Pixi. Entity.x/.y are parent-relative; the viewport
used them as the camera's spot, which framed a parented camera at the
wrong place.
2026-09-09 16:27:04 +02:00

72 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { CreateAssetCommand } from '../../src/core/commands/assetCommands';
import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
import { cameraOfItems, cameraOfWorld } from '../../src/viewport/activeCamera';
import { collectHolderItems } from '../../src/viewport/items';
import { makeSpriteScene, place, startWorld } from '../helpers';
function sceneWithCamera() {
const fixture = makeSpriteScene();
const camera = new CreateAssetCommand('camera', 'Cam');
fixture.stack.execute(camera);
return {
...fixture,
cameraId: camera.asset.id,
switchOff: (objectId: string) =>
fixture.stack.execute(new SetOverrideCommand(fixture.sceneId, objectId, 'enabled', false)),
/** What the game view frames while not playing. */
preview: () => cameraOfItems(collectHolderItems(fixture.project, fixture.sceneOf(), 0).items),
};
}
describe('the camera the game view frames', () => {
it('previews through the very camera that playing would use', () => {
const f = sceneWithCamera();
const first = place(f, f.cameraId, { x: 0, y: 0 });
const second = place(f, f.cameraId, { x: 500, y: 0 });
const { world } = startWorld(f);
const playedThrough = () => world.activeCamera()?.objectId;
expect(f.preview()?.id).toBe(first);
expect(playedThrough()).toBe(first);
f.switchOff(first);
world.syncFromModel();
expect(f.preview()?.id).toBe(second);
expect(playedThrough()).toBe(second);
f.switchOff(second);
world.syncFromModel();
expect(f.preview()).toBeNull();
expect(world.activeCamera()).toBeUndefined();
});
it('shows nothing rather than a switched-off camera, parents counted', () => {
const f = sceneWithCamera();
const holder = place(f, f.spriteId, { x: 0, y: 0 });
const cam = place(f, f.cameraId, { x: 0, y: 0 }, holder);
expect(f.preview()?.id).toBe(cam);
f.switchOff(holder);
expect(f.preview()).toBeNull();
});
it('has no camera to frame while the scene holds none', () => {
const f = sceneWithCamera();
place(f, f.spriteId, { x: 0, y: 0 });
expect(f.preview()).toBeNull();
});
it('places a nested camera at its world position, not at its offset below the parent', () => {
const f = sceneWithCamera();
const holder = place(f, f.spriteId, { x: 300, y: 200 });
const cam = place(f, f.cameraId, { x: 320, y: 210 }, holder);
const { world } = startWorld(f);
// The entity's own coordinates are the offset; framing by them would put the view on the parent.
const entity = world.activeCamera()!;
expect({ x: entity.x, y: entity.y }).toEqual({ x: 20, y: 10 });
expect(cameraOfWorld(world)).toMatchObject({ id: cam, x: 320, y: 210 });
expect(f.preview()).toMatchObject({ id: cam, x: 320, y: 210 });
});
});