Merge Move and slide into Move with a 'Collide with solids' toggle
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ecb20cf02
commit
b0860ca07f
5 files changed
+24
-34
No files matched your search
@@ -103,15 +103,16 @@ and `collider` (the own one). `Is touching(a, b)` answers the same question on d
|
||||
object stands for its collider children, a collider for itself. The geometry lives in
|
||||
`src/core/properties/collider.ts` so editor and runtime agree on it.
|
||||
|
||||
**Blocking** is opt-in per movement: the `Move and slide` node (same inputs as `Move`) moves
|
||||
an object one axis at a time and, after each axis, pushes its *solid* colliders back to just
|
||||
touch any solid collider they ran into – so the other axis still moves and the object slides
|
||||
along walls. Its `blocked x` / `blocked y` outputs tell which axes hit. A collider with
|
||||
**Blocking** is opt-in per movement: the `Move` node has a **Collide with solids** setting
|
||||
(default off). When enabled it moves an object one axis at a time and, after each axis, pushes
|
||||
its *solid* colliders back to just touch any solid collider they ran into – so the other axis
|
||||
still moves and the object slides along walls. Its `blocked x` / `blocked y` outputs tell which
|
||||
axes hit (always `false` with the setting off). A collider with
|
||||
**Trigger (no blocking)** enabled (`isTrigger`, drawn amber instead of green) still produces
|
||||
collision events but never blocks. Pairs that already overlapped before the step are left
|
||||
alone, so an object that starts inside a wall can leave it and stacked objects never jolt apart.
|
||||
Colliders of the moving object and of anything parented under it travel with it and cannot
|
||||
block it. Plain `Move` / `Set position` ignore colliders entirely.
|
||||
block it. `Move` without the setting and `Set position` ignore colliders entirely.
|
||||
|
||||
## Stack
|
||||
|
||||
|
||||
+11
-22
@@ -293,35 +293,24 @@ registerNode({
|
||||
type: 'Move',
|
||||
label: 'Move',
|
||||
category: 'action',
|
||||
description: 'Moves an object by (dx, dy) × scale × dt. Connect dt to an event\'s dt for per-second movement; leave it at 1 for per-frame.',
|
||||
inputs: [exec(), obj('object'), num('dx'), num('dy'), { ...num('scale'), default: 1 }, { ...num('dt'), default: 1 }],
|
||||
outputs: [exec()],
|
||||
params: [],
|
||||
run: (_ctx, i) => {
|
||||
const target = asEntity(i.object);
|
||||
if (target) {
|
||||
const factor = asNumber(i.scale) * asNumber(i.dt);
|
||||
target.x += asNumber(i.dx) * factor;
|
||||
target.y += asNumber(i.dy) * factor;
|
||||
}
|
||||
return { next: 'exec' };
|
||||
},
|
||||
});
|
||||
registerNode({
|
||||
type: 'MoveAndSlide',
|
||||
label: 'Move and slide',
|
||||
category: 'action',
|
||||
description: 'Like Move, but solid colliders of the object stop at other solid colliders and slide along them. Outputs which axes were blocked.',
|
||||
description: 'Moves an object by (dx, dy) × scale × dt. Connect dt to an event\'s dt for per-second movement; leave it at 1 for per-frame. With "Collide with solids" on, the object\'s solid colliders stop at other solid colliders and slide along them; blocked x/y tell which axes hit.',
|
||||
inputs: [exec(), obj('object'), num('dx'), num('dy'), { ...num('scale'), default: 1 }, { ...num('dt'), default: 1 }],
|
||||
outputs: [exec(), bool('blockedX', 'blocked x'), bool('blockedY', 'blocked y')],
|
||||
params: [],
|
||||
run: (ctx, i) => {
|
||||
params: [{ key: 'collide', label: 'Collide with solids', kind: 'boolean', default: false }],
|
||||
run: (ctx, i, p) => {
|
||||
const target = asEntity(i.object);
|
||||
let blockedX = false;
|
||||
let blockedY = false;
|
||||
if (target) {
|
||||
const factor = asNumber(i.scale) * asNumber(i.dt);
|
||||
({ blockedX, blockedY } = ctx.moveAndSlide(target, asNumber(i.dx) * factor, asNumber(i.dy) * factor));
|
||||
const dx = asNumber(i.dx) * factor;
|
||||
const dy = asNumber(i.dy) * factor;
|
||||
if (asBoolean(p.collide)) {
|
||||
({ blockedX, blockedY } = ctx.moveAndSlide(target, dx, dy));
|
||||
} else {
|
||||
target.x += dx;
|
||||
target.y += dy;
|
||||
}
|
||||
}
|
||||
return { next: 'exec', outputs: { blockedX, blockedY } };
|
||||
},
|
||||
|
||||
@@ -206,12 +206,12 @@ describe('move and slide', () => {
|
||||
expect(crateA.x).toBe(-95);
|
||||
});
|
||||
|
||||
it('the Move and slide node moves the owner and outputs the blocked axes', () => {
|
||||
it('the Move node with "Collide with solids" moves the owner and outputs the blocked axes', () => {
|
||||
const { project, stack, sceneId, behavior, a } = setup();
|
||||
// On update → Move and slide(Owner, dx = 1000, dt) → Log(blocked x)
|
||||
// On update → Move(Owner, dx = 1000, dt, collide) → Log(blocked x)
|
||||
const update = new AddNodeCommand(behavior.id, 'OnUpdate', 0, 0);
|
||||
const owner = new AddNodeCommand(behavior.id, 'Owner', 0, 0);
|
||||
const move = new AddNodeCommand(behavior.id, 'MoveAndSlide', 0, 0, { dx: 1000 });
|
||||
const move = new AddNodeCommand(behavior.id, 'Move', 0, 0, { dx: 1000, collide: true });
|
||||
const log = new AddNodeCommand(behavior.id, 'Log', 0, 0);
|
||||
for (const c of [update, owner, move, log]) stack.execute(c);
|
||||
const connect = (from: AddNodeCommand, fp: string, to: AddNodeCommand, tp: string) =>
|
||||
|
||||
+2
-2
@@ -582,7 +582,7 @@ try {
|
||||
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 and slide" parks the red
|
||||
// 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).
|
||||
const holdRight = async (ms) => {
|
||||
@@ -607,7 +607,7 @@ try {
|
||||
return w.entities.find((e) => e.objectId === 'o1');
|
||||
};
|
||||
const blocked = await playAgainstBlue(false);
|
||||
check('Move and slide 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 - -100) < 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}`);
|
||||
await evaluate(`__gm.state.select({ kind: 'object', objectId: 'oc2' })`);
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ if (!projectId) {
|
||||
* "Mover" behavior in five nodes: every frame, move the owner along the normalized arrow-key
|
||||
* axis, scaled by the `speed` variable per second (On update's dt feeds the dt input).
|
||||
* Diagonals are not faster than straight moves because Axis input normalizes the vector, and
|
||||
* "Move and slide" stops the owner's solid colliders at other solid colliders.
|
||||
* Move's "Collide with solids" stops the owner's solid colliders at other solid colliders.
|
||||
*/
|
||||
const moverGraph = {
|
||||
nodes: [
|
||||
@@ -22,7 +22,7 @@ const moverGraph = {
|
||||
{ id: 'n-owner', type: 'Owner', x: 0, y: 120, params: {} },
|
||||
{ id: 'n-axis', type: 'AxisInput', x: 0, y: 200, params: {} },
|
||||
{ id: 'n-speed', type: 'GetVariable', x: 0, y: 400, params: { variable: 'speed' } },
|
||||
{ id: 'n-move', type: 'MoveAndSlide', x: 340, y: 0, params: {} },
|
||||
{ id: 'n-move', type: 'Move', x: 340, y: 0, params: { collide: true } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'e1', from: { node: 'n-update', port: 'exec' }, to: { node: 'n-move', port: 'exec' } },
|
||||
|
||||
Reference in new issue
Block a user