534 lines
21 KiB
Text
534 lines
21 KiB
Text
[#_lesson_11_hitting_things]
|
|
== Lezione 11: Colpire le cose
|
|
|
|
image::learn/11-hitting-things.png[La lezione finita, 480]
|
|
|
|
Il tuo gioco della decima lezione funziona. I colpi spezzano le rocce, le rocce
|
|
costano vite e le esplosioni arrivano nei punti giusti. Guardalo bene, però, e
|
|
vedrai che bara, in tutte e due le direzioni. Una roccia che scivola oltre la
|
|
punta di un'ala ti toglie una vita. Un colpo che attraversa il cielo vuoto
|
|
accanto a una roccia la spezza lo stesso.
|
|
|
|
In un gioco due cose si toccano quando lo dicono i numeri. Nessuno guarda le
|
|
immagini. Questa lezione mette d'accordo i numeri con quello che vede il
|
|
giocatore: sostituisce il controllo che hai scritto a mano con quello del
|
|
motore, rimpicciolisce i riquadri controllati perché calzino sulle cose che
|
|
contengono, e ti dà un interruttore che ti mostra quei riquadri. L'immagine qui
|
|
sopra è il gioco con quell'interruttore acceso.
|
|
|
|
[#_where_you_are]
|
|
=== Dove sei arrivato
|
|
|
|
Il gioco chiede «queste due cose si toccano?» esattamente in due punti, e tutti
|
|
e due usano `overlapping`, la funzione che hai scritto nella nona lezione. Uno è
|
|
in `updateRocks`, dove una roccia incontra la nave:
|
|
|
|
[source,lua]
|
|
----
|
|
if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
L'altro è in `updateShots`, dove un colpo incontra una roccia:
|
|
|
|
[source,lua]
|
|
----
|
|
if not gone and overlapping(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
Tutto ciò che c'è in questa lezione cambia quelle due righe e aggiunge qualche
|
|
piccola funzione che le alimenti. Il resto del gioco non cambia. Lo script
|
|
completo e finito è in `learn/11-hitting-things.singe`, se ti perdi.
|
|
|
|
[#_read_it_once_more]
|
|
=== Rileggila ancora una volta
|
|
|
|
Questa parte l'ha già fatta la nona lezione: `overlapping` è la collisione tra
|
|
rettangoli scritta a mano. Rileggila ancora una volta prima di buttarla via,
|
|
perché è l'idea tutta intera:
|
|
|
|
[source,lua]
|
|
----
|
|
function overlapping(ax, ay, aw, ah, bx, by, bw, bh)
|
|
if ax + aw <= bx then
|
|
return false
|
|
end
|
|
if bx + bw <= ax then
|
|
return false
|
|
end
|
|
if ay + ah <= by then
|
|
return false
|
|
end
|
|
if by + bh <= ay then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
----
|
|
|
|
Alla domanda «queste due cose si toccano?» è difficile rispondere direttamente
|
|
e facile rispondere al contrario. Due rettangoli *non* si toccano quando uno dei
|
|
due sta tutto a sinistra dell'altro, o tutto a destra, o tutto sopra, o tutto
|
|
sotto. Non c'è un quinto modo di mancarsi. Se nessuno di questi quattro è vero,
|
|
si toccano.
|
|
|
|
Ogni `if` è uno dei quattro modi di mancarsi. `ax + aw` è il bordo destro del
|
|
primo rettangolo, quindi `ax + aw <= bx` si legge «il primo finisce prima che
|
|
cominci il secondo», che è quello che «tutto a sinistra» vuol dire in numeri.
|
|
La stessa forma altre tre volte, per destra, sopra e sotto, con `y` e le
|
|
altezze al posto di `x` e delle larghezze, e l'unica cosa che resta è `true`.
|
|
|
|
Questi sono gli otto numeri che servono a qualunque controllo di collisione: un
|
|
angolo e una dimensione per ciascuna delle due cose. Tieni a mente la forma,
|
|
perché stai per cancellarla.
|
|
|
|
[#_the_engine_already_has_it]
|
|
=== Il motore ce l'ha già
|
|
|
|
Hai incontrato `collideRects` nella settima lezione, dove acchiappava i blocchi
|
|
che cadevano in Dodge. La nona lezione ha scritto lo stesso controllo a mano
|
|
perché tu potessi vederci dentro. Ci hai guardato dentro. Cancella
|
|
`overlapping` e cambia le due righe di *Dove sei arrivato* perché chiamino
|
|
invece il motore:
|
|
|
|
[source,lua]
|
|
----
|
|
if collideRects(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
if not gone and collideRects(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
|
|
----
|
|
|
|
Gli stessi otto numeri, nello stesso ordine: l'angolo e la dimensione del primo
|
|
rettangolo, poi quelli del secondo. Eseguilo, e il gioco si comporta
|
|
esattamente come prima, ed è questo il punto. L'unica differenza sta proprio
|
|
sul bordo. Il tuo `<=` diceva che due rettangoli che si incontrano lungo un
|
|
bordo non si toccano. `collideRects` dice che si toccano, e un rettangolo senza
|
|
larghezza o altezza non tocca proprio niente. Questi casi scomodi sono il punto
|
|
in cui le versioni scritte a mano sbagliano in silenzio, e sono uno dei motivi
|
|
per usare quella del motore.
|
|
|
|
`collideRects` fa parte di una famiglia di controlli di collisione, e la
|
|
famiglia è una delle parti di quello che questo libro chiama il *kit di
|
|
gioco*: una manciata di semplici funzioni per le cose che quasi ogni gioco un
|
|
tempo si scriveva da sé. Controllare se due cose si toccano è una. Salvare un
|
|
numero per la volta successiva è un'altra, ed è la dodicesima lezione.
|
|
Aspettare un po', far scorrere un numero dolcemente da un valore a un altro e
|
|
mostrare quanto è durato ogni fotogramma sono le altre. Il Riferimento dà a
|
|
ogni parte una sezione tutta sua: Collisioni, Salvataggio, Timer, Tween e
|
|
Statistiche.
|
|
|
|
I controlli di collisione sono i più semplici del mucchio: domande e basta,
|
|
senza handle da conservare e niente da preparare o liberare. E nessuno di loro
|
|
sposta qualcosa. `collideRects` ti dice che la nave e la roccia sono nello
|
|
stesso posto; che cosa farne lo decidi tu, e tutto ciò che c'è di interessante
|
|
in un gioco succede in quella decisione.
|
|
|
|
[#_the_picture_is_not_the_hit_box]
|
|
=== L'immagine non è la hitbox
|
|
|
|
La decima lezione ha promesso di spiegare perché il rettangolo attorno a una
|
|
roccia rotonda ha la forma sbagliata. Anche il rettangolo attorno alla nave è
|
|
sbagliato, e per lo stesso motivo.
|
|
|
|
Disegna su carta il rettangolo 32 per 24 della nave e colora la nave. È un
|
|
triangolo con due pinne, quindi c'è cielo vuoto in tutti e due gli angoli in
|
|
alto e tra le pinne. La roccia è rotonda, quindi c'è cielo vuoto in tutti e
|
|
quattro gli angoli del suo quadrato. Una roccia che sfiora l'angolo in alto del
|
|
rettangolo della nave non ha colpito la nave. È passata oltre l'ala, e un
|
|
giocatore che ci perde una vita dirà che il gioco ha barato -- e avrà ragione.
|
|
|
|
Quindi il riquadro che controlli non è l'immagine che disegni. È più piccolo, e
|
|
sta dentro l'immagine. Aggiungi tre numeri alle costanti in cima al tuo file:
|
|
|
|
[source,lua]
|
|
----
|
|
local SHIP_PAD_X = 6
|
|
local SHIP_PAD_Y = 5
|
|
local ROCK_PAD = 4
|
|
----
|
|
|
|
Il margine è quanto più all'interno di ogni bordo comincia la cosa vera. Sei
|
|
pixel tolti da ogni lato della nave e cinque da sopra e da sotto lasciano un
|
|
riquadro di 20 per 14 in mezzo a un'immagine di 32 per 24, che è più o meno
|
|
dove sta lo scafo. Quattro tolti da ogni lato della roccia lasciano 16 per 16,
|
|
ed è quasi tutto roccia.
|
|
|
|
Tutti e due i riquadri sono un po' più piccoli delle cose che rappresentano, ed
|
|
è voluto. Un riquadro appena troppo piccolo regala al giocatore dei passaggi per
|
|
un pelo che sembrano bravura. Un riquadro appena troppo grande gli dà dei colpi
|
|
subiti che sembrano un imbroglio. I giocatori perdonano i primi e mai i
|
|
secondi.
|
|
|
|
Invece di scrivere quei calcoli a ogni controllo, scrivili una volta per ogni
|
|
tipo di cosa:
|
|
|
|
[source,lua]
|
|
----
|
|
function rockBox(rock)
|
|
return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2
|
|
end
|
|
|
|
|
|
function shipBox()
|
|
return shipX + SHIP_PAD_X, shipY + SHIP_PAD_Y, shipWidth - SHIP_PAD_X * 2, shipHeight - SHIP_PAD_Y * 2
|
|
end
|
|
----
|
|
|
|
Le dimensioni vengono da `rockWidth`, `shipWidth` e compagnia, le misure che la
|
|
nona lezione ha preso dalle immagini, quindi nel codice di gioco non c'è ancora
|
|
una sola dimensione in pixel scritta a mano. Ridisegna la nave più grande e il
|
|
riquadro cresce con lei.
|
|
|
|
Ognuna di queste funzioni restituisce quattro numeri insieme, separati da
|
|
virgole. Una funzione può restituire tutti i valori che vuole, e il modo per
|
|
raccoglierli è un elenco di nomi a sinistra del segno `=`:
|
|
|
|
[source,lua]
|
|
----
|
|
function shipHitsRock(rock)
|
|
local sx, sy, sw, sh = shipBox()
|
|
local rx, ry, rw, rh = rockBox(rock)
|
|
|
|
return collideRects(sx, sy, sw, sh, rx, ry, rw, rh)
|
|
end
|
|
----
|
|
|
|
Ora il controllo in `updateRocks` si legge come la domanda che sta facendo:
|
|
|
|
[source,lua]
|
|
----
|
|
if shipHitsRock(rock) then
|
|
----
|
|
|
|
I calcoli del margine stanno esattamente in due posti, e i numeri che usano
|
|
sono in cima al file, quindi puoi regolare quanto il gioco sembra onesto
|
|
cambiando un numero invece di andare a caccia tra i tuoi controlli.
|
|
|
|
Metti le nuove funzioni al loro posto in ordine alfabetico, come chiedeva la
|
|
nona lezione: `rockBox` dopo `onSoundCompleted`, poi `shipBox` e
|
|
`shipHitsRock`.
|
|
|
|
[#_firing_back]
|
|
=== Rispondere al fuoco
|
|
|
|
Il lato della nave è sistemato. L'altra collisione del gioco è quella con cui
|
|
rispondi al fuoco: ogni colpo contro ogni roccia, in `updateShots`. Ha lo
|
|
stesso problema che aveva la nave, in formato ridotto. Un colpo è un
|
|
rettangolo di 4 per 10 controllato contro l'intero quadrato di 24 per 24 della
|
|
roccia, quindi un colpo che passa soltanto per l'angolo vuoto accanto a una
|
|
roccia la spezza lo stesso.
|
|
|
|
Potresti dare anche al colpo un riquadro con il suo margine. Prima però guarda
|
|
i numeri. Togli un margine a un colpo largo quattro pixel e non resta quasi
|
|
niente, e controllare un intero rettangolo per scoprire se un filo di luce ha
|
|
toccato qualcosa è più macchinario di quanto la domanda meriti.
|
|
|
|
[#_a_shot_is_almost_a_point]
|
|
=== Un colpo è quasi un punto
|
|
|
|
Controlla invece la punta. `collidePointRect` chiede se un punto cade dentro un
|
|
rettangolo -- due numeri per il punto, poi l'angolo e la dimensione del
|
|
rettangolo:
|
|
|
|
[source,lua]
|
|
----
|
|
function shotHitsRock(shot, rock)
|
|
local rx, ry, rw, rh = rockBox(rock)
|
|
|
|
return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh)
|
|
end
|
|
----
|
|
|
|
`shot.y` è la cima del colpo e `shot.x + shotWidth / 2` è a metà della sua
|
|
larghezza, quindi il punto controllato è il centro della punta del colpo. È lì
|
|
che un giocatore pensa che sia un colpo. Dalla parte della roccia, la domanda
|
|
usa lo stesso `rockBox` che usa la nave, quindi una roccia ha la stessa
|
|
dimensione qualunque cosa la colpisca.
|
|
|
|
Va dopo `shipHitsRock`, e il controllo in `updateShots` diventa:
|
|
|
|
[source,lua]
|
|
----
|
|
if not gone and shotHitsRock(shot, rock) then
|
|
----
|
|
|
|
[#_deciding_what_happened]
|
|
=== Decidere che cosa è successo
|
|
|
|
Nessuno dei controlli di collisione fa niente. Rispondono, e decide il tuo
|
|
codice. Ecco i due punti che decidono, con dentro le loro nuove domande. Prima
|
|
`updateRocks`:
|
|
|
|
[source,lua]
|
|
----
|
|
if shipHitsRock(rock) then
|
|
local channel = soundPlay(boomClip)
|
|
|
|
newRock(rock)
|
|
lives = lives - 1
|
|
if lives <= 0 then
|
|
dying = true
|
|
deathChannel = channel
|
|
if channel < 0 then
|
|
endGame()
|
|
end
|
|
end
|
|
end
|
|
----
|
|
|
|
E il ciclo dentro `updateShots`:
|
|
|
|
[source,lua]
|
|
----
|
|
for _, rock in ipairs(rocks) do
|
|
if not gone and shotHitsRock(shot, rock) then
|
|
newRock(rock)
|
|
score = score + HIT_SCORE
|
|
gone = true
|
|
soundPlay(boomClip, 0, 45)
|
|
end
|
|
end
|
|
----
|
|
|
|
Tutto quello che sta sotto le domande è esattamente ciò che ci ha lasciato la
|
|
decima lezione. Una roccia che colpisce la nave fa un botto, torna in cima e
|
|
costa una vita. Una roccia centrata da un colpo fa un botto, torna in cima e
|
|
dà punti. Se ne va anche il colpo, e il `not gone` impedisce a un colpo solo di
|
|
spezzare due rocce. Cambia una qualunque di queste cose e cambi il gioco, senza
|
|
toccare un solo controllo di collisione, ed è per questo che le domande e le
|
|
risposte stanno separate.
|
|
|
|
Eseguilo. Passa vicinissimo a una roccia e lasciale sfiorare una pinna: la vita
|
|
resta tua. Spara appena accanto a una roccia e il colpo tira dritto. Spara nel
|
|
mezzo di una roccia e si spezza, come sempre.
|
|
|
|
[#_seeing_the_boxes]
|
|
=== Vedere i riquadri
|
|
|
|
Le hitbox sono invisibili, e questo rende invisibile anche un errore al loro
|
|
interno. Disegnale mentre ci lavori. Un'altra costante in cima:
|
|
|
|
[source,lua]
|
|
----
|
|
local SHOW_BOXES = false
|
|
----
|
|
|
|
Altre due funzioni, che vanno proprio in cima all'ordine alfabetico:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawBox(x, y, width, height)
|
|
overlayBox(x, y, x + width - 1, y + height - 1)
|
|
end
|
|
|
|
|
|
function drawBoxes()
|
|
colorForeground(0, 255, 0)
|
|
drawBox(shipBox())
|
|
for _, rock in ipairs(rocks) do
|
|
drawBox(rockBox(rock))
|
|
end
|
|
end
|
|
----
|
|
|
|
E in `onOverlayUpdate`, dopo che è stata disegnata la nave e prima che ci vada
|
|
sopra il punteggio:
|
|
|
|
[source,lua]
|
|
----
|
|
spriteDraw(shipSprite, shipX, shipY)
|
|
if SHOW_BOXES then
|
|
drawBoxes()
|
|
end
|
|
drawHud()
|
|
----
|
|
|
|
Cambia `SHOW_BOXES` in `true` e sopra la grafica compaiono dei contorni verdi,
|
|
come nell'immagine all'inizio di questa lezione. Rimettilo com'era quando hai
|
|
finito. Un interruttore come questo, una parola in cima a un file che accende
|
|
un'immagine di quello che il programma crede, vale più di tutte le occhiate del
|
|
mondo.
|
|
|
|
[#_what_just_happened_7]
|
|
=== Cos'è appena successo
|
|
|
|
[source,lua]
|
|
----
|
|
function rockBox(rock)
|
|
return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2
|
|
end
|
|
----
|
|
|
|
Quattro valori da un solo `return`, separati da virgole. Lua ti restituisce
|
|
volentieri tutti quelli che vuoi, e chi chiama decide quanti tenerne. Quando
|
|
una chiamata come questa è l'*ultima* cosa dentro le parentesi di un'altra
|
|
chiamata, passano tutti e quattro, ed è per questo che `drawBox(rockBox(rock))`
|
|
funziona e dà a `drawBox` i suoi quattro argomenti. In qualunque altro punto di
|
|
un elenco di argomenti sopravvive solo il primo valore. Quella regola morde
|
|
esattamente una volta, e morde alla fine di questa lezione.
|
|
|
|
[source,lua]
|
|
----
|
|
return collideRects(sx, sy, sw, sh, rx, ry, rw, rh)
|
|
----
|
|
|
|
`collideRects` vuole otto numeri: angolo e dimensione, angolo e dimensione.
|
|
Restituisce `true` o `false` e non cambia niente. Siccome la risposta esce
|
|
dritta dalla funzione con `return`, `shipHitsRock(rock)` si legge come una
|
|
domanda dovunque venga usata.
|
|
|
|
[source,lua]
|
|
----
|
|
return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh)
|
|
----
|
|
|
|
`collidePointRect` ne vuole sei: un punto, poi un rettangolo. Il bordo conta
|
|
come interno, quindi un colpo esattamente sul confine colpisce. Usala ogni
|
|
volta che uno dei due lati della domanda è abbastanza piccolo da rendere
|
|
irrilevante la sua dimensione.
|
|
|
|
[source,lua]
|
|
----
|
|
colorForeground(0, 255, 0)
|
|
overlayBox(x, y, x + width - 1, y + height - 1)
|
|
----
|
|
|
|
Vengono tutte e due dalla settima lezione. `overlayBox` prende due *angoli*,
|
|
non un angolo e una dimensione -- l'unico punto di questa lezione in cui i
|
|
numeri cambiano forma -- e il `- 1` c'è perché entrambi gli angoli sono
|
|
inclusi. `colorForeground` è un interruttore che resta dove l'hai messo, quindi
|
|
tutto ciò che disegna `drawBoxes` viene verde. Gli sprite e `overlayPrint` lo
|
|
ignorano, quindi il resto del gioco ha lo stesso aspetto di prima.
|
|
|
|
[#_the_rest_of_the_family]
|
|
=== Il resto della famiglia
|
|
|
|
`collideRects` e `collidePointRect` bastano per la maggior parte dei giochi 2D.
|
|
Le altre ci sono per le forme che i rettangoli descrivono male, e ognuna è lo
|
|
stesso tipo di domanda semplice:
|
|
|
|
* `collideCircles` e `collidePointCircle`, per qualunque cosa rotonda. Un
|
|
cerchio è un centro e un raggio.
|
|
* `collideRectCircle`, per una cosa rotonda contro una quadrata.
|
|
* `collidePointPolygon`, per una forma con angoli tutti suoi -- un drago, un
|
|
continente, un pulsante dalla forma strana. Le passi un elenco piatto di
|
|
numeri `x`, `y`, `x`, `y`.
|
|
* `collideSegments`, per due segmenti che si incrociano.
|
|
|
|
A essere onesti, la tua roccia è rotonda, e i controlli con i cerchi le calzano
|
|
meglio di qualunque riquadro. Il margine è la risposta economica, e basta per
|
|
la maggior parte dei giochi, ed è per questo che viene prima. La risposta
|
|
esatta è una delle cose da provare qui sotto.
|
|
|
|
`collideSegments` conta più di quanto sembri. Il tuo colpo si muove di sei
|
|
pixel per fotogramma e le rocce hanno sedici pixel di hitbox, quindi un colpo
|
|
non può superarne una senza essere controllato mentre ci sta dentro. Rendi il
|
|
colpo abbastanza veloce -- quaranta pixel per fotogramma -- e salta da sotto
|
|
una roccia a sopra, senza mai trovarsi nello stesso posto della roccia, e non
|
|
colpisce mai niente. La cura è tracciare la linea da dove era il colpo a dove
|
|
si trova adesso, e chiedere a `collideSegments` se quella linea attraversa un
|
|
bordo. Alle cose lente non serve.
|
|
|
|
In Singe c'è una sola callback di collisione, `onCollision`, e non è per
|
|
questo. Appartiene al motore fisico 3D: corpi con una massa, che cadono e
|
|
rimbalzano uno contro l'altro in tre dimensioni, e ti dicono dove si sono
|
|
toccati e con quanta forza. La incontrerai nella ventiquattresima lezione.
|
|
Niente sull'overlay si segnala da solo, e niente ne ha bisogno. Le tue rocce
|
|
sono in un elenco, sai quante sono, e chiedere costa poco.
|
|
|
|
E quando vuoi sapere non solo *se* due cose si sono incontrate ma dove dovrebbe
|
|
finire quella che si muove -- scivolando lungo un muro invece di fermarsi di
|
|
colpo dentro -- le chiamate collide di proposito non rispondono. Per
|
|
esattamente quel lavoro con Singe viene fornita una libreria chiamata `bump`, e
|
|
la sezione Librerie incluse del Riferimento spiega come raggiungerla.
|
|
|
|
[#_try_it_11]
|
|
=== Prova tu
|
|
|
|
. *Togli il margine.* Metti `ROCK_PAD` a `0` e gioca per un minuto. Poi mettilo
|
|
a `8` e gioca di nuovo. In uno dei due casi sembra che il gioco ti stia
|
|
mentendo.
|
|
. *Guarda che cosa stai controllando.* Metti `SHOW_BOXES` a `true` e osserva i
|
|
contorni verdi mentre una roccia passa accanto alla nave. Ora metti
|
|
`SHIP_PAD_X` a `16`, gioca, e nota che niente riesce più a colpirti. Scopri
|
|
perché dai calcoli in `shipBox`.
|
|
. *Rendi il colpo troppo veloce.* Metti `SHOT_SPEED` a `40`. Conta quante rocce
|
|
colpisci su dieci. Prima di rimetterlo com'era, ricava dai numeri perché
|
|
succede.
|
|
. *Usa tutto il colpo.* Riscrivi `shotHitsRock` in modo che usi `collideRects`
|
|
con il rettangolo intero del colpo invece di `collidePointRect` con la sua
|
|
punta. Provale tutte e due e decidi quale preferisci; non c'è una risposta
|
|
giusta, ed è una cosa che vale la pena sapere.
|
|
. *Rendi rotonda la roccia.* Dai alla roccia un cerchio invece di un riquadro:
|
|
il suo centro è a `rock.x + rockWidth / 2` in orizzontale e a
|
|
`rock.y + rockHeight / 2` in verticale, e un raggio di `10` va più o meno
|
|
bene. Usa `collideRectCircle` per la nave e `collidePointCircle` per il
|
|
colpo, e disegna il cerchio in `drawBoxes` con `overlayCircle`, che prende un
|
|
centro e un raggio.
|
|
|
|
[#_break_it_on_purpose_10]
|
|
=== Rompilo apposta
|
|
|
|
I due riquadri sono già quattro valori ciascuno, quindi viene la tentazione di
|
|
passarli direttamente e saltare gli otto nomi locali:
|
|
|
|
[source,lua]
|
|
----
|
|
function shipHitsRock(rock)
|
|
return collideRects(shipBox(), rockBox(rock))
|
|
end
|
|
----
|
|
|
|
Sembra giusto ed è sbagliato. Eseguilo e il gioco si ferma prima di disegnare
|
|
qualunque cosa:
|
|
|
|
----
|
|
186:collideRects: Expected 8 argument(s), got 5.
|
|
----
|
|
|
|
La riga è quella che hai appena scritto, quindi la tua potrebbe non
|
|
essere la 186. Cinque, però. `rockBox(rock)` è l'ultima cosa tra le parentesi,
|
|
quindi sono entrati tutti e quattro i suoi valori. `shipBox()` no, quindi è stato
|
|
ridotto a uno. Uno più quattro fa cinque, e `collideRects` non tira a
|
|
indovinare gli altri tre.
|
|
|
|
Questo errore viene dal motore e non da Lua, quindi ha la stessa forma di
|
|
quelli della nona e della decima lezione: il numero davanti è la riga, poi il
|
|
nome della funzione che si è rifiutata, poi la lamentela. Rimetti gli otto nomi
|
|
locali.
|
|
|
|
[#_what_you_learned_11]
|
|
=== Cosa hai imparato
|
|
|
|
* La collisione è aritmetica sui rettangoli, e puoi scriverla da solo.
|
|
* Due rettangoli si mancano esattamente in quattro modi; se nessuno di questi è
|
|
vero, si colpiscono.
|
|
* `collideRects` prende un angolo e una dimensione per ciascuno di due
|
|
rettangoli e risponde `true` o `false`. Due bordi che si toccano appena
|
|
contano come contatto.
|
|
* I controlli di collisione fanno parte del kit di gioco: semplici funzioni,
|
|
senza niente da preparare. Salvataggio, Timer, Tween e Statistiche sono il
|
|
resto.
|
|
* `collidePointRect` chiede se un punto sta dentro un rettangolo, che è la
|
|
domanda giusta per qualunque cosa piccola.
|
|
* Il riquadro che controlli è più piccolo dell'immagine che disegni, altrimenti
|
|
il gioco sembra ingiusto.
|
|
* Una funzione può restituire più valori, e solo l'ultima chiamata in un elenco
|
|
di argomenti li passa tutti.
|
|
* I controlli di collisione rispondono e basta. Quello che succede dopo è il
|
|
tuo codice, e tenere separate le due cose ti permette di cambiarne una sola.
|
|
* Disegnare le hitbox sullo schermo trasforma un bug invisibile in uno visibile.
|
|
* `onCollision` è per i corpi della fisica 3D, non per gli sprite sull'overlay.
|
|
|
|
[#_next_time_11]
|
|
=== La prossima volta
|
|
|
|
Il gioco conta il tuo punteggio e ti toglie le vite, e nel momento in cui esci
|
|
il punteggio migliore che qualcuno abbia mai fatto è sparito. Comincia nel
|
|
mezzo dell'azione nell'istante in cui si carica. E tiene traccia di quello che
|
|
sta facendo con due variabili vero-o-falso, `over` e `dying`, che è esattamente
|
|
la trappola da cui ti metteva in guardia la settima lezione. La dodicesima
|
|
lezione gli dà una schermata del titolo, una variabile sola al posto di quelle
|
|
due e un punteggio record che domani c'è ancora.
|