482 lines
22 KiB
Text
482 lines
22 KiB
Text
[#_lesson_8_when_it_goes_wrong]
|
|
== Lektion 8: Wenn etwas schiefgeht
|
|
|
|
image::learn/08-when-it-goes-wrong.png[Die fertige Lektion, 480]
|
|
|
|
Du hast ein Spiel geschrieben. Ab jetzt wirst du mehr Zeit damit verbringen, Programme zu
|
|
reparieren, als sie zu schreiben, und das ist kein Zeichen, dass mit dir etwas nicht stimmt. Das
|
|
ist die Arbeit. Der Unterschied zwischen jemandem, dem Programmieren Spaß macht, und jemandem, der
|
|
es aufgibt, ist fast nie Talent; es ist die Frage, ob er gelernt hat zu lesen, was der Computer
|
|
ihm sagt.
|
|
|
|
In dieser Lektion gibt es also nichts Neues zu bauen. Es geht um die fünf Meldungen, die du für den
|
|
Rest deines Lebens am häufigsten sehen wirst, was jede davon wirklich bedeutet, und um vier Wege,
|
|
den Fehler dahinter aufzuspüren. Lass `dodge.singe` aus Lektion sieben offen, denn du wirst es
|
|
mehrmals absichtlich kaputt machen.
|
|
|
|
[#_two_kinds_of_stop]
|
|
=== Zwei Arten anzuhalten
|
|
|
|
Singe hält aus zwei verschiedenen Gründen an, und es sagt das auf zwei verschiedene Arten.
|
|
|
|
Wenn das Skript gar nicht erst startet, bekommst du diese Form:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
|
|
----
|
|
|
|
Lua liest deine ganze Datei, bevor es auch nur ein Wort davon ausführt, und das hier heißt, dass es
|
|
aus dem Gelesenen nicht schlau wurde. Überhaupt nichts ist gelaufen.
|
|
|
|
Wenn das Skript gut gestartet ist, aber später etwas schiefging, bekommst du stattdessen diese
|
|
Form:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped')
|
|
stack traceback:
|
|
dodge/dodge.singe:98: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Das Spiel lief. Singe hat dein `onOverlayUpdate` aufgerufen, wie es das sechzigmal pro Sekunde tut,
|
|
und diesmal kam der Aufruf nicht zurück.
|
|
|
|
Beide Formen enthalten dieselben drei Dinge, und das sind genau die drei, die du willst: eine
|
|
Datei, eine Zeile und eine Beschwerde. Lies sie in dieser Reihenfolge, jedes Mal.
|
|
|
|
Die Zeilennummern in dieser Lektion stammen aus dem fertigen Listing von Lektion sieben. Liegen
|
|
deine um eine oder zwei Zeilen daneben, dann tut es deine Datei auch, und nichts am Lesen eines
|
|
Fehlers hängt davon ab, dass die Nummer stimmt.
|
|
|
|
[#_the_ones_that_stop_it_before_it_starts]
|
|
=== Die, die es aufhalten, bevor es anfängt
|
|
|
|
Das sind die leichten, und es sind auch die, die sich am schlimmsten anfühlen, weil der Bildschirm
|
|
schwarz bleibt und es nichts zu sehen gibt.
|
|
|
|
Geh in `updatePlaying` und lösch das Wort `then` aus der Zeile `if spawnTimer <= 0 then`.
|
|
Speichere:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
|
|
----
|
|
|
|
Lua ist ans Ende der `if`-Zeile gekommen, wollte das Wort `then` und hat stattdessen den Anfang
|
|
der nächsten Zeile gefunden -- das ist `spawnBlock()`, und daher kommt der Teil
|
|
`near 'spawnBlock'`. „Near“ heißt „hier habe ich es gemerkt“, und das ist meistens das Erste
|
|
*nach* dem, was du weggelassen hast.
|
|
|
|
Jetzt ein fieserer. Setz `then` wieder ein und lösch stattdessen das `end` in der letzten Zeile von
|
|
`spawnBlock`. Speichere:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:148: 'end' expected (to close 'function' at line 58) near <eof>
|
|
----
|
|
|
|
Zeile 148 ist das Ende der Datei, gleich hinter ihrer letzten Zeile. An Zeile 148 ist nichts
|
|
falsch. Lua hat bis ganz zum
|
|
Ende gelesen -- `<eof>` nennt es das Ende einer Datei -- und dabei immer noch auf ein `end`
|
|
gewartet, das nie kam, und bis dahin hatte es jede Funktion nach `spawnBlock` verschluckt, als
|
|
gehöre sie dazu.
|
|
|
|
Die nützliche Hälfte dieser Meldung ist `(to close 'function' at line 58)`, und dort fängt
|
|
`spawnBlock` an. Wenn eine Meldung aufs Ende der Datei zeigt, glaub dem, was in den Klammern steht,
|
|
nicht der Zeilennummer. Das ist auch der Grund, warum die Einrückung so wichtig ist: Ein fehlendes
|
|
`end` ist in einer Reihe von Zeilen fast unsichtbar und sofort offensichtlich, sobald alles darunter
|
|
eine Stufe zu weit eingerückt ist.
|
|
|
|
Zwei weitere, die du wiedererkennen solltest, denn beiden begegnest du noch diese Woche:
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:41: unfinished string near '"GAME OVER)'
|
|
----
|
|
|
|
Ein Anführungszeichen, das du geöffnet und nie geschlossen hast; das kennst du aus Lektion eins.
|
|
|
|
----
|
|
Error running script: dodge/dodge.singe:135: 'then' expected near '='
|
|
----
|
|
|
|
Das ist `if state = "playing" then`. Ein `=` setzt eine Variable, zwei `==` stellen eine Frage, und
|
|
Lua lässt dich in einem `if` keine setzen. Es sieht nach einer seltsamen Beschwerde aus, und sie
|
|
bedeutet immer dasselbe.
|
|
|
|
[#_attempt_to_index_a_nil_value]
|
|
=== Attempt to Index a Nil Value
|
|
|
|
Diese Meldung wirst du mit großem Abstand am häufigsten sehen.
|
|
|
|
Geh in `spawnBlock` und schreib `block` in der ersten Zeile, die es benutzt, falsch, sodass dort
|
|
`blocl.x = math.random(0, screenWidth - BLOCK_SIZE)` steht. Speichere, drück die Leertaste:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:60: attempt to index a nil value (global 'blocl')
|
|
stack traceback:
|
|
dodge/dodge.singe:60: in function 'spawnBlock'
|
|
dodge/dodge.singe:92: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
*Indizieren* ist das, was der Punkt tut. `blocl.x` heißt „geh in das Ding namens `blocl` und such
|
|
darin das `x`“. Das geht nur bei einer Tabelle.
|
|
|
|
*Nil* nennt Lua einen Namen, hinter dem nichts steht. Du hast es bisher selten gesehen, weil du
|
|
sorgfältig allem einen Wert gegeben hast, aber jeder Name, den du nie benutzt hast, ist schon
|
|
`nil`, und wer einen Namen falsch tippt, erfindet einen nagelneuen.
|
|
|
|
Die ganze Meldung sagt also: *Du hast einen Punkt auf etwas angewendet, das es nicht gibt*.
|
|
|
|
Der Teil in Klammern ist das Geschenk. `global 'blocl'` ist genau der Name des Dings, das leer
|
|
war. Such in deiner Datei danach. Kommt er genau einmal vor, ist es ein Tippfehler, und du hast ihn
|
|
in zehn Sekunden gefunden.
|
|
|
|
Das Wort vor dem Namen sagt dir, wo Lua danach gesucht hat:
|
|
|
|
* `global` -- ein Name, vor dem nirgends ein `local` steht.
|
|
* `local` -- ein Name, den du in der Funktion, in der du gerade bist, mit `local` deklariert hast.
|
|
* `field` -- ein Name nach einem Punkt, wie das `y` in `block.y`.
|
|
* `upvalue` -- ein `local` von weiter außen, das diese Funktion sehen kann, und das sind alle
|
|
Variablen von `dodge.singe`, denn sie werden oben in der Datei deklariert und in Funktionen
|
|
benutzt.
|
|
|
|
`field` ist die andere häufige Ursache, und die ist überhaupt kein Tippfehler. Wenn du
|
|
`block.hit.when` geschrieben und `block.hit` nie gesetzt hast, bekommst du
|
|
`attempt to index a nil value (field 'hit')`. Der Punkt, der scheitert, ist der zweite. Lua sagt
|
|
dir, welcher, indem es ihn beim Namen nennt.
|
|
|
|
[#_attempt_to_call_a_nil_value]
|
|
=== Attempt to Call a Nil Value
|
|
|
|
Mach `blocl` wieder rückgängig. Jetzt schreib eine Engine-Funktion falsch: Ändere in `drawGame`
|
|
das `overlayPrint` in `overlayPirnt`. Speichere:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:36: attempt to call a nil value (global 'overlayPirnt')
|
|
stack traceback:
|
|
dodge/dodge.singe:36: in function 'drawGame'
|
|
dodge/dodge.singe:140: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Dasselbe `nil`, anderes Vergehen. *Aufrufen* ist das, was die Klammern tun, und das geht nur bei
|
|
einer Funktion. Es gibt keine Funktion namens `overlayPirnt`, also ist `overlayPirnt` gleich
|
|
`nil`, also haben die Klammern nichts, was sie aufrufen könnten.
|
|
|
|
Das passiert aus vier Gründen, nach Häufigkeit geordnet:
|
|
|
|
. Du hast eine deiner eigenen Funktionen falsch geschrieben.
|
|
. Du hast eine von Singe falsch geschrieben. Nichts in der Engine prüft deine Rechtschreibung, wenn
|
|
die Datei geladen wird, also ist ein falsch geschriebener Engine-Name einfach ein weiterer Name,
|
|
der zufällig leer ist, und er kostet dich nichts, bis die Zeile läuft.
|
|
. Die Funktion gibt es in Singe überhaupt nicht -- es war eine vernünftige Vermutung, und
|
|
vernünftige Vermutungen liegen etwa die Hälfte der Zeit daneben. Die Funktionsliste in der
|
|
Referenz ist der einzige Weg, sicher zu sein.
|
|
. Du hast sie `local` gemacht und von irgendwo oberhalb der Stelle aufgerufen, an der sie
|
|
deklariert ist. Genau darum sind die Funktionen in `dodge.singe` global.
|
|
|
|
Es gibt eine viel schlimmere Version dieses Fehlers, und du bist ihr in Lektion eins begegnet, ohne
|
|
es zu wissen: Schreib einen *Callback* falsch, und du bekommst überhaupt keine Meldung.
|
|
`onOverlayUpdated` ist kein Fehler. Es ist eine völlig ordentliche Funktion, von der Singe nie
|
|
gehört hat und die es nie aufrufen wird, also bleibt der Bildschirm schwarz, und nichts beschwert
|
|
sich. Wenn ein ganzer Teil des Spiels überhaupt nichts tut, verdächtige den Namen.
|
|
|
|
[#_attempt_to_perform_arithmetic_on_a_nil_value]
|
|
=== Attempt to Perform Arithmetic on a Nil Value
|
|
|
|
Setz `overlayPrint` wieder ein. Schreib in `updatePlaying` in der Fall-Zeile `blockSpeed` falsch,
|
|
sodass sie `block.y = block.y + blockSped` lautet:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped')
|
|
stack traceback:
|
|
dodge/dodge.singe:98: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Rechnen heißt `+`, `-`, `*` und `/`. Du kannst nicht nichts zu einer Zahl addieren. Wieder nennen
|
|
die Klammern das leere Ding, und wieder ist die häufigste Ursache ein Tippfehler -- aber dieser
|
|
hier hat noch eine zweite Ursache, die du kennen solltest, weil sie schwerer zu sehen ist.
|
|
|
|
Eine Variable, der du nie eine Zahl gegeben hast, ist `nil`, nicht null. Wenn du dem Spiel einen
|
|
Highscore hinzufügst und oben `local hiScore` ohne `= 0` schreibst, steht darin `nil`, bis etwas
|
|
sie setzt, und das erste `hiScore + score` hält das Spiel an. Lua lässt Zahlen nicht für dich bei
|
|
null anfangen. Das machst du.
|
|
|
|
Zwei nahe Verwandte, mit derselben Ursache und demselben Heilmittel:
|
|
|
|
----
|
|
dodge/dodge.singe:37: attempt to concatenate a nil value (upvalue 'hiScore')
|
|
----
|
|
|
|
`..`, das einen String an etwas hängt, das nicht da ist. Den siehst du jedes Mal, wenn du eine
|
|
Punktzahl ausgibst, die du zu setzen vergessen hast -- hier `hiScore`, angehängt an die
|
|
Punktezeile in `drawGame`, die jetzt Zeile 37 ist, weil `local hiScore` darüber steht.
|
|
|
|
----
|
|
dodge/dodge.singe:105: attempt to compare nil with number
|
|
----
|
|
|
|
Das ist `if score > hiScore then`, unter `state = "over"` gesetzt, um beim Ende eines Spiels die
|
|
beste Punktzahl festzuhalten, ohne dass etwas in `hiScore` steht. Beachte, dass diese Meldung nichts
|
|
in Klammern nennt. Wenn Lua dir nicht sagen kann, welche Seite leer war, zeigt dir der übernächste
|
|
Abschnitt, wie du es herausfindest.
|
|
|
|
[#_an_argument_of_the_wrong_kind]
|
|
=== Ein Argument der falschen Art
|
|
|
|
Jede Meldung bisher kam von Lua. Diese kommt von Singe, und sie sieht völlig anders aus, also lohnt
|
|
es sich, sie absichtlich kennenzulernen.
|
|
|
|
Angenommen, du willst sehen, was in `blocks` steckt, und versuchst, es auszugeben. Füg `drawGame`
|
|
diese Zeile hinzu, unter der Punktzahl:
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(1, 3, blocks)
|
|
----
|
|
|
|
----
|
|
37:overlayPrint: Argument 3 must be a string.
|
|
----
|
|
|
|
Kein Dateiname, kein Traceback, kein `attempt to` irgendwas. Eine Beschwerde der Engine fängt mit
|
|
der Zeilennummer an, dann kommt der Name der Funktion, die sich geweigert hat, dann, was sie
|
|
wollte. Hier hat `overlayPrint` eine Tabelle bekommen, wo es Text braucht, und es hat angehalten,
|
|
statt etwas Sinnloses zu zeichnen.
|
|
|
|
Die Schwestermeldung zählt, statt zu prüfen:
|
|
|
|
----
|
|
34:overlayBox: Expected 4 argument(s), got 3.
|
|
----
|
|
|
|
Das ist ein `overlayBox`, dem eine Ecke fehlt. Das seltsam aussehende `argument(s)` stammt von der
|
|
Engine, nicht aus einem Tippfehler von dir.
|
|
|
|
Wenn du so eine bekommst, ist der Eintrag zu dieser Funktion in der Referenz die Antwort, und das
|
|
kostet dich eine Minute. Jeder Eintrag listet die Argumente der Reihe nach auf, mit dem, was jedes
|
|
sein muss. Singe prüft sie, weil die Alternative ein Spiel ist, das zehn Minuten läuft und dann
|
|
Müll zeichnet.
|
|
|
|
[#_finding_out_what_is_really_in_there]
|
|
=== Herausfinden, was wirklich drinsteckt
|
|
|
|
Die Hälfte der Fehlersuche ist überhaupt nicht das Lesen von Fehlermeldungen. Es ist der Fall, in
|
|
dem nichts abstürzt und nichts funktioniert: Blöcke, die nie erscheinen, eine Spielfigur, die sich
|
|
nicht bewegen will, eine Punktzahl, die bei null hängt. Dafür musst du in das laufende Spiel
|
|
hineinsehen, und dafür gibt es zwei Wege.
|
|
|
|
Der erste ist `debugPrint`, das eine Zeile in das Terminal schreibt, aus dem du Singe gestartet
|
|
hast. Nimm die Zeile, aus der du nicht schlau wirst, und setz eins darüber:
|
|
|
|
[source,lua]
|
|
----
|
|
debugPrint("blocks " .. #blocks .. ", timer " .. spawnTimer)
|
|
----
|
|
|
|
Jetzt siehst du dir an, was wirklich da ist, statt dessen, wovon du sicher bist, dass es da ist,
|
|
und in dieser Lücke wohnen die Fehler. Gib `playerX` auf dieselbe Weise aus, und du siehst auch die
|
|
Wahrheit darüber: Es wird als `160.0` ausgegeben, nicht als `160`, weil Teilen mit `/` in Lua immer eine Zahl mit
|
|
Nachkommateil ergibt, auch wenn der Nachkommateil null ist.
|
|
|
|
Zwei Dinge werden dich beißen. Das erste ist, dass `debugPrint` auf Text oder einer Zahl besteht;
|
|
gib ihm etwas anderes, und es hält das Spiel an, so wie `overlayPrint` es getan hat.
|
|
`debugPrint(blocks)` zeigt dir keine Liste, und `debugPrint("left " .. goingLeft)` ganz oben in
|
|
`updatePlaying` zeigt dir auch kein `true` oder `false`:
|
|
|
|
----
|
|
dodge/dodge.singe:78: attempt to concatenate a boolean value (upvalue 'goingLeft')
|
|
----
|
|
|
|
`tostring` ist die Lösung. Es nimmt überhaupt alles und gibt dir Text dafür, also ist
|
|
`tostring(goingLeft)` entweder `"true"` oder `"false"`, und die Zeile funktioniert.
|
|
|
|
Das zweite ist, dass `onOverlayUpdate` sechzigmal pro Sekunde läuft, also gibt ein `debugPrint`
|
|
darin sechzig Zeilen pro Sekunde aus, und du kannst keine davon lesen. Die Referenz sagt das auch
|
|
in ihrem Eintrag: Der Aufruf ist billig, aber nichts bremst ihn.
|
|
|
|
Bevor du irgendetwas anderes machst, setz einen Schalter vor jede Debug-Zeile, die du hinzufügst,
|
|
damit du den Lärm abstellen kannst, ohne sie alle wieder aufspüren zu müssen:
|
|
|
|
[source,lua]
|
|
----
|
|
local DEBUG = true
|
|
|
|
|
|
function debugLog(text)
|
|
if DEBUG then
|
|
debugPrint(text)
|
|
end
|
|
end
|
|
----
|
|
|
|
Jedes `debugLog` im Spiel gehorcht diesem einen `true`. Ändere es in `false`, und das Spiel wird
|
|
still; ändere es zurück, und alles ist wieder da.
|
|
|
|
Dann gib etwas aus, wenn etwas *passiert*, statt in jedem Frame:
|
|
|
|
[source,lua]
|
|
----
|
|
debugLog("hit at y " .. math.floor(block.y) .. ", lives now " .. lives)
|
|
----
|
|
|
|
Oder gib es nach einem Timer aus, der genau so heruntergezählt wird, wie Blöcke erzeugt werden:
|
|
|
|
[source,lua]
|
|
----
|
|
reportTimer = reportTimer - 1
|
|
if reportTimer <= 0 then
|
|
debugReport()
|
|
reportTimer = REPORT_FRAMES
|
|
end
|
|
----
|
|
|
|
Im Ordner `learn` liegt das Ganze als `08-when-it-goes-wrong.singe`: Es ist das Spiel aus Lektion
|
|
sieben mit einem Bericht etwa alle zwei Sekunden, einer Zeile, wenn du getroffen wirst, und einer
|
|
Zeile, wenn das Spiel endet.
|
|
|
|
Der zweite Weg hineinzusehen ist, es auf den Bildschirm zu bringen, und das ist besser für alles,
|
|
was sich in jedem Frame ändert, denn sechzig Zeilen pro Sekunde in einem Terminal sind nutzlos,
|
|
aber eine Zahl, die in der Ecke tickt, lässt sich leicht beobachten:
|
|
|
|
[source,lua]
|
|
----
|
|
if DEBUG then
|
|
overlayPrint(1, 3, "BLOCKS " .. #blocks .. " PLAYER " .. playerX)
|
|
end
|
|
----
|
|
|
|
[#_cutting_the_problem_in_half]
|
|
=== Das Problem halbieren
|
|
|
|
Wenn du nicht siehst, welche von zwanzig Zeilen falsch ist, hör auf zu suchen und fang an
|
|
wegzunehmen.
|
|
|
|
Setz zwei Bindestriche an den Anfang einer Zeile, und Lua ignoriert den Rest davon. Das ist ein
|
|
*Kommentar*, und meistens hinterlässt man sich damit Notizen, aber die bessere Verwendung ist diese:
|
|
|
|
[source,lua]
|
|
----
|
|
-- overlayPrint(1, 1, "SCORE " .. score .. " LIVES " .. lives)
|
|
----
|
|
|
|
Kommentier die Hälfte von `drawGame` aus und starte es. Ist das Problem noch da, steckt es in der
|
|
Hälfte, die du behalten hast. Ist es weg, steckt es in der Hälfte, die du entfernt hast. Setz diese
|
|
Hälfte wieder ein und halbiere *sie*. Zwanzig Zeilen brauchen fünf solche Runden, und jede Runde
|
|
ist einmal speichern und einmal hinsehen.
|
|
|
|
Das funktioniert nicht nur mit Zeilen. Kommentier den Rumpf von `updatePlaying` aus, und die Blöcke
|
|
hören auf, sich zu bewegen, und das sagt dir, ob das, was du jagst, im Bewegen steckt oder im
|
|
Zeichnen. Kommentier den Zweig mit `collideRects` aus und sieh nach, ob das Spiel aufhört
|
|
abzustürzen. Du versuchst dabei nicht, irgendetwas zu reparieren. Du versuchst herauszufinden, wo
|
|
es nicht ist.
|
|
|
|
[#_going_round_faster]
|
|
=== Schnellere Runden
|
|
|
|
All das hängt davon ab, dass du etwas in ein paar Sekunden ausprobieren kannst, und dafür ist `-R`
|
|
da. Du benutzt es seit Lektion eins; hier steht, was es wirklich tut, und das solltest du jetzt
|
|
wissen, wo du dich darauf stützen wirst.
|
|
|
|
Singe beobachtet jede Skriptdatei, die dein Spiel geladen hat. Wenn du eine speicherst, fängt das
|
|
Spiel wieder von vorne an: Alles, was das Skript gemacht hat, wird weggeworfen, und deine Datei
|
|
läuft neu, in der Zeit, die das Lesen der Datei braucht, und nicht in der Zeit, die das Starten der
|
|
Engine braucht. `F5` macht dasselbe auf Wunsch, ohne dass du etwas speicherst.
|
|
|
|
Eine Taste, die du während des Neuladens gedrückt hältst, wird ignoriert, bis du sie loslässt, also
|
|
läuft die Spielfigur nach einem Neuladen nicht von allein davon.
|
|
|
|
Die beiden Arten anzuhalten verhalten sich hier verschieden, und es ist nützlich zu wissen, welche
|
|
du vor dir hast, ohne die Meldung zu lesen. Ein Skript, das sich nicht kompilieren lässt, wird
|
|
*gemeldet und überstanden*: Das Fenster bleibt offen und leer, die Datei wird weiter beobachtet,
|
|
und wenn du die Korrektur speicherst, kommt das Spiel zurück. Ein Absturz in einem Callback -- alle
|
|
`attempt to`-Meldungen und die Beschwerden der Engine über Argumente -- reißt Singe mit, und du
|
|
startest es von Hand neu.
|
|
|
|
[#_reading_a_traceback]
|
|
=== Einen Traceback lesen
|
|
|
|
Die Zeilen unter `stack traceback:` sind die Liste der Funktionen, die gerade liefen, als alles
|
|
anhielt, die innerste zuerst. Hier ist noch einmal der von `blocl`:
|
|
|
|
----
|
|
dodge/dodge.singe:60: in function 'spawnBlock'
|
|
dodge/dodge.singe:92: in function 'updatePlaying'
|
|
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Lies ihn von unten nach oben, und er ist ein Satz. Singe hat `onOverlayUpdate` aufgerufen. In
|
|
Zeile 136 hat `onOverlayUpdate` `updatePlaying` aufgerufen. In Zeile 92 hat `updatePlaying`
|
|
`spawnBlock` aufgerufen. In Zeile 60 hat `spawnBlock` aufgegeben.
|
|
|
|
Die oberste Zeile ist die Stelle, an der es kaputtging. Die Zeilen darunter zeigen, wie es dorthin
|
|
kam, und die brauchst du, wenn die oberste Zeile unschuldig aussieht. Eine Funktion, die eine Zahl
|
|
zu `block.y` addiert, ist nicht falsch; eine Funktion, die einen Block ohne `y` in die Liste gelegt
|
|
hat, schon. Nur die Spur verbindet die beiden, und die Spur wird dir jedes einzelne Mal
|
|
ausgegeben.
|
|
|
|
[#_the_habit]
|
|
=== Die Gewohnheit
|
|
|
|
Alles oben ist Technik. Das hier ist die Gewohnheit, und sie ist wichtiger:
|
|
|
|
*Ändere eine Sache. Starte es. Sieh dir an, was passiert ist.*
|
|
|
|
Wenn etwas nicht funktionieren will, ist die Versuchung groß, vier Dinge auf einmal zu ändern, weil
|
|
eines davon es ja sein muss. Mach das, und es funktioniert auf eine neue Art nicht, und jetzt weißt
|
|
du nicht, welches der vier es war oder ob sich zwei davon gegenseitig aufheben. Du hast das Problem
|
|
größer gemacht und besser versteckt.
|
|
|
|
Eine Änderung. Starten. Hinsehen. Es fühlt sich langsamer an. Es ist das Schnellste, was es gibt,
|
|
und jeder erfahrene Programmierer, dem du je begegnest, macht es so, aus genau dem Grund, den du
|
|
gleich herausfinden wirst.
|
|
|
|
[#_try_it_8]
|
|
=== Probier es aus
|
|
|
|
. *Lern sie alle kennen.* Mach jeden der fünf Fehler aus dieser Lektion absichtlich in
|
|
`dodge.singe`, einen nach dem anderen, und lies die Meldung, bevor du ihn behebst. Du wirst nie
|
|
wieder Angst vor ihnen haben.
|
|
. *Beobachte den Erzeuger.* Setz ein `debugPrint` in `spawnBlock`, das das `x` des neuen Blocks
|
|
ausgibt. Spiel dreißig Sekunden lang, dann sieh dir die Zahlen an. Sind sie über den Bildschirm
|
|
verteilt, oder bevorzugen sie eine Seite?
|
|
. *Schalt es ab.* Ändere im Skript der Lektion `DEBUG` in `false` und überzeug dich davon, dass
|
|
nichts ausgegeben wird und das Spiel unverändert ist.
|
|
. *Ein Fehler ohne Fehlermeldung.* Ändere in `updatePlaying` `if spawnTimer <= 0 then` in
|
|
`if spawnTimer == 0 then`, was so aussieht, als bedeute es dasselbe. Starte es und fang ein Spiel
|
|
an. Es gibt keine Fehlermeldung, nichts stürzt ab, und kein einziger Block fällt je, du kannst
|
|
also nicht einmal verlieren. Setz ein `debugLog` in `spawnBlock`, füg `spawnTimer` der ersten
|
|
Zeile hinzu, die `debugReport` ausgibt, und spiel noch einmal. Finde aus dem, was sie sagen, und
|
|
aus dem, was eines von beiden nie sagt, heraus, warum `==` die falsche Frage war.
|
|
. *Mach den Namen eines Callbacks kaputt.* Benenne `onInputPressed` in `onInputPress` um und starte
|
|
es. Keine Fehlermeldung wird ausgegeben, nichts stürzt ab, die Spielfigur bewegt sich nicht, und die Leertaste
|
|
startet kein Spiel. Dann erklär dir selbst, warum das der gefährlichste Fehler in dieser Lektion
|
|
ist.
|
|
|
|
[#_what_you_learned_8]
|
|
=== Was du gelernt hast
|
|
|
|
* Fehlermeldungen haben eine Datei, eine Zeile und eine Beschwerde. Lies sie in dieser Reihenfolge.
|
|
* `Error running script` heißt, dass nichts gelaufen ist; die Form mit einem Traceback heißt, dass
|
|
es lief und angehalten hat.
|
|
* `nil` ist ein Name, hinter dem nichts steht, und es steckt hinter den meisten Fehlermeldungen,
|
|
die du je sehen wirst.
|
|
* Das Wort in Klammern nennt das leere Ding und sagt, ob es global, lokal, ein Feld oder ein
|
|
Upvalue war.
|
|
* Ein fehlendes `end` wird am Ende der Datei gemeldet, und die Klammern sagen dir, wo der Fehler
|
|
wirklich steckt.
|
|
* Beschwerden der Engine sehen anders aus: eine Zeilennummer, ein Funktionsname und was sie
|
|
stattdessen wollte.
|
|
* `debugPrint` zeigt dir, was wirklich in einer Variablen steckt. `tostring` macht alles
|
|
ausgebbar, und ein Schalter wie `DEBUG` stellt den Lärm ab.
|
|
* Kommentier mit `--` eine Hälfte aus, um herauszufinden, in welcher Hälfte das Problem steckt.
|
|
* Ein falsch geschriebener Callback erzeugt überhaupt keine Fehlermeldung, und das macht ihn
|
|
schlimmer als einen Fehler, der eine erzeugt.
|
|
* Ändere eine Sache, starte es, sieh hin.
|
|
|
|
[#_next_time_8]
|
|
=== Beim nächsten Mal
|
|
|
|
Damit endet Teil eins. Du kannst ein Spiel schreiben, und jetzt kannst du auch eins reparieren,
|
|
und das heißt, dass alles ab hier Anbau ist und kein Fundament mehr. Teil zwei fängt damit an,
|
|
diese farbigen Kästen durch echte Bilder zu ersetzen, und von da an sieht das Spiel, das du baust,
|
|
allmählich wie ein Spiel aus, das auch jemand anders spielen wollen würde.
|