546 lines
22 KiB
Text
546 lines
22 KiB
Text
[#_lesson_24_physics]
|
|
== Lezione 24: Fisica
|
|
|
|
image::learn/24-physics.png[La lezione finita, 480]
|
|
|
|
Fino a ora ogni cosa che si muoveva nei tuoi giochi si muoveva perché i conti li
|
|
facevi tu. Un numero saliva di due a ogni fotogramma; se superava un bordo lo
|
|
facevi tornare indietro. È il modo onesto di far muovere un gioco, e la maggior
|
|
parte dei giochi è fatta così.
|
|
|
|
Questa lezione passa i conti a qualcun altro. Un *motore fisico* simula il
|
|
mondo -- la gravità, le cadute, le scivolate, le pile, i rimbalzi, i rotolamenti
|
|
e le cose che si urtano -- e muove i tuoi nodi al posto tuo. Alla fine avrai un
|
|
vassoio di casse e un cannone che le butta giù. L'ultima sezione parla di quando
|
|
non fare niente di tutto questo, ed è la parte da leggere due volte.
|
|
|
|
[#_what_it_does_and_what_it_does_not]
|
|
=== Che cosa fa, e che cosa non fa
|
|
|
|
Dai a un nodo un *corpo* e la simulazione se ne prende carico. Da quel momento
|
|
cade per gravità, atterra su qualunque cosa ci sia sotto, si inclina quando
|
|
viene colpito fuori centro, resta fermo in una pila, scivola fino a fermarsi, e
|
|
il nodo segue ovunque la simulazione lo metta. Smetti di impostarne la
|
|
posizione. È questo il patto: cedi la posizione e la rotazione di quel nodo, e
|
|
in cambio ottieni un comportamento che non hai dovuto scrivere.
|
|
|
|
Quello che non fa è qualsiasi cosa che riguardi il tuo gioco. Non sa che cos'è
|
|
un punteggio, né che la cassa rossa è quella che vale punti, né che cadere dal
|
|
bordo costa una vita. Non anima un personaggio che cammina, non sa che
|
|
sensazione dovrebbe dare un salto, e non farà muovere un oggetto in un modo che
|
|
puoi prevedere esattamente. La fisica è una macchina che produce un caos
|
|
plausibile.
|
|
|
|
Le unità sono metri, chilogrammi e secondi. La gravità è `0, -9.81, 0` a meno
|
|
che tu non dica altrimenti, cioè quella della Terra, verso il basso, in metri al
|
|
secondo quadrato. Una cassa larga un'unità è larga un metro, e cade come tale.
|
|
Se la tua scena è costruita a un'altra scala, tutto sembrerà cadere nello
|
|
sciroppo o in un cartone animato, e la soluzione è costruire in metri.
|
|
|
|
[#_a_floor_and_one_crate]
|
|
=== Un pavimento e una cassa
|
|
|
|
Crea una cartella, crea `crates.singe` e scrivi questo. È una scena, una lastra,
|
|
una cassa sopra la lastra e tre righe di fisica.
|
|
|
|
[source,lua]
|
|
----
|
|
sceneEnable(true)
|
|
sceneSetBackground(16, 18, 26)
|
|
sceneSetAmbient(40, 42, 50)
|
|
|
|
local stone = materialNew()
|
|
materialSetColor(stone, 110, 110, 120)
|
|
materialSetRoughness(stone, 0.9)
|
|
|
|
local wood = materialNew()
|
|
materialSetColor(wood, 190, 140, 80)
|
|
materialSetRoughness(wood, 0.8)
|
|
|
|
local floor = nodeNew()
|
|
nodeSetMesh(floor, meshBox(16, 0.4, 16), stone)
|
|
nodeSetPosition(floor, 0, -0.2, 0)
|
|
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
|
|
|
|
local crate = nodeNew()
|
|
nodeSetMesh(crate, meshBox(0.8, 0.8, 0.8), wood)
|
|
nodeSetPosition(crate, 0.3, 4, 0)
|
|
nodeSetRotation(crate, 20, 0, 15)
|
|
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
|
|
bodySetMass(crate, 4)
|
|
|
|
local sun = lightNew(LIGHT_DIRECTIONAL)
|
|
nodeSetPosition(sun, -5, 9, 6)
|
|
nodeLookAt(sun, 0, 0, 0)
|
|
lightSetIntensity(sun, 1.7)
|
|
lightSetShadow(sun, true)
|
|
|
|
local camera = nodeNew()
|
|
nodeSetPosition(camera, 0, 4, 11)
|
|
nodeLookAt(camera, 0, 1, 0)
|
|
cameraSet(camera)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Eseguilo con `Singe -R crates`. La cassa cade da quattro metri d'altezza,
|
|
atterra su uno spigolo perché l'hai inclinata, si rovescia su una faccia e si
|
|
ferma. Premi *F5* per eseguirlo di nuovo e guardala un'altra volta.
|
|
|
|
Niente in `onOverlayUpdate` muove qualcosa. Hanno fatto tutto le due chiamate a
|
|
`bodyNew`.
|
|
|
|
[#_what_just_happened_20]
|
|
=== Cos'è appena successo
|
|
|
|
[source,lua]
|
|
----
|
|
bodyNew(floor, BODY_STATIC, SHAPE_BOX, 16, 0.4, 16)
|
|
----
|
|
|
|
`bodyNew` dà un corpo a un nodo. Prende il nodo, che tipo di corpo è, che forma
|
|
deve usare la simulazione per lui, e le dimensioni di quella forma.
|
|
|
|
Ci sono tre tipi, e scegliere quello giusto è gran parte dell'abilità.
|
|
|
|
* `BODY_STATIC` non si muove mai. Viene messo dove si trova il nodo quando crei
|
|
il corpo, e lì resta per sempre. Pavimenti, muri, il terreno, il cabinato, il
|
|
livello. Spostare il suo nodo dopo non sposta il corpo, ed è una trappola che
|
|
vale la pena ricordare: statico vuol dire statico.
|
|
* `BODY_DYNAMIC` viene mosso dalla simulazione. Cade, rimbalza, scivola, viene
|
|
spinto, e si trascina dietro il suo nodo. Questo è una cassa, un barile, una
|
|
palla, un pezzo di detrito.
|
|
* `BODY_KINEMATIC` va dove metti *tu* il suo nodo, e spinge via i corpi
|
|
dinamici senza mai essere spinto a sua volta. Una piattaforma mobile, una
|
|
racchetta, una porta su un binario, il pugno che un personaggio fa partire. È
|
|
il ponte tra i tuoi conti e la simulazione: tu guidi lui, lui guida loro.
|
|
|
|
La forma non è la mesh. È un sostituto approssimativo che la simulazione usa
|
|
perché sa rispondere a domande su una scatola molto più in fretta che su
|
|
novecento triangoli. `SHAPE_BOX` prende una larghezza, un'altezza e una
|
|
profondità; `SHAPE_SPHERE` prende un raggio; `SHAPE_CAPSULE` e `SHAPE_CYLINDER`
|
|
prendono un raggio e un'altezza. C'è `SHAPE_HULL`, che avvolge come una
|
|
pellicola qualunque mesh ci sia sotto il nodo -- la risposta solita per un
|
|
modello -- e `SHAPE_MESH`, i triangoli esatti, che lo scenario statico può usare
|
|
e un oggetto che cade no.
|
|
|
|
Le dimensioni tocca a te azzeccarle. Niente controlla che il corpo che hai
|
|
descritto corrisponda alla mesh che hai disegnato, e una cassa disegnata a `0.8`
|
|
con un corpo fatto a `2` fluttuerà sopra il pavimento e farà cadere le cose a
|
|
distanza. Quando qualcosa si comporta come se avesse un'altra dimensione, ce
|
|
l'ha.
|
|
|
|
[source,lua]
|
|
----
|
|
bodySetMass(crate, 4)
|
|
----
|
|
|
|
La massa di un corpo dinamico viene dalla sua forma alla densità dell'acqua, il
|
|
che rende le cose molto più pesanti di quanto ti aspetti: una scatola grande
|
|
quanto una porta pesa circa centoquaranta chilogrammi, e niente di quello che
|
|
puoi lanciarle contro la sposterà. Imposta la massa di qualsiasi cosa debba
|
|
sembrare leggera. Quattro chilogrammi sono una cassa che potresti sollevare.
|
|
|
|
La massa conta perché le forze vengono ripartite in base a essa. La stessa
|
|
spinta sposta più lontano una cosa leggera, e un impulso pari a massa per
|
|
velocità è quello che serve per mettere in moto qualcosa a quella velocità.
|
|
Regola prima la massa e poi le forze, o finirai per inseguire la tua coda.
|
|
|
|
[#_making_things_move]
|
|
=== Far muovere le cose
|
|
|
|
Tre chiamate spingono in giro un corpo dinamico, e non sono intercambiabili.
|
|
|
|
`bodySetVelocity(node, x, y, z)` dice «adesso stai viaggiando a questa
|
|
velocità», sostituendo qualunque cosa stesse facendo. È un servizio, un lancio,
|
|
una comparsa con già un po' di velocità addosso.
|
|
|
|
`bodyApplyImpulse(node, ix, iy, iz)` è un colpo: un calcio, un'esplosione, una
|
|
mazza che incontra una palla. Si aggiunge a quello che il corpo stava già
|
|
facendo. Dagli un punto del mondo come altri tre numeri e colpisce *lì* invece
|
|
che al centro, quindi una cassa colpita vicino alla cima si rovescia invece di
|
|
scivolare.
|
|
|
|
`bodyApplyForce(node, fx, fy, fz)` è una spinta che dura: un razzo, un
|
|
ventilatore, un nastro trasportatore. Una forza applicata per un solo
|
|
fotogramma non fa quasi niente, il che sorprende chi si aspettava un impulso.
|
|
|
|
La gravità stessa è `physicsSetGravity(x, y, z)` e vale per tutto il mondo. La
|
|
luna è `physicsSetGravity(0, -1.62, 0)`, lo spazio è tutto zeri, e un gioco con
|
|
la gravità di lato è a una riga di distanza.
|
|
|
|
Altre due impostazioni completano la sensazione di un oggetto.
|
|
`bodySetBounce(node, amount)` va da `0`, che si ferma di colpo, a `1`, che torna
|
|
indietro con tutto quello con cui è arrivato; il valore predefinito è un `0.1`
|
|
appena percettibile. `bodySetFriction(node,
|
|
amount)` è `0` per il ghiaccio e `0.5` per impostazione predefinita. Entrambe si
|
|
combinano tra le due cose che si toccano, quindi un pavimento ghiacciato vuole
|
|
il numero basso sul pavimento, non su tutto quello che ci cammina sopra.
|
|
|
|
[#_knowing_when_things_touch]
|
|
=== Sapere quando le cose si toccano
|
|
|
|
[source,lua]
|
|
----
|
|
function onCollision(nodeA, nodeB, x, y, z, speed)
|
|
if speed > 4 then
|
|
knocks = knocks + 1
|
|
end
|
|
end
|
|
----
|
|
|
|
Lì `knocks` è una variabile tua, che aspetta in cima al file con
|
|
`local knocks = 0`, e il giocattolo qui sotto la stampa.
|
|
|
|
`onCollision` è una callback, come `onOverlayUpdate`: la scrivi tu, il motore la
|
|
chiama. Arriva una volta per ogni nuovo contatto -- due corpi che restano a
|
|
contatto non lo segnalano di nuovo -- con entrambi i nodi, il punto del mondo in
|
|
cui si sono incontrati, e la velocità con cui si sono incontrati. La velocità è
|
|
quello che permette a una botta e a uno schianto di suonare diversi, ed è il
|
|
motivo per cui il controllo qui sopra ignora il lento assestarsi di una pila.
|
|
|
|
L'altra metà della coppia è `onTrigger`. Rendi un corpo un trigger con
|
|
`bodySetTrigger(node, true)` e smette di spingere le cose: diventa una zona che
|
|
segnala che cosa ci entra e che cosa ne esce tramite `onTrigger(trigger, other,
|
|
entered)`. Un traguardo, una porta, una fossa, un oggetto da raccogliere. È il
|
|
discendente 3D dei controlli di collisione dell'undicesima lezione, e per
|
|
qualsiasi cosa che abbia la forma di «il giocatore è arrivato qui?» è la
|
|
chiamata che vuoi, invece di un corpo solido.
|
|
|
|
[#_the_toy]
|
|
=== Il giocattolo
|
|
|
|
Ora tutto quanto. Parti dallo script qui sopra e trasformalo in questo: un
|
|
vassoio con quattro pareti, sei casse in piramide, e una palla d'acciaio che
|
|
puoi puntare e sparare. I pezzi sono tutti cose che hai già incontrato.
|
|
|
|
Tutto quello che il nuovo codice ricorda va in cima al file, sopra la scena:
|
|
|
|
[source,lua]
|
|
----
|
|
local crates = {}
|
|
local balls = {}
|
|
local aimX = 0
|
|
local aimLeft = false
|
|
local aimRight = false
|
|
local knocks = 0
|
|
local showShapes = false
|
|
----
|
|
|
|
Altri due handle condivisi vanno insieme ai materiali: un metallo lucido per la
|
|
palla e per il segnalino di mira, e una sola mesh di cassa per tutte e sei le
|
|
casse.
|
|
|
|
[source,lua]
|
|
----
|
|
local steel = materialNew()
|
|
materialSetColor(steel, 200, 205, 215)
|
|
materialSetMetallic(steel, 1)
|
|
materialSetRoughness(steel, 0.25)
|
|
|
|
local crateMesh = meshBox(0.8, 0.8, 0.8)
|
|
----
|
|
|
|
Le pareti del vassoio sono quattro scatole statiche, fatte in un ciclo a partire
|
|
da una tabella di posizioni, ed è la sesta lezione che fa un lavoro onesto:
|
|
|
|
[source,lua]
|
|
----
|
|
local wallMesh = meshBox(16, 1, 0.4)
|
|
|
|
for _, side in ipairs({ { 0, -8, 0 }, { 0, 8, 0 }, { -8, 0, 90 }, { 8, 0, 90 } }) do
|
|
local wall = nodeNew()
|
|
nodeSetMesh(wall, wallMesh, stone)
|
|
nodeSetPosition(wall, side[1], 0.5, side[2])
|
|
nodeSetRotation(wall, 0, side[3], 0)
|
|
bodyNew(wall, BODY_STATIC, SHAPE_BOX, 16, 1, 0.4)
|
|
end
|
|
----
|
|
|
|
Ogni voce è una X, una Z e una rotazione attorno a Y. Due pareti stanno di
|
|
traverso dietro e davanti; due sono girate di novanta gradi per fare i lati.
|
|
Una sola mesh basta per tutte e quattro, e il corpo viene creato dopo che il
|
|
nodo è stato posizionato e girato, perché un corpo statico prende la posizione e
|
|
la rotazione del nodo nel momento in cui viene creato.
|
|
|
|
Le casse vengono da una funzione, così sei casse sono sei righe:
|
|
|
|
[source,lua]
|
|
----
|
|
local function crateAt(x, y)
|
|
local crate = nodeNew()
|
|
|
|
nodeSetMesh(crate, crateMesh, wood)
|
|
nodeSetPosition(crate, x, y, -2)
|
|
bodyNew(crate, BODY_DYNAMIC, SHAPE_BOX, 0.8, 0.8, 0.8)
|
|
bodySetMass(crate, 4)
|
|
crates[#crates + 1] = crate
|
|
end
|
|
----
|
|
|
|
Tiene ogni cassa che crea nella tabella `crates`, ed è così che più tardi la
|
|
callback di disegno può fare una domanda a tutte. Sei chiamate costruiscono la
|
|
piramide, tre sul pavimento, due sopra quelle e una in cima:
|
|
|
|
[source,lua]
|
|
----
|
|
crateAt(-0.9, 0.4)
|
|
crateAt(0, 0.4)
|
|
crateAt(0.9, 0.4)
|
|
crateAt(-0.45, 1.25)
|
|
crateAt(0.45, 1.25)
|
|
crateAt(0, 2.1)
|
|
----
|
|
|
|
Le file superiori stanno un dito più in alto di quelle sotto e cadono al loro
|
|
posto appena il gioco parte. Lasciare un piccolo spazio è più facile che
|
|
impilare con precisione, ed è più gentile verso la simulazione che consegnarle
|
|
una pila già schiacciata.
|
|
|
|
Sparare crea ogni volta una palla nuova:
|
|
|
|
[source,lua]
|
|
----
|
|
local function fire()
|
|
local ball = nodeNew()
|
|
|
|
nodeSetMesh(ball, meshSphere(0.35, 24), steel)
|
|
nodeSetPosition(ball, aimX, 1.2, 6)
|
|
bodyNew(ball, BODY_DYNAMIC, SHAPE_SPHERE, 0.35)
|
|
bodySetMass(ball, 3)
|
|
bodySetBounce(ball, 0.3)
|
|
bodySetVelocity(ball, 0, 2, -14)
|
|
balls[#balls + 1] = ball
|
|
if #balls > 5 then
|
|
nodeDelete(balls[1])
|
|
table.remove(balls, 1)
|
|
end
|
|
end
|
|
----
|
|
|
|
Guarda l'ordine. Il nodo viene creato, riceve una forma da disegnare, viene
|
|
*posizionato*, e solo allora riceve un corpo. Un corpo dinamico parte da dove si
|
|
trova il suo nodo, e dopo il traffico va nell'altro senso: è il corpo che muove
|
|
il nodo. Impostare da te, dopo, la posizione di un nodo dinamico significa
|
|
combattere la simulazione, e vince la simulazione. Quando qualcosa va spostato,
|
|
dagli una velocità, colpiscilo con un impulso, o fai quello che fa questa
|
|
funzione -- creane uno nuovo e lascia andare quello vecchio.
|
|
|
|
Lasciar andare quello vecchio è `nodeDelete`, che libera il nodo, tutto quello
|
|
che c'è sotto e il corpo che ci viaggiava sopra. Senza quelle tre righe ogni
|
|
colpo resterebbe nel mondo per sempre, e qualche centinaio di palle dopo la
|
|
simulazione farebbe un sacco di lavoro su cose che nessuno vede. Fare pulizia
|
|
fa parte dell'usare la fisica.
|
|
|
|
I comandi e la visualizzazione sono il resto:
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_BUTTON1 then
|
|
fire()
|
|
elseif what == SWITCH_BUTTON3 then
|
|
showShapes = not showShapes
|
|
if showShapes then
|
|
physicsSetDebug(DEBUG_SHAPES + DEBUG_CONTACTS)
|
|
else
|
|
physicsSetDebug(DEBUG_NONE)
|
|
end
|
|
elseif what == SWITCH_LEFT then
|
|
aimLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
aimRight = true
|
|
end
|
|
end
|
|
----
|
|
|
|
La barra spaziatrice spara, Maiusc accende e spegne la vista della fisica, e le
|
|
frecce, tenute premute, fanno scorrere il punto di lancio. Una piastra di
|
|
metallo piatta senza corpo, spostata a ogni fotogramma in `aimX`, mostra da dove
|
|
partirà la prossima palla. Lo script completo, con la callback del rilascio e il
|
|
resto della preparazione, è `24-physics.singe` nella cartella `learn`.
|
|
|
|
`physicsSetDebug` è la chiamata più utile di questa lezione. Disegna quello che
|
|
la simulazione contiene davvero -- le forme vere in wireframe, e una croce rossa
|
|
dovunque due corpi si sono toccati in questo passo -- sopra la tua scena. La
|
|
prima volta che una cassa fluttua un palmo sopra il pavimento, o ci cade
|
|
attraverso, o fa cadere qualcosa da due metri di distanza, accendila e la
|
|
risposta sarà sullo schermo. Aggiungi `DEBUG_STATIC` per vedere anche il
|
|
pavimento e le pareti. Finché è spenta non costa niente.
|
|
|
|
Infine, il conteggio:
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
local over = 0
|
|
local settled = true
|
|
|
|
if aimLeft then
|
|
aimX = math.max(aimX - 0.08, -5)
|
|
end
|
|
if aimRight then
|
|
aimX = math.min(aimX + 0.08, 5)
|
|
end
|
|
nodeSetPosition(marker, aimX, 0.05, 6)
|
|
|
|
for _, crate in ipairs(crates) do
|
|
local pitch, _, roll = nodeGetRotation(crate)
|
|
|
|
if math.abs(pitch) > 30 or math.abs(roll) > 30 then
|
|
over = over + 1
|
|
end
|
|
if not bodyIsResting(crate) then
|
|
settled = false
|
|
end
|
|
end
|
|
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Arrows aim. Space fires. Shift shows the shapes.")
|
|
overlayPrint(2, 4, "knocked over " .. over .. " of " .. #crates .. " hard hits " .. knocks)
|
|
if settled then
|
|
overlayPrint(2, 6, "everything has settled")
|
|
end
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Una cassa conta come rovesciata quando è inclinata di più di trenta gradi, che
|
|
è una regola del gioco scritta in una riga di aritmetica sopra qualunque cosa
|
|
abbia fatto la simulazione. `nodeGetRotation` dà tre angoli e qui servono il
|
|
primo e il terzo, quindi quello in mezzo viene raccolto in una variabile
|
|
chiamata `_`. Quel trattino basso è un nome normale, senza niente di speciale,
|
|
usato per convenzione per dire «questo lo ignoro».
|
|
|
|
`bodyIsResting` dice se la simulazione ha addormentato un corpo perché ha
|
|
smesso di muoversi. È così che sai che un tiro è finito: non un timer, non una
|
|
stima, ma ogni cassa addormentata. Un gioco di bowling conta i punti di ogni
|
|
turno proprio lì.
|
|
|
|
[#_when_not_to_use_it]
|
|
=== Quando non usarla
|
|
|
|
La fisica è lo strumento più allettante di qualsiasi motore e quello che fa
|
|
sprecare più fine settimana. Ci sono tre cose vere su di lei, e nessuna è colpa
|
|
del motore.
|
|
|
|
*È costosa.* Ogni corpo dinamico è lavoro a ogni passo, sessanta volte al
|
|
secondo, che qualcuno lo guardi o no. Quaranta casse vanno bene. Quattromila
|
|
no, e su una macchina piccola nemmeno quattrocento.
|
|
|
|
*È imprevedibile.* Esegui il tuo giocattolo due volte e le casse atterrano in
|
|
modo diverso. È ciò che la fa sembrare viva, ed è anche il motivo per cui un
|
|
gioco non può contarci per niente che debba venire uguale ogni volta. Una porta
|
|
che deve aprirsi, una piattaforma che deve arrivare, un salto che il giocatore
|
|
deve poter fare: quelli sono aritmetica, o un tween, o un corpo cinematico su un
|
|
percorso che hai scritto tu. Non lasciare mai una cosa che il giocatore deve
|
|
fare a una simulazione che può farla finire dietro una cassa.
|
|
|
|
*È difficile da regolare.* La massa, l'attrito, il rimbalzo, la forma e la
|
|
dimensione si tirano l'una con l'altra, e «non sembra giusto» raramente ha una
|
|
causa sola. Ogni ora che passi a rendere convincente una cassa fisica è un'ora
|
|
che non hai passato sul gioco.
|
|
|
|
Quindi usala quando il caos *è* il punto: cose che crollano, detriti che volano,
|
|
una pila che si assesta, una palla che sbatacchia per una stanza, un veicolo che
|
|
corre su un terreno accidentato, un ragdoll che ruzzola giù per una scala.
|
|
Nessuno sa scriverlo a mano e nessuno sa dirti esattamente che aspetto dovrebbe
|
|
avere.
|
|
|
|
Non usarla quando sai che cosa dovrebbe succedere. Un proiettile che vola
|
|
dritto sono due righe della seconda lezione. Una piattaforma che va su e giù è
|
|
`math.sin`. Un giocatore che cammina e salta come i giocatori si aspettano vuole
|
|
un controller del personaggio (`playerNew`, nel riferimento), perché una persona
|
|
non è una scatola e non si comporta mai come tale. Un nemico che pattuglia
|
|
vuole un percorso. Un menu che entra scorrendo vuole un tween.
|
|
|
|
La regola che funziona meglio: usa la fisica per le cose che il giocatore
|
|
guarda, e l'aritmetica per le cose che il giocatore fa.
|
|
|
|
[#_try_it_24]
|
|
=== Prova tu
|
|
|
|
. *Cambia il mondo.* Aggiungi `physicsSetGravity(0, -1.62, 0)` vicino
|
|
all'inizio e spara di nuovo. Poi prova `0, -30, 0`. Le stesse casse, un altro
|
|
pianeta, una riga.
|
|
. *Cambia la palla.* Rendi la palla dieci volte più pesante con `bodySetMass(ball,
|
|
30)`, poi dieci volte più leggera con `0.3`, mantenendo la stessa velocità.
|
|
Poi rimetti la massa com'era e cambia invece il `-14`. Due modi di colpire più
|
|
forte, e non danno la stessa sensazione.
|
|
. *Fai le casse di ghiaccio.* Aggiungi `bodySetFriction(crate, 0.02)` in
|
|
`crateAt`, e aggiungi una riga che dia un attrito basso anche al pavimento.
|
|
Guarda la pila che si rifiuta di stare in piedi fin dall'inizio.
|
|
. *Sbaglia apposta la forma.* In `crateAt`, cambia le dimensioni del corpo in
|
|
`2, 2, 2` lasciando la mesh a `0.8`. Eseguilo, guarda le casse fluttuare e
|
|
spingersi a vicenda dal nulla, poi premi Maiusc e guarda la vista di debug
|
|
spiegarlo in una sola immagine.
|
|
. *Conta i punti come si deve.* Usa `bodyIsResting` su ogni cassa per accorgerti
|
|
di quando il tiro è finito, e solo allora conta quante sono rovesciate e
|
|
aggiungilo a un punteggio. Questa è la parte di un gioco di bowling che tiene
|
|
il punteggio.
|
|
|
|
[#_break_it_on_purpose_23]
|
|
=== Rompilo apposta
|
|
|
|
Aggiungi una riga sotto il `bodyNew` del pavimento per renderlo pesante, che è
|
|
una cosa ragionevole da pensare di volere:
|
|
|
|
[source,lua]
|
|
----
|
|
bodySetMass(floor, 200)
|
|
----
|
|
|
|
Singe ferma il gioco con qualcosa del genere:
|
|
|
|
----
|
|
32:bodySetMass: Node 1 is not a dynamic body, or the mass is not positive.
|
|
----
|
|
|
|
Il primo numero è la riga nel tuo file, quindi il tuo sarà diverso.
|
|
|
|
Il numero dopo `Node` è l'handle del nodo, il numero che ti ha restituito
|
|
`nodeNew`. La lamentela è esatta: la massa appartiene alle cose che la
|
|
simulazione muove, e un corpo statico non viene mosso da niente, quindi non ha
|
|
una massa da impostare. Chiederlo è un segno che il corpo è del tipo sbagliato
|
|
per quello che avevi in mente -- e se vuoi davvero un pavimento pesante che si
|
|
possa spostare, quello che vuoi è un corpo dinamico con una massa grande, o uno
|
|
cinematico che sposti tu.
|
|
|
|
Lo stesso messaggio compare per una massa di `0`, che la gente scrive intendendo
|
|
«senza peso». Senza peso non è una massa. È un mondo senza gravità, oppure
|
|
`playerSetGravityScale` su un controller del personaggio.
|
|
|
|
[#_what_you_learned_24]
|
|
=== Cosa hai imparato
|
|
|
|
* Un corpo si attacca a un nodo, e da quel momento il nodo e il corpo sono una
|
|
cosa sola.
|
|
* Statico non si muove mai, dinamico viene mosso dalla simulazione, e
|
|
cinematico viene mosso da te e spinge via tutto il resto.
|
|
* La forma di collisione è un sostituto della mesh, e tocca a te fare in modo
|
|
che le sue dimensioni corrispondano a quello che hai disegnato.
|
|
* La massa predefinita è il volume della forma alla densità dell'acqua, quindi
|
|
impostala su qualsiasi cosa debba sembrare leggera.
|
|
* `bodySetVelocity` sostituisce il moto di un corpo, `bodyApplyImpulse` è un
|
|
colpo, e `bodyApplyForce` è una spinta che dura.
|
|
* Non impostare la posizione di un nodo dinamico; eliminalo e creane uno nuovo
|
|
quando devi farlo ripartire da capo.
|
|
* `onCollision` segnala ogni nuovo contatto con il suo punto e la sua velocità,
|
|
e un corpo trigger segnala arrivi e partenze tramite `onTrigger`.
|
|
* `bodyIsResting` ti dice quando tutto si è fermato, che di solito è quando un
|
|
turno è finito.
|
|
* `physicsSetDebug` disegna la vista della simulazione stessa, e risponde alla
|
|
maggior parte delle domande sulla fisica più in fretta che leggere il codice.
|
|
* La fisica è costosa, irripetibile e pignola. Usala per il caos, non per le
|
|
cose da cui dipende il gioco.
|
|
|
|
[#_next_time_24]
|
|
=== La prossima volta
|
|
|
|
Le casse che cadono sono un tipo di spettacolo. L'altro tipo è fatto di mille
|
|
cosine che vivono mezzo secondo: scintille, fumo, fuoco, pioggia e polvere. La
|
|
prossima lezione parla di emettitori, e finisce con un'esplosione. È la
|
|
venticinquesima lezione.
|