singe/docs/de/lessons/24-physics.adoc

524 lines
23 KiB
Text

[#_lesson_24_physics]
== Lektion 24: Physik
image::learn/24-physics.png[Die fertige Lektion, 480]
Bis jetzt hat sich alles, was sich in deinen Spielen bewegt hat, bewegt, weil du gerechnet hast.
Eine Zahl ist in jedem Frame um zwei gestiegen; wenn sie über einen Rand hinaus war, hast du sie
umgedreht. Das ist der ehrliche Weg, ein Spiel in Bewegung zu bringen, und die meisten Spiele
bestehen daraus.
Diese Lektion gibt die Rechnerei an jemand anderen ab. Eine *Physik-Engine* simuliert die Welt --
Schwerkraft, Fallen, Rutschen, Stapeln, Abprallen, Rollen und Dinge, die zusammenstoßen -- und
bewegt deine Knoten für dich. Am Ende hast du eine Wanne voller Kisten und eine Kanone, die sie
umwirft. Im letzten Abschnitt geht es darum, wann du nichts davon tun solltest, und das ist der
Teil, den du zweimal lesen solltest.
[#_what_it_does_and_what_it_does_not]
=== Was sie tut und was nicht
Gib einem Knoten einen *Körper*, und die Simulation übernimmt ihn. Von da an fällt er durch die
Schwerkraft, landet auf dem, was unter ihm ist, kippt, wenn er außerhalb der Mitte getroffen
wird, liegt ruhig in einem Stapel, rutscht aus, bis er steht, und der Knoten folgt, wohin die
Simulation ihn auch setzt. Du setzt seine Position nicht mehr selbst. Das ist der Handel: Du
gibst Position und Drehung dieses Knotens ab und bekommst dafür Verhalten, das du nicht schreiben
musstest.
Was sie nicht tut, ist irgendetwas, das dein Spiel betrifft. Sie weiß nicht, was eine Punktzahl
ist, oder dass die rote Kiste die ist, die Punkte bringt, oder dass das Herunterfallen von der
Kante ein Leben kostet. Sie animiert keine gehende Figur, sie weiß nicht, wie sich ein Sprung
anfühlen soll, und sie wird ein Objekt nicht so bewegen, dass du es genau vorhersagen kannst.
Physik ist eine Maschine, die glaubwürdiges Chaos erzeugt.
Die Einheiten sind Meter, Kilogramm und Sekunden. Die Schwerkraft ist `0, -9.81, 0`, wenn du
nichts anderes sagst, und das ist die der Erde, nach unten, in Metern pro Sekunde zum Quadrat.
Eine Kiste, die eine Einheit breit ist, ist einen Meter breit, und sie fällt auch so. Wenn deine
Szene in einem anderen Maßstab gebaut ist, sieht alles aus, als fiele es durch Sirup oder durch
einen Zeichentrickfilm, und die Lösung ist, in Metern zu bauen.
[#_a_floor_and_one_crate]
=== Ein Boden und eine Kiste
Leg einen Ordner an, leg `crates.singe` an und tipp das ein. Es ist eine Szene, eine Platte, eine
Kiste über der Platte und drei Zeilen Physik.
[source,lua]
----
sceneEnable(true)
sceneSetBackground(16, 18, 26)
sceneSetAmbient(40, 42, 50)
local stone = materialNew()
materialSetColor(stone, 110, 110, 120)
materialSetRoughness(stone, 0.9)
local wood = materialNew()
materialSetColor(wood, 190, 140, 80)
materialSetRoughness(wood, 0.8)
local floor = nodeNew()
nodeSetMesh(floor, meshBox(16, 0.4, 16), stone)
nodeSetPosition(floor, 0, -0.2, 0)
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
local crate = nodeNew()
nodeSetMesh(crate, meshBox(0.8, 0.8, 0.8), wood)
nodeSetPosition(crate, 0.3, 4, 0)
nodeSetRotation(crate, 20, 0, 15)
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
bodySetMass(crate, 4)
local sun = lightNew(LIGHT_DIRECTIONAL)
nodeSetPosition(sun, -5, 9, 6)
nodeLookAt(sun, 0, 0, 0)
lightSetIntensity(sun, 1.7)
lightSetShadow(sun, true)
local camera = nodeNew()
nodeSetPosition(camera, 0, 4, 11)
nodeLookAt(camera, 0, 1, 0)
cameraSet(camera)
function onOverlayUpdate()
return OVERLAY_UPDATED
end
----
Starte es mit `Singe -R crates`. Die Kiste fällt aus vier Metern Höhe, landet auf einer Ecke,
weil du sie gekippt hast, schaukelt auf eine Seite und bleibt liegen. Drück *F5*, um es noch
einmal zu starten und es dir noch einmal anzusehen.
Nichts in `onOverlayUpdate` bewegt irgendetwas. Die beiden Aufrufe von `bodyNew` haben alles
erledigt.
[#_what_just_happened_20]
=== Was gerade passiert ist
[source,lua]
----
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
----
`bodyNew` gibt einem Knoten einen Körper. Es nimmt den Knoten, welche Art von Körper es ist,
welche Form die Simulation dafür benutzen soll, und die Größen dieser Form.
Es gibt drei Arten, und die richtige auszuwählen ist der größte Teil der Kunst.
* `BODY_STATIC` bewegt sich nie. Er wird dort hingesetzt, wo der Knoten ist, wenn du den Körper
erzeugst, und dort bleibt er für immer. Böden, Wände, der Erdboden, der Automat, das Level. Den
Knoten danach zu bewegen bewegt den Körper nicht, und das ist eine Falle, die man sich merken
sollte: statisch heißt statisch.
* `BODY_DYNAMIC` wird von der Simulation bewegt. Er fällt, prallt ab, rutscht, wird geschubst und
zieht seinen Knoten mit. Das ist eine Kiste, ein Fass, ein Ball, ein Trümmerstück.
* `BODY_KINEMATIC` geht dorthin, wo *du* seinen Knoten hinsetzt, und schiebt dynamische Körper aus
dem Weg, ohne je selbst geschubst zu werden. Eine bewegliche Plattform, ein Schläger, eine Tür
auf einer Schiene, die schwingende Faust einer Figur. Er ist die Brücke zwischen deiner
Rechnerei und der Simulation: Du steuerst ihn, er steuert sie.
Die Form ist nicht das Mesh. Sie ist ein grober Ersatz, den die Simulation benutzt, weil sie
Fragen über einen Quader viel schneller beantworten kann als über neunhundert Dreiecke.
`SHAPE_BOX` nimmt eine Breite, eine Höhe und eine Tiefe; `SHAPE_SPHERE` nimmt einen Radius;
`SHAPE_CAPSULE` und `SHAPE_CYLINDER` nehmen einen Radius und eine Höhe. Es gibt `SHAPE_HULL`, das
das Mesh unter dem Knoten wie mit Schrumpffolie umhüllt -- die übliche Antwort für ein Modell --
und `SHAPE_MESH`, die exakten Dreiecke, die statische Kulissen benutzen dürfen und ein fallendes
Objekt nicht.
Die Größen richtig hinzubekommen ist deine Sache. Nichts prüft, ob der Körper, den du beschrieben
hast, zu dem Mesh passt, das du gezeichnet hast, und eine Kiste, die mit `0.8` gezeichnet und
mit einem Körper von `2` erzeugt wurde, schwebt über dem Boden und wirft Dinge aus der Entfernung
um. Wenn sich etwas verhält, als hätte es eine andere Größe, dann hat es die auch.
[source,lua]
----
bodySetMass(crate, 4)
----
Die Masse eines dynamischen Körpers ergibt sich aus seiner Form bei der Dichte von Wasser, und
das macht Dinge viel schwerer, als du erwartest: Ein Quader von der Größe einer Tür wiegt
ungefähr hundertvierzig Kilogramm, und nichts, was du darauf werfen kannst, bewegt ihn. Setz die
Masse von allem, was sich leicht anfühlen soll. Vier Kilogramm sind eine Kiste, die du hochheben
könntest.
Die Masse ist wichtig, weil Kräfte durch sie geteilt werden. Derselbe Stoß bewegt ein leichtes
Ding weiter, und es braucht einen Impuls von Masse mal Geschwindigkeit, um etwas mit dieser
Geschwindigkeit in Bewegung zu setzen. Stimm zuerst die Masse ab und danach die Kräfte, sonst
drehst du dich im Kreis.
[#_making_things_move]
=== Dinge in Bewegung bringen
Drei Aufrufe schubsen einen dynamischen Körper herum, und sie sind nicht austauschbar.
`bodySetVelocity(node, x, y, z)` sagt „du bewegst dich jetzt mit dieser Geschwindigkeit“ und
ersetzt, was er vorher getan hat. Das ist ein Aufschlag, ein Abschuss, ein Erscheinen mit schon
etwas Tempo.
`bodyApplyImpulse(node, ix, iy, iz)` ist ein Treffer: ein Tritt, eine Explosion, ein Schläger, der
einen Ball trifft. Er kommt zu dem dazu, was der Körper schon getan hat. Gib ihm als drei weitere
Zahlen einen Punkt in der Welt, und er trifft *dort* statt in der Mitte, sodass eine Kiste, die
oben getroffen wird, umkippt, statt zu rutschen.
`bodyApplyForce(node, fx, fy, fz)` ist ein Schub, der anhält: eine Rakete, ein Ventilator, ein
Förderband. Eine Kraft, die einen Frame lang wirkt, tut fast nichts, und das überrascht Leute,
die einen Impuls erwartet haben.
Die Schwerkraft selbst ist `physicsSetGravity(x, y, z)`, und sie gilt für die ganze Welt. Der
Mond ist `physicsSetGravity(0, -1.62, 0)`, der Weltraum ist alles Nullen, und ein Spiel mit
seitlicher Schwerkraft ist eine Zeile entfernt.
Zwei weitere Einstellungen runden das Gefühl eines Objekts ab. `bodySetBounce(node, amount)`
reicht von `0`, das sofort liegen bleibt, bis `1`, das mit allem zurückkommt, womit es angekommen
ist; der Standard ist ein kaum spürbares `0.1`. `bodySetFriction(node, amount)` ist `0` für Eis
und standardmäßig `0.5`. Beide werden zwischen den zwei Dingen kombiniert, die sich berühren,
also braucht ein vereister Boden die kleine Zahl auf dem Boden, nicht auf allem, was darüber
läuft.
[#_knowing_when_things_touch]
=== Wissen, wann sich Dinge berühren
[source,lua]
----
function onCollision(nodeA, nodeB, x, y, z, speed)
if speed > 4 then
knocks = knocks + 1
end
end
----
`knocks` ist dort eine Variable von dir, die oben in der Datei mit `local knocks = 0` wartet, und
das Spielzeug weiter unten gibt sie aus.
`onCollision` ist ein Callback wie `onOverlayUpdate`: Du schreibst ihn, die Engine ruft ihn auf.
Er kommt einmal für jeden neuen Kontakt -- zwei Körper, die sich weiter berühren, melden sich
nicht noch einmal -- mit beiden Knoten, dem Punkt in der Welt, an dem sie sich getroffen haben,
und wie schnell sie sich getroffen haben. Durch die Geschwindigkeit können ein Anstupsen und ein
Krachen verschieden klingen, und sie ist der Grund, warum der Test oben das sanfte Setzen eines
Stapels ignoriert.
Die andere Hälfte des Paars ist `onTrigger`. Mach einen Körper mit `bodySetTrigger(node, true)`
zu einem Trigger, und er hört auf, Dinge zu schieben: Er wird zu einem Bereich, der über
`onTrigger(trigger, other, entered)` meldet, was hineinkommt und was ihn verlässt. Eine
Ziellinie, ein Türdurchgang, eine Grube, ein Gegenstand zum Aufsammeln. Er ist der 3D-Nachfahre
der Kollisionsprüfungen aus Lektion elf, und für alles, was nach „hat der Spieler diese Stelle
erreicht“ aussieht, ist er das, was du willst, statt eines festen Körpers.
[#_the_toy]
=== Das Spielzeug
Jetzt das Ganze. Fang mit dem Skript von oben an und bau es hierzu um: eine Wanne mit vier
Wänden, sechs Kisten in einer Pyramide und eine Stahlkugel, die du ausrichten und abschießen
kannst. Die Teile kennst du alle schon.
Alles, was sich der neue Code merkt, kommt ganz oben in die Datei, über die Szene:
[source,lua]
----
local crates = {}
local balls = {}
local aimX = 0
local aimLeft = false
local aimRight = false
local knocks = 0
local showShapes = false
----
Zu den Materialien kommen zwei weitere gemeinsam genutzte Handles: ein poliertes Metall für die
Kugel und die Zielmarkierung und ein Kisten-Mesh für alle sechs Kisten.
[source,lua]
----
local steel = materialNew()
materialSetColor(steel, 200, 205, 215)
materialSetMetallic(steel, 1)
materialSetRoughness(steel, 0.25)
local crateMesh = meshBox(0.8, 0.8, 0.8)
----
Die Wände der Wanne sind vier statische Quader, gemacht in einer Schleife aus einer Tabelle von
Platzierungen, und das ist Lektion sechs, die ehrliche Arbeit leistet:
[source,lua]
----
local wallMesh = meshBox(16, 1, 0.4)
for _, side in ipairs({ { 0, -8, 0 }, { 0, 8, 0 }, { -8, 0, 90 }, { 8, 0, 90 } }) do
local wall = nodeNew()
nodeSetMesh(wall, wallMesh, stone)
nodeSetPosition(wall, side[1], 0.5, side[2])
nodeSetRotation(wall, 0, side[3], 0)
bodyNew(wall, BODY_STATIC, SHAPE_BOX, 16, 1, 0.4)
end
----
Jeder Eintrag ist ein X, ein Z und eine Drehung um Y. Zwei Wände liegen quer hinten und vorn;
zwei sind um neunzig Grad gedreht und bilden die Seiten. Ein Mesh reicht für alle vier, und der
Körper wird erst erzeugt, nachdem der Knoten platziert und gedreht ist, weil ein statischer
Körper die Position und Drehung des Knotens aus dem Moment übernimmt, in dem er erzeugt wird.
Die Kisten kommen aus einer Funktion, damit sechs davon sechs Zeilen sind:
[source,lua]
----
local function crateAt(x, y)
local crate = nodeNew()
nodeSetMesh(crate, crateMesh, wood)
nodeSetPosition(crate, x, y, -2)
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
bodySetMass(crate, 4)
crates[#crates + 1] = crate
end
----
Sie bewahrt jede Kiste, die sie macht, in der Tabelle `crates` auf, und so kann der
Zeichen-Callback später allen eine Frage stellen. Sechs Aufrufe bauen die Pyramide, drei auf dem
Boden, zwei darauf und eine obendrauf:
[source,lua]
----
crateAt(-0.9, 0.4)
crateAt(0, 0.4)
crateAt(0.9, 0.4)
crateAt(-0.45, 1.25)
crateAt(0.45, 1.25)
crateAt(0, 2.1)
----
Die oberen Reihen sitzen eine Fingerbreite über denen darunter und fallen in dem Moment an ihren
Platz, in dem das Spiel startet. Eine kleine Lücke zu lassen ist einfacher, als genau zu stapeln,
und es ist freundlicher zur Simulation, als ihr einen Haufen zu geben, der schon zusammengepresst
ist.
Jeder Schuss macht eine neue Kugel:
[source,lua]
----
local function fire()
local ball = nodeNew()
nodeSetMesh(ball, meshSphere(0.35, 24), steel)
nodeSetPosition(ball, aimX, 1.2, 6)
bodyNew(ball, BODY_DYNAMIC, SHAPE_SPHERE, 0.35)
bodySetMass(ball, 3)
bodySetBounce(ball, 0.3)
bodySetVelocity(ball, 0, 2, -14)
balls[#balls + 1] = ball
if #balls > 5 then
nodeDelete(balls[1])
table.remove(balls, 1)
end
end
----
Sieh dir die Reihenfolge an. Der Knoten wird erzeugt, bekommt eine Form zum Zeichnen, wird
*platziert* und bekommt erst dann einen Körper. Ein dynamischer Körper fängt dort an, wo sein
Knoten ist, und danach läuft der Verkehr andersherum: Der Körper bewegt den Knoten. Wenn du die
Position eines dynamischen Knotens danach selbst setzt, kämpfst du gegen die Simulation, und die
Simulation gewinnt. Wenn etwas bewegt werden muss, gib ihm eine Geschwindigkeit, triff es mit
einem Impuls, oder mach es wie hier -- erzeug ein neues und lass das alte gehen.
Das alte gehen zu lassen ist `nodeDelete`, das den Knoten freigibt, alles darunter und den
Körper, der darauf mitfuhr. Ohne diese drei Zeilen bliebe jeder Schuss für immer in der Welt, und
ein paar hundert Kugeln später würde die Simulation eine Menge Arbeit für Dinge erledigen, die
niemand sehen kann. Aufräumen gehört dazu, wenn man Physik benutzt.
Die Steuerung und die Anzeige sind der Rest:
[source,lua]
----
function onInputPressed(what)
if what == SWITCH_BUTTON1 then
fire()
elseif what == SWITCH_BUTTON3 then
showShapes = not showShapes
if showShapes then
physicsSetDebug(DEBUG_SHAPES + DEBUG_CONTACTS)
else
physicsSetDebug(DEBUG_NONE)
end
elseif what == SWITCH_LEFT then
aimLeft = true
elseif what == SWITCH_RIGHT then
aimRight = true
end
end
----
Die Leertaste feuert, Umschalt schaltet die Physikansicht ein und aus, und mit gedrückten
Pfeiltasten verschiebst du den Abschusspunkt. Eine flache Metallplatte ohne Körper, die in jedem
Frame auf `aimX` gesetzt wird, zeigt, woher die nächste Kugel kommt. Das vollständige Skript, mit
dem Callback fürs Loslassen und dem Rest der Einrichtung, ist `24-physics.singe` im Ordner
`learn`.
`physicsSetDebug` ist der nützlichste Aufruf in dieser Lektion. Er zeichnet, was die Simulation
tatsächlich enthält -- die echten Formen als Drahtgitter und ein rotes Kreuz überall dort, wo
sich in diesem Schritt zwei Körper berührt haben -- über deine Szene. Wenn zum ersten Mal eine
Kiste eine Handbreit über dem Boden schwebt, durch ihn hindurchfällt oder etwas aus zwei Metern
Entfernung umwirft, schalte das ein, und die Antwort steht auf dem Bildschirm. Füg
`DEBUG_STATIC` hinzu, um auch den Boden und die Wände zu sehen. Solange es aus ist, kostet es
nichts.
Zum Schluss das Zählen:
[source,lua]
----
function onOverlayUpdate()
local over = 0
local settled = true
if aimLeft then
aimX = math.max(aimX - 0.08, -5)
end
if aimRight then
aimX = math.min(aimX + 0.08, 5)
end
nodeSetPosition(marker, aimX, 0.05, 6)
for _, crate in ipairs(crates) do
local pitch, _, roll = nodeGetRotation(crate)
if math.abs(pitch) > 30 or math.abs(roll) > 30 then
over = over + 1
end
if not bodyIsResting(crate) then
settled = false
end
end
overlayClear()
overlayPrint(2, 2, "Arrows aim. Space fires. Shift shows the shapes.")
overlayPrint(2, 4, "knocked over " .. over .. " of " .. #crates .. " hard hits " .. knocks)
if settled then
overlayPrint(2, 6, "everything has settled")
end
return OVERLAY_UPDATED
end
----
Eine Kiste zählt als umgeworfen, wenn sie mehr als dreißig Grad geneigt ist, und das ist eine
Spielregel, geschrieben in einer Zeile Rechnerei, die auf dem aufsetzt, was die Simulation getan
hat. `nodeGetRotation` liefert drei Winkel, und hier werden der erste und der dritte gebraucht,
also wird der mittlere in einer Variablen namens `_` aufgefangen. Dieser Unterstrich ist ein
gewöhnlicher Name, an dem nichts Besonderes ist, und wird nach Konvention benutzt, um zu sagen:
„Den hier ignoriere ich.“
`bodyIsResting` sagt, ob die Simulation einen Körper schlafen gelegt hat, weil er sich nicht mehr
bewegt. Daran erkennst du, dass ein Schuss vorbei ist: kein Timer, kein Raten, sondern jede Kiste
schläft. Ein Bowlingspiel wertet seinen Frame genau an dieser Stelle.
[#_when_not_to_use_it]
=== Wann du sie nicht benutzen solltest
Physik ist das verlockendste Werkzeug in jeder Engine und das, das die meisten Wochenenden
verschwendet. Drei Dinge sind über sie wahr, und keins davon ist die Schuld der Engine.
*Sie ist teuer.* Jeder dynamische Körper ist Arbeit in jedem Schritt, sechzigmal pro Sekunde, ob
ihn jemand ansieht oder nicht. Vierzig Kisten sind in Ordnung. Viertausend nicht, und auf einem
kleinen Rechner auch vierhundert nicht.
*Sie ist unberechenbar.* Starte dein Spielzeug zweimal, und die Kisten landen anders. Das lässt es
lebendig aussehen, und es ist auch der Grund, warum sich ein Spiel bei nichts darauf verlassen
kann, was jedes Mal gleich ausgehen muss. Eine Tür, die aufgehen muss, eine Plattform, die
ankommen muss, ein Sprung, den der Spieler schaffen können muss: Das ist Rechnerei, oder ein
Tween, oder ein kinematischer Körper auf einem Pfad, den du geschrieben hast. Überlass nie etwas,
das der Spieler tun muss, einer Simulation, die es hinter eine Kiste fallen lassen kann.
*Sie ist schwer abzustimmen.* Die Masse, die Reibung, das Abprallen, die Form und die Größe ziehen
alle aneinander, und „es fühlt sich falsch an“ hat selten nur eine Ursache. Jede Stunde, die du
damit verbringst, dass sich eine Physik-Kiste gut anfühlt, ist eine Stunde, die du nicht mit dem
Spiel verbracht hast.
Greif also zu ihr, wenn das Chaos der eigentliche Zweck *ist*: Dinge, die umkippen, fliegende
Trümmer, ein Haufen, der sich setzt, ein Ball, der durch einen Raum scheppert, ein Fahrzeug, das
über unebenes Gelände rollt, eine Ragdoll, die eine Treppe hinunterpurzelt. Das kann niemand von
Hand schreiben, und niemand kann dir genau sagen, wie es aussehen soll.
Greif nicht zu ihr, wenn du weißt, was passieren soll. Ein Geschoss, das geradeaus fliegt, sind
zwei Zeilen aus Lektion zwei. Eine Plattform, die hoch- und runterfährt, ist `math.sin`. Eine
Spielfigur, die so geht und springt, wie Spieler es erwarten, will einen Character-Controller
(`playerNew`, in der Referenz), weil ein Mensch kein Quader ist und sich nie wie einer verhält.
Ein Gegner, der patrouilliert, will einen Pfad. Ein Menü, das hereingleitet, will einen Tween.
Die Regel, die am besten taugt: Benutz Physik für die Dinge, denen der Spieler zusieht, und
Rechnerei für die Dinge, die der Spieler tut.
[#_try_it_24]
=== Probier es aus
. *Ändere die Welt.* Füg `physicsSetGravity(0, -1.62, 0)` weit oben ein und feuere noch einmal.
Dann probier `0, -30, 0`. Dieselben Kisten, ein anderer Planet, eine Zeile.
. *Ändere die Kugel.* Mach die Kugel mit `bodySetMass(ball, 30)` zehnmal schwerer, dann mit
`0.3` zehnmal leichter, und lass die Geschwindigkeit gleich. Dann setz die Masse zurück und
ändere stattdessen die `-14`. Zwei Wege, härter zuzuschlagen, und sie fühlen sich nicht gleich
an.
. *Mach die Kisten zu Eis.* Füg in `crateAt` `bodySetFriction(crate, 0.02)` ein und füg eine
Zeile hinzu, die auch dem Boden eine niedrige Reibung gibt. Sieh zu, wie sich der Haufen von
vornherein weigert, stehen zu bleiben.
. *Mach die Form absichtlich falsch.* Ändere in `crateAt` die Größen des Körpers auf `2, 2, 2`,
während das Mesh bei `0.8` bleibt. Starte es, sieh zu, wie die Kisten schweben und sich aus dem
Nichts schubsen, dann drück Umschalt und sieh zu, wie die Debug-Ansicht es in einem Bild
erklärt.
. *Werte es richtig.* Benutz `bodyIsResting` auf jeder Kiste, um zu bemerken, wann der Schuss
vorbei ist, und zähl erst dann, wie viele umgefallen sind, und addier das zu einer Punktzahl.
Das ist die Wertungshälfte eines Bowlingspiels.
[#_break_it_on_purpose_23]
=== Mach es absichtlich kaputt
Füg unter dem `bodyNew` des Bodens eine Zeile ein, die den Boden schwer macht, und das ist etwas,
von dem man vernünftigerweise denken könnte, dass man es will:
[source,lua]
----
bodySetMass(floor, 200)
----
Singe hält das Spiel mit etwas wie dem hier an:
----
32:bodySetMass: Node 1 is not a dynamic body, or the mass is not positive.
----
Die erste Zahl ist die Zeile in deiner eigenen Datei, also wird deine anders sein.
Die Zahl nach `Node` ist das Handle des Knotens, die Zahl, die `nodeNew` zurückgegeben hat. Die
Beschwerde ist genau: Masse gehört zu Dingen, die die Simulation bewegt, und ein statischer
Körper wird von nichts bewegt, also hat er keine Masse, die man setzen könnte. Dass du danach
fragst, ist ein Zeichen, dass der Körper die falsche Art für das ist, was du im Sinn hattest --
und wenn du wirklich einen schweren Boden willst, der sich verschieben lässt, dann willst du einen
dynamischen Körper mit großer Masse oder einen kinematischen, den du selbst bewegst.
Dieselbe Meldung erscheint bei einer Masse von `0`, die Leute schreiben, wenn sie „schwerelos“
meinen. Schwerelos ist keine Masse. Es ist eine Welt ohne Schwerkraft oder
`playerSetGravityScale` auf einem Character-Controller.
[#_what_you_learned_24]
=== Was du gelernt hast
* Ein Körper hängt an einem Knoten, und von da an sind Knoten und Körper ein Ding.
* Statisch bewegt sich nie, dynamisch wird von der Simulation bewegt, und kinematisch wird von
dir bewegt und schiebt alles andere beiseite.
* Die Kollisionsform ist ein Ersatz für das Mesh, und du bist dafür verantwortlich, dass ihre
Größen zu dem passen, was du gezeichnet hast.
* Die Masse ist standardmäßig das Volumen der Form bei der Dichte von Wasser, also setz sie bei
allem, was sich leicht anfühlen soll.
* `bodySetVelocity` ersetzt die Bewegung eines Körpers, `bodyApplyImpulse` ist ein Treffer, und
`bodyApplyForce` ist ein Schub, der anhält.
* Setz nicht die Position eines dynamischen Knotens; lösch ihn und mach einen neuen, wenn du ihn
neu anfangen lassen musst.
* `onCollision` meldet jeden neuen Kontakt mit seinem Punkt und seiner Geschwindigkeit, und ein
Trigger-Körper meldet Ankünfte und Abgänge über `onTrigger`.
* `bodyIsResting` sagt dir, wann alles stillsteht, und dann ist meistens ein Zug vorbei.
* `physicsSetDebug` zeichnet die eigene Sicht der Simulation, und es beantwortet die meisten
Physikfragen schneller, als Code zu lesen.
* Physik ist teuer, nicht wiederholbar und fummelig. Benutz sie für das Chaos, nicht für die
Dinge, von denen das Spiel abhängt.
[#_next_time_24]
=== Beim nächsten Mal
Fallende Kisten sind eine Art von Spektakel. Die andere Art besteht aus tausend winzigen Dingen,
die eine halbe Sekunde leben: Funken, Rauch, Feuer, Regen und Staub. In der nächsten Lektion geht
es um Emitter, und sie endet mit einer Explosion. Das ist Lektion fünfundzwanzig.