From bc2a138c8c9a5aa293a6aba5d07b1a928c5315e3 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Sun, 20 Sep 2026 18:15:40 +0200 Subject: [PATCH] Raycast filtering by collision layers (#158): Colliders gained the "Layers" setting of the Raycast node with the one-sided rule - a ray is not a collider and never reads the collidesWith of what it hits - and the note that a running behavior instance keeps the graph it started with; Physics lost the line that a layer filter for rays comes later and names #157 as the only open point; Behaviors describes the node; Development carries the check sum 1153 Co-Authored-By: Claude Opus 5 --- Behaviors.md | 2 +- Colliders.md | 30 +++++++++++++++++++----------- Development.md | 2 +- Physics.md | 31 ++++++++++++++++--------------- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Behaviors.md b/Behaviors.md index 9bafd1c..6345e88 100644 --- a/Behaviors.md +++ b/Behaviors.md @@ -116,7 +116,7 @@ shown on the node and editable in the inspector. behavior that started it unless `detached` says otherwise), `Load scene` (swaps the running scene once the frame is over — see [Scenes](Scenes)), `Log`, `Stop game`, `Restart game`, `Apply force`, `Apply impulse`, `Set velocity`, `Set angular velocity`, `Set gravity` (see [Physics](Physics)), - `Raycast` (what a ray meets first: object, collider, point, normal, distance — see [Colliders](Colliders#raycast)), + `Raycast` (what a ray meets first on the layers it looks at: object, collider, point, normal, distance — see [Colliders](Colliders#raycast)), `Ignore one-way` (fall through one-way colliders and tiles for some seconds — see [Colliders](Colliders#one-way)). A node type is one file exporting a `NodeDef` (ports, settings, `run`), listed in `nodes/index.ts`; palette, ports, validation and inspector fields are generated from it. A port may declare a `default` for its inline value diff --git a/Colliders.md b/Colliders.md index d6369a8..0fd11c5 100644 --- a/Colliders.md +++ b/Colliders.md @@ -1,12 +1,12 @@ -A **collider** is an asset (`shape` box, circle, capsule or polygon – see +A **collider** is an asset (`shape` box, circle, capsule or polygon — see [Shapes](#shapes)) placed like any other object — typically as a child of the sprite it belongs to, so its position is the offset from that *body* and it moves with it. Right-click an object in **Scene objects** → *Add collider* to get one with one click: a box collider sized like the sprite, parented at its centre. Colliders can be reused across objects, overridden per instance and stacked (hitbox + hurtbox on one sprite; colliders of the same body never collide with each -other). They are editor symbols: green outlines in edit mode, invisible in play mode. Three more rows – -friction, bounciness, density – are the material a rigidbody feels (see [Physics](Physics)). +other). They are editor symbols: green outlines in edit mode, invisible in play mode. Three more rows — +friction, bounciness, density — are the material a rigidbody feels (see [Physics](Physics)). A collider takes the transform of its body: a box turns and grows with the sprite it sits on, a circle grows (see *Transform* in [Scene editing](Scene-editing)). A turned box is tested as the @@ -15,15 +15,15 @@ its slant. ## Shapes -- **Box** – `width` × `height`. -- **Circle** – `radius`. -- **Capsule** (#151) – fills `width` × `height` and is rounded along the longer side: its radius is +- **Box** — `width` × `height`. +- **Circle** — `radius`. +- **Capsule** (#151) — fills `width` × `height` and is rounded along the longer side: its radius is half the shorter side. As wide as tall it is a circle. The shape for a figure that should slide over edges instead of catching on them. -- **Polygon** (#151) – `points`, a list of `{x, y}` corners in the collider's own units, so they +- **Polygon** (#151) — `points`, a list of `{x, y}` corners in the collider's own units, so they turn, grow and mirror with the object. A new polygon collider is a triangle. With the collider selected in the scene view every corner has a handle: **drag** a corner to move it, **press on an - edge** to put a corner there (and drag it on), **double-click** a corner to take it away – three + edge** to put a corner there (and drag it on), **double-click** a corner to take it away — three corners always stay. Each is one undo step. The list can also be edited in the inspector. A polygon may be concave: it is cut into convex pieces (ear clipping, then the cuts that are not needed are joined again), which is what collision works with. One that crosses itself or has no @@ -33,7 +33,7 @@ Box against box, circle against circle and box against circle keep their own exa pair with a capsule or a polygon goes one common way (`core/properties/convex.ts`): each shape is a set of convex pieces, each piece a point, a segment or a polygon grown by a radius, and two pieces are tested along their separating axes, apart by their real distance. A square polygon meets -everything exactly as the box it stands for – a unit test holds the two against each other. +everything exactly as the box it stands for — a unit test holds the two against each other. Before the behaviors, every frame sorts the colliders that overlap (world transforms applied; touching edges do not count) into *enter*, *stay* and *exit*. While playing that is the physics @@ -59,7 +59,7 @@ colliders, a tile counts 0.5). The contact's normal decides which way "along" is runs into an edge rolls up over it, an object pressed onto a slope slides down it unless the pair is rough enough to hold it (friction ≥ tan of the slope), and one pressed against a wall slides along it the slower the harder it presses. Up to four contacts are followed per move (a corner -between floor and wall is two). There is no bounce: `Move` sets a distance, not a speed – a +between floor and wall is two). There is no bounce: `Move` sets a distance, not a speed — a dynamic body bounces (see [Physics](Physics)). The outputs: `blocked x` / `blocked y` tell which of the object's own axes something stood against (a floor blocks y, a wall x, a slope each axis the move went along); @@ -135,6 +135,11 @@ check, a laser, a hitscan shot. - Met are solid colliders of every shape and the cells of a tilemap, outlined ones too. Triggers only with **Hit triggers** on. A one-way collider or tile only from the side it blocks from. +- **Layers** (#158) is a tick per collision layer of the project, all of them ticked to begin with: + the ray only meets what sits on one of the ticked layers. That is the one place where the layers + are *not* symmetric — a ray is not a collider, so what it may look at is its own business and a + collider's "Collides with" has no say in it. A shot that passes friends is `Layers` without their + layer; a ground check that ignores the water is the same thing one layer over. - With **Ignore owner** on (the default) the behavior's owner and everything parented below it are not met. And a shape the ray *starts inside* is never met — so a ray cast from the middle of a figure finds what is around it; one starting on an outline and running in meets it at 0. @@ -144,4 +149,7 @@ check, a laser, a hitscan shot. everything is right now — not about the last physics step. - In the editor run every ray cast shows in the scene view for a quarter of a second: green to where it met something, grey to its end when it met nothing. -- Only the first hit for now; a list of all hits and a filter by collision layer (#152) come later. +- Only the first hit for now; a list of all hits (#157) comes later. +- A setting reaches a *running* instance only when that instance starts afresh: the interpreter + holds the graph it began with, so a behavior that already runs in the editor keeps casting with + the layers it started on until it is switched off and on (or Play is pressed). diff --git a/Development.md b/Development.md index 603ded7..df1396f 100644 --- a/Development.md +++ b/Development.md @@ -13,7 +13,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs npm run build # writes dist/ and regenerates index.html ./build.sh # the same under `set -o pipefail`, for callers that pipe the output php tools/make-user.php # an account; every tool signs in (see Accounts) -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1148 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1153 editor checks, screenshots in /tmp/gm-e2e-*.png GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result diff --git a/Physics.md b/Physics.md index 5031243..7aac3ef 100644 --- a/Physics.md +++ b/Physics.md @@ -1,6 +1,6 @@ Play simulates **rigidbodies** on the physics engine [Rapier 2D](https://rapier.rs) (#146): they fall, slide, bounce, tip over and come to rest. What has no rigidbody behaves exactly as it did -before the engine – see *Without a rigidbody* below. +before the engine — see *Without a rigidbody* below. ## The rigidbody @@ -13,13 +13,13 @@ edit mode and not at all in the game. Its rows: |---|---|---| | Body type | Dynamic | **Dynamic** falls and is pushed. **Static** never moves on its own. **Kinematic** goes wherever the object is put (a graph, an animation) and pushes dynamic bodies out of its way. | | Mass (0 = auto) | 0 | 0 lets the colliders' density and size decide; anything else is the mass itself, in kg. | -| Gravity scale | 1 | How strongly the scene's gravity pulls; 0 floats, −1 falls upwards. | +| Gravity scale | 1 | How strongly the scene's gravity pulls; 0 floats, -1 falls upwards. | | Linear damping | 0 | Slows the movement down by itself, like air. | | Angular damping | 0 | The same for turning. | | Fixed rotation | off | On: the body never turns (a player). Off: a box that lands on an edge tips over. | | Continuous (CCD) | off | For small, fast bodies that would otherwise pass through thin walls between two steps. | -The body carries every collider below its object – the object's own collider children and those of +The body carries every collider below its object — the object's own collider children and those of objects parented further down, unless one of those has a rigidbody of its own. A rigidbody that is switched off is as if it were not there. A body without any solid collider still falls, with its mass or 1 kg. @@ -45,7 +45,7 @@ A trigger (`isTrigger`) weighs nothing and never pushes; it only reports. A capsule collider is the engine's capsule; a polygon is one convex polygon per piece it is cut into (see [Colliders](Colliders#shapes)), all on the same body, so a concave shape rests and rolls -as the shape it is – a crate dropped into a U lands on its inner floor, not on the lid across the +as the shape it is — a crate dropped into a U lands on its inner floor, not on the lid across the notch. A rigidbody's fixed **Mass** is shared out among the pieces by area, and density weighs each by its own area. @@ -61,7 +61,7 @@ It is read every frame, so an animation over the scene can change it while playi The engine steps at a fixed **60 steps per second**, whatever the frames take; a frame that took longer catches up with at most 8 steps and forgets the rest, so a hitch does not make the game run fast for a while after. What the game view shows of a dynamic body lies between the last two steps, -so it moves evenly on any display – which also means it is drawn up to one step (1/60 s) behind the +so it moves evenly on any display — which also means it is drawn up to one step (1/60 s) behind the engine. ## Collision events @@ -81,17 +81,17 @@ made in the same frame. An object with colliders but no rigidbody is what every project was before: its colliders go wherever the object is put, nothing pulls on them, and dynamic bodies bounce off them as off a wall. -Whether two of them touch is decided by the same overlap test as before – touching edges do not -count, only overlapping does – the engine only proposes which pairs to test. A placed tilemap is +Whether two of them touch is decided by the same overlap test as before — touching edges do not +count, only overlapping does — the engine only proposes which pairs to test. A placed tilemap is static ground made of its merged solid cells (see [Tilemaps](Tilemaps)). `Move` with *Collide with solids* works as described in [Colliders](Colliders): the object stops flush at what -is in its way and goes on along its surface, less what friction takes – the same friction a +is in its way and goes on along its surface, less what friction takes — the same friction a dynamic body feels, combined the same way (the average), and the same in Play and the editor run. -A dynamic body that stops such a move gets **pushed** into the contact – along its normal, so a -crate hit at its corner is shoved away from the corner, not along the move – as one body meeting another +A dynamic body that stops such a move gets **pushed** into the contact — along its normal, so a +crate hit at its corner is shoved away from the corner, not along the move — as one body meeting another without bounce: the speed it is pushed with is the move spread over the frame, and how much of it -the body takes depends on the two masses – a mover as heavy as a crate shoves it along, a crate +the body takes depends on the two masses — a mover as heavy as a crate shoves it along, a crate forty times heavier hardly shifts (measured: 133 of 161 units in a second, and 0.2). The mover weighs what its rigidbody's **Mass** says, or else what its colliders weigh by their density; a dynamic mover what the engine says. The mover stays flush in front of the crate and never gets @@ -152,7 +152,7 @@ first time the console points to the nodes above, which move it with the physics ## Play and the editor run The editor loads the engine right after opening the project; Play waits for it if it is not there -yet (*Loading the physics engine…*), and does not start if it could not be loaded – the notice and +yet (*Loading the physics engine…*), and does not start if it could not be loaded — the notice and the console say why. There is no falling back to the old detection: a game would collide differently. @@ -161,11 +161,12 @@ are placed, because what the editor run moves is saved. The nodes above do nothi ## What is not there yet -- `Move` stepping up onto a ledge, or a limit on how steep a slope it walks up – that is for the +- `Move` stepping up onto a ledge, or a limit on how steep a slope it walks up — that is for the graph, with `hit` and `normal` (#150). `Move` does not use Rapier's character controller: in #147 it caught on the seams between boxes lying side by side (10 of 100 steps along a floor of 12 crates), kept a gap of up to 1.7 units and knows no friction; `Move` works out its contacts itself, in double precision, and slides over the same seams without catching (#150). - The simulation in the editor run (#148). -- Joints (#154), and a ray filtered by collision layers (#158). Raycasts (#153), one-way platforms - (#155) and the collision layers themselves (#152) are there, see [Colliders](Colliders#raycast). +- Joints (#154), and "Raycast all" as a list of every hit (#157). Raycasts (#153) with their layer + filter (#158), one-way platforms (#155) and the collision layers themselves (#152) are there, + see [Colliders](Colliders#raycast).