Seed the saved demo scene (crate colliders) and make collider symbols grabbable only at their outline

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 16:14:01 +02:00
1 parent b0860ca07f
commit cb8885359f
3 files changed
+49 -17

No files matched your search

+23 -15
View File
@@ -245,7 +245,7 @@ try {
await evaluate(`__gm.state.select({ kind: 'object', sceneId: 'scene1', objectId: 'o3' })`);
await keyPress('Delete', 'Delete', { windowsVirtualKeyCode: 46 });
await sleep(200);
check('camera object deleted', (await evaluate('__gm.state.activeScene.objects.length')) === 3);
check('camera object deleted', (await evaluate('__gm.state.activeScene.objects.length')) === 5);
await clickText('▶ Play');
await sleep(300);
info = await evaluate('JSON.stringify(__gm.viewportInfo())');
@@ -257,7 +257,7 @@ try {
await sleep(300);
await keyPress('z', 'KeyZ', { modifiers: 2, windowsVirtualKeyCode: 90 });
await sleep(200);
check('undo restored the camera object', (await evaluate('__gm.state.activeScene.objects.length')) === 4);
check('undo restored the camera object', (await evaluate('__gm.state.activeScene.objects.length')) === 6);
// 8. Context menu on the Crate asset → menu shows "Place in scene".
await clickText('Crate', 'right');
@@ -268,7 +268,7 @@ try {
await clickText('Place in scene');
await sleep(200);
const count = await evaluate('__gm.state.activeScene.objects.length');
check('placing from the menu adds an object', count === 5, `objects=${count}`);
check('placing from the menu adds an object', count === 7, `objects=${count}`);
// 9. Create a new asset through the + menu.
await clickText('+');
@@ -493,7 +493,9 @@ try {
let o2 = JSON.parse(await objectJson('o2'));
check('dropping a row onto another parents it, keeping the world position', o2.parentId === 'o1' && o2.position.x === 270 && o2.position.y === -80, JSON.stringify(o2));
const order = JSON.parse(await evaluate(`JSON.stringify(__gm.hierarchyOrder())`));
check('children are listed directly below their parent', order.indexOf('o2') === order.indexOf('o1') + 1 && order.indexOf('o4') === order.indexOf('o1') + 2, JSON.stringify(order));
// o1's subtree: o2 (list order before o4) with its collider child, then o4, then o1's collider.
const o1At = order.indexOf('o1');
check('children are listed directly below their parent', JSON.stringify(order.slice(o1At + 1, o1At + 5)) === JSON.stringify(['o2', 'oc2', 'o4', 'oc1']), JSON.stringify(order));
await shot('20-parented');
// 14a. Moving the parent (X field of the red crate) drags the child along in world space.
@@ -543,6 +545,9 @@ try {
// 15. Colliders. Right-click the red crate → "Add collider": a collider asset sized like the
// crate (100×60) is placed as its child at the centre, selected and drawn in the overlay.
// The seed already gives both crates a "Crate collider" child (oc1/oc2), so the added one
// is undone again afterwards to keep exactly one collider per body for 15a/15b.
const objectsBeforeCollider = await evaluate('__gm.state.activeScene.objects.length');
const redRow3 = await locate('Red crate');
await click(redRow3.x, redRow3.y, 'right');
await sleep(150);
@@ -555,13 +560,16 @@ try {
check('collider is drawn in the overlay layer', colObj && (await evaluate(`__gm.layerOf('${colObj.id}')`)) === 'overlay');
check('collider inspector shows the shape menu', Boolean(await evaluate(`__gm.locate('Box ▾')`)));
await shot('21-collider');
await keyPress('z', 'KeyZ', { modifiers: 2, windowsVirtualKeyCode: 90 });
await sleep(200);
const colliderAssetGone = colAsset ? !(await evaluate(`__gm.state.project.assets.some(a => a.id === '${colAsset.id}')`)) : false;
check('undo removes the added collider object and its asset', (await evaluate('__gm.state.activeScene.objects.length')) === objectsBeforeCollider && colliderAssetGone);
// 15a. Give the blue crate the same collider, wire "On collision enter → Log(other)" into the
// Mover (owner = red crate) and play with the crates overlapping: the log names the blue
// crate exactly once (enter, not stay), and colliders are not rendered in play mode.
// 15a. Wire "On collision enter → Log(other)" into the Mover (owner = red crate) and play with
// the crates overlapping (each carries its seeded collider): the log names the blue crate
// exactly once (enter, not stay), and colliders are not rendered in play mode.
await evaluate(`(() => {
const scene = __gm.state.activeScene;
scene.objects.push({ id: 'oc2', assetId: '${colAsset.id}', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: 'o2' });
const mover = __gm.state.project.assets.find(a => a.id === 'mover');
mover.graph.nodes.push({ id: 'n-hit', type: 'OnCollisionEnter', x: 0, y: 600, params: {} }, { id: 'n-hitlog', type: 'Log', x: 340, y: 600, params: {} });
mover.graph.edges.push(
@@ -581,10 +589,10 @@ try {
await keyPress('Escape', 'Escape', { windowsVirtualKeyCode: 27 });
await sleep(200);
// 15b. Solid vs trigger. Put the blue crate 150 to the right of the red one (both 100 wide →
// 50 of free travel) and hold ArrowRight: the Mover's Move (Collide with solids) parks the red
// crate flush against the blue one at world x = −100. Making the blue collider a
// trigger lets it pass through instead (the collider is then drawn amber).
// 15b. Solid vs trigger. Put the blue crate 150 to the right of the red one (red 100 wide, the
// blue collider 140 wide → 30 of free travel) and hold ArrowRight: the Mover's Move
// (Collide with solids) parks the red crate flush against the blue one at world x = −120.
// Making the blue collider a trigger lets it pass through instead (drawn amber).
const holdRight = async (ms) => {
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'ArrowRight', code: 'ArrowRight', windowsVirtualKeyCode: 39 });
await sleep(ms);
@@ -594,7 +602,7 @@ try {
await evaluate(`(() => {
const scene = __gm.state.activeScene;
scene.objects.find(o => o.id === 'o2').position = { x: 0, y: 0, z: 0 };
scene.objects.find(o => o.id === 'oc2').overrides = ${trigger ? '{ isTrigger: true }' : '{}'};
scene.objects.find(o => o.id === 'oc2').overrides = ${trigger ? '{ width: 140, isTrigger: true }' : '{ width: 140 }'};
__gm.state.select(null);
__gm.state.preview();
})()`);
@@ -607,9 +615,9 @@ try {
return w.entities.find((e) => e.objectId === 'o1');
};
const blocked = await playAgainstBlue(false);
check('Move with "Collide with solids" stops the red crate flush against the blue crate\'s solid collider', blocked && Math.abs(blocked.x - -100) < 0.01, `world x=${blocked?.x}`);
check('Move with "Collide with solids" stops the red crate flush against the blue crate\'s solid collider', blocked && Math.abs(blocked.x - -120) < 0.01, `world x=${blocked?.x}`);
const passed = await playAgainstBlue(true);
check('a trigger collider does not block (the crate passes through)', passed && passed.x > -100 + 20, `world x=${passed?.x}`);
check('a trigger collider does not block (the crate passes through)', passed && passed.x > -120 + 20, `world x=${passed?.x}`);
await evaluate(`__gm.state.select({ kind: 'object', objectId: 'oc2' })`);
await sleep(200);
await shot('22-trigger-collider');
+5
View File
@@ -51,10 +51,15 @@ const project = {
{ id: 'o3', assetId: 'cam', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: '' },
// The mover is parented to the red crate (its owner); its position is local to it.
{ id: 'o4', assetId: 'mover', name: '', position: { x: 0, y: 110, z: 0 }, overrides: {}, parentId: 'o1' },
// Both crates carry the shared "Crate collider" as a centred child; the blue crate's
// instance is widened to match its 140-wide sprite override.
{ id: 'oc1', assetId: 'collider', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {}, parentId: 'o1' },
{ id: 'oc2', assetId: 'collider', name: '', position: { x: 0, y: 0, z: 0 }, overrides: { width: 140 }, parentId: 'o2' },
],
},
{ id: 'crate', type: 'sprite', name: 'Crate', props: { width: 100, height: 60, color: 0xe0553f } },
{ id: 'cam', type: 'camera', name: 'Main camera', props: { width: 640, height: 360, zoom: 1 } },
{ id: 'collider', type: 'collider', name: 'Crate collider', props: { shape: 'box', width: 100, height: 60, radius: 50, isTrigger: false } },
{
id: 'mover',
type: 'behavior',