singe/docs/it/lessons/26-navigation.adoc

625 lines
24 KiB
Text

[#_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.