[#_lesson_26_characters_that_move_themselves] == Lezione 26: Personaggi che si muovono da soli image::learn/26-navigation.png[La lezione finita, 480] In questa lezione costruisci una stanza con un muro che la taglia a metà e ci metti dentro due personaggi. Uno lo mandi in un angolo premendo un pulsante. L'altro sceglie un posto a caso, ci va, e quando arriva ne sceglie un altro, all'infinito. Nessuno dei due va a sbattere contro il muro, e tu non scrivi mai una riga che dica come aggirarlo. Questa è una novità. Ogni lezione fino a questa ha mosso le cose dove dicevi tu. Questa è la prima lezione in cui il gioco capisce qualcosa da solo. [#_the_problem_with_walking_toward_something] === Il problema di camminare verso qualcosa Ecco il modo ovvio per far camminare un personaggio verso un punto, ed è il modo in cui tutti lo scrivono la prima volta. Prendi la destinazione, sottrai dove sei e fai un passettino lungo la linea tra le due, a ogni fotogramma, finché non ci arrivi. In una stanza vuota funziona benissimo. Metti un muro nella stanza e smette di funzionare, e smette in un modo che sembra stupido più che rotto: il personaggio marcia contro il muro e resta lì a strusciarcisi contro, a un metro da una porta che non riesce a vedere, perché niente in «fai un passo verso la destinazione» ha la minima idea che nel mondo ci siano delle forme. Non puoi uscirne a forza di toppe. Girare di lato quando tocchi un muro fa incastrare il personaggio nel primo angolo che trova. E non basta nemmeno sapere dove sono i muri, perché la strada attorno a un muro può essere lunga, può allontanarsi dalla destinazione prima di tornare indietro, e può anche non esistere affatto. Quello che serve è un modo per fare una vera domanda: *come cammino da qui a lì?* Per rispondere serve qualcosa che conosca la forma del pavimento. [#_what_a_navigation_mesh_is] === Che cos'è una navmesh Mettiti in piedi in una stanza e guarda il pavimento. Su una parte potresti camminare. Su un'altra no: è sotto un tavolo, dentro un muro, oltre il bordo di un balcone, o in cima a un gradino troppo alto da salire. Ora immagina di dipingere la parte su cui potresti camminare e di buttare via tutto il resto -- i muri, i mobili, il soffitto, lo scenario. Quello che ti resta è una forma piatta, o più d'una, con dei buchi dove ci sono i pilastri e dei vuoti dove ci sono i dislivelli. Quella forma dipinta è una *navmesh* (mesh di navigazione), ed è tutto ciò che serve a un personaggio per pianificare una camminata. Non è il livello. È una mappa semplificata delle parti del livello su cui una persona può stare in piedi, e siccome è semplice, alle domande su di essa si risponde in fretta. Singe la crea per te dal livello che hai già. Gli dici quanto sono grandi i personaggi, gli consegni la geometria, e lui calcola che cosa è percorribile: pavimenti e rampe entro la pendenza che consenti, gradini non più alti di quanto consenti, con un margine tolto attorno a ogni muro e a ogni dislivello, così un personaggio che ha una sua larghezza non attraversa gli spigoli. Questo si chiama *precalcolo*, succede una volta sola, e il risultato si può salvare in un file e caricare la volta dopo. [#_a_room_with_a_wall_in_it] === Una stanza con un muro Crea una cartella chiamata `navigation` e dentro comincia un file chiamato `navigation.singe`. Questa parte è una scena tridimensionale, che è l'argomento della ventiduesima lezione: una *scena* è un mondo disegnato in tre dimensioni, e tutto ciò che contiene è appeso a un *nodo*, cioè una posizione, una rotazione e una scala a cui puoi attaccare una forma. Se hai saltato quella lezione, puoi seguire lo stesso questa. Prendi i prossimi due blocchi come «un pavimento grigio di venti unità per venti, tre muri color sabbia piantati sopra e una telecamera che guarda giù su tutto quanto». [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) ---- Qui le unità sono metri, perché è quello che presuppone il mondo tridimensionale di Singe. I primi due muri attraversano la stanza a metà con un varco di tre metri tra loro: una porta. Il terzo è un moncone che sporge nella metà lontana, per dare ai personaggi qualcosa da aggirare una volta passati. Aggiungi una luce e una telecamera, così c'è qualcosa da vedere: [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 ---- Eseguilo con `Singe -R navigation`. Un pavimento grigio, un muro con un varco e un moncone, visti dall'alto e da dietro. [#_bake_the_floor] === Precalcola il pavimento Sei righe trasformano quella stanza in una mappa: [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) ---- Mettile dopo i muri e prima della luce. Poi aggiungi una riga a `onOverlayUpdate`, sotto `overlayClear`, così puoi vedere che cosa hai ottenuto: [source,lua] ---- navDraw(nav, 0, 170, 190) ---- Eseguilo di nuovo. Una ragnatela di triangoli verde-azzurri è stesa sul pavimento, e si ferma prima dei muri. Lo spazio che lascia non è una decorazione: è il margine di 0,4 metri che serve a un personaggio con un raggio di 0,4 metri perché le sue spalle non passino attraverso la pietra. La porta è ancora una porta nella mesh, perché tre metri bastano per passarci; fai il varco di mezzo metro e il blu si chiuderebbe sopra, e per tutti i personaggi del tuo gioco lì non ci sarebbe proprio nessuna porta. Guardala per un momento, perché `navDraw` è lo strumento che prenderai in mano ogni volta che un personaggio fa qualcosa di inspiegabile. Nove volte su dieci la risposta è sul pavimento davanti a te. [#_somebody_to_walk_it] === Qualcuno che ci cammini Un personaggio è un nodo. Che aspetto abbia non è affare della navigazione, quindi costruisci la cosa più economica che mostri da che parte è rivolto: una scatola con un naso. [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 ---- Questa va insieme a `addWall`. Le serve un colore, quindi aggiungine uno accanto agli altri materiali: [source,lua] ---- local walkerLook = materialNew() materialSetColor(walkerLook, 225, 85, 60) materialSetEmissive(walkerLook, 70, 18, 10) ---- E adesso la riga per cui esiste questa lezione. Dopo `navBuild`: [source,lua] ---- local walker = makeWalker(-7, 7, walkerLook) local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- `navAgentNew` mette il nodo sulla mesh e restituisce un *agente*: la parola del motore per una cosa che è disposto a far camminare al posto tuo. Da questo momento la posizione di quel nodo è del motore. A ogni fotogramma lo sposta e lo gira verso la direzione in cui va, e continuerà a farlo finché non gli dici dove andare o non gli togli l'agente. Eseguilo. Una scatola rossa con un naso compare nell'angolo vicino e non fa assolutamente niente, perché non le hai ancora chiesto niente. [#_sending_it_somewhere] === Mandarlo da qualche parte Dagli un elenco di posti e un pulsante. [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 ---- Stampa il messaggio, così vedi che cosa sta succedendo. In `onOverlayUpdate`, dopo `navDraw`: [source,lua] ---- overlayPrint(2, 2, message) ---- Eseguilo e premi il pulsante uno -- la *barra spaziatrice*, se non hai cambiato niente. La scatola si gira, cammina fino all'angolo lontano della sua metà della stanza e si ferma. Premi di nuovo. Cammina lungo il muro, trova la porta, la attraversa, esce dall'altra parte e si ferma nell'angolo lontano. Premi una terza volta e aggira il moncone fino al quarto angolo. Non le hai detto della porta. Niente nel tuo script parla della porta. L'ha trovata lei, perché la porta è un buco in una forma, e aggirare i buchi è proprio ciò a cui serve la mesh. Ogni voce di `posts` è fatta di tre numeri, una x, una y e una z, perché un punto in un mondo tridimensionale ne vuole tre. La Y è in alto, e le tue sono tutte a zero perché qui tutto sta per terra. La tabella `posts` è una tabella di tabelle, come nella sesta lezione, e `posts[post]` è una di quelle interne. [#_knowing_when_it_arrives] === Sapere quando arriva La scatola si ferma quando arriva, ma il tuo script non sa che è arrivata. Chiedi di essere avvisato: [source,lua] ---- function onNavArrived(agent) message = string.format("Arrived at post %d.", post) end ---- Questa è una callback, la stessa idea di `onOverlayUpdate` nella prima lezione: tu la scrivi, il motore la chiama. Questa viene eseguita una volta sola, nel momento in cui un agente raggiunge la destinazione che gli è stata data. Non quando è vicino, e non a ogni fotogramma dopo -- una volta, all'arrivo. Le viene passato l'agente, perché un gioco ne ha più di uno. Quell'argomento è il numero che ti ha restituito `navAgentNew`, e confrontarlo è il modo per capire quale dei tuoi personaggi è appena arrivato dove stava andando. [#_when_there_is_no_path] === Quando non c'è un percorso Premi il pulsante una quinta volta e il messaggio dice `No path to post 5. Staying put.`, e la scatola non si muove. La postazione cinque è a `{ 30, 0, 30 }`, cioè venti metri fuori da una stanza larga venti metri. Lì non c'è pavimento percorribile, non c'è una strada per arrivarci e non c'è niente di sensato che un personaggio possa farci. `navAgentMoveTo` lo dice restituendo `false`, e lascia l'agente esattamente com'era. Questa è la parte che i principianti saltano, ed è quella che morde. I giochi veri chiedono di continuo camminate impossibili, perché la destinazione di solito è il giocatore, e il giocatore va dappertutto: in piedi su una cassa, a metà di un salto, in acqua, dietro una porta che si è chiusa, o in un varco che la navmesh ha tolto perché troppo stretto. Ognuno di questi casi fa restituire `false` a un `navAgentMoveTo` perfettamente ragionevole. Che cosa voglia dire lo decide il tuo script. Una guardia che non riesce a raggiungerti dovrebbe tornare a fare la ronda, o camminare fino al punto più vicino che *può* raggiungere e aspettare lì, o gridare. Quello che non deve fare è niente, mentre il tuo codice ignora in silenzio un `false` restituito e tu ti chiedi per un'ora perché un nemico su venti è congelato. Qui aiutano altre due chiamate, e vale la pena conoscerle prima che ti servano. `navNearest` prende un punto qualsiasi e restituisce il posto più vicino sulla mesh, o `nil` se lì vicino non c'è niente, ed è così che trasformi «dov'è il giocatore» in «un posto dove un personaggio può davvero stare in piedi». E `navPath` fa la stessa domanda di `navAgentMoveTo` senza muovere nessuno: restituisce gli angoli del percorso come una tabella di punti, o `nil` quando un percorso non c'è. Puoi misurarlo, disegnarlo o decidere che la camminata è troppo lunga per prendersi il disturbo. Disegnarlo è il trucco utile. Aggiungi questo a `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 ---- Ora una linea gialla va dalla scatola a dovunque stia andando, piegandosi attraverso la porta e accorciandosi mentre cammina. Mandala alla postazione cinque e la linea non viene proprio disegnata, perché `navPath` ha restituito `nil` e l'istruzione `if` l'ha intercettato. Stai guardando la decisione che sta prendendo l'agente, disegnata sul pavimento. [#_a_second_walker] === Un secondo camminatore Un personaggio che evita i muri è navigazione. Due personaggi che evitano i muri *e si evitano a vicenda* sono quello che il motore ti dà davvero, e costano quattro righe in più. Aggiungi un materiale blu accanto a quello rosso, poi questo sotto il primo agente: [source,lua] ---- local wanderer = makeWalker(6, -6, wandererLook) local wandererAgent = navAgentNew(nav, wanderer, AGENT_RADIUS, AGENT_HEIGHT, 1.4) ---- Mandalo da qualche parte per farlo partire, dopo la telecamera: [source,lua] ---- navAgentMoveTo(wandererAgent, navRandomPoint(nav)) ---- E insegna a `onNavArrived` a distinguerli: [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` sceglie un posto sulla mesh percorribile -- uno qualsiasi, mai dentro un muro -- e restituisce tre numeri, che sono esattamente quello che `navAgentMoveTo` vuole subito dopo. Siccome la callback scatta all'arrivo e la sua risposta è un altro viaggio, adesso la scatola blu vaga per la stanza finché il gioco resta acceso, con quattro righe e nessuno stato. Guarda i due incontrarsi nella porta. Nessuno si pianta di colpo e nessuno passa attraverso l'altro: tutti e due si scostano un po', per tempo, e si incrociano. Sono gli agenti che si girano attorno a vicenda, ed è la differenza tra personaggi e mobili. Lo script finito è `learn/26-navigation.singe`. [#_what_just_happened_22] === Cos'è appena successo [source,lua] ---- local nav = navNew(AGENT_RADIUS, AGENT_HEIGHT, 45, 0.3) ---- Avvia una navmesh, e i quattro numeri sono la descrizione della persona a cui è destinata: larga 0,8 metri, alta 1,8 metri, capace di camminare su una pendenza fino a quarantacinque gradi e di salire un gradino fino a 0,3 metri. Ognuno di questi cambia la forma che ne esce. Un personaggio più largo perde più pavimento nel margine attorno ai muri e può trovare chiusa una porta. Uno più alto non passa sotto una trave bassa. Consenti una pendenza più ripida e una rampa diventa una strada; consentine meno e diventa una scogliera. Il raggio decide anche con quanta precisione viene misurato il pavimento, quindi un raggio molto piccolo su un livello molto grande richiede molto tempo di precalcolo. Prima di scegliere i tuoi numeri vale la pena leggere la voce del Riferimento per `navNew`. [source,lua] ---- navAddNode(nav, ground) navBuild(nav) ---- `navAddNode` consegna la geometria -- un nodo e tutto ciò che gli sta appeso sotto, così com'è nel mondo in quel momento, quindi sistema le cose prima di aggiungerle. Una chiamata prende un intero modello caricato. Contano solo le forme vere: una luce, un suono o un corpo fisico da soli non aggiungono niente. `navBuild` fa il precalcolo, ed è la chiamata lenta di questa lezione: niente che noteresti per una stanza come questa, un decimo di secondo per una cattedrale su un computer desktop, parecchi secondi su un Raspberry Pi. Fallo mentre è visibile una schermata del titolo, non mentre il giocatore aspetta. Dopo, la geometria viene buttata via e la mesh è chiusa. Non puoi aggiungere altro. Se il tuo livello cambia forma -- si apre una porta, crolla un ponte -- precalcoli un'altra mesh, e `navDelete` libera quella vecchia. Su una macchina lenta preferiresti non precalcolare affatto, quindi `navSave` scrive la mesh finita in un file e `navLoad` la rilegge, costruita e pronta. Precalcola una volta sulla tua macchina, distribuisci il file con il gioco, e il giocatore non aspetta mai. [source,lua] ---- local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- Un agente è un nodo che il motore ha accettato di far camminare al posto tuo. Il raggio e l'altezza qui sono quelli che usa per stare alla larga dagli altri agenti, e l'ultimo numero è la sua velocità massima in metri al secondo -- 2,2 è un passo svelto, 1,4 è una passeggiata, 6 è uno scatto. Da qui in poi, non impostare tu la posizione di quel nodo. Due cose che muovono lo stesso nodo si fanno la guerra, e la guerra si vede come un movimento a scatti. Se il tuo personaggio deve essere spintonato dalla fisica oltre che camminare, c'è una chiamata apposta -- `navAgentSetPlayer` passa invece la guida a un controller della fisica -- e la voce del riferimento che la riguarda spiega quando ti serve. [source,lua] ---- local ok = navAgentMoveTo(walkerAgent, x, y, z) ---- Imposta la destinazione. Non ci cammina; dice dov'è il «lì», e il motore se ne occupa nei fotogrammi successivi. Puoi richiamarla in qualsiasi momento e la nuova destinazione sostituisce quella vecchia, ed è così che una guardia insegue un giocatore che corre: la ripunti una o due volte al secondo, non sessanta. E restituisce `true` o `false`, e tu devi guardare. [source,lua] ---- navAgentStop(walkerAgent) ---- Sopra non è usata, ma è l'altra metà della coppia: dimentica la destinazione, e l'agente rallenta fino a fermarsi dov'è. `onNavArrived` non scatta, perché non è arrivato. Usala quando il motivo del viaggio è venuto meno. [#_fast_is_not_the_point] === Il punto non è la velocità Un'altra chiamata, perché è quella che trasforma tutto questo da una dimostrazione in qualcosa che sembra un gioco. [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` ti dice come si sta muovendo l'agente in questo momento: non dove sta andando, ma quanto veloce e in che direzione si sta davvero spostando in questo fotogramma. Guarda il numero mentre la scatola parte e mentre si ferma. Accelera dolcemente e rallenta dolcemente. Quel numero è ciò a cui appendi un'animazione. Un personaggio le cui gambe si muovono alla velocità a cui si sta davvero spostando sembra vivo; uno il cui ciclo di camminata va a ritmo fisso mentre accelera sembra che stia pattinando. Con un vero modello al posto della scatola confronteresti quella velocità con un paio di soglie e riprodurresti un'animazione da fermo, una camminata o una corsa -- che è l'argomento della ventitreesima lezione, e l'esempio del riferimento sotto `navAgentGetVelocity` è scritto proprio per quello. Ed è questo il vero punto della lezione. Un personaggio che prende la linea dritta e veloce e si struscia lungo un muro è peggio di uno che parte un secondo dopo, si gira come si deve, passa dalla porta e rallenta quando arriva. Nessuno che guarda un gioco misura quanto in fretta lo ha raggiunto un nemico. Se ne accorge subito, senza saper dire perché, quando qualcosa si muove in un modo in cui una cosa viva non si muoverebbe. [#_try_it_26] === Prova tu . *Chiudi la porta.* Cambia i primi due muri così che il varco tra loro sia di mezzo metro invece che di tre: `addWall(-4, 0, 13, 0.6)` e `addWall(6.5, 0, 7, 0.6)`. Eseguilo e guarda che cosa mostra `navDraw` prima di premere qualsiasi cosa. Poi manda la scatola dall'altra parte e guarda che cosa fa. . *Fai ingrassare tutti.* Imposta `AGENT_RADIUS` a `2.0` ed eseguilo. La porta è ancora larga tre metri e i triangoli blu lì sono spariti. Non c'è niente di rotto; la mesh adesso sta rispondendo a una domanda diversa. . *Mandala dove c'è il mouse.* Prendi il punto sotto il puntatore del mouse con `sceneUnproject`, agganciale alla mesh con `navNearest` e manda lì la scatola quando premi il pulsante. `navNearest` che restituisce `nil` è il caso che devi gestire, e succede ogni volta che il puntatore è fuori dal pavimento. . *Aggiungi altri quattro vagabondi.* Metti le quattro righe del camminatore blu in un ciclo `for`, come nella quarta lezione, tieni gli agenti in una tabella e lasciali vagare tutti. Guarda la porta quando tre di loro la vogliono nello stesso momento. . *Precalcolala una volta sola.* Chiama `navSave(nav, "room.nav")` dopo `navBuild`, poi sostituisci `navNew`, le chiamate a `navAddNode` e `navBuild` con un unico `navLoad("room.nav", AGENT_RADIUS, AGENT_HEIGHT)`. Ora cambia la posizione di un muro ed eseguilo di nuovo. Quello che vedi è la trappola che viene con le mesh precalcolate, e vale la pena incontrarla una volta apposta. [#_break_it_on_purpose_25] === Rompilo apposta Il precalcolo chiude la mesh. Prova ad aggiungere un'altra cosa a cose fatte -- una cassa che hai messo tardi, o un muro che avevi dimenticato -- mettendo questa riga subito dopo `navBuild(nav)`: [source,lua] ---- navAddNode(nav, ground) ---- Singe si ferma prima che il gioco parta, indicando la riga del tuo file in cui si trova quella chiamata: ---- 68:navAddNode: The mesh is already built. ---- La forma di un errore del motore è la riga, la chiamata che ha protestato e la lamentela, e questa lamentela vuol dire quello che dice: `navBuild` ha consumato la geometria che gli era stata data e non c'è più niente a cui aggiungere. La soluzione non è mai spostare la chiamata a `navAddNode`. È accorgerti che hai la geometria di due livelli e vuoi due mesh, o che hai precalcolato troppo presto. Aggiungi tutto, poi costruisci, e costruisci per ultimo. [#_what_you_learned_26] === Cosa hai imparato * Fare passi verso una destinazione funziona in una stanza vuota e fallisce appena c'è un muro, e nessuna quantità di toppe lo sistema. * Una navmesh è una mappa semplificata del pavimento su cui un personaggio può stare in piedi, con i muri, i dislivelli e il margine attorno a loro tolti. * `navNew` descrive il personaggio a cui è destinata la mesh, `navAddNode` consegna la geometria del livello e `navBuild` la precalcola. Dopo il precalcolo non si può aggiungere altro. * `navSave` e `navLoad` tengono una mesh precalcolata in un file, così il giocatore non deve mai aspettarla. * `navDraw` ti mostra la mesh, ed è la prima cosa da guardare quando un personaggio si comporta in modo strano. * `navAgentNew` affida un nodo al motore perché lo faccia camminare; da quel momento non spostare tu quel nodo. * `navAgentMoveTo` imposta una destinazione e restituisce `false` quando non c'è modo di arrivarci. Gestirlo è compito tuo, e succede spesso. * `onNavArrived` viene chiamata una volta quando un agente raggiunge la sua destinazione, e le viene detto quale agente. * `navPath` risponde alla stessa domanda senza muovere nessuno, e `nil` vuol dire nessun percorso. * Gli agenti si girano attorno l'un l'altro oltre che attorno al livello. * `navAgentGetVelocity` è il modo in cui un ciclo di camminata si accorda al terreno percorso. [#_next_time_26] === La prossima volta Le tue due scatole adesso girano per una stanza in silenzio. La ventisettesima lezione parla di quello che ci metti dietro: musica che va in loop come si deve e resta a tempo con il video, e il MIDI, che permette a un gioco di suonare uno strumento -- o a un vero strumento di suonare il gioco.