singe/docs/de/lessons/11-hitting-things.adoc

491 lines
22 KiB
Text

[#_lesson_11_hitting_things]
== Lektion 11: Dinge treffen
image::learn/11-hitting-things.png[Die fertige Lektion, 480]
Dein Spiel aus Lektion zehn funktioniert. Schüsse zerbrechen Felsen, Felsen kosten Leben, und die
Explosionen knallen an den richtigen Stellen. Sieh aber genau hin, und du merkst, dass es
schummelt, und zwar in beide Richtungen. Ein Felsen, der an der Flügelspitze vorbeigleitet, kostet
ein Leben. Ein Schuss, der neben einem Felsen durch den leeren Himmel fliegt, zerbricht ihn
trotzdem.
Zwei Dinge in einem Spiel berühren sich, wenn die Zahlen das sagen. Niemand sieht sich die Bilder
an. Diese Lektion bringt die Zahlen mit dem in Einklang, was der Spieler sieht: Sie tauscht deinen
selbst geschriebenen Test gegen den der Engine, verkleinert die geprüften Boxen, bis sie zu den
Dingen darin passen, und gibt dir einen Schalter, der dir diese Boxen zeigt. Das Bild oben ist das
Spiel mit eingeschaltetem Schalter.
[#_where_you_are]
=== Wo du stehst
Das Spiel fragt an genau zwei Stellen „berühren sich diese beiden?“, und beide benutzen
`overlapping`, die Funktion, die du in Lektion neun eingetippt hast. Die eine steht in
`updateRocks`, wo ein Felsen auf das Schiff trifft:
[source,lua]
----
if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
----
Die andere steht in `updateShots`, wo ein Schuss auf einen Felsen trifft:
[source,lua]
----
if not gone and overlapping(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
----
Alles in dieser Lektion ändert diese beiden Zeilen und fügt ein paar kleine Funktionen hinzu, die
sie füttern. Sonst ändert sich am Spiel nichts. Das ganze fertige Skript liegt in
`learn/11-hitting-things.singe`, falls du dich verläufst.
[#_read_it_once_more]
=== Lies es noch einmal
Diesen Teil hat Lektion neun schon erledigt: `overlapping` ist Rechteckkollision, von Hand
ausgeschrieben. Lies es noch einmal, bevor du es wegwirfst, denn es ist die ganze Idee:
[source,lua]
----
function overlapping(ax, ay, aw, ah, bx, by, bw, bh)
if ax + aw <= bx then
return false
end
if bx + bw <= ax then
return false
end
if ay + ah <= by then
return false
end
if by + bh <= ay then
return false
end
return true
end
----
Direkt ist die Frage „berühren sich diese beiden?“ schwer zu beantworten, rückwärts ist sie
leicht. Zwei Rechtecke berühren sich *nicht*, wenn eins von ihnen ganz links vom anderen liegt,
oder ganz rechts, oder ganz darüber, oder ganz darunter. Eine fünfte Art, sich zu verfehlen, gibt
es nicht. Wenn keine dieser vier stimmt, berühren sie sich.
Jedes `if` ist eine der vier Arten, sich zu verfehlen. `ax + aw` ist der rechte Rand des ersten
Rechtecks, also liest sich `ax + aw <= bx` als „das erste hört auf, bevor das zweite anfängt“, und
genau das bedeutet „ganz links“ in Zahlen. Dieselbe Form noch dreimal, für rechts, darüber und
darunter, mit `y` und den Höhen statt `x` und den Breiten, und dann bleibt nur noch `true` übrig.
Das sind die acht Zahlen, die jeder Kollisionstest braucht: eine Ecke und eine Größe für jedes der
beiden Dinge. Behalt die Form im Kopf, denn gleich löschst du sie.
[#_the_engine_already_has_it]
=== Die Engine hat es schon
`collideRects` kennst du aus Lektion sieben, wo es in Dodge die fallenden Blöcke erwischt hat.
Lektion neun hat denselben Test von Hand ausgeschrieben, damit du hineinsehen kannst. Du hast
hineingesehen. Lösch `overlapping` und ändere die beiden Zeilen aus *Wo du stehst* so, dass sie
stattdessen die Engine aufrufen:
[source,lua]
----
if collideRects(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
----
[source,lua]
----
if not gone and collideRects(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
----
Dieselben acht Zahlen, in derselben Reihenfolge: Ecke und Größe des ersten Rechtecks, dann die des
zweiten. Starte es, und das Spiel spielt sich genau wie vorher, und genau darum geht es. Der
einzige Unterschied liegt ganz am Rand. Dein `<=` hat gesagt, dass sich zwei Rechtecke, die sich
an einer Kante treffen, nicht berühren. `collideRects` sagt, dass sie es tun, und ein Rechteck ohne
Breite oder Höhe berührt überhaupt nichts. In solchen kniffligen Fällen gehen selbst geschriebene
Versionen still und leise schief, und sie sind ein Grund, die der Engine zu benutzen.
`collideRects` gehört zu einer Familie von Kollisionstests, und die Familie ist ein Teil dessen,
was dieses Buch das *Spiele-Kit* nennt: eine Handvoll schlichter Funktionen für die Dinge, die
früher fast jedes Spiel für sich selbst geschrieben hat. Zu prüfen, ob sich zwei Dinge berühren,
ist eins davon. Eine Zahl fürs nächste Mal aufzuheben ist ein anderes, und das ist Lektion zwölf.
Eine Weile warten, eine Zahl sanft von einem Wert zum anderen gleiten lassen und zeigen, wie lange
jeder Frame gedauert hat, sind der Rest. Die Referenz gibt jedem Teil einen eigenen Abschnitt:
Kollision, Spielstand, Timer, Tween und Statistik.
Die Kollisionstests sind die schlichtesten von allen: schlichte Fragen, ohne Handles, die man
aufheben muss, und ohne etwas, das man einrichten oder freigeben muss. Und keiner davon bewegt
irgendetwas. `collideRects` sagt dir, dass Schiff und Felsen am selben Ort sind; was du dagegen
tust, entscheidest du, und alles Interessante an einem Spiel passiert in dieser Entscheidung.
[#_the_picture_is_not_the_hit_box]
=== Das Bild ist nicht die Hitbox
Lektion zehn hat versprochen zu erklären, warum das Rechteck um einen runden Felsen die falsche
Form hat. Das Rechteck um das Schiff ist auch falsch, und zwar aus demselben Grund.
Zeichne das Rechteck des Schiffs, 32 mal 24, auf Papier und mal das Schiff darin aus. Es ist ein
Dreieck mit zwei Flossen, also ist in beiden oberen Ecken und zwischen den Flossen leerer Himmel.
Der Felsen ist rund, also ist in allen vier Ecken seines Quadrats leerer Himmel. Ein Felsen, der
die obere Ecke des Schiffsrechtecks streift, hat das Schiff nicht getroffen. Er ist am Flügel
vorbeigeflogen, und ein Spieler, der dafür ein Leben verliert, wird sagen, dass das Spiel
geschummelt hat -- und er hat recht.
Die Box, die du prüfst, ist also nicht das Bild, das du zeichnest. Sie ist kleiner, und sie sitzt
im Bild. Füg den Konstanten oben in deiner Datei drei Zahlen hinzu:
[source,lua]
----
local SHIP_PAD_X = 6
local SHIP_PAD_Y = 5
local ROCK_PAD = 4
----
Der Abstand sagt, wie weit vom jeweiligen Rand nach innen das echte Ding anfängt. Sechs Pixel weg
an jeder Seite des Schiffs und fünf oben und unten lassen eine Box von 20 mal 14 in der Mitte eines
Bilds von 32 mal 24 übrig, und da ist ungefähr der Rumpf. Vier weg an jeder Seite des Felsens
lassen 16 mal 16 übrig, und das ist fast nur Felsen.
Beide Boxen sind ein bisschen kleiner als die Dinge, für die sie stehen, und das ist Absicht. Eine
Box, die etwas zu klein ist, beschert dem Spieler Beinahe-Treffer, die sich wie Können anfühlen.
Eine Box, die etwas zu groß ist, beschert ihm Treffer, die sich wie Schummelei anfühlen. Spieler
verzeihen das Erste und nie das Zweite.
Statt diese Rechnung bei jedem Test auszuschreiben, schreib sie einmal pro Art von Ding:
[source,lua]
----
function rockBox(rock)
return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2
end
function shipBox()
return shipX + SHIP_PAD_X, shipY + SHIP_PAD_Y, shipWidth - SHIP_PAD_X * 2, shipHeight - SHIP_PAD_Y * 2
end
----
Die Größen kommen aus `rockWidth`, `shipWidth` und ihren Freunden, den Messungen, die Lektion neun
an den Bildern vorgenommen hat, also steht im Spielcode immer noch keine einzige nackte
Pixelgröße. Zeichne das Schiff größer, und die Box wächst mit.
Jede dieser Funktionen gibt vier Zahlen auf einmal zurück, durch Kommas getrennt. Eine Funktion
darf so viele Werte zurückgeben, wie sie will, und auffangen kannst du sie mit einer Liste von
Namen links vom `=`:
[source,lua]
----
function shipHitsRock(rock)
local sx, sy, sw, sh = shipBox()
local rx, ry, rw, rh = rockBox(rock)
return collideRects(sx, sy, sw, sh, rx, ry, rw, rh)
end
----
Jetzt liest sich der Test in `updateRocks` wie die Frage, die er stellt:
[source,lua]
----
if shipHitsRock(rock) then
----
Die Rechnung mit dem Abstand steht an genau zwei Stellen, und die Zahlen, die sie benutzt, stehen
oben in der Datei, also kannst du einstellen, wie fair sich das Spiel anfühlt, indem du eine Zahl
änderst, statt deine Tests zu durchsuchen.
Setz die neuen Funktionen an ihre Plätze im Alphabet, wie Lektion neun es verlangt hat: `rockBox`
nach `onSoundCompleted`, dann `shipBox` und `shipHitsRock`.
[#_firing_back]
=== Zurückschießen
Damit ist die Seite des Schiffs erledigt. Die andere Kollision im Spiel ist die, mit der du
zurückschießt: jeder Schuss gegen jeden Felsen, in `updateShots`. Sie hat dasselbe Problem wie das
Schiff, nur in klein. Ein Schuss ist ein Rechteck von 4 mal 10, das gegen das ganze Quadrat des
Felsens von 24 mal 24 geprüft wird, also zerbricht ein Schuss, der nur durch die leere Ecke neben
einem Felsen fliegt, ihn trotzdem.
Du könntest dem Schuss eine eigene Box mit Abstand geben. Sieh dir aber zuerst die Zahlen an. Zieh
von einem Schuss, der vier Pixel breit ist, einen Abstand ab, und es bleibt fast nichts übrig, und
ein ganzes Rechteck zu prüfen, nur um herauszufinden, ob ein Lichtsplitter etwas berührt hat, ist
mehr Maschinerie, als die Frage verdient.
[#_a_shot_is_almost_a_point]
=== Ein Schuss ist fast ein Punkt
Prüf stattdessen die Spitze. `collidePointRect` fragt, ob ein Punkt in einem Rechteck liegt --
zwei Zahlen für den Punkt, dann Ecke und Größe des Rechtecks:
[source,lua]
----
function shotHitsRock(shot, rock)
local rx, ry, rw, rh = rockBox(rock)
return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh)
end
----
`shot.y` ist die Oberkante des Schusses, und `shot.x + shotWidth / 2` liegt auf seiner halben
Breite, also ist der geprüfte Punkt die Mitte der Nase des Schusses. Dort glaubt ein Spieler, dass
ein Schuss ist. Auf der Seite des Felsens steht dasselbe `rockBox`, das auch der Test des Schiffs
benutzt, also ist ein Felsen gleich groß, egal was ihn trifft.
Die Funktion kommt nach `shipHitsRock`, und der Test in `updateShots` wird zu:
[source,lua]
----
if not gone and shotHitsRock(shot, rock) then
----
[#_deciding_what_happened]
=== Entscheiden, was passiert ist
Keiner der Kollisionstests tut etwas. Sie antworten, und dein Code entscheidet. Hier sind die
beiden Stellen, die entscheiden, mit ihren neuen Fragen darin. Zuerst `updateRocks`:
[source,lua]
----
if shipHitsRock(rock) then
local channel = soundPlay(boomClip)
newRock(rock)
lives = lives - 1
if lives <= 0 then
dying = true
deathChannel = channel
if channel < 0 then
endGame()
end
end
end
----
Und die Schleife in `updateShots`:
[source,lua]
----
for _, rock in ipairs(rocks) do
if not gone and shotHitsRock(shot, rock) then
newRock(rock)
score = score + HIT_SCORE
gone = true
soundPlay(boomClip, 0, 45)
end
end
----
Alles unter den Fragen ist genau das, was Lektion zehn dort hinterlassen hat. Ein Felsen, der das
Schiff trifft, knallt, geht zurück nach oben und kostet ein Leben. Ein Felsen, der abgeschossen
wird, knallt, geht zurück nach oben und bringt Punkte. Der Schuss verschwindet auch, und das
`not gone` verhindert, dass ein Schuss zwei Felsen zerbricht. Ändere irgendetwas davon, und du
änderst das Spiel, ohne einen einzigen Kollisionstest anzufassen, und genau darum werden die
Fragen und die Antworten getrennt gehalten.
Starte es. Flieg dicht an einem Felsen vorbei und lass ihn eine Flosse streifen: Du behältst das
Leben. Schieß knapp neben einen Felsen, und der Schuss segelt vorbei. Schieß mitten in einen
hinein, und er zerbricht, wie immer.
[#_seeing_the_boxes]
=== Die Boxen sehen
Die Hitboxen sind unsichtbar, und damit ist auch ein Fehler darin unsichtbar. Zeichne sie, während
du arbeitest. Noch eine Konstante oben:
[source,lua]
----
local SHOW_BOXES = false
----
Zwei weitere Funktionen, und die kommen ganz an den Anfang des Alphabets:
[source,lua]
----
function drawBox(x, y, width, height)
overlayBox(x, y, x + width - 1, y + height - 1)
end
function drawBoxes()
colorForeground(0, 255, 0)
drawBox(shipBox())
for _, rock in ipairs(rocks) do
drawBox(rockBox(rock))
end
end
----
Und in `onOverlayUpdate`, nachdem das Schiff gezeichnet ist und bevor die Punkte obendrauf kommen:
[source,lua]
----
spriteDraw(shipSprite, shipX, shipY)
if SHOW_BOXES then
drawBoxes()
end
drawHud()
----
Ändere `SHOW_BOXES` in `true`, und grüne Umrisse erscheinen über den Grafiken, wie im Bild oben
in dieser Lektion. Ändere es zurück, wenn du fertig bist. Ein Schalter wie dieser, ein Wort oben in
einer Datei, das ein Bild davon einschaltet, was das Programm glaubt, ist mehr wert als noch so
viel Anstarren.
[#_what_just_happened_7]
=== Was gerade passiert ist
[source,lua]
----
function rockBox(rock)
return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2
end
----
Vier Werte aus einem `return`, durch Kommas getrennt. Lua gibt gern so viele zurück, wie du willst,
und der Aufrufer entscheidet, wie viele er behält. Wenn so ein Aufruf das *Letzte* in den Klammern
eines anderen Aufrufs ist, werden alle vier weitergereicht, und darum funktioniert
`drawBox(rockBox(rock))` und gibt `drawBox` seine vier Argumente. Überall sonst in einer
Argumentliste überlebt nur der erste Wert. Diese Regel beißt genau einmal zu, und zwar am Ende
dieser Lektion.
[source,lua]
----
return collideRects(sx, sy, sw, sh, rx, ry, rw, rh)
----
`collideRects` will acht Zahlen: Ecke und Größe, Ecke und Größe. Es gibt `true` oder `false`
zurück und ändert nichts. Weil die Antwort mit `return` direkt wieder aus der Funktion herauskommt,
liest sich `shipHitsRock(rock)` überall, wo es benutzt wird, wie eine Frage.
[source,lua]
----
return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh)
----
`collidePointRect` will sechs: einen Punkt, dann ein Rechteck. Der Rand zählt als innen, also
trifft ein Schuss genau auf der Grenze. Benutz es immer dann, wenn eine Seite der Frage so klein
ist, dass ihre Größe keine Rolle spielt.
[source,lua]
----
colorForeground(0, 255, 0)
overlayBox(x, y, x + width - 1, y + height - 1)
----
Beide kennst du aus Lektion sieben. `overlayBox` nimmt zwei *Ecken*, nicht eine Ecke und eine
Größe -- die einzige Stelle in dieser Lektion, an der die Zahlen ihre Form ändern -- und das `- 1`
ist da, weil beide Ecken dazugehören. `colorForeground` ist ein Schalter, der umgelegt bleibt, also
kommt alles, was `drawBoxes` zeichnet, grün heraus. Sprites und `overlayPrint` kümmern sich nicht
darum, also sieht der Rest des Spiels aus wie vorher.
[#_the_rest_of_the_family]
=== Der Rest der Familie
`collideRects` und `collidePointRect` tragen die meisten 2D-Spiele. Die anderen sind für die
Formen da, die Rechtecke schlecht beschreiben, und jede ist dieselbe Art von schlichter Frage:
* `collideCircles` und `collidePointCircle`, für alles Runde. Ein Kreis ist ein Mittelpunkt und
ein Radius.
* `collideRectCircle`, für ein rundes Ding gegen ein eckiges.
* `collidePointPolygon`, für eine Form mit eigenen Ecken -- ein Drache, ein Kontinent, ein seltsam
geformter Knopf. Du gibst ihm eine flache Liste von Zahlen: `x`, `y`, `x`, `y`.
* `collideSegments`, für zwei Strecken, die sich kreuzen.
Ehrlich gesagt ist dein Felsen rund, und die Kreistests passen besser zu ihm als jede Box. Der
Abstand ist die billige Antwort, und für die meisten Spiele reicht er, darum kommt er zuerst. Die
genaue Antwort ist eins der Dinge, die du unten ausprobieren kannst.
`collideSegments` ist wichtiger, als es klingt. Dein Schuss bewegt sich sechs Pixel pro Frame, und
die Felsen haben sechzehn Pixel Hitbox, also kann ein Schuss nicht an einem vorbei, ohne in ihm
geprüft zu werden. Mach den Schuss schnell genug -- vierzig Pixel pro Frame -- und er springt von
unterhalb eines Felsens nach oberhalb davon, ist kein einziges Mal am selben Ort wie der Felsen
und trifft nie irgendetwas. Die Linie von dort, wo der Schuss war, bis dorthin, wo er jetzt ist, zu
ziehen und `collideSegments` zu fragen, ob diese Linie einen Rand kreuzt, ist das Heilmittel.
Langsame Dinge brauchen es nicht.
Es gibt einen Kollisions-Callback in Singe, `onCollision`, und der ist nicht dafür da. Er gehört
zur 3D-Physik-Engine: Körper mit Masse, die in drei Dimensionen fallen und voneinander abprallen
und dir sagen, wo sie sich berührt haben und wie heftig. Du lernst ihn in Lektion vierundzwanzig
kennen. Nichts auf dem Overlay meldet sich von selbst, und nichts muss das. Deine Felsen sind in
einer Liste, du weißt, wie viele es sind, und Fragen ist billig.
Und wenn du nicht nur wissen willst, *ob* sich zwei Dinge getroffen haben, sondern auch, wo das
bewegte landen soll -- an einer Wand entlanggleiten, statt mitten in ihr stehen zu bleiben -- dann
beantworten die Kollisionsaufrufe das mit Absicht nicht. Für genau diese Aufgabe liegt Singe eine
Bibliothek namens `bump` bei, und der Abschnitt Mitgelieferte Bibliotheken in der Referenz sagt,
wie du an sie herankommst.
[#_try_it_11]
=== Probier es aus
. *Nimm den Abstand raus.* Setz `ROCK_PAD` auf `0` und spiel eine Minute. Dann setz es auf `8`
und spiel noch einmal. Bei einem davon fühlt es sich an, als würde das Spiel dich anlügen.
. *Sieh dir an, was du prüfst.* Setz `SHOW_BOXES` auf `true` und beobachte die grünen Umrisse,
während ein Felsen am Schiff vorbeizieht. Jetzt setz `SHIP_PAD_X` auf `16`, spiel und achte
darauf, dass dich nichts mehr treffen kann. Finde anhand der Rechnung in `shipBox` heraus,
warum.
. *Mach den Schuss zu schnell.* Setz `SHOT_SPEED` auf `40`. Zähl, wie viele von zehn Felsen du
triffst. Rechne an den Zahlen aus, warum das passiert, bevor du es zurückänderst.
. *Nimm den ganzen Schuss.* Schreib `shotHitsRock` so um, dass es `collideRects` mit dem vollen
Rechteck des Schusses benutzt statt `collidePointRect` mit seiner Nase. Spiel beide und
entscheide, welche dir lieber ist; es gibt keine richtige Antwort, und das zu wissen lohnt sich.
. *Mach den Felsen rund.* Gib dem Felsen einen Kreis statt einer Box: Sein Mittelpunkt liegt
`rock.x + rockWidth / 2` nach rechts und `rock.y + rockHeight / 2` nach unten, und ein Radius
von `10` passt ungefähr. Nimm `collideRectCircle` für das Schiff und `collidePointCircle` für
den Schuss, und zeichne den Kreis in `drawBoxes` mit `overlayCircle`, das einen Mittelpunkt und
einen Radius nimmt.
[#_break_it_on_purpose_10]
=== Mach es absichtlich kaputt
Die beiden Boxen sind schon je vier Werte, also ist es verlockend, sie direkt hineinzustecken und
die acht lokalen Namen wegzulassen:
[source,lua]
----
function shipHitsRock(rock)
return collideRects(shipBox(), rockBox(rock))
end
----
Das sieht richtig aus und ist falsch. Starte es, und das Spiel hält an, bevor es überhaupt etwas
zeichnet:
----
186:collideRects: Expected 8 argument(s), got 5.
----
Die Zeile ist die, die du gerade getippt hast, deine ist also vielleicht nicht 186. Die Fünf ist
aber dieselbe. `rockBox(rock)` ist das Letzte in den Klammern, also sind alle vier seiner Werte
hineingegangen. `shipBox()` ist es nicht, also wurde es auf einen gekürzt. Eins plus vier ist
fünf, und `collideRects` rät die anderen drei nicht.
Dieser Fehler kommt von der Engine und nicht von Lua, darum hat er dieselbe Form wie die in
Lektion neun und zehn: Die Zahl vorne ist die Zeile, dann kommt der Name der Funktion, die sich
geweigert hat, dann die Beschwerde. Setz die acht lokalen Namen wieder ein.
[#_what_you_learned_11]
=== Was du gelernt hast
* Kollision ist Rechnen mit Rechtecken, und du kannst sie selbst schreiben.
* Zwei Rechtecke verfehlen sich auf genau vier Arten; wenn keine davon stimmt, treffen sie sich.
* `collideRects` nimmt eine Ecke und eine Größe für jedes von zwei Rechtecken und antwortet
`true` oder `false`. Ränder, die sich nur berühren, zählen für ihn als Berührung.
* Die Kollisionstests gehören zum Spiele-Kit, schlichte Funktionen, bei denen man nichts
einrichten muss. Spielstand, Timer, Tween und Statistik sind der Rest davon.
* `collidePointRect` fragt, ob ein Punkt in einem Rechteck liegt, und das ist die richtige Frage
für alles, was klein ist.
* Die Box, die du prüfst, ist kleiner als das Bild, das du zeichnest, sonst fühlt sich das Spiel
unfair an.
* Eine Funktion kann mehrere Werte zurückgeben, und nur der letzte Aufruf in einer Argumentliste
reicht sie alle weiter.
* Die Kollisionstests antworten nur. Was danach passiert, ist dein Code, und wenn du die beiden
getrennt hältst, kannst du jedes davon allein ändern.
* Wenn du deine Hitboxen auf den Bildschirm zeichnest, wird aus einem unsichtbaren Fehler ein
sichtbarer.
* `onCollision` ist für Körper in der 3D-Physik, nicht für Sprites auf dem Overlay.
[#_next_time_11]
=== Beim nächsten Mal
Das Spiel zählt deine Punkte und nimmt dir Leben, und sobald du es beendest, ist die beste
Punktzahl, die je jemand erreicht hat, weg. Es steckt mitten in der Action, sobald es geladen ist.
Und es merkt sich, was es gerade tut, mit zwei Wahr-oder-falsch-Variablen, `over` und `dying`, und
das ist genau die Falle, vor der dich Lektion sieben gewarnt hat. Lektion zwölf gibt ihm einen
Titelbildschirm, eine Variable statt dieser beiden und einen Highscore, der morgen noch da ist.