[#_lesson_5_your_own_functions] == Lektion 5: Deine eigenen Funktionen image::learn/05-functions.png[Die fertige Lektion, 480] Lektion vier hat mit einer Warnung über dein eigenes Programm geendet: `onOverlayUpdate` rechnet aus, wohin hundertzwanzig Sterne kommen, entscheidet, wie hell jeder ist, zeichnet sie und zählt die Drift, und das ist eine Funktion, die vier Aufgaben erledigt. Gleich soll sie eine fünfte übernehmen, denn ein Sternenfeld, vor dem nichts ist, ist kein Spiel. In dieser Lektion nimmst du sie auseinander. Du schreibst eigene Funktionen -- Funktionen, von denen Singe nie gehört hat und die es nie aufrufen wird -- und gibst die Arbeit an sie ab. Am Ende ist das ganze Sternenfeld eine Zeile, in der `drawStars()` steht, unten auf dem Bildschirm ist wieder ein Schiff, und das Programm ist leichter zu lesen als damals, als es weniger getan hat. [#_start_a_new_folder_4] === Leg einen neuen Ordner an Ein Ordner namens `functions`, darin eine Datei namens `functions.singe`, und in einem Terminal läuft `Singe -R functions`. Kopier das fertige Programm aus Lektion vier hinein. Hier ist es, unverändert, und es lohnt sich, es einmal laufen zu lassen, bevor du irgendetwas anfasst: [source,lua] ---- LAYERS = 3 STARS_PER_LAYER = 40 ACROSS_STEP = 37 DOWN_STEP = 61 LAYER_SHIFT = 13 LAYER_SPEED = 0.5 LAYER_BRIGHT = 55 width = overlayGetWidth() height = overlayGetHeight() drift = 0 function onOverlayUpdate() drift = drift + 1 overlayClear() for layer = 1, LAYERS do for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT colorForeground(shade, shade, shade) overlayPlot(starX, starY) end end return OVERLAY_UPDATED end ---- Drei Ebenen von Sternen, die mit drei Geschwindigkeiten das Fenster hinuntertreiben. Nichts in der ersten Hälfte dieser Lektion ändert, was du siehst. Alles darin ändert, wie sich das Programm liest. [#_one_job_one_name] === Eine Aufgabe, ein Name Fang mit den zwei Zeilen an, die einen Stern auf den Bildschirm bringen. Füg über `onOverlayUpdate` das hier ein: [source,lua] ---- function drawStar(x, y, shade) colorForeground(shade, shade, shade) overlayPlot(x, y) end ---- Dann lösch in der inneren Schleife diese zwei Zeilen und ruf stattdessen die Funktion auf: [source,lua] ---- for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT drawStar(starX, starY, shade) end ---- Speichere. Das Bild ist dasselbe. Du hast schon früher Funktionen geschrieben -- `onOverlayUpdate` ist eine, `onInputPressed` und `onInputReleased` aus Lektion drei ebenso. Aber das sind Callbacks. Du schreibst sie, und Singe ruft sie auf. `drawStar` ist anders. Singe hat nie von ihr gehört. Sie läuft, weil *du* ihren Namen und Klammern geschrieben hast, genauso wie du `overlayPlot` aufrufst, und nichts auf der Welt führt sie aus, wenn du es nicht tust. Das ist der ganze Trick. Wenn du ab jetzt denkst „und jetzt zeichne einen Stern“, kannst du „zeichne einen Stern“ hinschreiben und es so meinen. [#_a_parameter_is_a_name_not_a_thing] === Ein Parameter ist ein Name, kein Ding Sieh dir die erste Zeile von `drawStar` noch einmal an. [source,lua] ---- function drawStar(x, y, shade) ---- Die drei Namen in den Klammern sind *Parameter*. Ein Parameter ist ein Name, den die Funktion für das benutzt, was der Aufrufer ihr übergeben hat. Wenn du `drawStar(starX, starY, shade)` schreibst, setzt Lua `x` auf das, was in `starX` steht, `y` auf das, was in `starY` steht, `shade` auf das, was in `shade` steht, und führt dann die Funktion aus. Die Namen haben nichts miteinander zu tun. Die Zahl des Aufrufers heißt da draußen `starX` und hier drinnen `x`, und es würde genauso funktionieren, wenn du sie `across` oder `banana` nennen würdest. In der Funktion gibt es nur die Namen in den Klammern, und sie enthalten Kopien von dem, was hereingekommen ist. Hier ergeben die leeren Klammern aus Lektion eins endlich einen Sinn. [source,lua] ---- function onOverlayUpdate() ---- Nichts in den Klammern heißt keine Parameter: Die Engine hat dieser Funktion nichts zu sagen, also gibt es nichts zu benennen. Vergleich das mit dem Callback, den du in Lektion drei geschrieben hast. [source,lua] ---- function onInputPressed(what) ---- Ein Parameter namens `what`. Diesem hier hat die Engine etwas zu sagen -- welches Steuerelement gedrückt wurde -- also gibt es einen Namen, der es auffängt. Du benutzt seit Lektion drei einen Parameter. Ein Parameter ist wirklich die eigene Kopie der Funktion, und es lohnt sich, dir das einmal selbst zu beweisen. Tipp das ein, ruf es aus `onOverlayUpdate` auf und nimm es danach wieder heraus: [source,lua] ---- function half(number) number = number // 2 return number end ---- Ruf sie als `half(drift)` auf. Die Funktion halbiert ihr `number` und gibt die Antwort zurück, und `drift` da draußen in `onOverlayUpdate` zählt weiter, als wäre nichts passiert. Eine Funktion kann nicht über einen Parameter hinausgreifen und die Variablen des Aufrufers ändern. Sie bekommt eine Kopie, und was sie mit der Kopie macht, ist ihre Sache. [#_handing_an_answer_back] === Eine Antwort zurückgeben `half` hat etwas getan, was die Funktionen, die du bisher geschrieben hast, nicht getan haben: Sie hat eine Antwort gegeben. Das ist die letzte Zeile. [source,lua] ---- return number ---- `return` gibt einen Wert an den zurück, der die Funktion aufgerufen hat, und der Aufruf wird zu diesem Wert. Schreib `smaller = half(drift)`, und Lua führt `half` aus, nimmt, was `half` zurückgegeben hat, und legt es in `smaller`. Du hast `return` in Lektion eins kennengelernt, wo `onOverlayUpdate` den Wert `OVERLAY_UPDATED` zurückgibt, um Singes Frage zu beantworten, ob sich etwas geändert hat. Das ist dasselbe Wort mit derselben Aufgabe, nur dass jetzt du derjenige bist, der die Frage stellt. Die Zeile mit der Helligkeit bettelt geradezu darum, eine Funktion mit einer Antwort zu werden. Füg diese hier hinzu: [source,lua] ---- function shadeFor(layer) return math.random(60, 90) + layer * LAYER_BRIGHT end ---- Der ganze Rumpf ist ein `return`, und das ist bei einer kleinen Funktion normal. Jetzt kann die innere Schleife die Antwort direkt an `drawStar` weitergeben, ohne einen Namen dazwischen: [source,lua] ---- drawStar(starX, starY, shadeFor(layer)) ---- Ein Aufruf in einem Aufruf. Lua rechnet zuerst den inneren aus, bekommt von `shadeFor` eine Zahl zurück und übergibt diese Zahl an `drawStar` als drittes Argument. So etwas hast du schon geschrieben, ohne es zu merken: Das `math.floor(drift * layer * LAYER_SPEED)` in Lektion vier ist ein Aufruf, dessen Antwort sofort an Ort und Stelle benutzt wird, mitten in einer größeren Rechnung. [#_two_answers_at_once] === Zwei Antworten auf einmal Die beiden Zeilen für die Position sind schwieriger, weil es zwei Zahlen sind, die zusammen ausgerechnet werden, und du müsstest dir eine aussuchen, die du zurückgibst. In den meisten Sprachen kann eine Funktion einen Wert zurückgeben, und zwei herauszubekommen ist lästig. Lua lässt dich so viele zurückgeben, wie du willst. [source,lua] ---- function starAt(star, layer) local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height return x, y end ---- Du fängst die Antworten auf, indem du links zwei Namen hinschreibst, durch ein Komma getrennt: [source,lua] ---- local x, y = starAt(star, layer) ---- Der erste Wert kommt in den ersten Namen, der zweite in den zweiten. Gibst du weniger Namen an, als die Funktion zurückgibt, werden die übrigen Werte weggeworfen: `local x = starAt(star, layer)` gibt dir die waagerechte Zahl und verliert die senkrechte, ohne Beschwerde. Gibst du mehr Namen an, als es Werte gibt, sind die überzähligen `nil`. Ignorier das Wort `local` in diesen beiden Blöcken noch eine Minute. Es bekommt gleich einen eigenen Abschnitt, und das ist der wichtige. [#_the_star_field_in_one_line] === Das Sternenfeld in einer Zeile Die innere Schleife besteht jetzt aus drei kurzen Zeilen, und alle drehen sich um eine Ebene von Sternen. Das ist eine Aufgabe mit einem Namen. [source,lua] ---- function drawLayer(layer) for star = 1, STARS_PER_LAYER do local x, y = starAt(star, layer) drawStar(x, y, shadeFor(layer)) end end ---- Und die äußere Schleife ist auch eine Aufgabe mit einem Namen: [source,lua] ---- function drawStars() for layer = 1, LAYERS do drawLayer(layer) end end ---- Funktionen, die Funktionen aufrufen, die Funktionen aufrufen, drei Stufen tief, und jede ist drei oder vier Zeilen lang, die du auf einmal im Kopf behalten kannst. `onOverlayUpdate` kann jetzt sagen, was ein Frame *ist*: [source,lua] ---- function onOverlayUpdate() drift = drift + 1 overlayClear() drawStars() return OVERLAY_UPDATED end ---- Das ist das Versprechen vom Ende der Lektion vier, eingelöst. [#_put_the_ship_back] === Hol das Schiff zurück Jetzt die fünfte Aufgabe, die, die aus dem alten `onOverlayUpdate` ein Durcheinander gemacht hätte. Hol das Kästchen zurück, das du in Lektion drei gesteuert hast, diesmal als Schiff, das nahe am unteren Bildschirmrand sitzt und sich nur nach links und rechts bewegt. Es braucht ein paar eigene Werte, oben bei den anderen, und die beiden Eingabe-Callbacks aus Lektion drei, ohne die Zweige für oben und unten: [source,lua] ---- PLAYER_WIDTH = 20 PLAYER_HEIGHT = 6 PLAYER_SPEED = 3 PLAYER_MARGIN = 6 playerX = width // 2 playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN movingLeft = false movingRight = false ---- [source,lua] ---- function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false end end ---- Es zu zeichnen sind zwei Zeilen, also ist es eine Funktion: [source,lua] ---- function drawPlayer() colorForeground(80, 255, 120) overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT) end ---- Beim Bewegen zahlt sich die Lektion aus. In Lektion drei hat es für jede Richtung vier Zeilen mit `if` und `elseif` gebraucht, um das Kästchen auf dem Bildschirm zu halten. Diese Idee -- eine Zahl zwischen einer Untergrenze und einer Obergrenze halten -- hat einen Namen, und der Name ist *clamp*: [source,lua] ---- function clamp(value, low, high) if value < low then return low end if value > high then return high end return value end ---- Drei Rückgaben, und höchstens eine davon passiert je, denn `return` gibt nicht nur eine Antwort zurück: Es hält die Funktion auf der Stelle an. Nichts danach läuft. Liegt der Wert unter der Untergrenze, gib die Untergrenze zurück und hör auf. Liegt er über der Obergrenze, gib die Obergrenze zurück und hör auf. Sonst gib zurück, was hereingekommen ist. [source,lua] ---- function movePlayer() if movingLeft then playerX = playerX - PLAYER_SPEED end if movingRight then playerX = playerX + PLAYER_SPEED end playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1) end ---- Eine Funktion, die ohne `return` endet, gibt nichts zurück, und das ist `nil`. Das ist richtig für `drawPlayer` und `movePlayer`, die aufgerufen werden, um etwas zu tun, nicht um etwas zu beantworten. In einer Funktion wie `clamp` ist es ein Fehler: Lass das letzte `return value` weg, und jede Position mitten im Bereich kommt als `nil` zurück. [#_where_names_live] === Wo Namen wohnen Jeder Name, den du seit Lektion zwei angelegt hast, war eine *globale Variable*. Eine globale Variable gehört dem ganzen Programm: `drift`, `playerX` und `LAYERS` können von jeder Funktion in der Datei jederzeit gelesen und geschrieben werden, und sie bleiben da, solange das Spiel läuft. Das klingt bequem, und bei sechs Namen ist es das auch. Der Ärger fängt bei sechzig an. Jede globale Variable ist ein Name, den sich das ganze Programm reserviert hat; wenn also irgendeine andere Funktion einen Platz für eine waagerechte Position braucht und ihn auch `starX` nennt, sind die beiden dieselbe Schublade, und jede überschreibt die Zahl der anderen. Wenn in einer globalen Variable etwas Falsches steht, kann es alles in der Datei dort hineingelegt haben, und um herauszufinden, was, musst du die ganze Datei lesen. Eine *lokale Variable* ist das Gegenmittel. Schreib das Wort `local` vor einen Namen, wenn du ihn zum ersten Mal benutzt, und der Name existiert nur innerhalb der `function`, der Schleife oder des `if`, in dem er geschrieben wurde. [source,lua] ---- function starAt(star, layer) local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height return x, y end ---- `x` und `y` gehen hier niemanden sonst etwas an. Eine andere Funktion kann ihr eigenes `x` haben, und die beiden begegnen sich nie. Wenn das `x` von `starAt` falsch ist, sind die drei Zeilen oben die einzigen drei Zeilen, die das gewesen sein können. Ohne das Wort `local` würden diese beiden Zeilen still zwei weitere globale Variablen anlegen, und genau das waren `starX` und `starY` die ganze Zeit. Jetzt der Teil, der alle überrascht. Tipp diese Funktion ein und setz eine Zeile in `onOverlayUpdate`, die dir zeigt, was sie zurückgibt: [source,lua] ---- function countUp() local counted = 0 counted = counted + 1 return counted end ---- [source,lua] ---- overlayPrint(0, 0, "Count: " .. countUp()) ---- `Count: 1`, und es bleibt bei `Count: 1`, solange das Spiel läuft, obwohl die Funktion sechzigmal pro Sekunde aufgerufen wird und jedes Mal eins dazuzählt. Eine lokale Variable wird frisch angelegt, wenn die Funktion anfängt, und ist weg, wenn die Funktion endet; also wird `counted` als `0` geboren, wird zu `1` und stirbt, sechzigmal pro Sekunde, für immer. Eine lokale Variable merkt sich nichts. Nimm die beiden wieder heraus, wenn du es dir angesehen hast. Das ist kein Mangel, sondern genau der Sinn: Eine lokale Variable kann nicht vom letzten Aufruf vergiftet werden, weil vom letzten Aufruf nichts übrig ist. Wenn du dir doch etwas von einem Aufruf zum nächsten merken musst (und `drift` und `playerX` müssen das), muss der Name außerhalb jeder Funktion wohnen. Setz ihn mit `local` davor an den Anfang der Datei: [source,lua] ---- local drift = 0 local playerX = width // 2 ---- Ein `local` ganz oben in der Datei, außerhalb aller Funktionen, lebt so lange wie das Skript und ist für jede Funktion darunter sichtbar. Auf das letzte Wort kommt es an: *darunter*. Eine lokale Variable existiert von ihrer eigenen Zeile an abwärts, also kann eine Funktion, die über der Zeile steht, die sie anlegt, sie nicht sehen. Dasselbe gilt für die Funktionen selbst, sobald du sie mit `local` markierst, und darum hat das fertige Skript die Reihenfolge, die es hat: zuerst die Werte, dann deine eigenen Funktionen, dann ganz zum Schluss die Callbacks. Geh jetzt die ganze Datei durch und setz `local` vor jeden Namen, den du angelegt hast -- die Konstanten in Großbuchstaben, `width` und `height`, `drift`, die vier Werte der Spielfigur und jede deiner eigenen Funktionen. Drei Namen bekommen es nicht: `onInputPressed`, `onInputReleased` und `onOverlayUpdate` müssen global bleiben, weil Singe sie über ihren Namen findet und ein lokaler Name von außerhalb der Datei unsichtbar ist. Sei ehrlich zu dir, was lokale Variablen nicht reparieren. Lua beschwert sich nicht, wenn du dich bei einem Namen vertippst, dem du etwas zuweist -- `movinLeft = true` statt `movingLeft = true`. Es legt eine brandneue globale Variable mit deinem Tippfehler als Namen an, setzt sie und lässt die echte in Ruhe. Nichts bewegt sich, nichts meldet einen Fehler, und du starrst zehn Minuten lang darauf. Davor retten dich lokale Variablen nicht. Den Namen zweimal zu lesen schon. [#_the_whole_thing_4] === Das Ganze Hier ist das fertige Programm, mit jedem Stück der Lektion darin. [source,lua] ---- local LAYERS = 3 local STARS_PER_LAYER = 40 local ACROSS_STEP = 37 local DOWN_STEP = 61 local LAYER_SHIFT = 13 local LAYER_SPEED = 0.5 local LAYER_BRIGHT = 55 local PLAYER_WIDTH = 20 local PLAYER_HEIGHT = 6 local PLAYER_SPEED = 3 local PLAYER_MARGIN = 6 local width = overlayGetWidth() local height = overlayGetHeight() local drift = 0 local playerX = width // 2 local playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN local movingLeft = false local movingRight = false local function clamp(value, low, high) if value < low then return low end if value > high then return high end return value end local function shadeFor(layer) return math.random(60, 90) + layer * LAYER_BRIGHT end local function starAt(star, layer) local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height return x, y end local function drawStar(x, y, shade) colorForeground(shade, shade, shade) overlayPlot(x, y) end local function drawLayer(layer) for star = 1, STARS_PER_LAYER do local x, y = starAt(star, layer) drawStar(x, y, shadeFor(layer)) end end local function drawStars() for layer = 1, LAYERS do drawLayer(layer) end end local function drawPlayer() colorForeground(80, 255, 120) overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT) end local function movePlayer() if movingLeft then playerX = playerX - PLAYER_SPEED end if movingRight then playerX = playerX + PLAYER_SPEED end playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1) end function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false end end function onOverlayUpdate() drift = drift + 1 overlayClear() movePlayer() drawStars() drawPlayer() return OVERLAY_UPDATED end ---- Auf zwei Dinge lohnt es sich hinzuweisen, bevor du weitermachst. `local function drawStar(x, y, shade)` markiert die Funktion selbst als lokal. Deine eigenen Funktionen sind Namen wie alle anderen, und sie gehören nur dieser Datei. Die drei Callbacks bleiben global, damit Singe sie finden kann. Und `onOverlayUpdate` besteht jetzt aus sechs Zeilen, die sagen, was ein Frame ist: die Drift zählen, den Bildschirm löschen, die Spielfigur bewegen, den Himmel zeichnen, die Spielfigur zeichnen, fertig. Es erledigt fünf Aufgaben, indem es fünf Dinge beim Namen nennt, die jeweils eine Aufgabe erledigen, und das ist die Form jeder Spielschleife, die du je schreiben wirst. [#_a_function_is_a_name_for_an_idea] === Eine Funktion ist ein Name für eine Idee `clamp` wird einmal aufgerufen. Sie herauszuziehen hat überhaupt keine Tipparbeit gespart -- vier Zeilen kamen heraus, eine ging hinein, und anderswo ist eine Funktion mit neun Zeilen aufgetaucht. Es hat sich trotzdem gelohnt. Eine Funktion ist nicht nur ein Weg, dich nicht zu wiederholen. Sie ist ein Weg, einer Idee einen Namen zu geben, damit der Code, der die Idee benutzt, den Namen sagen und weitermachen kann. „Halte die Spielfigur zwischen dem linken und dem rechten Rand“ ist ein Gedanke, den du einmal hattest. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)` ist dieser Gedanke, aufgeschrieben. Vier `if`-Anweisungen sind derselbe Gedanke, zerlegt in Stücke, die du jedes Mal, wenn du sie liest, im Kopf wieder zusammensetzen musst. Wenn du entscheidest, ob etwas es verdient, eine Funktion zu sein, frag nicht, wie oft du sie aufrufen wirst. Frag, ob es einen Namen hat. [#_try_it_5] === Probier es aus . *Benenne einen Parameter um.* Ändere in der Funktion `drawStar` überall `x` und `y` in `across` und `down`. Fass nichts außerhalb davon an. Starte es. Finde heraus, warum nichts kaputtgegangen ist. . *Zeichne sie andersherum.* Vertausch `drawStars()` und `drawPlayer()` in `onOverlayUpdate` und sieh dir das Schiff an. Dann stell sie zurück. Jede dieser fünf Zeilen ist eine Aufgabe, und ihre Reihenfolge auf dem Bildschirm ist ihre Reihenfolge in der Funktion. . *Benutz `clamp` für eine Helligkeit.* Ändere `shadeFor` zu `return clamp(math.random(60, 90) + layer * 90, 0, 200)` und sieh dir die drei Ebenen an. Dann nimm das `clamp` heraus und sieh noch einmal hin: `colorForeground` hat die Zahlen die ganze Zeit still für dich gedeckelt, und jetzt siehst du, wo. . *Ruf `drawStar` mit zwei Argumenten auf.* Schreib `drawStar(100, 100)` irgendwo in `drawLayer` und starte es. Der Fehler nennt genau das, was gefehlt hat. Lies ihn, bevor du das dritte Argument wieder einsetzt. . *Verschieb eine lokale Variable.* Nimm die Zeile `local drift = 0` und schieb sie ans Ende der Datei, unter alles andere. Starte es, lies den Fehler und denk über das Wort „darunter“ im Abschnitt oben nach. [#_break_it_on_purpose_5] === Mach es absichtlich kaputt In einer Datei mit lokalen Variablen kommt es auf die Reihenfolge an, und der Fehler, den du bekommst, wenn du diese Reihenfolge durcheinanderbringst, ist nicht der, den du erwartest. Nimm die ganze Funktion `drawStar` und schieb sie ans *Ende* des Skripts, unter `onOverlayUpdate`. Alles ist noch da, gleich geschrieben. Starte es: ---- Error executing function 'onOverlayUpdate': functions/functions.singe:50: attempt to call a nil value (global 'drawStar') stack traceback: functions/functions.singe:50: in upvalue 'drawLayer' functions/functions.singe:57: in upvalue 'drawStars' functions/functions.singe:101: in function 'onOverlayUpdate' ---- Das sieht anders aus als der Fehler in Lektion eins, weil es eine andere Art von Fehler ist. Der Fehler aus Lektion eins wurde erwischt, bevor das Skript lief. Dieser hier wurde erwischt, während es lief, und darum nennt Singe die Funktion, in der es gerade steckte, und darum steht darunter ein *Traceback*: die Liste, wer wen aufgerufen hat, das Neueste zuerst. Zeile 50 liegt in `drawLayer`, das von Zeile 57 in `drawStars` aufgerufen wurde, das von Zeile 101 in `onOverlayUpdate` aufgerufen wurde. Deine Zeilennummern weichen ab, wenn deine Datei nicht genau mit der oben übereinstimmt, und die Engine hängt darunter vielleicht noch eine oder zwei eigene Zeilen an. Das Wort `upvalue` ist Luas Name für eine lokale Variable, die sich eine Funktion aus der Datei um sie herum geliehen hat; lies vorerst darüber hinweg. Die Beschwerde selbst lohnt es sich auswendig zu lernen. „Attempt to call a nil value“ heißt, dass du Klammern hinter einen Namen gesetzt hast, in dem nichts stand. Das Wort `global` vor `drawStar` ist der Teil, der alles verrät: Du hast `drawStar` als lokal markiert, und als Lua `drawLayer` gelesen und nirgendwo darüber eine lokale Variable dieses Namens gefunden hat, blieb nur noch, es mit einer globalen zu versuchen -- und eine globale namens `drawStar` gibt es auch nicht. Setz die Funktion wieder über den Code, der sie aufruft, und der Fehler verschwindet. [#_what_you_learned_5] === Was du gelernt hast * Du kannst eigene Funktionen schreiben, und nichts führt sie aus außer deinen eigenen Aufrufen. * Die Namen in den Klammern einer Funktion sind Parameter, und sie sind die eigenen Kopien der Funktion von dem, was der Aufrufer übergeben hat. * `return` gibt einen Wert zurück und hält die Funktion an der Stelle an, und eine Funktion, die ohne ein solches endet, gibt `nil` zurück. * Ein Aufruf kann ein Argument eines anderen Aufrufs sein, und der innere passiert zuerst. * Lua kann mehr als einen Wert zurückgeben, und du fängst sie mit mehreren Namen auf, durch Kommas getrennt. * `local` legt einen Namen an, der nur dort existiert, wo er geschrieben wurde. * Eine lokale Variable in einer Funktion wird bei jedem Aufruf frisch angelegt und merkt sich nichts vom letzten. * Eine lokale Variable ist nur unterhalb der Zeile sichtbar, die sie anlegt, also kommen Werte und deine eigenen Funktionen über die Callbacks, die sie benutzen. * Callbacks bleiben global, weil Singe sie über ihren Namen findet. * Eine Funktion lohnt sich, wenn das, was sie tut, einen Namen hat, auch wenn du sie nur einmal aufrufst. [#_next_time_5] === Beim nächsten Mal Deine hundertzwanzig Sterne sind immer noch hundertzwanzig Kopien von drei Ideen. Nichts im Programm ist *ein Stern*: Es gibt eine Zahl und etwas Rechnerei, die aus der Zahl eine Position macht. Du kannst nicht sagen „dieser hier ist näher, also fällt er schneller und leuchtet heller“, weil es kein „dieser hier“ gibt, über das du es sagen könntest, und du kannst keinen Stern unten herausfallen und durch einen neuen ersetzen lassen, weil da nichts ist, was man entfernen könnte. Lektion sechs gibt dir das Ding, das einen Stern festhält, und dann einen Weg, hundertzwanzig davon festzuhalten und einen hunderteinundzwanzigsten hinzuzufügen, während das Spiel läuft. Es ist die nützlichste Lektion in diesem Teil des Buchs.