[#_lesson_26_characters_that_move_themselves] == Lektion 26: Figuren, die sich selbst bewegen image::learn/26-navigation.png[Die fertige Lektion, 480] In dieser Lektion baust du einen Raum mit einer Wand quer durch die Mitte und stellst zwei Figuren hinein. Die eine schickst du per Knopfdruck in eine Ecke. Die andere sucht sich zufällig irgendeinen Ort aus, läuft dorthin und sucht sich, wenn sie ankommt, einen anderen aus, für immer. Keine von beiden läuft in die Wand, und du schreibst nie eine Zeile, die sagt, wie man um sie herumkommt. Das ist neu. Jede Lektion bis zu dieser hat Dinge dorthin bewegt, wo du sie hinhaben wolltest. Das ist die erste Lektion, in der das Spiel selbst etwas herausfindet. [#_the_problem_with_walking_toward_something] === Das Problem mit dem Laufen auf etwas zu Hier ist der naheliegende Weg, eine Figur zu einem Punkt laufen zu lassen, und so schreibt es jeder zuerst. Nimm das Ziel, zieh ab, wo du bist, und geh ein kleines Stück auf der Linie dazwischen, in jedem Frame, bis du ankommst. In einem leeren Raum funktioniert das wunderbar. Stell eine Wand in den Raum, und es funktioniert nicht mehr, und zwar auf eine Art, die eher dumm als kaputt aussieht: Die Figur marschiert in die Wand, bleibt dort stehen und schrammt daran, einen Meter neben einem Durchgang, den sie nicht sehen kann, weil nichts in „geh auf das Ziel zu“ eine Ahnung davon hat, dass es in der Welt Formen gibt. Da kannst du dich nicht herausflicken. Wenn die Figur zur Seite abbiegt, sobald sie eine Wand berührt, bleibt sie in der ersten Ecke hängen, die sie findet. Es reicht auch nicht, zu wissen, wo die Wände sind, denn der Weg um eine Wand herum kann lang sein, kann sich erst vom Ziel entfernen, bevor er zurückkommt, und kann überhaupt nicht existieren. Was du brauchst, ist eine Möglichkeit, eine echte Frage zu stellen: *Wie laufe ich von hier nach dort?* Um sie zu beantworten, braucht es etwas, das die Form des Bodens kennt. [#_what_a_navigation_mesh_is] === Was ein Navigationsmesh ist Stell dich in einen Raum und sieh auf den Boden. Auf manchem davon könntest du laufen. Auf manchem nicht: Er liegt unter einem Tisch, in einer Wand, jenseits der Kante eines Balkons oder hinter einer Stufe, die zu hoch zum Hinaufsteigen ist. Stell dir jetzt vor, du malst den Teil an, auf dem du laufen könntest, und wirfst alles andere weg -- die Wände, die Möbel, die Decke, die Kulisse. Übrig bleibt eine flache Form, oder mehrere, mit Löchern, wo die Säulen sind, und Lücken, wo es hinuntergeht. Diese angemalte Form ist ein *Navigationsmesh*, und mehr braucht eine Figur nicht, um einen Weg zu planen. Es ist nicht das Level. Es ist eine vereinfachte Karte der Teile des Levels, auf denen ein Mensch stehen kann, und weil es einfach ist, lassen sich Fragen dazu schnell beantworten. Singe macht es für dich aus dem Level, das du schon hast. Du sagst, wie groß die Figuren sind, gibst ihm die Geometrie, und es findet heraus, was begehbar ist: Böden und Rampen innerhalb der Steigung, die du erlaubst, Stufen, die nicht höher sind, als du erlaubst, und um jede Wand und jede Kante wird ein Rand abgeschnitten, damit eine Figur, die eine Breite hat, keine Ecke schneidet. Das nennt man *Backen*, es passiert einmal, und das Ergebnis kann in einer Datei gespeichert und beim nächsten Mal geladen werden. [#_a_room_with_a_wall_in_it] === Ein Raum mit einer Wand darin Leg einen Ordner namens `navigation` an und darin eine Datei namens `navigation.singe`. Dieser Teil ist eine dreidimensionale Szene, und darum ging es in Lektion zweiundzwanzig: Eine *Szene* ist eine Welt, die in drei Dimensionen gezeichnet wird, und alles darin hängt an einem *Knoten*, also an einer Position, einer Drehung und einer Skalierung, an die du eine Form hängen kannst. Wenn du diese Lektion übersprungen hast, kannst du dieser hier trotzdem folgen. Nimm die nächsten beiden Blöcke einfach als „ein grauer Boden, zwanzig Einheiten im Quadrat, drei hellbraune Wände darauf und eine Kamera, die auf das Ganze hinunterschaut“. [source,lua] ---- local ROOM = 20 local WALL_HEIGHT = 2 local AGENT_RADIUS = 0.4 local AGENT_HEIGHT = 1.8 local WALK_SPEED = 2.2 sceneEnable(true) sceneSetBackground(28, 32, 42, 255) sceneSetAmbient(80, 86, 100) local floorLook = materialNew() materialSetColor(floorLook, 96, 102, 110) materialSetRoughness(floorLook, 0.9) local wallLook = materialNew() materialSetColor(wallLook, 150, 128, 104) materialSetRoughness(wallLook, 0.8) local ground = nodeNew() nodeSetMesh(ground, meshPlane(ROOM, ROOM), floorLook) local wallMesh = meshBox(1, WALL_HEIGHT, 1) local walls = {} function addWall(x, z, width, depth) local wall = nodeNew() nodeSetMesh(wall, wallMesh, wallLook) nodeSetScale(wall, width, 1, depth) nodeSetPosition(wall, x, WALL_HEIGHT / 2, z) walls[#walls + 1] = wall end addWall(-4, 0, 12, 0.6) addWall(7.5, 0, 5, 0.6) addWall(-3, -6, 0.6, 8) ---- Die Einheiten hier sind Meter, denn davon geht die dreidimensionale Welt von Singe aus. Die ersten beiden Wände laufen quer durch die Mitte des Raums, mit einer Lücke von drei Metern dazwischen: ein Durchgang. Die dritte ist ein Stummel, der in die hintere Hälfte ragt, damit die Figuren etwas haben, um das sie herumlaufen müssen, wenn sie durch sind. Füg ein Licht und eine Kamera hinzu, damit es etwas zu sehen gibt: [source,lua] ---- local sun = lightNew(LIGHT_DIRECTIONAL) nodeSetPosition(sun, 8, 14, 6) nodeLookAt(sun, 0, 0, 0) lightSetIntensity(sun, 3.5) lightSetShadow(sun, true) local camera = nodeNew() nodeSetPosition(camera, 0, 17, 19) nodeLookAt(camera, 0, 0, 0) cameraSet(camera) cameraSetPerspective(55, 0.1, 120) function onOverlayUpdate() overlayClear() return OVERLAY_UPDATED end ---- Starte es mit `Singe -R navigation`. Ein grauer Boden, eine Wand mit einer Lücke und ein Stummel, von oben und von hinten gesehen. [#_bake_the_floor] === Back den Boden Sechs Zeilen machen aus diesem Raum eine Karte: [source,lua] ---- local nav = navNew(AGENT_RADIUS, AGENT_HEIGHT, 45, 0.3) navAddNode(nav, ground) for _, wall in ipairs(walls) do navAddNode(nav, wall) end navBuild(nav) ---- Setz sie hinter die Wände und vor das Licht. Dann füg in `onOverlayUpdate` unter `overlayClear` eine Zeile ein, damit du siehst, was du bekommen hast: [source,lua] ---- navDraw(nav, 0, 170, 190) ---- Starte es noch einmal. Ein Netz aus blaugrünen Dreiecken liegt über dem Boden, und es hört vor den Wänden auf. Die Lücke, die es lässt, ist keine Dekoration: Es sind die 0,4 Meter Abstand, die eine Figur mit einem Radius von 0,4 Metern braucht, damit ihre Schultern nicht durch den Stein gehen. Der Durchgang ist im Mesh immer noch ein Durchgang, denn drei Meter sind breit genug, um hindurchzugehen; mach die Lücke einen halben Meter breit, und das Blau würde sich darüber schließen, und für jede Figur in deinem Spiel gäbe es dort überhaupt keine Tür. Sieh es dir einen Moment lang an, denn `navDraw` ist das Werkzeug, zu dem du jedes Mal greifen wirst, wenn eine Figur etwas Unerklärliches tut. In neun von zehn Fällen liegt die Antwort vor dir auf dem Boden. [#_somebody_to_walk_it] === Jemand, der darauf läuft Eine Figur ist ein Knoten. Wie sie aussieht, geht die Navigation nichts an, also bau das Billigste, was zeigt, in welche Richtung sie schaut: einen Kasten mit einer Nase dran. [source,lua] ---- function makeWalker(x, z, look) local walker = nodeNew() nodeSetPosition(walker, x, 0, z) local body = nodeNew() nodeSetParent(body, walker) nodeSetMesh(body, meshBox(0.7, AGENT_HEIGHT, 0.7), look) nodeSetPosition(body, 0, AGENT_HEIGHT / 2, 0) local nose = nodeNew() nodeSetParent(nose, body) nodeSetMesh(nose, meshSphere(0.22, 16), look) nodeSetPosition(nose, 0, 0.4, -0.45) return walker end ---- Das kommt zu `addWall`. Es braucht eine Farbe, also füg neben den anderen Materialien eine hinzu: [source,lua] ---- local walkerLook = materialNew() materialSetColor(walkerLook, 225, 85, 60) materialSetEmissive(walkerLook, 70, 18, 10) ---- Und jetzt die Zeile, für die es diese Lektion gibt. Nach `navBuild`: [source,lua] ---- local walker = makeWalker(-7, 7, walkerLook) local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- `navAgentNew` setzt den Knoten auf das Mesh und gibt einen *Agenten* zurück: das Wort der Engine für etwas, das sie bereit ist, für dich herumlaufen zu lassen. Ab diesem Moment gehört die Position dieses Knotens der Engine. In jedem Frame bewegt sie ihn und dreht ihn in die Richtung, in die er geht, und das tut sie so lange, bis du ihm sagst, wohin er gehen soll, oder den Agenten wegnimmst. Starte es. Ein roter Kasten mit einer Nase erscheint in der vorderen Ecke und tut absolut nichts, weil du noch um nichts gebeten hast. [#_sending_it_somewhere] === Ihn irgendwohin schicken Gib ihm eine Liste von Orten und einen Knopf. [source,lua] ---- local posts = { { 7, 0, 7 }, { 7, 0, -7 }, { -7, 0, -7 }, { -7, 0, 7 }, { 30, 0, 30 } } local post = 0 local message = "Button 1 sends the red one somewhere." function onInputPressed(what) if what ~= SWITCH_BUTTON1 then return end post = post % #posts + 1 local target = posts[post] if navAgentMoveTo(walkerAgent, target[1], target[2], target[3]) then message = string.format("Walking to post %d.", post) else message = string.format("No path to post %d. Staying put.", post) end end ---- Gib die Meldung aus, damit du siehst, was passiert. In `onOverlayUpdate`, nach `navDraw`: [source,lua] ---- overlayPrint(2, 2, message) ---- Starte es und drück Knopf eins -- standardmäßig die *Leertaste*. Der Kasten dreht sich, läuft in die hintere Ecke seiner eigenen Raumhälfte und bleibt stehen. Drück noch einmal. Er läuft an der Wand entlang, findet den Durchgang, geht hindurch, kommt auf der anderen Seite heraus und bleibt in der hinteren Ecke stehen. Drück ein drittes Mal, und er geht um den Stummel herum zur vierten Ecke. Du hast ihm nichts von dem Durchgang gesagt. Nichts in deinem Skript erwähnt den Durchgang. Er hat ihn gefunden, weil der Durchgang ein Loch in einer Form ist, und um Löcher herumzugehen ist genau das, wofür das Mesh da ist. Jeder Eintrag in `posts` besteht aus drei Zahlen, einem x, einem y und einem z, weil ein Punkt in einer dreidimensionalen Welt drei braucht. Y zeigt nach oben, und deine sind alle null, weil hier alles auf dem Boden liegt. Die Tabelle `posts` ist eine Tabelle aus Tabellen, aus Lektion sechs, und `posts[post]` ist eine der inneren. [#_knowing_when_it_arrives] === Wissen, wann er ankommt Der Kasten bleibt stehen, wenn er ankommt, aber dein Skript weiß nicht, dass er angekommen ist. Lass es dir sagen: [source,lua] ---- function onNavArrived(agent) message = string.format("Arrived at post %d.", post) end ---- Das ist ein Callback, dieselbe Idee wie `onOverlayUpdate` in Lektion eins: Du schreibst ihn, die Engine ruft ihn auf. Dieser läuft einmal, in dem Moment, in dem ein Agent das Ziel erreicht, das er bekommen hat. Nicht, wenn er in der Nähe ist, und nicht in jedem Frame danach -- einmal, bei der Ankunft. Er bekommt den Agenten übergeben, denn ein Spiel hat mehr als einen. Dieses Argument ist die Zahl, die `navAgentNew` zurückgegeben hat, und indem du es vergleichst, erkennst du, welche deiner Figuren gerade dort angekommen ist, wo sie hinwollte. [#_when_there_is_no_path] === Wenn es keinen Weg gibt Drück den Knopf ein fünftes Mal, und die Meldung lautet `No path to post 5. Staying put.`, und der Kasten bewegt sich nicht. Posten fünf liegt bei `{ 30, 0, 30 }`, und das ist zwanzig Meter außerhalb eines Raums, der zwanzig Meter breit ist. Dort gibt es keinen begehbaren Boden, es gibt keine Route dorthin, und es gibt nichts Vernünftiges, was eine Figur dagegen tun könnte. `navAgentMoveTo` sagt das, indem es `false` zurückgibt, und es lässt den Agenten genau so, wie er war. Das ist der Teil, den Anfänger überspringen, und es ist der Teil, der zubeißt. Echte Spiele bitten ständig um unmögliche Wege, denn das Ziel ist meistens der Spieler, und der Spieler kommt überallhin: Er steht auf einer Kiste, ist mitten im Sprung, im Wasser, hinter einer Tür, die zugefallen ist, oder durch eine Lücke geschlüpft, die das Navigationsmesh als zu schmal weggeschnitten hat. Jeder dieser Fälle lässt ein völlig vernünftiges `navAgentMoveTo` `false` zurückgeben. Dein Skript entscheidet, was das bedeutet. Eine Wache, die dich nicht erreichen kann, sollte wieder Streife gehen, oder zum nächsten Punkt laufen, den sie erreichen *kann*, und dort warten, oder rufen. Was sie nicht tun darf, ist nichts, während dein Code still ein zurückgegebenes `false` übergeht und du dich eine Stunde lang fragst, warum einer von zwanzig Gegnern erstarrt ist. Zwei andere Aufrufe helfen hier, und es lohnt sich, sie zu kennen, bevor du sie brauchst. `navNearest` nimmt irgendeinen Punkt und gibt die nächstgelegene Stelle auf dem Mesh zurück, oder `nil`, wenn nichts in der Nähe ist; so machst du aus „wo der Spieler ist“ „irgendwo, wo eine Figur wirklich stehen kann“. Und `navPath` stellt dieselbe Frage wie `navAgentMoveTo`, ohne jemanden zu bewegen: Es gibt die Ecken der Route als Tabelle von Punkten zurück, oder `nil`, wenn es keine Route gibt. Du kannst sie ausmessen, zeichnen oder entscheiden, dass der Weg zu lang ist, um sich damit abzugeben. Das Zeichnen ist der nützliche Trick. Füg das hier in `onOverlayUpdate` ein: [source,lua] ---- local target = posts[post] if target ~= nil then local x, y, z = nodeGetWorldPosition(walker) local route = navPath(nav, x, y, z, target[1], target[2], target[3]) if route ~= nil then for i = 2, #route do local a = route[i - 1] local b = route[i] lineDraw(a[1], a[2] + 0.1, a[3], b[1], b[2] + 0.1, b[3], 255, 215, 60) end end end ---- Jetzt läuft eine gelbe Linie vom Kasten bis dorthin, wo er hinwill, knickt durch den Durchgang ab und wird kürzer, während er läuft. Schick ihn zu Posten fünf, und die Linie wird gar nicht gezeichnet, weil `navPath` `nil` zurückgegeben und das `if` es abgefangen hat. Du siehst der Entscheidung zu, die der Agent trifft, auf den Boden gezeichnet. [#_a_second_walker] === Ein zweiter Läufer Eine Figur, die Wänden ausweicht, ist Navigation. Zwei Figuren, die Wänden *und einander* ausweichen, sind das, was dir die Engine tatsächlich gibt, und es kostet vier Zeilen mehr. Füg neben dem roten ein blaues Material hinzu, dann das hier unter dem ersten Agenten: [source,lua] ---- local wanderer = makeWalker(6, -6, wandererLook) local wandererAgent = navAgentNew(nav, wanderer, AGENT_RADIUS, AGENT_HEIGHT, 1.4) ---- Schick ihn nach der Kamera irgendwohin, damit er loslegt: [source,lua] ---- navAgentMoveTo(wandererAgent, navRandomPoint(nav)) ---- Und bring `onNavArrived` bei, die beiden auseinanderzuhalten: [source,lua] ---- function onNavArrived(agent) if agent == wandererAgent then navAgentMoveTo(agent, navRandomPoint(nav)) else message = string.format("Arrived at post %d.", post) end end ---- `navRandomPoint` sucht sich eine Stelle auf dem begehbaren Mesh aus -- irgendwo darauf, nie in einer Wand -- und gibt drei Zahlen zurück, und das ist genau das, was `navAgentMoveTo` als Nächstes haben will. Weil der Callback bei der Ankunft feuert und seine Antwort eine weitere Reise ist, wandert der blaue Kasten jetzt durch den Raum, solange das Spiel läuft, aus vier Zeilen und ganz ohne Zustand. Sieh zu, wie sich die beiden im Durchgang begegnen. Keiner bleibt abrupt stehen, und keiner läuft durch den anderen hindurch: Beide weichen rechtzeitig ein wenig zur Seite aus und gehen aneinander vorbei. Das sind die Agenten, die umeinander herumsteuern, und das ist der Unterschied zwischen Figuren und Möbeln. Das fertige Skript ist `learn/26-navigation.singe`. [#_what_just_happened_22] === Was gerade passiert ist [source,lua] ---- local nav = navNew(AGENT_RADIUS, AGENT_HEIGHT, 45, 0.3) ---- Beginnt ein Navigationsmesh, und die vier Zahlen sind eine Beschreibung des Menschen, für den es gedacht ist: 0,8 Meter breit, 1,8 Meter groß, fähig, eine Steigung von bis zu fünfundvierzig Grad zu gehen, und fähig, eine Stufe von bis zu 0,3 Metern hinaufzusteigen. Jede dieser Zahlen ändert die Form, die herauskommt. Eine breitere Figur verliert mehr Boden an den Rand um die Wände und findet einen Durchgang vielleicht verschlossen. Eine größere kann nicht unter einem niedrigen Balken durchgehen. Erlaube eine steilere Steigung, und eine Rampe wird zur Straße; erlaube weniger, und sie wird zur Klippe. Der Radius entscheidet auch, wie fein der Boden vermessen wird, also dauert das Backen bei einem sehr kleinen Radius über einem sehr großen Level lange. Den Eintrag zu `navNew` in der Referenz solltest du lesen, bevor du deine Zahlen wählst. [source,lua] ---- navAddNode(nav, ground) navBuild(nav) ---- `navAddNode` übergibt Geometrie -- einen Knoten und alles, was unter ihm hängt, so wie es in diesem Moment in der Welt steht, also stell die Dinge an ihren Platz, bevor du sie hinzufügst. Ein Aufruf nimmt ein ganzes geladenes Modell. Nur echte Formen zählen: Ein Licht, ein Sound oder ein Physikkörper für sich allein fügt nichts hinzu. `navBuild` erledigt das Backen, und es ist der langsame Aufruf in dieser Lektion: nichts, was du bemerken würdest, für einen Raum wie diesen, eine Zehntelsekunde für eine Kathedrale auf einem Desktop-Rechner, mehrere Sekunden auf einem Raspberry Pi. Mach es, während ein Titelbildschirm zu sehen ist, nicht, während der Spieler wartet. Danach wird die Geometrie weggeworfen, und das Mesh ist geschlossen. Du kannst nichts mehr hinzufügen. Wenn dein Level seine Form ändert -- eine Tür geht auf, eine Brücke stürzt ein -- backst du ein anderes Mesh, und `navDelete` gibt das alte frei. Auf einem langsamen Rechner willst du lieber gar nicht backen, also schreibt `navSave` das fertige Mesh in eine Datei, und `navLoad` liest es wieder ein, gebaut und bereit. Back einmal auf deinem eigenen Rechner, liefere die Datei mit dem Spiel aus, und der Spieler wartet nie. [source,lua] ---- local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- Ein Agent ist ein Knoten, den die Engine für dich herumlaufen lässt, weil sie sich dazu bereit erklärt hat. Radius und Höhe benutzt sie hier, um Abstand zu anderen Agenten zu halten, und die letzte Zahl ist seine Höchstgeschwindigkeit in Metern pro Sekunde -- 2,2 ist zügiges Gehen, 1,4 ist Schlendern, 6 ist ein Sprint. Setz ab jetzt die Position dieses Knotens nicht mehr selbst. Zwei Dinge, die einen Knoten bewegen, kämpfen miteinander, und der Kampf sieht aus wie Ruckeln. Wenn deine Figur nicht nur laufen, sondern auch von der Physik herumgeschubst werden soll, gibt es dafür einen Aufruf -- `navAgentSetPlayer` übergibt die Steuerung stattdessen einem Physik-Controller -- und der Eintrag dazu in der Referenz erklärt, wann du das willst. [source,lua] ---- local ok = navAgentMoveTo(walkerAgent, x, y, z) ---- Setzt das Ziel. Der Aufruf läuft nicht selbst dorthin; er sagt, wo „dort“ ist, und die Engine kümmert sich in den folgenden Frames darum. Du darfst ihn jederzeit erneut aufrufen, und das neue Ziel ersetzt das alte; so folgt eine Wache einem Spieler, der rennt: Richte sie ein- oder zweimal pro Sekunde neu aus, nicht sechzigmal. Und er gibt `true` oder `false` zurück, und du musst nachsehen. [source,lua] ---- navAgentStop(walkerAgent) ---- Wird oben nicht benutzt, ist aber die andere Hälfte des Paars: Es vergisst das Ziel, und der Agent wird langsamer, bis er dort steht, wo er gerade ist. `onNavArrived` feuert nicht, weil er nicht angekommen ist. Benutz es, wenn der Grund für die Reise weggefallen ist. [#_fast_is_not_the_point] === Auf Tempo kommt es nicht an Noch ein Aufruf, denn er macht aus einer Vorführung etwas, das wie ein Spiel aussieht. [source,lua] ---- local vx, vy, vz = navAgentGetVelocity(walkerAgent) local speed = math.sqrt(vx * vx + vz * vz) overlayPrint(2, 4, string.format("Speed %.2f", speed)) ---- `navAgentGetVelocity` sagt dir, wie sich der Agent gerade jetzt bewegt: nicht, wohin er geht, sondern wie schnell und in welche Richtung er in diesem Frame tatsächlich unterwegs ist. Beobachte die Zahl, wenn der Kasten losläuft und wenn er zum Stehen kommt. Er beschleunigt sanft und bremst sanft. An diese Zahl hängst du eine Animation. Eine Figur, deren Beine sich mit der Geschwindigkeit bewegen, mit der sie wirklich unterwegs ist, wirkt lebendig; eine, deren Laufzyklus mit fester Geschwindigkeit läuft, während sie beschleunigt, sieht aus, als liefe sie Schlittschuh. Mit einem echten Modell statt des Kastens würdest du diese Geschwindigkeit mit ein paar Schwellenwerten vergleichen und eine Steh-, eine Geh- oder eine Renn-Animation abspielen -- darum geht es in Lektion dreiundzwanzig, und das Beispiel in der Referenz unter `navAgentGetVelocity` ist genau dafür geschrieben. Und das ist der eigentliche Punkt dieser Lektion. Eine Figur, die die schnelle gerade Linie nimmt und an einer Wand entlangschrammt, ist schlechter als eine, die eine Sekunde später losgeht, sich richtig dreht, außen herum durch die Tür geht und langsamer wird, wenn sie ankommt. Niemand, der ein Spiel spielt, misst, wie schnell ein Gegner bei ihm war. Aber jeder merkt sofort, ohne sagen zu können, warum, wenn sich etwas so bewegt, wie sich kein Lebewesen bewegen würde. [#_try_it_26] === Probier es aus . *Mach die Tür zu.* Ändere die ersten beiden Wände so, dass die Lücke zwischen ihnen einen halben Meter breit ist statt drei: `addWall(-4, 0, 13, 0.6)` und `addWall(6.5, 0, 7, 0.6)`. Starte es und sieh dir an, was `navDraw` zeigt, bevor du irgendetwas drückst. Dann schick den Kasten hinüber und sieh, was er tut. . *Mach alle dick.* Setz `AGENT_RADIUS` auf `2.0` und starte es. Der Durchgang ist immer noch drei Meter breit, und die blauen Dreiecke sind daraus verschwunden. Nichts ist kaputt; das Mesh beantwortet jetzt eine andere Frage. . *Schick ihn zur Maus.* Nimm mit `sceneUnproject` den Punkt unter dem Mauszeiger, rück ihn mit `navNearest` auf das Mesh und schick den Kasten dorthin, wenn der Knopf gedrückt wird. Dass `navNearest` `nil` zurückgibt, ist der Fall, um den du dich kümmern musst, und er tritt immer dann ein, wenn der Zeiger nicht über dem Boden ist. . *Füg vier weitere Wanderer hinzu.* Pack die vier Zeilen des blauen Läufers in eine Schleife mit `for` aus Lektion vier, bewahr die Agenten in einer Tabelle auf und lass sie alle herumwandern. Beobachte den Durchgang, wenn drei von ihnen gleichzeitig hindurchwollen. . *Back es einmal.* Ruf `navSave(nav, "room.nav")` nach `navBuild` auf, dann ersetze `navNew`, die Aufrufe von `navAddNode` und `navBuild` durch ein einziges `navLoad("room.nav", AGENT_RADIUS, AGENT_HEIGHT)`. Jetzt ändere die Position einer Wand und starte es noch einmal. Was du siehst, ist die Falle, die mit gebackenen Meshes kommt, und es lohnt sich, ihr einmal absichtlich zu begegnen. [#_break_it_on_purpose_25] === Mach es absichtlich kaputt Backen schließt das Mesh. Versuch, nachträglich noch etwas hinzuzufügen -- eine Kiste, die du spät hingestellt hast, oder eine Wand, die du vergessen hast -- indem du diese Zeile direkt hinter `navBuild(nav)` setzt: [source,lua] ---- navAddNode(nav, ground) ---- Singe hält an, bevor das Spiel startet, und nennt die Zeile in deiner Datei, in der dieser Aufruf steht: ---- 68:navAddNode: The mesh is already built. ---- Ein Fehler der Engine hat die Form: die Zeile, der Aufruf, der sich gewehrt hat, und die Beschwerde, und diese Beschwerde meint, was sie sagt: `navBuild` hat die Geometrie verbraucht, die es bekommen hat, und es gibt nichts mehr, wozu man etwas hinzufügen könnte. Die Lösung ist nie, den Aufruf von `navAddNode` zu verschieben. Sie ist, zu bemerken, dass du Geometrie für zwei Level hast und zwei Meshes willst, oder dass du zu früh gebacken hast. Füg alles hinzu, dann bau, und bau zuletzt. [#_what_you_learned_26] === Was du gelernt hast * Auf ein Ziel zuzugehen funktioniert in einem leeren Raum und versagt, sobald es eine Wand gibt, und kein noch so eifriges Flicken behebt das. * Ein Navigationsmesh ist eine vereinfachte Karte des Bodens, auf dem eine Figur stehen kann, ohne die Wände, die Abgründe und den Abstand um sie herum. * `navNew` beschreibt die Figur, für die das Mesh ist, `navAddNode` übergibt die Geometrie des Levels, und `navBuild` backt es. Nach dem Backen kann nichts mehr hinzugefügt werden. * `navSave` und `navLoad` bewahren ein gebackenes Mesh in einer Datei auf, damit der Spieler nie darauf wartet. * `navDraw` zeigt dir das Mesh, und es ist das Erste, was du dir ansiehst, wenn sich eine Figur seltsam verhält. * `navAgentNew` gibt der Engine einen Knoten zum Herumlaufen; ab dann bewegst du diesen Knoten nicht mehr selbst. * `navAgentMoveTo` setzt ein Ziel und gibt `false` zurück, wenn es keinen Weg dorthin gibt. Darum musst du dich kümmern, und es passiert oft. * `onNavArrived` wird einmal aufgerufen, wenn ein Agent sein Ziel erreicht, und erfährt, welcher Agent es ist. * `navPath` beantwortet dieselbe Frage, ohne jemanden zu bewegen, und `nil` heißt: keine Route. * Agenten steuern umeinander herum, nicht nur um das Level herum. * Mit `navAgentGetVelocity` passt ein Laufzyklus zu der Strecke, die zurückgelegt wird. [#_next_time_26] === Beim nächsten Mal Deine beiden Kästen laufen jetzt schweigend durch einen Raum. In Lektion siebenundzwanzig geht es darum, was du darunterlegst: Musik, die sauber in einer Schleife läuft und mit dem Video im Takt bleibt, und MIDI, mit dem ein Spiel ein Instrument spielen kann -- oder ein echtes Instrument das Spiel.