Physik Stufe 1: Rigidbody, Collider-Material, Schwerkraft und PhysicsSystem auf Rapier #146

Closed
opened 2026-09-18 17:07:12 +02:00 by meleki · 2 comments
Owner

Stufe 1 aus #144. Voraussetzung ist #145 (Rapier ist eingebunden und wird im Editor geladen). Die Antworten in #144 gelten: eigenes Kind-Objekt „Rigidbody“, ohne Rigidbody bleibt alles wie heute, 100 px = 1 m, Schwerkraft (0, 980) px/s², Dynamic unter einem Parent in Weltkoordinaten, Skalieren baut die Form neu, keine deterministische Variante.

Befund (Stand d02bcca)

  • collider ist heute ein Kind-Objekt mit shape (box/circle), width, height, radius, isTrigger (src/core/properties/PropertySchema.ts:338). Material (Reibung, Bounciness, Dichte) gibt es nicht.
  • Die Scene hat nur backgroundColor (PropertySchema.ts:184), also keine Schwerkraft.
  • addGameSystems (src/runtime/systems/BehaviorSystem.ts:82) hängt CollisionSystem, AnimationSystem und BehaviorSystem an die Welt. Aufgerufen wird es an drei Stellen: PlaySession.ts:121 (Start), PlaySession.ts:142 (Scene-Wechsel) und EditorRun.ts:123 (Behaviors mit „run in editor“).
  • World.tick(dt) (src/runtime/World.ts:223) reicht das Frame-dt direkt an alle Systeme weiter. Einen festen Zeitschritt gibt es nicht.
  • Bewegt wird nur über Move → moveAndSlide (CollisionSystem.ts:121), die Events kommen aus OnCollisionEnter/Stay/Exit und IsTouching (src/core/graph/nodes/events/, data/IsTouching.ts).
  • loadPhysics() aus #145 ist asynchron, addGameSystems ist synchron. Weil der Editor Rapier schon beim Öffnen lädt, ist es beim Play-Start praktisch immer fertig, garantiert ist das aber nicht.

Zu bauen

Neues Kind-Objekt rigidbody (höchstens eins pro Objekt, wie der Collider im Add-Menü):

  • Body type: Static / Dynamic / Kinematic (Vorgabe Dynamic)
  • Gravity scale (1), Linear damping (0), Angular damping (0), Fixed rotation (aus), Continuous/CCD (aus)
  • Optional Mass: 0 = aus der Dichte der Collider, sonst feste Masse

Am Collider: Friction (Vorgabe 0.5), Bounciness (0), Density (1). Trigger bleibt und wird in Rapier ein Sensor.

An der Scene: Gravity X / Gravity Y in px/s², Vorgabe (0, 980).

Laufzeit, neues PhysicsSystem:

  • Rapier-Welt pro Spielwelt, fester Zeitschritt 60 Hz mit Akkumulator, höchstens einige Schritte pro Frame (kein Nachholen ohne Ende nach einem Hänger).
  • Objekte ohne Rigidbody, aber mit Collider = Static Body (bewegt über Move = Kinematic), wie in #144 Antwort 3.
  • Dynamic Bodies schreiben Position und Rotation zurück, in Weltkoordinaten simuliert und lokal zurückgerechnet.
  • Tilemap: ein Static Body aus solidRects (mit Collision scale aus #140).
  • Skaliert sich ein Objekt zur Laufzeit, wird seine Form neu gebaut.
  • Die Darstellung wird zwischen zwei Schritten interpoliert.

Events aus der Engine: OnCollisionEnter/Stay/Exit und IsTouching kommen aus Rapiers Kontakt- und Sensor-Events statt aus der eigenen Paarsuche. Die Enter-Events bekommen zusätzlich die Ausgänge Normal und Impulse.

Neue Nodes: Apply Force, Apply Impulse, Set Velocity, Get Velocity, Set Angular Velocity, Set Gravity (Scene).

Move mit „Collide with solids“ läuft über Rapiers KinematicCharacterController: Gleiten an Wänden, Schrägen, Stufen. Ohne Rigidbody und bei Kinematic wie bisher.

Wegfallend: CollisionSystem mit der O(n²)-Paarsuche und moveAndSlide im Spiel. collider.ts bleibt für Umrandung und Hit-Test im Editor.

Tests

  • Unit gegen die Engine: fallen und liegen bleiben, Abprallen mit Bounciness 0 / 0.5 / 1, Rutschen auf einer Schräge mit und ohne Reibung, Stoß zweier Massen (Impuls bleibt erhalten), Tilemap als Boden, Fixed rotation, Sensor-Events, fester Zeitschritt bei ungleichen Frame-Zeiten.
  • Bestehende Tests: e2e 08 (Collider), 40l (Collision scale) und die Unit-Tests zu Move-and-Slide werden auf Toleranzen umgestellt, wo die Engine im Nachkommabereich anders rechnet. Jede umgestellte Zahl steht im Abschlusskommentar.
  • e2e, neue Schritte in Modul 41: echte Szene, eine Kiste fällt auf den Boden und bleibt liegen, ein Ball springt ab und wird niedriger, Werte über das echte Inspector-Feld gesetzt.

Offen (mit Vorschlag)

  1. Umfang: Das ist viel für einen Commit. Vorschlag: zwei Issues. 1a = Rigidbody, Collider-Material, Scene-Gravity, PhysicsSystem, Tilemap, Events aus der Engine. 1b = die neuen Nodes und Move über den Character-Controller. Nach 1a fallen Dinge schon richtig, bewegen lassen sie sich aus dem Graph erst mit 1b.
  2. Play-Start, wenn Rapier noch lädt oder nicht geladen werden konnte: Vorschlag: Play wartet auf loadPhysics() (im Normalfall sofort fertig). Ist das Laden gescheitert, startet Play nicht und die Console sagt warum. Kein Rückfall auf die alte Kollision, sonst gäbe es zwei Wahrheiten.
  3. Move auf einem Dynamic Body: Vorschlag: Das Objekt wird teleportiert (Position gesetzt, Geschwindigkeit bleibt), und beim ersten Mal steht ein Hinweis in der Console, dass Force/Impulse/Velocity der Weg sind.
  4. Simulation im Editor (deine Antwort 4 in #144): Vorschlag: in Stufe 1 noch nicht. Das PhysicsSystem wird aber so gebaut, dass EditorRun es später dazuhängen kann, und bekommt dafür ein eigenes Issue.
  5. Fixed rotation als Vorgabe: Vorschlag: aus (Kisten kippen). Für Spielfiguren stellt man es ein.
  6. Kontaktdaten an Stay/Exit: Vorschlag: nur an Enter. Stay jeden Tick mit Normalen würde viel kosten und wird selten gebraucht.
Stufe 1 aus #144. Voraussetzung ist #145 (Rapier ist eingebunden und wird im Editor geladen). Die Antworten in #144 gelten: eigenes Kind-Objekt „Rigidbody“, ohne Rigidbody bleibt alles wie heute, 100 px = 1 m, Schwerkraft (0, 980) px/s², Dynamic unter einem Parent in Weltkoordinaten, Skalieren baut die Form neu, keine deterministische Variante. ## Befund (Stand `d02bcca`) - `collider` ist heute ein Kind-Objekt mit `shape` (box/circle), `width`, `height`, `radius`, `isTrigger` (`src/core/properties/PropertySchema.ts:338`). Material (Reibung, Bounciness, Dichte) gibt es nicht. - Die Scene hat nur `backgroundColor` (`PropertySchema.ts:184`), also keine Schwerkraft. - `addGameSystems` (`src/runtime/systems/BehaviorSystem.ts:82`) hängt `CollisionSystem`, `AnimationSystem` und `BehaviorSystem` an die Welt. Aufgerufen wird es an drei Stellen: `PlaySession.ts:121` (Start), `PlaySession.ts:142` (Scene-Wechsel) und `EditorRun.ts:123` (Behaviors mit „run in editor“). - `World.tick(dt)` (`src/runtime/World.ts:223`) reicht das Frame-`dt` direkt an alle Systeme weiter. Einen festen Zeitschritt gibt es nicht. - Bewegt wird nur über `Move` → `moveAndSlide` (`CollisionSystem.ts:121`), die Events kommen aus `OnCollisionEnter/Stay/Exit` und `IsTouching` (`src/core/graph/nodes/events/`, `data/IsTouching.ts`). - `loadPhysics()` aus #145 ist asynchron, `addGameSystems` ist synchron. Weil der Editor Rapier schon beim Öffnen lädt, ist es beim Play-Start praktisch immer fertig, garantiert ist das aber nicht. ## Zu bauen **Neues Kind-Objekt `rigidbody`** (höchstens eins pro Objekt, wie der Collider im Add-Menü): - Body type: Static / Dynamic / Kinematic (Vorgabe Dynamic) - Gravity scale (1), Linear damping (0), Angular damping (0), Fixed rotation (aus), Continuous/CCD (aus) - Optional Mass: 0 = aus der Dichte der Collider, sonst feste Masse **Am Collider:** Friction (Vorgabe 0.5), Bounciness (0), Density (1). Trigger bleibt und wird in Rapier ein Sensor. **An der Scene:** Gravity X / Gravity Y in px/s², Vorgabe (0, 980). **Laufzeit, neues `PhysicsSystem`:** - Rapier-Welt pro Spielwelt, fester Zeitschritt 60 Hz mit Akkumulator, höchstens einige Schritte pro Frame (kein Nachholen ohne Ende nach einem Hänger). - Objekte ohne Rigidbody, aber mit Collider = Static Body (bewegt über `Move` = Kinematic), wie in #144 Antwort 3. - Dynamic Bodies schreiben Position **und** Rotation zurück, in Weltkoordinaten simuliert und lokal zurückgerechnet. - Tilemap: ein Static Body aus `solidRects` (mit Collision scale aus #140). - Skaliert sich ein Objekt zur Laufzeit, wird seine Form neu gebaut. - Die Darstellung wird zwischen zwei Schritten interpoliert. **Events aus der Engine:** `OnCollisionEnter/Stay/Exit` und `IsTouching` kommen aus Rapiers Kontakt- und Sensor-Events statt aus der eigenen Paarsuche. Die Enter-Events bekommen zusätzlich die Ausgänge **Normal** und **Impulse**. **Neue Nodes:** Apply Force, Apply Impulse, Set Velocity, Get Velocity, Set Angular Velocity, Set Gravity (Scene). **`Move` mit „Collide with solids“** läuft über Rapiers `KinematicCharacterController`: Gleiten an Wänden, Schrägen, Stufen. Ohne Rigidbody und bei Kinematic wie bisher. **Wegfallend:** `CollisionSystem` mit der O(n²)-Paarsuche und `moveAndSlide` im Spiel. `collider.ts` bleibt für Umrandung und Hit-Test im Editor. ## Tests - **Unit gegen die Engine:** fallen und liegen bleiben, Abprallen mit Bounciness 0 / 0.5 / 1, Rutschen auf einer Schräge mit und ohne Reibung, Stoß zweier Massen (Impuls bleibt erhalten), Tilemap als Boden, Fixed rotation, Sensor-Events, fester Zeitschritt bei ungleichen Frame-Zeiten. - **Bestehende Tests:** e2e 08 (Collider), 40l (Collision scale) und die Unit-Tests zu Move-and-Slide werden auf Toleranzen umgestellt, wo die Engine im Nachkommabereich anders rechnet. Jede umgestellte Zahl steht im Abschlusskommentar. - **e2e, neue Schritte in Modul 41:** echte Szene, eine Kiste fällt auf den Boden und bleibt liegen, ein Ball springt ab und wird niedriger, Werte über das echte Inspector-Feld gesetzt. ## Offen (mit Vorschlag) 1. **Umfang:** Das ist viel für einen Commit. Vorschlag: zwei Issues. **1a** = Rigidbody, Collider-Material, Scene-Gravity, `PhysicsSystem`, Tilemap, Events aus der Engine. **1b** = die neuen Nodes und `Move` über den Character-Controller. Nach 1a fallen Dinge schon richtig, bewegen lassen sie sich aus dem Graph erst mit 1b. 2. **Play-Start, wenn Rapier noch lädt oder nicht geladen werden konnte:** Vorschlag: Play wartet auf `loadPhysics()` (im Normalfall sofort fertig). Ist das Laden gescheitert, startet Play nicht und die Console sagt warum. Kein Rückfall auf die alte Kollision, sonst gäbe es zwei Wahrheiten. 3. **`Move` auf einem Dynamic Body:** Vorschlag: Das Objekt wird teleportiert (Position gesetzt, Geschwindigkeit bleibt), und beim ersten Mal steht ein Hinweis in der Console, dass Force/Impulse/Velocity der Weg sind. 4. **Simulation im Editor (deine Antwort 4 in #144):** Vorschlag: in Stufe 1 noch nicht. Das `PhysicsSystem` wird aber so gebaut, dass `EditorRun` es später dazuhängen kann, und bekommt dafür ein eigenes Issue. 5. **Fixed rotation als Vorgabe:** Vorschlag: aus (Kisten kippen). Für Spielfiguren stellt man es ein. 6. **Kontaktdaten an Stay/Exit:** Vorschlag: nur an Enter. Stay jeden Tick mit Normalen würde viel kosten und wird selten gebraucht.
  1. passt
  2. passt
  3. passt
  4. passt, eigenes issue für später
  5. passt, default aus
  6. passt, vorerst kein stay, vielleicht später einfach stay aber nicht jeden tick - eigenes issue
1. passt 2. passt 3. passt 4. passt, eigenes issue für später 5. passt, default aus 6. passt, vorerst kein stay, vielleicht später einfach stay aber nicht jeden tick - eigenes issue
admin added this to the Development project 2026-09-18 18:01:02 +02:00
admin moved this to Next in Development on 2026-09-18 18:01:15 +02:00
Author
Owner

Umgesetzt (Stufe 1a) – Commit 78899b1

Alle sechs Vorschläge nach deinen Antworten (1121).

Was sich ändert

  • Neues Kind-Objekt Rigidbody (Rechtsklick in der Hierarchie → Add rigidbody, höchstens einer pro Objekt): Body type, Mass, Gravity scale, Linear/Angular damping, Fixed rotation, CCD.
  • Collider: Friction (0,5), Bounciness (0), Density (1). Scene: Gravity X/Y, Vorgabe (0, 980).
  • PhysicsSystem auf Rapier: fester Schritt 60 Hz (höchstens 8 pro Frame), Interpolation, gerechnet in Weltkoordinaten (auch unter gedrehtem Parent), Neubau der Form beim Skalieren, Versetzen durch den Graph behält die Geschwindigkeit, Tilemap als statischer Boden.
  • On collision enter hat die Ausgänge normal x, normal y, impulse.
  • Play wartet auf Rapier; scheitert das Laden, startet Play nicht und sagt warum.

Selbst entschieden (vom Issue nicht festgelegt)

  1. Ohne Rigidbody bleibt es wie heute, auch bei den Events: Rapier meldet Kontakt schon vor dem Berühren (gemessen). Rapier schlägt nur Paare vor; ohne Dynamic Body entscheidet die bisherige Überlappungsprüfung.
  2. Der Editor-Run behält das alte CollisionSystem (Erkennen ohne Simulation, Antwort 4).
  3. Bounciness: der größere Wert der beiden Collider gilt (wie Box2D/Unity 2D), nicht Rapiers Mittelwert.
  4. Ein Rigidbody ohne Collider fällt trotzdem (seine Masse, sonst 1 kg).
  5. Keinen Rigidbody bekommen Tilemap, Behavior, Collider und Rigidbody selbst; im „+“-Menü sind es jetzt 18 Typen.

Bekannte Grenzen

  • Das Bild läuft einen Physik-Schritt (1/60 s) hinterher – Preis der Interpolation.
  • Kisten mit Move schieben geht noch nicht (Dynamic Body blockiert Move-and-Slide wie eine Wand) → #147.
  • Beim Schieben durch einen Kinematic Body überlappen sich die beiden sichtbar, gemessen ~4 Einheiten bei 120 Einheiten/s.

Tests

npm run check                                            → 104 Files, 1430 Tests (vorher 1386), CHECK EXIT 0
npm run build                                            → Haupt-Bundle 265,03 → 269,39 KB gzip
GM_STEPS=1-16,24,25,33,36,38,39,40,41 (frisch geseedet)  → ALL PASSED – 622 checks
  • 21 neue Physik-Tests (Fallen/Liegen auch auf Tilemap, Bounciness 0/0,5/1, Schräge, Impulserhaltung, Masse, Fixed rotation, Parent, Versetzen, Skalieren, Kinematic, fester Schritt, Trigger, Normale/Impuls, Berühren vs. Überlappen, Collider aus).
  • Die bestehenden Kollisionstests laufen gegen beide Systeme – 23 davon auch mit dem PhysicsSystem grün, ohne Toleranz und ohne umgestellte Zahl.
  • Gegenprobe mit 7 eingebauten Fehlern, jeder von genau einem Test gefangen.
  • e2e 41b: 7 neue Checks über echtes Menü und echtes Feld; Bounce-Höhen 99,8 → 61,8 → 37,9 px bei Bounciness 0,8.
  • Nicht eigens getestet: Play-Start während Rapier noch lädt, Zurückschreiben unter gespiegeltem Parent.

CI zu 78899b1: grün. Gate (Task 130): 104 Testdateien, CHECK EXIT 0. Suite (Task 131): ALL PASSED – 1052 checks – die Summe ist damit gemessen; Smokes 24/19/21, also laufen auch die drei Beispielspiele mit dem neuen PhysicsSystem.

Doku: Wiki f690444 – neue Seite Physics, nachgezogen Colliders, Concepts, Home, Sidebar, Architecture, Development; README.

Folge-Issues: #147 (Stufe 1b: Nodes + Character-Controller), #148 (Simulation im Editor), #149 (Stay mit Kontaktdaten).

Abgenommen vom User, damit geschlossen.

## Umgesetzt (Stufe 1a) – Commit 78899b1 Alle sechs Vorschläge nach deinen Antworten (1121). **Was sich ändert** - **Neues Kind-Objekt Rigidbody** (Rechtsklick in der Hierarchie → *Add rigidbody*, höchstens einer pro Objekt): Body type, Mass, Gravity scale, Linear/Angular damping, Fixed rotation, CCD. - **Collider:** Friction (0,5), Bounciness (0), Density (1). **Scene:** Gravity X/Y, Vorgabe (0, 980). - **`PhysicsSystem`** auf Rapier: fester Schritt 60 Hz (höchstens 8 pro Frame), Interpolation, gerechnet in Weltkoordinaten (auch unter gedrehtem Parent), Neubau der Form beim Skalieren, Versetzen durch den Graph behält die Geschwindigkeit, Tilemap als statischer Boden. - **`On collision enter`** hat die Ausgänge `normal x`, `normal y`, `impulse`. - **Play wartet auf Rapier**; scheitert das Laden, startet Play nicht und sagt warum. **Selbst entschieden (vom Issue nicht festgelegt)** 1. Ohne Rigidbody bleibt es wie heute, auch bei den Events: Rapier meldet Kontakt schon vor dem Berühren (gemessen). Rapier schlägt nur Paare vor; ohne Dynamic Body entscheidet die bisherige Überlappungsprüfung. 2. Der Editor-Run behält das alte `CollisionSystem` (Erkennen ohne Simulation, Antwort 4). 3. Bounciness: der größere Wert der beiden Collider gilt (wie Box2D/Unity 2D), nicht Rapiers Mittelwert. 4. Ein Rigidbody ohne Collider fällt trotzdem (seine Masse, sonst 1 kg). 5. Keinen Rigidbody bekommen Tilemap, Behavior, Collider und Rigidbody selbst; im „+“-Menü sind es jetzt 18 Typen. **Bekannte Grenzen** - Das Bild läuft einen Physik-Schritt (1/60 s) hinterher – Preis der Interpolation. - Kisten mit `Move` schieben geht noch nicht (Dynamic Body blockiert Move-and-Slide wie eine Wand) → #147. - Beim Schieben durch einen Kinematic Body überlappen sich die beiden sichtbar, gemessen ~4 Einheiten bei 120 Einheiten/s. **Tests** ``` npm run check → 104 Files, 1430 Tests (vorher 1386), CHECK EXIT 0 npm run build → Haupt-Bundle 265,03 → 269,39 KB gzip GM_STEPS=1-16,24,25,33,36,38,39,40,41 (frisch geseedet) → ALL PASSED – 622 checks ``` - 21 neue Physik-Tests (Fallen/Liegen auch auf Tilemap, Bounciness 0/0,5/1, Schräge, Impulserhaltung, Masse, Fixed rotation, Parent, Versetzen, Skalieren, Kinematic, fester Schritt, Trigger, Normale/Impuls, Berühren vs. Überlappen, Collider aus). - Die bestehenden Kollisionstests laufen gegen beide Systeme – 23 davon auch mit dem `PhysicsSystem` grün, ohne Toleranz und ohne umgestellte Zahl. - Gegenprobe mit 7 eingebauten Fehlern, jeder von genau einem Test gefangen. - e2e 41b: 7 neue Checks über echtes Menü und echtes Feld; Bounce-Höhen 99,8 → 61,8 → 37,9 px bei Bounciness 0,8. - Nicht eigens getestet: Play-Start während Rapier noch lädt, Zurückschreiben unter gespiegeltem Parent. **CI zu 78899b1:** grün. Gate (Task 130): 104 Testdateien, `CHECK EXIT 0`. Suite (Task 131): `ALL PASSED – 1052 checks` – die Summe ist damit gemessen; Smokes 24/19/21, also laufen auch die drei Beispielspiele mit dem neuen `PhysicsSystem`. **Doku:** Wiki `f690444` – neue Seite *Physics*, nachgezogen Colliders, Concepts, Home, Sidebar, Architecture, Development; README. **Folge-Issues:** #147 (Stufe 1b: Nodes + Character-Controller), #148 (Simulation im Editor), #149 (Stay mit Kontaktdaten). Abgenommen vom User, damit geschlossen.
admin moved this to Done in Development on 2026-09-18 19:18:51 +02:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#146