[#_lesson_26_characters_that_move_themselves] == Leçon 26 : Des personnages qui se déplacent seuls image::learn/26-navigation.png[La leçon terminée, 480] Dans cette leçon, vous construisez une pièce coupée en deux par un mur, et vous y mettez deux personnages. L'un, vous l'envoyez dans un coin d'un appui sur un bouton. L'autre choisit un endroit au hasard, y marche, et quand il arrive en choisit un autre, indéfiniment. Aucun des deux ne se cogne dans le mur, et vous n'écrivez jamais une ligne qui dit comment le contourner. C'est nouveau. Jusqu'ici, chaque leçon a déplacé les choses là où vous leur disiez d'aller. C'est la première leçon où le jeu trouve quelque chose tout seul. [#_the_problem_with_walking_toward_something] === Le problème quand on marche vers quelque chose Voici la façon évidente de faire marcher un personnage jusqu'à un point, et c'est celle que tout le monde écrit en premier. Prenez la cible, soustrayez l'endroit où vous êtes, et avancez un peu le long de la ligne qui les relie, à chaque image, jusqu'à y arriver. Ça marche à merveille dans une pièce vide. Mettez un mur dans la pièce et ça ne marche plus, et ça ne marche plus d'une façon qui a l'air bête plutôt que cassée : le personnage marche droit dans le mur et reste là à frotter contre lui, à un mètre d'une porte qu'il ne voit pas, parce que rien dans « avancer vers la cible » n'a la moindre idée que le monde contient des formes. Vous ne vous en sortirez pas à coups de rustines. Faire un écart quand on touche un mur coince votre personnage dans le premier coin qu'il trouve. Et il ne suffit pas non plus de savoir où sont les murs, parce que le chemin qui contourne un mur peut être long, peut s'éloigner de la cible avant d'y revenir, et peut ne pas exister du tout. Ce qu'il faut, c'est un moyen de poser une vraie question : *comment aller à pied d'ici à là-bas ?* Pour y répondre, il faut quelque chose qui connaît la forme du sol. [#_what_a_navigation_mesh_is] === Ce qu'est un maillage de navigation Tenez-vous dans une pièce et regardez le sol. Sur une partie, vous pourriez marcher. Sur une autre, non : elle est sous une table, dans un mur, au-delà du bord d'un balcon, ou en haut d'une marche trop haute pour être gravie. Imaginez maintenant que vous peignez la partie où vous pourriez marcher, et que vous jetez tout le reste -- les murs, les meubles, le plafond, le décor. Il vous reste une forme plate, ou plusieurs, avec des trous là où sont les piliers et des vides là où sont les à-pics. Cette forme peinte est un *maillage de navigation*, et c'est tout ce dont un personnage a besoin pour prévoir un trajet. Ce n'est pas le niveau. C'est une carte simplifiée des parties du niveau où une personne peut se tenir debout, et parce qu'elle est simple, on peut répondre vite aux questions qui la concernent. Singe le fabrique pour vous à partir du niveau que vous avez déjà. Vous dites quelle taille font les personnages, vous lui donnez la géométrie, et il détermine ce qui est praticable : les sols et les rampes dans la limite de la pente que vous autorisez, les marches pas plus hautes que vous ne l'autorisez, avec une marge retirée autour de chaque mur et de chaque à-pic pour qu'un personnage qui a une largeur n'accroche pas un coin. Cela s'appelle le *précalcul*, ça se fait une fois, et le résultat peut être enregistré dans un fichier et chargé la fois suivante. [#_a_room_with_a_wall_in_it] === Une pièce avec un mur dedans Créez un dossier appelé `navigation` et commencez dedans un fichier appelé `navigation.singe`. Cette partie est une scène en trois dimensions, le sujet de la leçon vingt-deux : une *scène* est un monde dessiné en trois dimensions, et tout ce qu'elle contient est accroché à un *nœud*, c'est-à-dire une position, une rotation et une échelle auxquelles vous pouvez attacher une forme. Si vous avez sauté cette leçon, vous pouvez quand même suivre celle-ci. Prenez les deux blocs suivants pour « un sol gris de vingt unités de côté, trois murs beiges posés dessus, et une caméra qui regarde le tout d'en haut ». [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) ---- Les unités sont ici des mètres, parce que c'est ce que suppose le monde en trois dimensions de Singe. Les deux premiers murs traversent le milieu de la pièce avec un espace de trois mètres entre eux : une porte. Le troisième est un bout de mur qui avance dans la moitié du fond, pour que les personnages aient quelque chose à contourner une fois passés. Ajoutez une lumière et une caméra pour qu'il y ait quelque chose à voir : [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 ---- Lancez-le avec `Singe -R navigation`. Un sol gris, un mur avec une ouverture, et un bout de mur, vus d'en haut et de derrière. [#_bake_the_floor] === Précalculez le sol Six lignes font de cette pièce une carte : [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) ---- Mettez-les après les murs et avant la lumière. Puis ajoutez une ligne à `onOverlayUpdate`, sous `overlayClear`, pour voir ce que vous avez obtenu : [source,lua] ---- navDraw(nav, 0, 170, 190) ---- Relancez-le. Une toile de triangles bleu-vert est posée sur le sol, et elle s'arrête avant les murs. L'espace qu'elle laisse n'est pas décoratif : ce sont les 0,4 mètre de marge dont un personnage d'un rayon de 0,4 mètre a besoin pour que ses épaules ne traversent pas la pierre. La porte est toujours une porte dans le maillage, parce que trois mètres, c'est assez large pour passer ; réduisez l'ouverture à un demi-mètre et le bleu se refermerait dessus, et pour tous les personnages de votre jeu, il n'y aurait plus de porte du tout. Regardez-le un instant, parce que `navDraw` est l'outil que vous prendrez chaque fois qu'un personnage fera quelque chose d'inexplicable. Neuf fois sur dix, la réponse est sur le sol, sous vos yeux. [#_somebody_to_walk_it] === Quelqu'un pour y marcher Un personnage est un nœud. Son apparence ne regarde pas la navigation, alors construisez la chose la plus simple qui montre de quel côté il est tourné : une boîte avec un nez. [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 ---- Cela va avec `addWall`. Il lui faut une couleur, alors ajoutez-en une à côté des autres matériaux : [source,lua] ---- local walkerLook = materialNew() materialSetColor(walkerLook, 225, 85, 60) materialSetEmissive(walkerLook, 70, 18, 10) ---- Et maintenant, la ligne pour laquelle cette leçon existe. Après `navBuild` : [source,lua] ---- local walker = makeWalker(-7, 7, walkerLook) local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- `navAgentNew` pose le nœud sur le maillage et renvoie un *agent* : le mot du moteur pour quelque chose qu'il veut bien faire marcher pour vous. À partir de ce moment, la position de ce nœud appartient au moteur. À chaque image, il le déplace et le tourne face à la direction où il va, et il continuera ainsi jusqu'à ce que vous lui disiez où aller ou que vous retiriez l'agent. Lancez-le. Une boîte rouge avec un nez apparaît dans le coin le plus proche et ne fait absolument rien, parce que vous n'avez encore rien demandé. [#_sending_it_somewhere] === L'envoyer quelque part Donnez-lui une liste d'endroits et un bouton. [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 ---- Affichez le message pour voir ce qui se passe. Dans `onOverlayUpdate`, après `navDraw` : [source,lua] ---- overlayPrint(2, 2, message) ---- Lancez-le et appuyez sur le bouton un -- la *barre d'espace*, par défaut. La boîte se tourne, marche jusqu'au coin opposé de sa moitié de la pièce, et s'arrête. Appuyez encore. Elle longe le mur, trouve la porte, la franchit, ressort de l'autre côté, et s'arrête dans le coin du fond. Appuyez une troisième fois et elle contourne le bout de mur jusqu'au quatrième coin. Vous ne lui avez pas parlé de la porte. Rien dans votre script ne mentionne la porte. Elle l'a trouvée, parce que la porte est un trou dans une forme et que contourner les trous, c'est à ça que sert le maillage. Chaque entrée de `posts` est faite de trois nombres, un x, un y et un z, parce qu'un point dans un monde en trois dimensions en demande trois. Y va vers le haut, et les vôtres valent tous zéro parce que tout ici est au sol. La table `posts` est une table de tables, comme à la leçon six, et `posts[post]` est l'une des tables intérieures. [#_knowing_when_it_arrives] === Savoir quand il arrive La boîte s'arrête quand elle arrive, mais votre script ne sait pas qu'elle est arrivée. Demandez à être prévenu : [source,lua] ---- function onNavArrived(agent) message = string.format("Arrived at post %d.", post) end ---- C'est un callback, la même idée que `onOverlayUpdate` à la leçon un : vous l'écrivez, le moteur l'appelle. Celui-ci s'exécute une fois, au moment où un agent atteint la cible qu'on lui a donnée. Pas quand il en est près, et pas à chaque image ensuite -- une fois, à l'arrivée. Il reçoit l'agent, parce qu'un jeu en a plus d'un. Cet argument est le nombre que `navAgentNew` a renvoyé, et c'est en le comparant que vous savez lequel de vos personnages vient d'arriver là où il allait. [#_when_there_is_no_path] === Quand il n'y a pas de chemin Appuyez une cinquième fois sur le bouton et le message affiche `No path to post 5. Staying put.`, et la boîte ne bouge pas. Le poste cinq est en `{ 30, 0, 30 }`, soit vingt mètres en dehors d'une pièce de vingt mètres de large. Il n'y a pas de sol praticable là-bas, aucun chemin pour y aller, et rien de sensé qu'un personnage puisse y faire. `navAgentMoveTo` le dit en renvoyant `false`, et laisse l'agent exactement comme il était. C'est la partie que les débutants sautent, et c'est celle qui mord. Les vrais jeux demandent sans cesse des trajets impossibles, parce que la cible est en général le joueur et que le joueur va partout : debout sur une caisse, en plein saut, dans l'eau, derrière une porte qui s'est fermée, ou de l'autre côté d'un passage que le maillage de navigation a retiré parce qu'il était trop étroit. Chacun de ces cas fait renvoyer `false` à un `navAgentMoveTo` parfaitement raisonnable. C'est votre script qui décide de ce que cela veut dire. Un garde qui ne peut pas vous atteindre devrait reprendre sa ronde, ou marcher jusqu'au point le plus proche qu'il *peut* atteindre et y attendre, ou crier. Ce qu'il ne doit pas faire, c'est rien, pendant que votre code ignore en silence un `false` renvoyé et que vous vous demandez pendant une heure pourquoi un ennemi sur vingt reste figé. Deux autres appels aident ici, et ils valent d'être connus avant que vous en ayez besoin. `navNearest` prend n'importe quel point et renvoie l'endroit le plus proche sur le maillage, ou `nil` s'il n'y a rien près de lui ; c'est ainsi que vous transformez « là où est le joueur » en « un endroit où un personnage peut vraiment se tenir ». Et `navPath` pose la même question que `navAgentMoveTo` sans faire bouger personne : il renvoie les angles du trajet sous forme de table de points, ou `nil` quand il n'y a pas de trajet. Vous pouvez le mesurer, le dessiner, ou décider que le trajet est trop long pour s'en donner la peine. Le dessiner, c'est l'astuce utile. Ajoutez ceci à `onOverlayUpdate` : [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 ---- Maintenant, une ligne jaune va de la boîte jusqu'à l'endroit où elle se rend, en s'infléchissant par la porte et en raccourcissant à mesure qu'elle avance. Envoyez-la au poste cinq et la ligne n'est pas dessinée du tout, parce que `navPath` a renvoyé `nil` et que le `if` l'a intercepté. Vous regardez la décision que prend l'agent, dessinée sur le sol. [#_a_second_walker] === Un deuxième marcheur Un personnage qui évite les murs, c'est de la navigation. Deux personnages qui évitent les murs *et s'évitent entre eux*, c'est ce que le moteur vous donne vraiment, et cela coûte quatre lignes de plus. Ajoutez un matériau bleu à côté du rouge, puis ceci sous le premier agent : [source,lua] ---- local wanderer = makeWalker(6, -6, wandererLook) local wandererAgent = navAgentNew(nav, wanderer, AGENT_RADIUS, AGENT_HEIGHT, 1.4) ---- Envoyez-le quelque part pour le mettre en route, après la caméra : [source,lua] ---- navAgentMoveTo(wandererAgent, navRandomPoint(nav)) ---- Et apprenez à `onNavArrived` à distinguer les deux : [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` choisit un endroit sur le maillage praticable -- n'importe où dessus, jamais dans un mur -- et renvoie trois nombres, ce qui est exactement ce que `navAgentMoveTo` attend ensuite. Comme le callback se déclenche à l'arrivée et que sa réponse est un autre trajet, la boîte bleue erre maintenant dans la pièce tant que le jeu tourne, avec quatre lignes et aucun état. Regardez-les se croiser dans la porte. Aucun ne s'arrête net et aucun ne passe à travers l'autre : tous deux s'écartent un peu, à temps, et se croisent. Ce sont les agents qui se contournent l'un l'autre, et c'est la différence entre des personnages et des meubles. Le script terminé est `learn/26-navigation.singe`. [#_what_just_happened_22] === Ce qui vient de se passer [source,lua] ---- local nav = navNew(AGENT_RADIUS, AGENT_HEIGHT, 45, 0.3) ---- Commence un maillage de navigation, et les quatre nombres décrivent la personne à qui il est destiné : 0,8 mètre de large, 1,8 mètre de haut, capable de monter une pente jusqu'à quarante-cinq degrés, et de gravir une marche jusqu'à 0,3 mètre. Chacun de ces nombres change la forme qui en sort. Un personnage plus large perd plus de sol dans la marge autour des murs et peut trouver une porte fermée. Un plus grand ne peut pas passer sous une poutre basse. Autorisez une pente plus raide et une rampe devient une route ; autorisez-en moins et elle devient une falaise. Le rayon décide aussi de la finesse avec laquelle le sol est mesuré, si bien qu'un très petit rayon sur un très grand niveau prend longtemps à précalculer. L'entrée de la Référence pour `navNew` mérite d'être lue avant de choisir vos nombres. [source,lua] ---- navAddNode(nav, ground) navBuild(nav) ---- `navAddNode` transmet de la géométrie -- un nœud et tout ce qui est accroché en dessous, tel qu'il se trouve dans le monde à cet instant, alors placez les choses avant de les ajouter. Un seul appel prend tout un modèle chargé. Seules comptent les vraies formes : une lumière, un son ou un corps physique seul n'ajoute rien. `navBuild` fait le précalcul, et c'est l'appel lent de cette leçon : rien que vous remarqueriez pour une pièce comme celle-ci, un dixième de seconde pour une cathédrale sur un ordinateur de bureau, plusieurs secondes sur un Raspberry Pi. Faites-le pendant qu'un écran titre est affiché, pas pendant que le joueur attend. Ensuite, la géométrie est jetée et le maillage est fermé. Vous ne pouvez plus rien ajouter. Si votre niveau change de forme -- une porte s'ouvre, un pont s'effondre -- vous précalculez un autre maillage, et `navDelete` libère l'ancien. Sur une machine lente, vous préféreriez ne pas précalculer du tout, alors `navSave` écrit le maillage terminé dans un fichier et `navLoad` le relit, construit et prêt. Précalculez une fois sur votre propre machine, livrez le fichier avec le jeu, et le joueur n'attend jamais. [source,lua] ---- local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- Un agent est un nœud que le moteur a accepté de faire marcher pour vous. Le rayon et la hauteur servent ici à le tenir à l'écart des autres agents, et le dernier nombre est sa vitesse maximale en mètres par seconde -- 2,2 est une marche vive, 1,4 une flânerie, 6 un sprint. À partir de là, ne réglez plus vous-même la position de ce nœud. Deux choses qui déplacent un même nœud se battent, et la bagarre ressemble à des saccades. Si votre personnage doit être bousculé par la physique en plus de marcher, il y a un appel pour cela -- `navAgentSetPlayer` confie plutôt le pilotage à un contrôleur physique -- et son entrée dans la Référence explique quand vous en avez besoin. [source,lua] ---- local ok = navAgentMoveTo(walkerAgent, x, y, z) ---- Règle la cible. Il n'y marche pas ; il dit où est « là-bas », et le moteur s'en occupe au fil des images suivantes. Vous pouvez le rappeler à tout moment et la nouvelle cible remplace l'ancienne ; c'est ainsi qu'un garde suit un joueur qui court : réorientez-le une ou deux fois par seconde, pas soixante. Et il renvoie `true` ou `false`, et vous devez regarder. [source,lua] ---- navAgentStop(walkerAgent) ---- Pas utilisé plus haut, mais c'est l'autre moitié de la paire : il oublie la cible, et l'agent ralentit jusqu'à s'arrêter là où il est. `onNavArrived` ne se déclenche pas, puisqu'il n'est pas arrivé. Servez-vous-en quand la raison du trajet a disparu. [#_fast_is_not_the_point] === Aller vite n'est pas le but Encore un appel, parce que c'est lui qui transforme cette démonstration en quelque chose qui ressemble à un jeu. [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` vous dit comment l'agent se déplace en ce moment : pas où il va, mais à quelle vitesse et dans quelle direction il avance réellement à cette image. Regardez le nombre quand la boîte se met en route et quand elle s'arrête. Il monte en douceur et redescend en douceur. C'est à ce nombre que vous accrochez une animation. Un personnage dont les jambes bougent à la vitesse à laquelle il avance vraiment a l'air vivant ; un personnage dont le cycle de marche tourne à un rythme fixe pendant qu'il accélère a l'air de patiner. Avec un vrai modèle à la place de la boîte, vous compareriez cette vitesse à deux ou trois seuils et joueriez une animation d'attente, de marche ou de course -- c'est le sujet de la leçon vingt-trois, et l'exemple de la Référence sous `navAgentGetVelocity` est écrit exactement pour cela. Et c'est là le vrai sujet de cette leçon. Un personnage qui prend la ligne droite rapide et frotte le long d'un mur fait pire qu'un autre qui part une seconde plus tard, se tourne correctement, fait le tour par la porte et ralentit en arrivant. Personne, en regardant un jeu, ne mesure à quelle vitesse un ennemi l'a atteint. On remarque, tout de suite et sans pouvoir dire pourquoi, quand quelque chose bouge d'une façon dont un être vivant ne bougerait pas. [#_try_it_26] === Essayez . *Fermez la porte.* Changez les deux premiers murs pour que l'espace entre eux fasse un demi-mètre au lieu de trois : `addWall(-4, 0, 13, 0.6)` et `addWall(6.5, 0, 7, 0.6)`. Lancez-le et regardez ce que montre `navDraw` avant d'appuyer sur quoi que ce soit. Puis envoyez la boîte de l'autre côté et voyez ce qu'elle fait. . *Rendez tout le monde gros.* Mettez `AGENT_RADIUS` à `2.0` et lancez-le. La porte fait toujours trois mètres de large et les triangles bleus en ont disparu. Rien n'est cassé ; le maillage répond maintenant à une autre question. . *Envoyez-la vers la souris.* Prenez le point sous le pointeur de la souris avec `sceneUnproject`, ramenez-le sur le maillage avec `navNearest`, et envoyez-y la boîte quand le bouton est enfoncé. `navNearest` qui renvoie `nil`, c'est le cas que vous devez gérer, et il se produit chaque fois que le pointeur n'est pas sur le sol. . *Ajoutez quatre autres promeneurs.* Mettez les quatre lignes du marcheur bleu dans une boucle `for`, comme à la leçon quatre, gardez les agents dans une table, et laissez-les tous errer. Regardez la porte quand trois d'entre eux la veulent en même temps. . *Précalculez-le une fois.* Appelez `navSave(nav, "room.nav")` après `navBuild`, puis remplacez `navNew`, les appels à `navAddNode` et `navBuild` par un seul `navLoad("room.nav", AGENT_RADIUS, AGENT_HEIGHT)`. Changez maintenant la position d'un mur et relancez. Ce que vous voyez est le piège qui va avec les maillages précalculés, et il vaut la peine de le rencontrer une fois exprès. [#_break_it_on_purpose_25] === Cassez-le exprès Le précalcul ferme le maillage. Essayez d'ajouter une chose de plus après coup -- une caisse placée trop tard, ou un mur oublié -- en mettant cette ligne juste après `navBuild(nav)` : [source,lua] ---- navAddNode(nav, ground) ---- Singe s'arrête avant que le jeu démarre, en donnant la ligne de votre fichier où se trouve cet appel : ---- 68:navAddNode: The mesh is already built. ---- Une erreur du moteur a toujours la même forme : la ligne, l'appel qui a protesté, et la plainte, et cette plainte veut dire ce qu'elle dit : `navBuild` a consommé la géométrie qu'on lui avait donnée et il n'y a plus rien à quoi ajouter. Le remède, ce n'est jamais de déplacer l'appel à `navAddNode`. C'est de remarquer que vous avez la géométrie de deux niveaux et qu'il vous faut deux maillages, ou que vous avez précalculé trop tôt. Ajoutez tout, puis construisez, et construisez en dernier. [#_what_you_learned_26] === Ce que vous avez appris * Avancer vers une cible marche dans une pièce vide et échoue dès qu'il y a un mur, et aucune rustine n'y change rien. * Un maillage de navigation est une carte simplifiée du sol où un personnage peut se tenir, dont on a retiré les murs, les à-pics et la marge autour d'eux. * `navNew` décrit le personnage auquel le maillage est destiné, `navAddNode` transmet la géométrie du niveau, et `navBuild` la précalcule. Après le précalcul, on ne peut plus rien ajouter. * `navSave` et `navLoad` gardent un maillage précalculé dans un fichier pour que le joueur ne l'attende jamais. * `navDraw` vous montre le maillage, et c'est la première chose à regarder quand un personnage se conduit bizarrement. * `navAgentNew` confie un nœud au moteur pour qu'il le fasse marcher ; à partir de là, ne déplacez plus ce nœud vous-même. * `navAgentMoveTo` règle une cible et renvoie `false` quand il n'y a aucun moyen d'y arriver. Gérer ce cas, c'est votre travail, et cela arrive souvent. * `onNavArrived` est appelé une fois quand un agent atteint sa cible, et on lui dit quel agent. * `navPath` répond à la même question sans faire bouger personne, et `nil` veut dire pas de trajet. * Les agents se contournent entre eux en plus de contourner le niveau. * `navAgentGetVelocity` permet d'accorder un cycle de marche au terrain parcouru. [#_next_time_26] === La prochaine fois Vos deux boîtes se promènent maintenant dans une pièce en silence. La leçon vingt-sept parle de ce que vous mettez derrière elles : de la musique qui boucle proprement et reste au pas de la vidéo, et le MIDI, qui permet à un jeu de jouer d'un instrument -- ou à un vrai instrument de jouer au jeu.