Pick the frame's outline when adding a collider to a sprite (#218)
game-maker / gate (push) Successful in 2m49s
game-maker / suite (push) Successful in 13m30s

"Add collider" on a sprite always made a box of the sprite's size,
even when the sprite's picture is a frame that has a collision outline.
The outline then had to be picked by hand as "Frame collision".

- newColliderProps in colliderCommands.ts is the rule as a pure
  function. A sprite whose picture is a frame with an outline gets a
  collider with shape "frame" and that frame. Anything else keeps the
  box of the sprite's size.
- The frame is the one the sprite shows when the collider is added,
  also when only this placement overrides it.
- Decided here, the issue says nothing about size: a frame collider is
  as big as its frame, so a sprite drawn bigger or smaller with the
  same proportions gets the collider's scale set to match.
- Decided here as well: a sprite stretched out of the frame's
  proportions keeps the box, because an outline cannot be stretched
  in one direction.

Tests:
- Gate: 198 files, 2914 tests (2911 + 3), exit 0
- e2e 8,42: 46 checks passed (42: +1 for 42e-ii)
- Three new unit tests in tests/core/collider-parts.test.ts: the
  frame with undo, the scale with an overridden frame, and the cases
  that keep the box
- 42e-ii goes through the context menu: "Frame collision" with scale
  2 on the sprite wearing the wedge frame, a box on a sprite without
  a picture
- Five unit counter-probes fail, 1 to 2 tests each. They ran on
  stein2, not in the container.
- The browser counter-probe fails 42e-ii with shape "box"
- The e2e sum of 1617 is calculated (1616 + 1). Neither the full
  suite nor the smokes ran for this state.
- Vector art has no test of its own, it takes the same branch as a
  whole texture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-01 15:03:05 +02:00
1 parent defc34bd09
commit d39bf83ea9
4 files changed
+150 -7

No files matched your search

+73
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import { CreateAssetCommand, SetAssetPropertyCommand } from '../../src/core/commands/assetCommands';
import { AddFrameCommand, SetFrameCollisionCommand } from '../../src/core/commands/frameCommands';
import {
AddColliderCommand,
AddRigidbodyCommand,
@@ -7,6 +8,9 @@ import {
FitColliderCommand,
} from '../../src/core/commands/colliderCommands';
import { MoveSceneObjectCommand, SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
import type { TextureAsset } from '../../src/core/model/Asset';
import { findAsset } from '../../src/core/model/Project';
import { colliderFrame } from '../../src/core/properties/collider';
import { makeBehavior, makeSpriteScene, place } from '../helpers';
/** A 100×60 crate placed twice, the first with a collider of its own. */
@@ -108,3 +112,72 @@ describe('fitting a collider to its sprite (#222)', () => {
);
});
});
describe('a new collider on a sprite whose frame has an outline (#218)', () => {
const WEDGE = [
{ x: 0, y: 20 },
{ x: 40, y: 0 },
{ x: 40, y: 20 },
];
/** A sprite wearing a 40 × 20 frame at that size, and `added`: the props of a collider added to it. */
function wearing() {
const f = makeSpriteScene('Wedge');
const texture = new CreateAssetCommand('texture', 'Sheet');
f.stack.execute(texture);
Object.assign(findAsset(f.project, texture.asset.id) as TextureAsset, {
file: `${'a'.repeat(64)}.png`,
width: 64,
height: 64,
});
const frame = new AddFrameCommand(texture.asset.id, { x: 0, y: 0, w: 40, h: 20 });
f.stack.execute(frame);
const set = (key: string, value: number | string) =>
f.stack.execute(new SetAssetPropertyCommand(f.spriteId, key, value));
set('texture', frame.frameId);
set('width', 40);
set('height', 20);
const id = place(f, f.spriteId, { x: 0, y: 0 });
const added = () => {
const command = new AddColliderCommand(f.sceneId, id);
f.stack.execute(command);
return findAsset(f.project, command.assetId)!.props;
};
const outline = () =>
f.stack.execute(new SetFrameCollisionCommand(texture.asset.id, frame.frameId, [WEDGE]));
return { ...f, id, textureId: texture.asset.id, frameId: frame.frameId, set, added, outline };
}
it('wears that frame, and one undo takes collider and asset away again', () => {
const f = wearing();
f.outline();
const count = f.project.assets.length;
const props = f.added();
expect(props).toMatchObject({ shape: 'frame', frame: f.frameId, width: 40, height: 20, scale: 1 });
expect(colliderFrame(f.project, props)?.pieces).toHaveLength(1);
f.stack.undo();
expect(f.project.assets).toHaveLength(count);
});
it('is scaled to a sprite drawn bigger, and reads the frame this placement overrides', () => {
const f = wearing();
f.outline();
f.set('texture', '');
f.stack.execute(new SetOverrideCommand(f.sceneId, f.id, 'texture', f.frameId));
f.stack.execute(new SetOverrideCommand(f.sceneId, f.id, 'width', 100));
f.stack.execute(new SetOverrideCommand(f.sceneId, f.id, 'height', 50));
expect(f.added()).toMatchObject({ shape: 'frame', frame: f.frameId, width: 40, height: 20, scale: 2.5 });
});
it('stays the box of the sprite without an outline, with the whole texture or stretched', () => {
const f = wearing();
const box = { shape: 'box', width: 40, height: 20, scale: 1 };
expect(f.added()).toMatchObject(box);
f.outline();
f.set('texture', f.textureId);
expect(f.added()).toMatchObject(box);
f.set('texture', f.frameId);
f.set('height', 60);
expect(f.added()).toMatchObject({ ...box, height: 60 });
});
});