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:
1 parent
b0860ca07f
commit
cb8885359f
3 files changed
+49
-17
No files matched your search
+23
-15
@@ -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');
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in new issue
Block a user