diff --git a/API.md b/API.md index ee4d40d..4a38020 100644 --- a/API.md +++ b/API.md @@ -32,6 +32,10 @@ of admission. See [Accounts](Accounts). Which editor plugins a project switches on is the project's own field `plugins` (#119); the server has no switch of its own and hands out every plugin file it has. +The named collision layers are the project's field `collisionLayers` (#152), a list of up to 16 +names of which the first one is layer 0. A project file without it has the single layer *Default*, +which is what every collider is on, so the field needs no format bump either. + Every project document carries `formatVersion` (`PROJECT_FORMAT_VERSION` in `core/model/Project.ts`, currently 2). There are no load-time migrations: `serialize.ts` reads what it recognises and drops the rest. diff --git a/Architecture.md b/Architecture.md index 40db1b9..baaf1c1 100644 --- a/Architecture.md +++ b/Architecture.md @@ -68,7 +68,7 @@ ``` web/index.html Vite source page (dev) index.html built page (generated, served) -src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, and where one sits in the world), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), Scope (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead) +src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, and where one sits in the world), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), Scope (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by) src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry (with the side a one-way collider blocks from), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both) src/core/commands/ undoable commands, CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts src/core/graph/ graph model, NodeRegistry/NodeContext/values, interpreter, dropTarget, nested (behaviors as nodes), diff --git a/Colliders.md b/Colliders.md index e06e754..a3e571b 100644 --- a/Colliders.md +++ b/Colliders.md @@ -104,6 +104,27 @@ from above: Tiles can be one-way as well (see [Tilemaps](Tilemaps#one-way-tiles)). +## Collision layers + +A project has up to 16 named **collision layers** (#152), edited under **Project → Collision +layers…**; a new project has one, *Default*. Every collider and every tilemap names the layer it +sits on (**Layer**) and the layers it collides with (**Collides with**, a tick per layer). + +**Two things meet only when each one names the layer of the other.** The rule is symmetric, as in +the physics engine itself, so there is no pair that collides only one way: giving the player's +shots their own layer and leaving the player out of their *Collides with* is enough — the player +does not have to be changed as well, but the shots have to name what they *should* hit. + +- The default is layer 0 against every layer, which is what every collider written before there + were layers reads as. Nothing collides differently until a layer is named. +- One setting decides both: what does not collide reports no event either. To be notified without + being blocked, use **Trigger (no blocking)**. +- It filters everything alike — the engine in Play, `Move` with *Collide with solids*, and the + collision events of the editor run. +- A ray is not filtered by layers yet, that is #158. +- Only the *last* layer can be removed in the dialog: a collider names its layer by number, so + taking one out of the middle would move every collider below it onto another layer. + ## Raycast The **Raycast** node (#153) casts a ray from `from x/y` along `dir x/y` (any length, it is made a diff --git a/Concepts.md b/Concepts.md index 8379093..9c73ead 100644 --- a/Concepts.md +++ b/Concepts.md @@ -17,6 +17,7 @@ | **Variables asset** | Declarations and nothing else — no graph, no position, never placed. A `Variables` node naming it reads and writes **one value per key for the whole run**, however many graphs point at it. The values start at the declared ones on every Play and are never written back, so playing cannot change the project; while a run is on the asset's inspector shows them live and an edit there reaches the run at once. Two such assets may declare the same name without meeting. See [Behaviors](Behaviors). | | **Run in editor** | A switch only behaviors have (default off). With it on the behavior keeps running while the project is **not** playing — and unlike Play it then edits the *saved* model: what it moves or writes is a real change, autosaved and not on the undo stack. See *Running without Play*. | | **Collider** | A shape (box or circle) for collision detection, placed as a **child** of the object it belongs to (its *body*). Drawn as a green outline in edit mode only. | +| **Collision layer** | One of up to 16 named layers of the project (**Project → Collision layers…**). A collider or tilemap names the layer it is on and the layers it **collides with**; two of them meet only when each one names the layer of the other. A new project has one layer, *Default*, which every collider is on and meets — so nothing collides differently until a layer is named. See [Colliders](Colliders#collision-layers). | | **Rigidbody** | Placed as a **child** of an object, it hands that object to the physics engine while playing: it falls with the scene's gravity, bounces and rests on colliders, pushed by what it meets. At most one per object. See [Physics](Physics). | | **Override** | A per-object value that replaces the asset's value for one property. Only changed keys are stored; the inspector paints their label gold and offers a reset (↺) and an **apply** (↑) that writes the value into the asset instead. Both appear only where the value really differs from its base — an override carrying the asset's own value is none. | | **Default** | Every property has a schema default. Asset values that differ from it are marked gold in the inspector and can be reset (↺) the same way an override can. The asset's name in the **Assets** panel carries the same gold while any of them does (`assetDeviates`, `src/core/properties/deviation.ts` — the inspector marks the rows of that same schema, so the list cannot claim a deviation the inspector then fails to show). A behavior's variables are not in it: their default is declared on the variable itself, so the asset value cannot differ from it. | diff --git a/Development.md b/Development.md index f908476..fb0e921 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 # 1129 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1138 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 0432764..5031243 100644 --- a/Physics.md +++ b/Physics.md @@ -120,6 +120,16 @@ body shot up from below comes out on top and lands, one sliding sideways goes th falling at 2000 units per second lands with and without CCD. Collision events come on passing through as well: overlapping is colliding. +## Collision layers in the engine + +Rapier has the rule built in: every collider gets an **interaction group**, its layer in the high +16 bits and its *Collides with* mask in the low ones, set as both the collision group (what is +detected) and the solver group (what is pushed apart). The engine meets a pair when each side's +membership passes the other's filter, which is exactly what +[`layersMeet`](Colliders#collision-layers) says — so a pair that the layers keep apart is not +reported either, and nothing extra has to be filtered afterwards. Changing a layer rebuilds the +body's colliders, since the group sits on the collider itself. + ## Moving bodies from a graph | Node | What it does | @@ -157,5 +167,5 @@ are placed, because what the editor run moves is saved. The nodes above do nothi 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). -- Collision layers (#152) and joints (#154). Raycasts (#153) and one-way platforms (#155) are - there, see [Colliders](Colliders#raycast). +- 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). diff --git a/Tilemaps.md b/Tilemaps.md index a0bebaf..f46a260 100644 --- a/Tilemaps.md +++ b/Tilemaps.md @@ -150,6 +150,10 @@ that blocks (#173): where two rectangles or the pieces of outlined cells meet, n so a wall of several rectangles or a lake of many pieces shows as one shape. Collision itself keeps the pieces. +A placed tilemap carries **Layer** and **Collides with** like a collider does, for the whole map +(see [Colliders](Colliders#collision-layers)): ground only some things walk on is a map on its own +layer. Per layer of the map or per tile it is not — a cell belongs to the map. + ### Collision scale A tile's **Collision scale** (1 by default, 0.1 to 2) makes its cells block less or more than