456 lines
19 KiB
Text
456 lines
19 KiB
Text
[#_lesson_23_models_and_light]
|
|
== Lezione 23: Modelli e luce
|
|
|
|
image::learn/23-models.png[La lezione finita, 480]
|
|
|
|
Cubi e sfere ti porteranno lontano, ma prima o poi il gioco vuole un drago. Un
|
|
*modello* è una forma che qualcun altro ha costruito in un programma di
|
|
modellazione: centinaia di triangoli, i suoi colori e le sue superfici, a volte
|
|
uno scheletro e dei movimenti che vanno con esso, tutto in un solo file.
|
|
Caricarne uno sono due righe. Fargli avere un aspetto decente è questione di
|
|
luce, e la luce è gran parte di questa lezione.
|
|
|
|
Non ti servono grafica né download. La primissima volta che hai eseguito Singe,
|
|
il motore ha estratto una cartella chiamata `Singe` nella tua cartella di
|
|
lavoro, e lì dentro ci sono due modelli: `DragonModel.glb`, il drago della
|
|
schermata del titolo del motore, e `SingeText.glb`, la scritta che c'è sotto.
|
|
Questa lezione usa il drago.
|
|
|
|
[#_the_scene_around_it]
|
|
=== La scena attorno
|
|
|
|
Un modello ha bisogno di un posto dove stare. Crea una cartella, crea
|
|
`dragon.singe` al suo interno, e comincia con un pavimento e una telecamera.
|
|
Se hai fatto la ventiduesima lezione qui non c'è niente di nuovo; se non l'hai
|
|
fatta, è una scena accesa, una lastra piatta su cui stare e un punto da cui
|
|
guardare.
|
|
|
|
[source,lua]
|
|
----
|
|
sceneEnable(true)
|
|
sceneSetBackground(10, 12, 20)
|
|
sceneSetAmbient(28, 30, 38)
|
|
|
|
local stone = materialNew()
|
|
materialSetColor(stone, 105, 105, 115)
|
|
materialSetRoughness(stone, 0.9)
|
|
|
|
local floor = nodeNew()
|
|
nodeSetMesh(floor, meshBox(24, 0.4, 24), stone)
|
|
nodeSetPosition(floor, 0, -0.2, 0)
|
|
|
|
local camera = nodeNew()
|
|
nodeSetPosition(camera, 0, 2.6, 9)
|
|
nodeLookAt(camera, 0, 1.7, 0)
|
|
cameraSet(camera)
|
|
cameraSetPerspective(55, 0.1, 200)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Eseguilo con `Singe -R dragon`. Una lastra grigia, vista leggermente
|
|
dall'alto, su uno sfondo blu scuro. La lastra è larga ventiquattro unità e
|
|
spessa poco meno di mezza unità, e appoggiarla a `y = -0.2` mette la sua
|
|
superficie superiore esattamente a `y = 0`, il che rende facile ragionare su
|
|
tutto quello che metterai dopo: il pavimento è zero.
|
|
|
|
[#_loading_the_dragon]
|
|
=== Caricare il drago
|
|
|
|
Due righe.
|
|
|
|
[source,lua]
|
|
----
|
|
local dragonModel = modelLoad("Singe/DragonModel.glb")
|
|
local dragon = modelInstance(dragonModel)
|
|
----
|
|
|
|
Mettile dopo il pavimento e salva. Compare un drago, ed è decisamente troppo
|
|
grande per l'inquadratura -- stai guardando la sua parte centrale.
|
|
|
|
[#_what_just_happened_19]
|
|
=== Cos'è appena successo
|
|
|
|
Le due righe fanno lavori diversi, e la differenza conta.
|
|
|
|
`modelLoad` legge il file, passa le forme e le loro superfici alla scheda
|
|
grafica, e tiene tutto pronto. È la chiamata lenta. Falla una volta, quando il
|
|
gioco parte, e mai nel mezzo della partita.
|
|
|
|
`modelInstance` mette una copia nella scena e ti restituisce un nodo -- un nodo
|
|
normale, dello stesso tipo che ti dà `nodeNew`, che posizioni, ruoti, scali ed
|
|
elimini con le chiamate che già conosci. Creare un'istanza è la chiamata
|
|
economica. Carica un albero, creane quaranta istanze e hai un bosco, con una
|
|
sola copia delle forme sulla scheda grafica e quaranta posti in cui vengono
|
|
disegnate.
|
|
|
|
Con il modello arriva tutto ciò che il file conteneva: le forme, le superfici e
|
|
i loro colori, il modo in cui sono disposte le parti, l'eventuale scheletro e
|
|
gli eventuali movimenti. Per un modello caricato non costruisci i materiali; si
|
|
è portato i suoi.
|
|
|
|
[#_scale_and_the_first_thing_that_goes_wrong]
|
|
=== La scala, e la prima cosa che va storta
|
|
|
|
Il drago è alto circa sette unità e mezza. La tua telecamera è nove unità più
|
|
indietro. Ecco perché riempie la finestra.
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetScale(dragon, 0.5)
|
|
----
|
|
|
|
Aggiungila sotto la riga `modelInstance`. Adesso è alto meno di quattro unità,
|
|
sta sul pavimento ed entra nell'inquadratura.
|
|
|
|
Non è un dettaglio; è il primo problema più comune con un modello, e ha due
|
|
facce. Un modello costruito in centimetri arriva cento volte troppo grande e
|
|
vedi un'unica superficie enorme che riempie lo schermo, cosa che di solito
|
|
sembra «la mia scena è rotta» invece di «sono dentro il collo del drago». Un
|
|
modello costruito in qualche altra unità arriva così piccolo da essere un
|
|
puntino, o invisibile contro lo sfondo, e pensi che il file non si sia
|
|
caricato.
|
|
|
|
La soluzione è sempre la stessa: una scala sul nodo dell'istanza. Non sulle
|
|
parti, non nel programma di modellazione se puoi evitarlo, ma un solo
|
|
`nodeSetScale` sul nodo che ti ha dato `modelInstance`, scelto provando un
|
|
numero e guardando. `0.01` e `100` sono risposte perfettamente normali.
|
|
|
|
Quando non riesci a capire quanto è grande una cosa, datti un righello. Mettile
|
|
accanto un cubo di un'unità:
|
|
|
|
[source,lua]
|
|
----
|
|
local ruler = nodeNew()
|
|
nodeSetMesh(ruler, meshBox(1, 1, 1), stone)
|
|
nodeSetPosition(ruler, 3, 0.5, 0)
|
|
----
|
|
|
|
Un oggetto grande quanto una cassa accanto al modello ti dice con un'occhiata
|
|
se il modello è un drago o una montagna. Togli il righello quando hai finito.
|
|
|
|
Mentre lo sistemi, giralo verso di te:
|
|
|
|
[source,lua]
|
|
----
|
|
nodeSetRotation(dragon, 0, -60, 0)
|
|
----
|
|
|
|
Il drago è stato modellato rivolto lungo la X positiva, cioè verso destra,
|
|
quindi parte di profilo. Girarlo attorno a Y lo porta verso la telecamera. Il
|
|
«davanti» di un modello è la direzione che ha scelto chi l'ha costruito, quale
|
|
che sia, e per scoprirla devi girarlo e guardare.
|
|
|
|
[#_light]
|
|
=== La luce
|
|
|
|
Esegui quello che hai adesso. Il drago c'è, ed è deprimente: una forma piatta e
|
|
fioca, senza riflessi e senza ombra. È perché l'unica luce nella tua scena è
|
|
quella ambiente, il debole chiarore da ogni direzione che imposta
|
|
`sceneSetAmbient`.
|
|
|
|
È la delusione più comune in assoluto nel 3D, e capita a tutti: il modello si è
|
|
caricato perfettamente e la scena è nera, o quasi, perché niente lo illumina.
|
|
Prima di sospettare del file, metti una luce.
|
|
|
|
Ce ne sono di tre tipi, e ognuna è un nodo, quindi le posizioni e le punti con
|
|
`nodeSetPosition` e `nodeLookAt` come tutto il resto.
|
|
|
|
[source,lua]
|
|
----
|
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
|
nodeSetPosition(sun, -6, 9, 7)
|
|
nodeLookAt(sun, 0, 1.5, 0)
|
|
lightSetColor(sun, 255, 244, 224)
|
|
lightSetIntensity(sun, 1.8)
|
|
lightSetShadow(sun, true)
|
|
----
|
|
|
|
`LIGHT_DIRECTIONAL` è un sole. Viene trattato come se fosse infinitamente
|
|
lontano, quindi conta solo la direzione in cui splende e tutto nella scena ne
|
|
riceve la stessa quantità. La sua posizione serve a `nodeLookAt` per puntarlo.
|
|
È la luce che dà a una scena la sua forma principale, e la maggior parte delle
|
|
scene ne vuole esattamente una.
|
|
|
|
`lightSetIntensity` è la luminosità. Per una luce direzionale, `1` porta fino
|
|
al bianco una superficie bianca rivolta dritta verso di lei, quindi `1.8` è una
|
|
giornata luminosa. `lightSetColor` la tinge: un bianco un po' caldo per il sole,
|
|
un blu freddo per la luce della luna, e per il resto la sezione sulle luci del
|
|
riferimento.
|
|
|
|
`lightSetShadow(sun, true)` è quello che fa stare il drago sul pavimento invece
|
|
di farlo fluttuare sopra. Le ombre sono spente per impostazione predefinita
|
|
perché ogni luce che proietta ombre costa un altro passaggio su tutta la scena a
|
|
ogni fotogramma. Accendile per l'unica luce che conta e lasciale spente per le
|
|
altre; una macchina piccola ti ringrazierà.
|
|
|
|
[source,lua]
|
|
----
|
|
local torch = lightNew(LIGHT_POINT)
|
|
nodeSetPosition(torch, 3.5, 1.2, 3.0)
|
|
lightSetColor(torch, 255, 150, 70)
|
|
lightSetIntensity(torch, 14)
|
|
lightSetRange(torch, 12)
|
|
----
|
|
|
|
`LIGHT_POINT` è una lampadina. Sta in un punto, splende in tutte le direzioni e
|
|
si affievolisce con la distanza. Quell'affievolirsi è il motivo per cui il
|
|
numero è `14` e non `1`: l'intensità di una luce puntiforme è la sua luminosità
|
|
nel punto in cui si trova la luce, e dopo poche unità non ne resta molta. Una
|
|
lampadina che illumina una stanza di solito vuole qualcosa nell'ordine delle
|
|
decine. Se una luce puntiforme sembra non fare niente, il motivo è quasi sempre
|
|
questo.
|
|
|
|
`lightSetRange` dice fin dove arriva prima di spegnersi del tutto, il che evita
|
|
che una lampada in una stanza illumini debolmente quella accanto, e impostarlo
|
|
non costa niente.
|
|
|
|
Il terzo tipo è `LIGHT_SPOT`, un cono che punta lungo la Z negativa del nodo
|
|
stesso, con `lightSetCone` per l'angolo del centro luminoso e del bordo
|
|
sfumato: una torcia elettrica, un faro d'auto, un riflettore. Possono splendere
|
|
fino a otto luci contemporaneamente, più di quante ne servano alla maggior parte
|
|
delle scene. Se ne vuoi di più, in realtà ne vuoi di meno.
|
|
|
|
Eseguilo adesso. Un sole quasi bianco da sinistra, un caldo bagliore arancione
|
|
da destra, un'ombra sulla lastra, e un drago con un davanti e un dietro.
|
|
|
|
[#_materials_at_the_level_you_need_today]
|
|
=== I materiali, quanto ti basta per oggi
|
|
|
|
Il drago si è portato i suoi materiali, e sono tre: un metallo scuro, un metallo
|
|
più chiaro color champagne, e un vetro chiaro in cui lo sguardo entra un poco.
|
|
La maggior parte delle sue parti è ricavata da più di uno. Non devi toccare
|
|
niente di tutto questo, e la maggior parte delle volte non dovresti.
|
|
|
|
Quando vuoi davvero cambiare l'aspetto di una parte di un modello, crei un
|
|
materiale come nella ventiduesima lezione e lo metti sulla parte:
|
|
|
|
[source,lua]
|
|
----
|
|
local red = materialNew()
|
|
materialSetColor(red, 220, 50, 40)
|
|
materialSetRoughness(red, 0.4)
|
|
nodeSetMaterial(nodeFind("crest", dragon), red)
|
|
----
|
|
|
|
`nodeFind(name, root)` cerca in un'istanza di un modello una parte con il nome
|
|
che aveva nel file e ne restituisce il nodo, oppure `nil` quando quel nome non
|
|
c'è. È così che arrivi dentro un modello: una torretta, una ruota, una mano a
|
|
cui appendere una torcia, o la cresta del drago.
|
|
|
|
La cresta è un pezzo solo di un solo materiale, quindi il suo nodo porta la
|
|
forma stessa e la riga qui sopra la ridipinge. Una parte costruita con più
|
|
materiali viene tenuta come un nodo con un figlio per ogni pezzo, e mettere un
|
|
materiale sul genitore non cambia niente che tu possa vedere. Quando un
|
|
`nodeSetMaterial` sembra non fare niente, il motivo è questo, e
|
|
`nodeGetChildren` ti mostrerà che cosa c'è sotto.
|
|
|
|
Quattro numeri descrivono la maggior parte delle superfici. Il colore, da
|
|
`materialSetColor`. `materialSetRoughness`, da `0` per uno specchio a `1` per
|
|
il gesso. `materialSetMetallic`, che è `0` per quasi tutto -- vernice, legno,
|
|
plastica, pelle -- e `1` per il metallo vero, e i valori intermedi servono a
|
|
poco. E `materialSetEmissive`, che fa brillare una superficie del suo colore,
|
|
per schermi, lampade e ferro rovente, senza illuminare niente attorno.
|
|
|
|
Una cosa da sapere sul metallo: una superficie metallica ti mostra i suoi
|
|
riflessi invece di un colore suo, quindi in una scena senza niente da riflettere
|
|
sembra scura e spenta. È per questo che il metallo scuro del drago ha bisogno
|
|
del sole per prendere vita. La soluzione vera è un cielo (`sceneSetSky`), che
|
|
avvolge la scena in un panorama che si vede dietro tutto e allo stesso tempo la
|
|
illumina; ne parla «L'aspetto del fotogramma» nel riferimento.
|
|
|
|
[#_movement_the_model_brings_with_it]
|
|
=== I movimenti che il modello si porta dietro
|
|
|
|
Molti modelli contengono animazioni: una camminata, una corsa, una posa da
|
|
fermo, una porta che si apre. Sono salvate nel file, e riprodurne una è una sola
|
|
chiamata sul nodo radice dell'istanza.
|
|
|
|
[source,lua]
|
|
----
|
|
animationPlay(dragon, "Walk", true)
|
|
----
|
|
|
|
Il secondo argomento è il nome della clip come lo salva il file, oppure il suo
|
|
numero. `true` vuol dire ripeterla all'infinito. Altri due numeri facoltativi
|
|
impostano la velocità e la dissolvenza incrociata:
|
|
`animationPlay(hero, "Run", true, 1.2, 0.4)` riproduce la corsa un quinto più
|
|
veloce e ci sfuma dentro in quattro decimi di secondo, così una camminata
|
|
diventa una corsa senza scatti. `animationStop`, `animationPause` e
|
|
`animationIsPlaying` fanno quello che dice il loro nome.
|
|
|
|
Quella riga non puoi ancora eseguirla, ed ecco il motivo onesto: il drago nella
|
|
tua cartella `Singe` non ha animazioni. È un modello, non uno spettacolo.
|
|
Chiedere una clip che non c'è ferma il gioco con un errore, quindi non dare mai
|
|
niente per scontato -- chiedi:
|
|
|
|
[source,lua]
|
|
----
|
|
local clips = modelGetAnimations(dragonModel)
|
|
|
|
if #clips > 0 then
|
|
animationPlay(dragon, 1, true)
|
|
else
|
|
debugPrint("DragonModel.glb carries no animation clips. The wings are turned by hand.")
|
|
end
|
|
----
|
|
|
|
`modelGetAnimations` restituisce una tabella con i nomi delle clip nel file, in
|
|
ordine, quindi `clips[1]` è la prima e `#clips` è quante ce ne sono -- gli
|
|
strumenti per le tabelle della sesta lezione, su una tabella che ha riempito il
|
|
motore. Eseguilo e guarda la console: il messaggio compare, perché quel
|
|
conteggio è zero.
|
|
|
|
Prendi l'abitudine di fare quel controllo con ogni modello che non hai fatto tu.
|
|
Costa tre righe e trasforma «il gioco è morto all'avvio» in una frase nel log.
|
|
|
|
[#_moving_the_parts_yourself]
|
|
=== Muovere le parti da te
|
|
|
|
Un modello senza animazioni non è una statua. Le sue parti sono nodi, e i nodi
|
|
girano.
|
|
|
|
[source,lua]
|
|
----
|
|
local wingLeft = nodeFind("wingL", dragon)
|
|
local wingRight = nodeFind("wingR", dragon)
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
local flap = math.sin(singeGetTicks() / 260) * 22
|
|
|
|
nodeSetRotation(wingLeft, flap, 0, 0)
|
|
nodeSetRotation(wingRight, -flap, 0, 0)
|
|
nodeRotate(dragon, 0, 0.25, 0)
|
|
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Singe/DragonModel.glb")
|
|
overlayPrint(2, 4, "animation clips in the file: " .. #clips)
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Salvalo. Le ali battono, il drago gira lentamente su se stesso, e l'ombra sul
|
|
pavimento batte con loro.
|
|
|
|
`singeGetTicks` è il numero di millisecondi da quando il motore è partito,
|
|
quindi sale regolarmente per sempre. `math.sin` prende quel numero che sale e
|
|
restituisce un valore che scivola dolcemente da `-1` a `1` e indietro, un
|
|
battito completo circa ogni secondo e mezzo con questo divisore. Moltiplicare
|
|
per `22` lo trasforma in un angolo di ventidue gradi da ogni parte. Dividere per
|
|
un numero più grande rallenta il battito; moltiplicare per un numero più grande
|
|
lo allarga. Quella riga da sola è l'animazione più economica che esista, e vale
|
|
la pena tenerla in tasca.
|
|
|
|
Le ali girano attorno al proprio asse X, e girano attorno alla spalla invece che
|
|
attorno al centro dell'animale, perché chi ha costruito il file ha messo il nodo
|
|
di ogni parte dove va l'articolazione. Un modello che non è stato costruito così
|
|
farà girare le sue parti attorno al centro del modello, e la soluzione è
|
|
spostare il nodo della parte, di cui parla la sezione sui nodi del riferimento.
|
|
|
|
Nota che il girare di tutto il drago non disturba il girare delle ali. Le parti
|
|
sono appese sotto la radice dell'istanza, quindi ruotare la radice si porta
|
|
dietro tutto, e la rotazione propria di ogni ala si aggiunge sopra. Quella
|
|
disposizione -- una cosa dentro una cosa dentro una cosa, ognuna che si muove
|
|
nel mondo del suo genitore -- è ciò a cui serve un albero della scena.
|
|
|
|
[#_your_own_models]
|
|
=== I tuoi modelli
|
|
|
|
Quando ne fai di tuoi, esportali da Blender -- o da Maya, o da 3ds Max, o da
|
|
qualsiasi altro programma moderno -- come *glTF 2.0 binario*, cioè un file
|
|
`.glb`. È la scelta predefinita di Blender ed è l'unico formato di modelli che
|
|
Singe legge.
|
|
|
|
L'unica regola è che il file deve essere autosufficiente: le forme, le immagini
|
|
sopra di esse e i movimenti, tutti dentro un solo `.glb`. Un file che fa
|
|
riferimento a un `.bin` o a un `.png` che gli sta accanto viene rifiutato, con
|
|
un messaggio che nomina quello che voleva. L'esportatore di Blender fa la cosa
|
|
giusta con le sue impostazioni predefinite.
|
|
|
|
Non tutto quello che puoi costruire sopravvive al viaggio. La sezione «Modelli
|
|
e animazione» del Riferimento, nel capitolo «Scene 3D», è l'elenco: che cosa viene
|
|
letto, che cosa viene ignorato senza dire niente, e i limiti (uno scheletro può
|
|
avere fino a centoventotto giunti, per esempio). Leggila una volta prima di
|
|
passare un fine settimana su un modello, non dopo.
|
|
|
|
[#_try_it_23]
|
|
=== Prova tu
|
|
|
|
. *Spegni le luci.* Commenta tutto il blocco `sun` mettendo `--` all'inizio di
|
|
ogni riga, ed eseguilo. Poi fai lo stesso con `sceneSetAmbient`. Ricordati
|
|
che aspetto ha, perché un giorno succederà senza che tu lo voglia.
|
|
. *Sposta il sole.* Cambia `nodeLookAt(sun, 0, 1.5, 0)` in
|
|
`nodeLookAt(sun, 0, 20, 0)` così il sole punta sopra la testa del drago.
|
|
Guarda l'ombra e i riflessi sparire. Puntare una luce non è decorazione.
|
|
. *Scalda la torcia.* Alza l'intensità della luce puntiforme da `14` a `60` e
|
|
abbassa la sua portata a `4`. Poi spostala in `(0, 6, 0)`, sopra il drago.
|
|
Una luce puntiforme è una lampadina, e stai scegliendo dove appenderla.
|
|
. *Crea due istanze.* Chiama `modelInstance(dragonModel)` una seconda volta,
|
|
tienila in un'altra variabile, scalala e mettila in `(-4, 0, -3)`. Un
|
|
caricamento, due draghi. Poi prova invece a caricare il modello due volte e
|
|
capisci perché non dovresti.
|
|
. *Trova un'altra parte.* Le parti del drago si chiamano `head`, `jaw`, `neck`,
|
|
`tail`, `body`, `crest`, `hornL`, `hornR`, `wingL`, `wingR`, `foreLegL`,
|
|
`foreLegR`, `hindLegL` e `hindLegR`. Fai ondeggiare la coda con un suo `math.sin` a una
|
|
velocità diversa da quella delle ali. Poi prova `nodeFind("wings", dragon)` e
|
|
guarda che cosa fa un nome sbagliato quando passi quel `nil` dritto a
|
|
`nodeSetRotation`.
|
|
|
|
[#_break_it_on_purpose_22]
|
|
=== Rompilo apposta
|
|
|
|
Cambia il nome del file in `Singe/Dragonmodel.glb` -- una sola maiuscola di
|
|
differenza -- ed eseguilo. Singe si ferma prima che il gioco parta:
|
|
|
|
----
|
|
13:modelLoad: Unable to read Singe/Dragonmodel.glb.
|
|
----
|
|
|
|
La riga, la chiamata e la lamentela, in quest'ordine. «Unable to read» vuol
|
|
dire che il motore ha cercato e non ha trovato niente, il che è quasi sempre una
|
|
di tre cose: il nome è scritto male, le maiuscole sono sbagliate, o il file non
|
|
è dove hai detto. Su Windows le maiuscole sarebbero state perdonate e poi il tuo
|
|
gioco non avrebbe funzionato per nessuno su Linux, che è un bug peggiore di
|
|
questo. Scrivi i nomi esattamente.
|
|
|
|
Lo stesso messaggio con un nome di file giusto vuol dire che il file non è
|
|
dove l'ha cercato il motore. La cartella `Singe` viene creata nella cartella *da cui*
|
|
esegui il motore, quindi se hai avviato Singe da un'altra parte, anche
|
|
`Singe/DragonModel.glb` punta da un'altra parte.
|
|
|
|
[#_what_you_learned_23]
|
|
=== Cosa hai imparato
|
|
|
|
* `modelLoad` legge un `.glb` una volta; `modelInstance` ne mette delle copie
|
|
tutte le volte che vuoi.
|
|
* La radice di un'istanza è un nodo normale: posizionala, ruotala, scalala ed
|
|
eliminala come qualsiasi altro.
|
|
* Un modello si porta dietro le sue forme e i suoi materiali.
|
|
* Un modello arriva alla dimensione che ha usato il suo autore, ed è per questo
|
|
che un `nodeSetScale` sull'istanza fa parte del collocarlo.
|
|
* Una scena senza luce è nera o quasi, ed è la prima cosa da controllare quando
|
|
un modello ha un aspetto sbagliato.
|
|
* Una luce direzionale è un sole, una luce puntiforme è una lampadina che si
|
|
affievolisce con la distanza e vuole un'intensità molto più grande, e un
|
|
faretto è un cono.
|
|
* Le ombre sono spente finché `lightSetShadow` non le accende, e ognuna ha un
|
|
costo.
|
|
* `nodeFind` raggiunge una parte di un modello con il nome che ha nel file.
|
|
* `modelGetAnimations` dice che cosa può riprodurre un modello, e chiedere una
|
|
clip che non c'è è un errore.
|
|
* Un modello senza animazioni si può comunque muovere una parte alla volta.
|
|
|
|
[#_next_time_23]
|
|
=== La prossima volta
|
|
|
|
Tutto in questa scena si muove perché gliel'hai detto tu, fotogramma per
|
|
fotogramma. La prossima lezione passa quel lavoro a un motore fisico: casse che
|
|
cadono, rotolano, si impilano e si fanno cadere a vicenda senza che tu scriva
|
|
una riga di aritmetica -- e la domanda più difficile di quando dovresti
|
|
lasciarglielo fare. È la ventiquattresima lezione.
|