Umbrella-Issue: Kollision soll reagieren statt nur anzuhalten. Die einzelnen Stufen werden nach den Entscheidungen unten eigene Issues. #141 (Kollisions-Polygon pro Frame) hängt davon ab, siehe unten.
Befund
Heute gibt es keine Physik, nur Erkennen und Anhalten.CollisionSystem sortiert Überlappungen in enter/stay/exit (src/runtime/systems/CollisionSystem.ts:54). Bewegt wird ausschließlich über den Node Move mit „Collide with solids“ (src/core/graph/nodes/action/Move.ts:22) → moveAndSlide (CollisionSystem.ts:121): Achse für Achse, an der Wand anhalten, entlanggleiten. Es gibt keine Geschwindigkeit, keine Masse, keinen Impuls, keine Reibung und kein Abprallen. Zwei bewegte Objekte schieben sich nicht, eins bleibt einfach stehen. Daher das „starre“ Gefühl.
Schwerkraft muss man sich im Graph selbst bauen (eigene Variable für die Geschwindigkeit, jeden Tick Move).
Formen: nur box und circle (src/core/properties/PropertySchema.ts:338). Jedes Formpaar ist in src/core/properties/collider.ts von Hand geschrieben (SAT, Kreis/Kreis, Box/Kreis per Bisektion). Jede neue Form braucht Überlappung und exitDistance gegen alle anderen, beim Polygon (#141) auch konkav.
Die Paarsuche ist O(n²) pro Tick (CollisionSystem.ts:59), ohne Broadphase.
Pixi kann das nicht: Pixi v8 ist reiner Renderer. In node_modules/pixi.js/lib gibt es keinen Treffer für rigid body, gravity oder restitution. Physik muss also dazukommen, selbst geschrieben oder als Library.
Library statt selbst schreiben (gemessen am 2026-09-18, npm)
Zum Vergleich: Unser Haupt-Bundle dist/assets/index-*.js hat heute 263 KB gzip.
Library
Version, Stand
Lizenz
Größe gzip
Anmerkung
Rapier 2D (@dimforge/rapier2d-compat)
0.20.0, Aug 2026
Apache-2.0
~795 KB (rapier.mjs, WASM eingebettet)
Rust/WASM. Alles an Bord: Static/Dynamic/Kinematic, Reibung, Restitution, Dichte/Masse, Gravity Scale, CCD, Sensoren, Kollisionsgruppen, Joints, KinematicCharacterController (= unser Move-and-Slide), Formen Capsule/Segment/ConvexHull/Polyline/Heightfield/Compound und convexDecomposition für konkave Polygone. Deterministische Variante als eigenes Paket. Init ist asynchron.
Box2D v3 (box2d3-wasm)
5.2.0, Feb 2026
MIT
~180 KB (WASM 153 + JS 26)
Sehr gute Stapelstabilität, CCD, Chains, CastMover/SolvePlanes für Charaktere. Community-Port eines einzelnen Maintainers, Embind-API mit manuellem Speicher. Polygone nur konvex und klein (Box2D-Konstante 8 Ecken, im Port nicht nachgemessen). Zerlegung konkaver Polygone müssten wir selbst machen.
Planck.js
1.5.0, Apr 2026
MIT
~55 KB
Box2D 2.x in reinem TypeScript, synchron, läuft ohne Aufwand in vitest. Konvexe Polygone bis 12 Ecken (maxPolygonVertices = 12). Kein Character-Controller, keine Zerlegung. Ältere Solver-Generation.
Matter.js
0.20.0, Jun 2024
MIT
~26 KB
Seit 2 Jahren ohne Release, bekannt wacklig beim Stapeln. Scheidet aus.
Vorschlag: Rapier. Die Library deckt alles aus diesem Issue ab, auch Polygone (#141) und den Charakter-Controller, und wird aktiv gepflegt. Der Preis ist die Größe. Sie wird deshalb erst beim Start des Spiels per dynamic import() geladen, und der Editor selbst bleibt so groß wie heute. Ist die Größe ein Ausschlusskriterium, wäre Box2D v3 die Alternative.
Zu bauen (Stufen, werden eigene Issues)
Stufe 0 – Spike (kurz, eigenes Issue): Rapier lazy im Play-Modus laden. Gemessen werden Ladezeit, Größe im Build, Init in vitest und im e2e-Container. Eine Kiste fällt auf eine Box. Danach ist entschieden, ob die Library bleibt.
Stufe 1 – Physik v1:
Body-Typ pro Objekt: Static / Dynamic / Kinematic, dazu Gravity scale, Linear/Angular damping, Fixed rotation, Continuous (CCD, gegen Tunneln schneller Objekte).
Am Collider: Friction, Bounciness (Restitution), Density (daraus die Masse). Optional eine feste Masse statt Dichte. Trigger bleibt.
Scene: Gravity (x, y) in px/s².
Laufzeit: fester Zeitschritt 60 Hz mit Akkumulator, Darstellung interpoliert. Dynamic Bodies schreiben Position und Rotation zurück.
Tilemap: ein Static Body aus den zusammengefassten Rechtecken (solidRects, inkl. #140).
Die EventsOnCollisionEnter/Stay/Exit und IsTouching kommen aus der Engine. Dazu die Kontaktdaten (Normale, Stärke) als Ausgänge.
Neue Nodes: Apply Force, Apply Impulse, Set/Get Velocity, Set Angular Velocity, Set Gravity.
Move mit „Collide with solids“ läuft für Kinematic über den Character-Controller: Gleiten an Wänden, Stufen, Schrägen.
Stufe 2 – Formen: Capsule, Polygon mit Punkt-Editing im Scene-View (konkav → intern zerlegt). Danach setzt #141 (Polygon pro Frame, Tilemap) darauf auf, statt eigene Polygon-Mathematik zu schreiben.
Später, jeweils eigenes Issue: Kollisionsebenen/Masken, Raycast-Node, Joints (Scharnier, Feder), One-Way-Plattformen.
Tests: Unit-Tests gegen die Engine (fallen, abprallen, rutschen auf Schräge mit/ohne Reibung, Masse beim Stoß, Tilemap als Boden). Dazu e2e mit echter Szene: eine Kiste fällt und bleibt auf dem Boden liegen, ein Ball springt ab.
Offen (mit Vorschlag)
Library: Vorschlag Rapier, lazy geladen (siehe Tabelle). Alternativen: Box2D v3 (klein, aber Community-Port) oder Planck (winzig, reines TS, weniger Funktionen).
Wo die Body-Einstellungen wohnen: Vorschlag: ein neues Kind-Objekt „Rigidbody“ neben den Collidern, so wie Collider heute Kinder sind. Ein Objekt hat höchstens einen. Alternative: Felder direkt an Sprite/Text/Container.
Objekte ohne Rigidbody (alle heutigen Projekte): Vorschlag: Sie verhalten sich wie heute: Collider ohne Body = Static, bewegt über Move = Kinematic. Es braucht keine Migration. Die Zahlen können sich im Nachkommabereich verschieben, weil ab dann die Engine rechnet und nicht mehr moveAndSlide. Einige Tests (e2e 08, 40l, Unit-Tests zu Move-and-Slide) werden auf Toleranzen umgestellt.
Eine Wahrheit zur Laufzeit: Vorschlag: Im Spiel rechnet nur die Engine, auch Trigger und Events. collider.ts bleibt für den Editor (Umrandung, Hit-Test), damit der Editor die Library nicht laden muss.
Einheiten: Vorschlag: Nach außen bleibt alles in Pixeln (px, px/s, px/s²). Intern gilt 100 px = 1 m, weil die Engines auf Meter abgestimmt sind. Der User sieht davon nichts.
Vorgabe der Schwerkraft: Vorschlag: (0, 980) px/s² pro Scene. Das betrifft nur Dynamic Bodies, also ändert sich für bestehende Projekte (Top-Down) nichts.
Dynamic Body unter einem Parent: Vorschlag: Er wird in Weltkoordinaten simuliert und lokal zurückgeschrieben. Bewegt sich der Parent selbst, zieht er den Body nicht mit (wie in Unity). Der Inspector weist darauf hin.
Skalieren zur Laufzeit (Animation, Graph): Vorschlag: Die Form wird bei einer Änderung neu gebaut. Das ist nicht billig, aber korrekt.
Determinismus: Vorschlag: vorerst die normale Variante. Die deterministische (rapier2d-deterministic-compat) nehmen wir erst, wenn Replays oder Netzwerk kommen.
Umbrella-Issue: Kollision soll **reagieren** statt nur anzuhalten. Die einzelnen Stufen werden nach den Entscheidungen unten eigene Issues. #141 (Kollisions-Polygon pro Frame) hängt davon ab, siehe unten.
## Befund
- **Heute gibt es keine Physik, nur Erkennen und Anhalten.** `CollisionSystem` sortiert Überlappungen in enter/stay/exit (`src/runtime/systems/CollisionSystem.ts:54`). Bewegt wird ausschließlich über den Node `Move` mit „Collide with solids“ (`src/core/graph/nodes/action/Move.ts:22`) → `moveAndSlide` (`CollisionSystem.ts:121`): Achse für Achse, an der Wand anhalten, entlanggleiten. Es gibt keine Geschwindigkeit, keine Masse, keinen Impuls, keine Reibung und kein Abprallen. Zwei bewegte Objekte schieben sich nicht, eins bleibt einfach stehen. Daher das „starre“ Gefühl.
- **Schwerkraft muss man sich im Graph selbst bauen** (eigene Variable für die Geschwindigkeit, jeden Tick `Move`).
- **Formen:** nur `box` und `circle` (`src/core/properties/PropertySchema.ts:338`). Jedes Formpaar ist in `src/core/properties/collider.ts` von Hand geschrieben (SAT, Kreis/Kreis, Box/Kreis per Bisektion). Jede neue Form braucht Überlappung und `exitDistance` gegen alle anderen, beim Polygon (#141) auch konkav.
- **Die Paarsuche ist O(n²) pro Tick** (`CollisionSystem.ts:59`), ohne Broadphase.
- **Pixi kann das nicht:** Pixi v8 ist reiner Renderer. In `node_modules/pixi.js/lib` gibt es keinen Treffer für rigid body, gravity oder restitution. Physik muss also dazukommen, selbst geschrieben oder als Library.
## Library statt selbst schreiben (gemessen am 2026-09-18, npm)
Zum Vergleich: Unser Haupt-Bundle `dist/assets/index-*.js` hat heute **263 KB gzip**.
| Library | Version, Stand | Lizenz | Größe gzip | Anmerkung |
|---|---|---|---|---|
| **Rapier 2D** (`@dimforge/rapier2d-compat`) | 0.20.0, Aug 2026 | Apache-2.0 | **~795 KB** (`rapier.mjs`, WASM eingebettet) | Rust/WASM. Alles an Bord: Static/Dynamic/Kinematic, Reibung, Restitution, Dichte/Masse, Gravity Scale, CCD, Sensoren, Kollisionsgruppen, Joints, **KinematicCharacterController** (= unser Move-and-Slide), Formen Capsule/Segment/ConvexHull/Polyline/Heightfield/Compound und **`convexDecomposition`** für konkave Polygone. Deterministische Variante als eigenes Paket. Init ist asynchron. |
| **Box2D v3** (`box2d3-wasm`) | 5.2.0, Feb 2026 | MIT | **~180 KB** (WASM 153 + JS 26) | Sehr gute Stapelstabilität, CCD, Chains, `CastMover`/`SolvePlanes` für Charaktere. Community-Port eines einzelnen Maintainers, Embind-API mit manuellem Speicher. Polygone nur konvex und klein (Box2D-Konstante 8 Ecken, im Port nicht nachgemessen). Zerlegung konkaver Polygone müssten wir selbst machen. |
| **Planck.js** | 1.5.0, Apr 2026 | MIT | **~55 KB** | Box2D 2.x in reinem TypeScript, synchron, läuft ohne Aufwand in vitest. Konvexe Polygone bis 12 Ecken (`maxPolygonVertices = 12`). Kein Character-Controller, keine Zerlegung. Ältere Solver-Generation. |
| Matter.js | 0.20.0, **Jun 2024** | MIT | ~26 KB | Seit 2 Jahren ohne Release, bekannt wacklig beim Stapeln. Scheidet aus. |
**Vorschlag: Rapier.** Die Library deckt alles aus diesem Issue ab, auch Polygone (#141) und den Charakter-Controller, und wird aktiv gepflegt. Der Preis ist die Größe. Sie wird deshalb **erst beim Start des Spiels** per dynamic `import()` geladen, und der Editor selbst bleibt so groß wie heute. Ist die Größe ein Ausschlusskriterium, wäre Box2D v3 die Alternative.
## Zu bauen (Stufen, werden eigene Issues)
**Stufe 0 – Spike (kurz, eigenes Issue):** Rapier lazy im Play-Modus laden. Gemessen werden Ladezeit, Größe im Build, Init in vitest und im e2e-Container. Eine Kiste fällt auf eine Box. Danach ist entschieden, ob die Library bleibt.
**Stufe 1 – Physik v1:**
- **Body-Typ pro Objekt:** Static / Dynamic / Kinematic, dazu Gravity scale, Linear/Angular damping, Fixed rotation, Continuous (CCD, gegen Tunneln schneller Objekte).
- **Am Collider:** Friction, Bounciness (Restitution), Density (daraus die Masse). Optional eine feste Masse statt Dichte. Trigger bleibt.
- **Scene:** Gravity (x, y) in px/s².
- **Laufzeit:** fester Zeitschritt 60 Hz mit Akkumulator, Darstellung interpoliert. Dynamic Bodies schreiben Position **und Rotation** zurück.
- **Tilemap:** ein Static Body aus den zusammengefassten Rechtecken (`solidRects`, inkl. #140).
- **Die Events** `OnCollisionEnter/Stay/Exit` und `IsTouching` kommen aus der Engine. Dazu die Kontaktdaten (Normale, Stärke) als Ausgänge.
- **Neue Nodes:** Apply Force, Apply Impulse, Set/Get Velocity, Set Angular Velocity, Set Gravity.
- **`Move` mit „Collide with solids“** läuft für Kinematic über den Character-Controller: Gleiten an Wänden, Stufen, Schrägen.
**Stufe 2 – Formen:** Capsule, Polygon mit Punkt-Editing im Scene-View (konkav → intern zerlegt). Danach setzt #141 (Polygon pro Frame, Tilemap) darauf auf, statt eigene Polygon-Mathematik zu schreiben.
**Später, jeweils eigenes Issue:** Kollisionsebenen/Masken, Raycast-Node, Joints (Scharnier, Feder), One-Way-Plattformen.
**Tests:** Unit-Tests gegen die Engine (fallen, abprallen, rutschen auf Schräge mit/ohne Reibung, Masse beim Stoß, Tilemap als Boden). Dazu e2e mit echter Szene: eine Kiste fällt und bleibt auf dem Boden liegen, ein Ball springt ab.
## Offen (mit Vorschlag)
1. **Library:** Vorschlag Rapier, lazy geladen (siehe Tabelle). Alternativen: Box2D v3 (klein, aber Community-Port) oder Planck (winzig, reines TS, weniger Funktionen).
2. **Wo die Body-Einstellungen wohnen:** Vorschlag: ein neues Kind-Objekt **„Rigidbody“** neben den Collidern, so wie Collider heute Kinder sind. Ein Objekt hat höchstens einen. Alternative: Felder direkt an Sprite/Text/Container.
3. **Objekte ohne Rigidbody (alle heutigen Projekte):** Vorschlag: Sie verhalten sich wie heute: Collider ohne Body = Static, bewegt über `Move` = Kinematic. Es braucht keine Migration. Die Zahlen können sich im Nachkommabereich verschieben, weil ab dann die Engine rechnet und nicht mehr `moveAndSlide`. Einige Tests (e2e 08, 40l, Unit-Tests zu Move-and-Slide) werden auf Toleranzen umgestellt.
4. **Eine Wahrheit zur Laufzeit:** Vorschlag: Im Spiel rechnet **nur** die Engine, auch Trigger und Events. `collider.ts` bleibt für den Editor (Umrandung, Hit-Test), damit der Editor die Library nicht laden muss.
5. **Einheiten:** Vorschlag: Nach außen bleibt alles in Pixeln (px, px/s, px/s²). Intern gilt 100 px = 1 m, weil die Engines auf Meter abgestimmt sind. Der User sieht davon nichts.
6. **Vorgabe der Schwerkraft:** Vorschlag: (0, 980) px/s² pro Scene. Das betrifft nur Dynamic Bodies, also ändert sich für bestehende Projekte (Top-Down) nichts.
7. **Dynamic Body unter einem Parent:** Vorschlag: Er wird in Weltkoordinaten simuliert und lokal zurückgeschrieben. Bewegt sich der Parent selbst, zieht er den Body nicht mit (wie in Unity). Der Inspector weist darauf hin.
8. **Skalieren zur Laufzeit** (Animation, Graph): Vorschlag: Die Form wird bei einer Änderung neu gebaut. Das ist nicht billig, aber korrekt.
9. **Determinismus:** Vorschlag: vorerst die normale Variante. Die deterministische (`rapier2d-deterministic-compat`) nehmen wir erst, wenn Replays oder Netzwerk kommen.
bin mir nicht sicher was du meinst, aber der editor sollte die library auch laden, weil vielleicht will ich außerhalb vom play mode später auch simulieren
passt 100px = 1m
vorschlag passt
vorschlag passt
vorschlag passt (korrekt besser als billig)
passt, lassen wir erstmal weg
1. Rapier passt
2. eigenes kind object "Rigidbody" passt
3. vorschlag passt
4. bin mir nicht sicher was du meinst, aber der editor sollte die library auch laden, weil vielleicht will ich außerhalb vom play mode später auch simulieren
5. passt 100px = 1m
6. vorschlag passt
7. vorschlag passt
8. vorschlag passt (korrekt besser als billig)
9. passt, lassen wir erstmal weg
Stufe 1b: #147, dazu #150 (Move-and-Slide entlang der Kontaktnormale mit Friction)
Stufe 2: #151 (Capsule, Polygon mit Punkt-Editing, Zerlegung konkaver Polygone)
Aufbauend: #141 (Kollisions-Umrisse pro Frame, auch für Autotiles)
„Später“ steht jetzt in eigenen Issues: #152 (Ebenen/Masken), #153 (Raycast), #154 (Joints), #155 (One-Way-Plattformen). Weiter offen aus der Physik-Reihe: #148 (Simulation im Editor-Run) und #149 (On collision stay mit Kontaktdaten).
Damit schließe ich das Sammel-Issue.
Alle Stufen sind erledigt:
- Stufe 0: #145
- Stufe 1a: #146
- Stufe 1b: #147, dazu #150 (Move-and-Slide entlang der Kontaktnormale mit Friction)
- Stufe 2: #151 (Capsule, Polygon mit Punkt-Editing, Zerlegung konkaver Polygone)
- Aufbauend: #141 (Kollisions-Umrisse pro Frame, auch für Autotiles)
„Später“ steht jetzt in eigenen Issues: #152 (Ebenen/Masken), #153 (Raycast), #154 (Joints), #155 (One-Way-Plattformen). Weiter offen aus der Physik-Reihe: #148 (Simulation im Editor-Run) und #149 (`On collision stay` mit Kontaktdaten).
Damit schließe ich das Sammel-Issue.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Umbrella-Issue: Kollision soll reagieren statt nur anzuhalten. Die einzelnen Stufen werden nach den Entscheidungen unten eigene Issues. #141 (Kollisions-Polygon pro Frame) hängt davon ab, siehe unten.
Befund
CollisionSystemsortiert Überlappungen in enter/stay/exit (src/runtime/systems/CollisionSystem.ts:54). Bewegt wird ausschließlich über den NodeMovemit „Collide with solids“ (src/core/graph/nodes/action/Move.ts:22) →moveAndSlide(CollisionSystem.ts:121): Achse für Achse, an der Wand anhalten, entlanggleiten. Es gibt keine Geschwindigkeit, keine Masse, keinen Impuls, keine Reibung und kein Abprallen. Zwei bewegte Objekte schieben sich nicht, eins bleibt einfach stehen. Daher das „starre“ Gefühl.Move).boxundcircle(src/core/properties/PropertySchema.ts:338). Jedes Formpaar ist insrc/core/properties/collider.tsvon Hand geschrieben (SAT, Kreis/Kreis, Box/Kreis per Bisektion). Jede neue Form braucht Überlappung undexitDistancegegen alle anderen, beim Polygon (#141) auch konkav.CollisionSystem.ts:59), ohne Broadphase.node_modules/pixi.js/libgibt es keinen Treffer für rigid body, gravity oder restitution. Physik muss also dazukommen, selbst geschrieben oder als Library.Library statt selbst schreiben (gemessen am 2026-09-18, npm)
Zum Vergleich: Unser Haupt-Bundle
dist/assets/index-*.jshat heute 263 KB gzip.@dimforge/rapier2d-compat)rapier.mjs, WASM eingebettet)convexDecompositionfür konkave Polygone. Deterministische Variante als eigenes Paket. Init ist asynchron.box2d3-wasm)CastMover/SolvePlanesfür Charaktere. Community-Port eines einzelnen Maintainers, Embind-API mit manuellem Speicher. Polygone nur konvex und klein (Box2D-Konstante 8 Ecken, im Port nicht nachgemessen). Zerlegung konkaver Polygone müssten wir selbst machen.maxPolygonVertices = 12). Kein Character-Controller, keine Zerlegung. Ältere Solver-Generation.Vorschlag: Rapier. Die Library deckt alles aus diesem Issue ab, auch Polygone (#141) und den Charakter-Controller, und wird aktiv gepflegt. Der Preis ist die Größe. Sie wird deshalb erst beim Start des Spiels per dynamic
import()geladen, und der Editor selbst bleibt so groß wie heute. Ist die Größe ein Ausschlusskriterium, wäre Box2D v3 die Alternative.Zu bauen (Stufen, werden eigene Issues)
Stufe 0 – Spike (kurz, eigenes Issue): Rapier lazy im Play-Modus laden. Gemessen werden Ladezeit, Größe im Build, Init in vitest und im e2e-Container. Eine Kiste fällt auf eine Box. Danach ist entschieden, ob die Library bleibt.
Stufe 1 – Physik v1:
solidRects, inkl. #140).OnCollisionEnter/Stay/ExitundIsTouchingkommen aus der Engine. Dazu die Kontaktdaten (Normale, Stärke) als Ausgänge.Movemit „Collide with solids“ läuft für Kinematic über den Character-Controller: Gleiten an Wänden, Stufen, Schrägen.Stufe 2 – Formen: Capsule, Polygon mit Punkt-Editing im Scene-View (konkav → intern zerlegt). Danach setzt #141 (Polygon pro Frame, Tilemap) darauf auf, statt eigene Polygon-Mathematik zu schreiben.
Später, jeweils eigenes Issue: Kollisionsebenen/Masken, Raycast-Node, Joints (Scharnier, Feder), One-Way-Plattformen.
Tests: Unit-Tests gegen die Engine (fallen, abprallen, rutschen auf Schräge mit/ohne Reibung, Masse beim Stoß, Tilemap als Boden). Dazu e2e mit echter Szene: eine Kiste fällt und bleibt auf dem Boden liegen, ein Ball springt ab.
Offen (mit Vorschlag)
Move= Kinematic. Es braucht keine Migration. Die Zahlen können sich im Nachkommabereich verschieben, weil ab dann die Engine rechnet und nicht mehrmoveAndSlide. Einige Tests (e2e 08, 40l, Unit-Tests zu Move-and-Slide) werden auf Toleranzen umgestellt.collider.tsbleibt für den Editor (Umrandung, Hit-Test), damit der Editor die Library nicht laden muss.rapier2d-deterministic-compat) nehmen wir erst, wenn Replays oder Netzwerk kommen.Alle Stufen sind erledigt:
„Später“ steht jetzt in eigenen Issues: #152 (Ebenen/Masken), #153 (Raycast), #154 (Joints), #155 (One-Way-Plattformen). Weiter offen aus der Physik-Reihe: #148 (Simulation im Editor-Run) und #149 (
On collision staymit Kontaktdaten).Damit schließe ich das Sammel-Issue.