singe/docs/it/lessons/09-pictures.adoc

789 lines
28 KiB
Text

[#_lesson_9_pictures]
== Lezione 9: Immagini
image::learn/09-pictures.png[La lezione finita, 480]
Tutto quello che hai disegnato finora l'hai disegnato con riquadri, linee e il
testo semplice del motore. Era voluto: otto lezioni senza grafica volevano dire
otto lezioni in cui niente poteva andare storto tranne il tuo programma. Ora i
riquadri diventano un'astronave e qualche roccia.
Un'immagine che il motore disegna sull'overlay si chiama *sprite*. Questa
lezione li carica, li disegna, li misura, li mette nell'ordine giusto e scorre
un'immagine che contiene diversi fotogrammi di un'animazione. Alla fine, il
gioco della settima lezione sembrerà un gioco.
[#_the_art_kit]
=== Il kit di grafica
Non devi disegnare niente. Questo libro viene con una piccola cartella di
grafica, e tutto ciò che c'è in questa lezione e nella prossima la usa.
Trova la cartella `art` che è arrivata con il libro e copia l'intera cartella
nella cartella di questa lezione, accanto al tuo script. Dovresti ottenere
questo:
----
singe/ <- la tua cartella di lavoro
Singe/
data/
rocks/
rocks.singe
art/
spaceship.png
asteroid.png
bullet.png
star.png
walk.png
shoot.wav
boom.wav
----
La tua grafica va dentro la cartella del tuo gioco, e la cartella `Singe` del
motore resta dov'è, fuori nella cartella di lavoro. Questo conta tra un momento,
perché decide come indichi un file quando lo carichi.
La cartella `Singe` è quella che il motore ha estratto per te al tuo primissimo
avvio, nella prima lezione. Vale la pena sapere che cosa contiene, perché anche
quei file li puoi usare, e ogni lettore di questo libro li ha:
* `Singe/click.wav` -- un breve clic, che il menu del motore usa per la sua
prova del suono.
* `Singe/FreeSansBold.ttf` -- un font TrueType, per la diciannovesima lezione.
* `Singe/menuIntro.flac` -- la musica che suona il menu del motore. La userai
nella decima lezione.
* `Singe/menuBackground.mkv` -- un video, per la quattordicesima lezione.
* `Singe/DragonModel.glb` e `Singe/SingeText.glb` -- due modelli 3D, per la
ventitreesima lezione.
* `Singe/missing.png` -- l'immagine che il motore disegna quando un gioco ne
chiede una che non riesce a trovare.
Le immagini del kit sono piccole apposta. L'astronave è larga 32 pixel e alta
24, una roccia è 24 per 24, un colpo è 4 per 10 e una stella è 8 per 8. Sembra
minuscolo finché non ti ricordi quanto è grande davvero lo schermo, che è la
prossima cosa da chiarire.
[#_how_big_the_screen_is]
=== Quanto è grande lo schermo
Senza un video da riprodurre, Singe dà al tuo gioco un canvas largo 720 pixel e
alto 480, e la superficie su cui disegni -- l'*overlay* -- è la metà in ciascuna
direzione: *360 per 240*. Singe lo stira per riempire la finestra al posto tuo,
quindi un'astronave di 32 pixel occupa circa un decimo della larghezza dello
schermo, che è più o meno l'aspetto di un'astronave in un gioco da sala giochi.
Non scrivere mai 360 e 240 nel tuo programma. Chiedi:
[source,lua]
----
local screenWidth = overlayGetWidth()
local screenHeight = overlayGetHeight()
----
Quelle due riportano la dimensione dell'overlay, e un gioco che chiede è un
gioco che funziona ancora quando qualcuno lo avvia a una dimensione diversa.
C'è una cosa che non conta in quei pixel: `overlayPrint`, della prima lezione,
conta in celle di carattere. Ogni cella è larga 6 pixel e alta 13, quindi un
overlay di 360 per 240 fa 60 colonne per 18 righe di testo. Gli sprite sono in
pixel, `overlayPrint` è in celle, e confondere le due cose vale un pomeriggio di
confusione, se glielo permetti.
[#_one_picture_on_screen]
=== Un'immagine sullo schermo
Comincia un nuovo file accanto alla tua cartella `art` e scrivici questo.
[source,lua]
----
dofile("Singe/Framework.singe")
local ship = spriteLoad(DIR .. "art/spaceship.png")
function onOverlayUpdate()
overlayClear()
spriteDraw(ship, 160, 100)
return OVERLAY_UPDATED
end
function onShutdown()
spriteUnload(ship)
end
----
Avvialo. Una piccola astronave blu sta vicino al centro di uno schermo nero, con
la punta verso l'alto.
[#_what_just_happened_5]
=== Cos'è appena successo
Quattro cose nuove, una alla volta.
[source,lua]
----
local ship = spriteLoad(DIR .. "art/spaceship.png")
----
`spriteLoad` legge un file di immagine dal disco, lo decodifica e tiene
l'immagine in memoria. Quello che ti restituisce non è l'immagine. È un
*handle*: un semplice numero, un biglietto d'ingresso, il modo del motore per
dire «quella lì». Ridai quel numero al motore ogni volta che vuoi fare qualcosa
con l'immagine, e non ti serve mai sapere che numero sia, né preoccupartene.
Stampalo, se vuoi -- `debugPrint(ship)` dell'ottava lezione ti mostrerà un
numero piccolo -- ma il numero in sé non è affar tuo, e niente nel tuo programma
dovrebbe mai dipendere da quale numero risulti. L'unica regola è che lo tieni da
qualche parte dove puoi riprenderlo, che è esattamente ciò a cui serve una
variabile.
`DIR` è nuovo anche lui, e risolve un problema creato dalla struttura che usi
dalla prima lezione. Singe cerca i file a partire dalla cartella in cui ti
trovavi quando l'hai avviato, *non* a partire dal tuo script. Tu lo avvii dalla
cartella di lavoro, quindi un semplice `art/spaceship.png` manda il motore a
cercare `singe/art/spaceship.png`, là fuori accanto alla cartella `Singe`. La
tua grafica non è lì. Sta un livello più giù, in `rocks/art/spaceship.png`,
con il tuo script.
`DIR` colma quel divario. È un pezzo di testo che contiene la cartella in cui si
trova il tuo script, con la barra già in fondo, quindi
`DIR .. "art/spaceship.png"` -- che unisce due pezzi di testo con `..`, come fai
dalla prima parte -- indica l'immagine correttamente comunque sia stato avviato
il gioco, e continua a indicarla correttamente quando sposti l'intera cartella
da un'altra parte.
`DIR` è uno dei nomi che definisce `Singe/Framework.singe`, ed è a questo che
serve la riga `dofile` in cima al tuo script. Hai incontrato quella riga, e ciò
che contiene, nella settima lezione, e questo è il momento in cui si guadagna il
posto: senza di lei non c'è `DIR`, e senza `DIR` il motore non trova la tua
astronave.
Usa `DIR` per ogni file che il tuo gioco carica, da qui alla fine del libro.
[source,lua]
----
spriteDraw(ship, 160, 100)
----
Disegna lo sprite con quell'handle, in quella posizione. Come ogni chiamata di
disegno, funziona solo dentro `onOverlayUpdate`.
La posizione è l'*angolo in alto a sinistra* dell'immagine, non il suo centro.
Chiedi `(160, 100)` e il pixel più in alto e più a sinistra dell'astronave
atterra lì, e il resto pende verso il basso e verso destra. Questo frega tutti
una volta, di solito quando provano a centrare qualcosa e viene fuori spostato
in basso a destra di metà della sua dimensione.
Se preferisci indicare il centro, dillo:
[source,lua]
----
spriteDraw(ship, 160, 100, true)
----
Un quarto argomento `true` vuol dire «tratta quel punto come il centro». Tutte e
due le forme sono nella voce di `spriteDraw` nel Riferimento, insieme ad altre
due che stirano l'immagine dentro un rettangolo.
[source,lua]
----
function onShutdown()
spriteUnload(ship)
end
----
`onShutdown` è una callback, come `onOverlayUpdate` e le due callback dei
comandi della terza lezione. Il motore la chiama una volta, quando il tuo gioco
sta finendo. È lì che restituisci quello che hai preso: `spriteUnload` libera
l'immagine e butta via l'handle.
A rigore, potresti ometterla. Il motore libera tutto ciò che il tuo script ha
caricato quando il gioco finisce, quindi uno `spriteUnload` dimenticato non è
una perdita di memoria. Scrivila comunque. Costa una riga, dice che cosa possiede
il tuo programma, e fra un minuto vedrai il caso in cui dimenticarsi di scaricare
conta davvero.
Un avvertimento su quell'handle: una volta scaricato uno sprite, il numero è
morto. Disegnare con quel numero non si limita a non disegnare niente: termina
il tuo gioco con un errore.
[#_transparency_and_why_it_is_a_png]
=== La trasparenza, e perché è un PNG
L'astronave è un triangolo con due alette. Il file in cui vive è un rettangolo,
32 per 24, perché ogni file di immagine è un rettangolo. Quindi che fine hanno
fatto gli angoli?
Sono *trasparenti*. Un PNG può dire, per ogni singolo pixel, quanto è solido, e
le immagini del kit dicono «proprio non c'è» per ogni pixel fuori dalla forma.
Singe lo rispetta, quindi lo sfondo nero si vede attraverso gli angoli e
l'astronave ha un contorno invece di un riquadro intorno.
È per questo che il kit è in PNG e non in JPEG. Un JPEG non può proprio
memorizzare la trasparenza -- ti darebbe l'astronave in un riquadro grigio -- e
in più sbava i bordi netti, che è l'ultima cosa di cui ha bisogno la grafica
piccola. Per la grafica di un gioco, PNG. Per una fotografia che riempie tutto
lo schermo e non ha parti trasparenti, JPEG va bene ed è molto più piccolo. La
voce di `spriteLoad` nel Riferimento elenca tutti i formati che Singe sa leggere, e
sono parecchi.
Provalo tu stesso: cambia il nero con qualcos'altro disegnando un riquadro
dietro l'astronave prima di disegnare l'astronave. Gli angoli mostreranno
qualunque cosa ci sia dietro, che è proprio il punto.
[#_do_not_load_while_you_draw]
=== Non caricare mentre disegni
Ecco l'errore che questa sezione esiste per impedire. Sembra del tutto
ragionevole:
[source,lua]
----
function onOverlayUpdate()
overlayClear()
local ship = spriteLoad(DIR .. "art/spaceship.png")
spriteDraw(ship, 160, 100)
return OVERLAY_UPDATED
end
----
Eseguilo e funziona. Disegna l'astronave. Niente si lamenta. Non avresti nessun
motivo di pensare che qualcosa non vada.
`onOverlayUpdate` gira circa sessanta volte al secondo. Sono sessanta copie nuove
dell'astronave caricate in memoria ogni secondo, tremilaseicento al minuto, e
nessuna viene mai scaricata. Il gioco gira benissimo per due minuti, poi
rallenta, poi la macchina resta senza memoria e il gioco muore. Peggio ancora,
muore durante la demo e non mentre lo stavi provando.
La regola: *carica una volta, disegna molte volte*. Caricare è lento e occupa
memoria. Disegnare è veloce e non ne occupa. Quindi `spriteLoad` va in cima al
tuo file, fuori da ogni funzione, dove viene eseguito una volta sola all'avvio
dello script.
Non riguarda solo gli sprite. Ogni `somethingLoad` del motore funziona così --
i suoni nella decima lezione, i font nella diciannovesima, i modelli nella
ventitreesima. Se ti ritrovi a scrivere `Load` dentro una funzione che gira a
ogni fotogramma, fermati e spostalo fuori.
[#_how_big_is_it]
=== Quanto è grande?
Sai che l'astronave è 32 per 24 perché te l'ha detto questo libro. Il tuo
programma non lo sa, e non dovrebbe averne bisogno. Chiedi:
[source,lua]
----
local shipWidth = spriteGetWidth(ship)
local shipHeight = spriteGetHeight(ship)
----
Ora puoi tenere l'astronave sullo schermo, perché la posizione più a destra in
cui può stare è `screenWidth - shipWidth`, e metterla al centro, perché il
centro è `(screenWidth - shipWidth) / 2`. Nessuna delle due righe contiene un
numero che dovrebbe cambiare se qualcuno ridisegnasse l'astronave un po' più
grande.
Entrambe riportano la dimensione *con cui lo sprite verrebbe disegnato in questo
momento*. Se mai scali o ruoti uno sprite, con `spriteScale` o `spriteRotate`,
la risposta cambia di conseguenza. Di solito è quello che vuoi, e ti sorprenderà
esattamente una volta.
[#_drawing_order]
=== L'ordine di disegno
Gli sprite non si fondono. Il successivo copre il precedente, pixel per pixel,
esattamente come attaccare ritagli di carta su un muro. Quindi l'ordine delle
tue chiamate `spriteDraw` è l'ordine dal fondo verso il davanti:
[source,lua]
----
drawStars()
drawRocks()
drawShots()
spriteDraw(shipSprite, shipX, shipY)
drawHud()
----
Le stelle sono le più lontane, quindi vanno giù per prime. L'astronave sta
davanti a tutto nell'area di gioco. Il punteggio sta sopra tutto quanto.
Inverti l'ordine e la tua astronave disegnata con tanta cura passa la partita a
nascondersi dietro il campo stellare, e non c'è nessun errore a dirtelo -- solo
un'astronave che non vedi.
`overlayClear` viene ancora per primo, prima di tutto questo. Disegnare non
sostituisce mai, copre soltanto, quindi il fotogramma comincia vuoto ogni volta.
[#_frames]
=== Fotogrammi
Apri `art/walk.png` con un qualsiasi visualizzatore di immagini. Non è
l'immagine di una persona. È l'immagine di quattro persone, una accanto
all'altra: la stessa figura in quattro momenti di una camminata, ognuna larga 24
pixel, in una striscia larga 96.
Un'immagine che contiene diversi fotogrammi di un'animazione è uno *sprite
sheet*, ed è così che viene conservata quasi tutta l'animazione 2D. Un file, un
caricamento, un handle, e il gioco sceglie quale fetta disegnare.
Crea un secondo file, `walk.singe`, accanto al primo:
[source,lua]
----
dofile("Singe/Framework.singe")
local walk = spriteLoadFrames(4, DIR .. "art/walk.png")
local walkFrame = 1
local walkTick = 0
local walkX = 0
function onOverlayUpdate()
overlayClear()
walkTick = walkTick + 1
if walkTick >= 8 then
walkTick = 0
walkFrame = walkFrame + 1
if walkFrame > 4 then
walkFrame = 1
end
end
walkX = walkX + 1
if walkX > overlayGetWidth() then
walkX = -spriteFrameWidth(walk)
end
spriteDrawFrame(walk, walkX, 100, walkFrame)
return OVERLAY_UPDATED
end
function onShutdown()
spriteUnload(walk)
end
----
Avvialo con `Singe rocks/walk.singe`. Una piccola figura attraversa lo schermo camminando
con passo regolare, sparisce oltre il bordo destro e rientra da sinistra.
`spriteLoadFrames` è `spriteLoad` con una cosa in più da dire: quanti fotogrammi
ci sono nella striscia. *Il conteggio viene per primo*, prima del nome del file,
che è il contrario di quello che pensano quasi tutti. Divide la larghezza
dell'immagine per il conteggio e si ricorda le fette. Quattro fotogrammi in una
striscia di 96 pixel vuol dire quattro fotogrammi larghi 24 pixel. Sbaglia il
conteggio -- diciamo 3 -- e ottieni tre fette da 32 pixel, ognuna con dei pezzi
di due figure, che è un modo memorabilmente buffo di scoprire che hai scritto il
numero sbagliato.
`spriteDrawFrame` disegna una fetta. Prende l'handle, dove metterla e quale
fotogramma, *in quest'ordine*, e i fotogrammi sono numerati *a partire da 1*: il
fotogramma 1 è la figura più a sinistra e il fotogramma 4 quella più a destra.
Attento a quell'1. Altrove nella famiglia degli sprite -- `spriteSetFrame` e
`spriteGetFrame` -- i fotogrammi sono numerati da 0. È un incidente della
storia, sono tutte e due nel Riferimento, e questo libro usa `spriteDrawFrame` e
conta da 1.
`spriteFrameWidth` riporta la larghezza di un fotogramma, qui 24, invece dei 96
dell'intera striscia. `spriteFrameHeight` è il suo compagno. Quando vuoi sapere
quanto è larga una fetta di uno sprite sheet, sono queste le due a cui chiedere,
e non `spriteGetWidth`.
Il numero del fotogramma è un numero in una variabile come qualsiasi altro,
quindi la camminata è aritmetica:
[source,lua]
----
walkTick = walkTick + 1
if walkTick >= 8 then
walkTick = 0
walkFrame = walkFrame + 1
if walkFrame > 4 then
walkFrame = 1
end
end
----
Conta i fotogrammi che passano. Ogni otto, passa all'immagine successiva, e dopo
la quarta immagine torna alla prima. Sessanta fotogrammi al secondo diviso otto
fa tra i sette e gli otto passi al secondo, che per un ciclo di quattro
fotogrammi è una camminata svelta ma credibile. Cambia l'8 e cambi la velocità:
più grande è, più è lenta.
Ti chiederai perché stai contando, quando il motore ha `spritePlay` per far
girare un'animazione al posto tuo. Ce l'ha, e per una GIF animata è la risposta
giusta, perché una GIF porta dentro il file la tempistica di ciascuno dei suoi
fotogrammi. Una semplice striscia come `walk.png` non porta nessuna tempistica,
quindi il motore la fa girare alla massima velocità consentita -- cento
fotogrammi al secondo, venticinque cicli di camminata completi ogni secondo, una
macchia indistinta. Quando i fotogrammi vengono da una striscia, conta tu.
[#_the_game_gets_its_artwork]
=== Il gioco riceve la sua grafica
Ora metti tutto insieme. Il gioco della settima lezione guidava un riquadro
lungo il fondo dello schermo e gli faceva cadere addosso altri riquadri. Ogni
riquadro diventa un'immagine, e già che ci sei l'astronave riceve qualcosa con
cui sparare.
Le modifiche sono tutte collegate fra loro, quindi ecco lo script intero. È più
lungo di qualsiasi cosa tu abbia scritto, e non c'è niente dentro che tu non
abbia già incontrato.
[source,lua]
----
dofile("Singe/Framework.singe")
local SHIP_SPEED = 3
local SHOT_SPEED = 6
local ROCK_COUNT = 6
local STAR_COUNT = 40
local START_LIVES = 3
local HIT_SCORE = 10
local shipSprite = spriteLoad(DIR .. "art/spaceship.png")
local rockSprite = spriteLoad(DIR .. "art/asteroid.png")
local shotSprite = spriteLoad(DIR .. "art/bullet.png")
local starSprite = spriteLoad(DIR .. "art/star.png")
local screenWidth = overlayGetWidth()
local screenHeight = overlayGetHeight()
local shipWidth = spriteGetWidth(shipSprite)
local shipHeight = spriteGetHeight(shipSprite)
local rockWidth = spriteGetWidth(rockSprite)
local rockHeight = spriteGetHeight(rockSprite)
local shotWidth = spriteGetWidth(shotSprite)
local shotHeight = spriteGetHeight(shotSprite)
local shipX = (screenWidth - shipWidth) / 2
local shipY = screenHeight - shipHeight - 4
local goLeft = false
local goRight = false
local rocks = {}
local shots = {}
local stars = {}
local score = 0
local lives = START_LIVES
local over = false
function drawHud()
overlayPrint(1, 1, "SCORE " .. score)
overlayPrint(50, 1, "LIVES " .. lives)
if over then
overlayPrint(25, 8, "GAME OVER")
overlayPrint(17, 10, "PRESS 1 TO PLAY AGAIN")
end
end
function drawRocks()
for _, rock in ipairs(rocks) do
spriteDraw(rockSprite, rock.x, rock.y)
end
end
function drawShots()
for _, shot in ipairs(shots) do
spriteDraw(shotSprite, shot.x, shot.y)
end
end
function drawStars()
for _, star in ipairs(stars) do
spriteDraw(starSprite, star.x, star.y)
end
end
function newRock(rock)
rock.x = math.random(0, screenWidth - rockWidth)
rock.y = -rockHeight - math.random(0, 160)
rock.speed = math.random(8, 20) / 10
end
function onInputPressed(what)
if over then
if what == SWITCH_START1 then
startGame()
end
return
end
if what == SWITCH_LEFT then
goLeft = true
elseif what == SWITCH_RIGHT then
goRight = true
elseif what == SWITCH_BUTTON1 then
shots[#shots + 1] = { x = shipX + shipWidth / 2 - shotWidth / 2, y = shipY }
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
goLeft = false
elseif what == SWITCH_RIGHT then
goRight = false
end
end
function onOverlayUpdate()
overlayClear()
updateStars()
if not over then
updateShip()
updateShots()
updateRocks()
end
drawStars()
drawRocks()
drawShots()
spriteDraw(shipSprite, shipX, shipY)
drawHud()
return OVERLAY_UPDATED
end
function onShutdown()
spriteUnload(shipSprite)
spriteUnload(rockSprite)
spriteUnload(shotSprite)
spriteUnload(starSprite)
end
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
function startGame()
shipX = (screenWidth - shipWidth) / 2
score = 0
lives = START_LIVES
over = false
shots = {}
for i = 1, ROCK_COUNT do
rocks[i] = {}
newRock(rocks[i])
end
end
function updateRocks()
for _, rock in ipairs(rocks) do
rock.y = rock.y + rock.speed
if rock.y > screenHeight then
newRock(rock)
end
if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then
newRock(rock)
lives = lives - 1
if lives <= 0 then
over = true
end
end
end
end
function updateShip()
if goLeft then
shipX = shipX - SHIP_SPEED
end
if goRight then
shipX = shipX + SHIP_SPEED
end
if shipX < 0 then
shipX = 0
end
if shipX > screenWidth - shipWidth then
shipX = screenWidth - shipWidth
end
end
function updateShots()
for i = #shots, 1, -1 do
local shot = shots[i]
local gone = false
shot.y = shot.y - SHOT_SPEED
if shot.y + shotHeight < 0 then
gone = true
end
for _, rock in ipairs(rocks) do
if not gone and overlapping(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then
newRock(rock)
score = score + HIT_SCORE
gone = true
end
end
if gone then
table.remove(shots, i)
end
end
end
function updateStars()
for _, star in ipairs(stars) do
star.y = star.y + star.speed
if star.y > screenHeight then
star.y = 0
star.x = math.random(0, screenWidth - 1)
end
end
end
for i = 1, STAR_COUNT do
stars[i] = { x = math.random(0, screenWidth - 1), y = math.random(0, screenHeight - 1), speed = math.random(1, 3) / 4 }
end
startGame()
----
Le frecce sinistra e destra guidano. La barra spaziatrice spara. Quando le rocce
ti hanno tolto l'ultima vita, `1` comincia una nuova partita.
Alcune cose lì dentro meritano di essere indicate.
Le quattro chiamate `spriteLoad` e le otto misure stanno in cima, fuori da ogni
funzione, quindi avvengono una volta sola. Tutto ciò che viene dopo è scritto in
termini di `screenWidth`, `shipWidth` e compagnia, e in tutto il codice di gioco
non c'è un solo numero di pixel scritto a mano.
Le funzioni sono in ordine alfabetico, un'abitudine che vale la pena prendere
adesso. A Lua non importa minimamente -- è per te, fra sei mesi, quando
cercherai `updateShots` in un file con dentro quaranta funzioni.
Le ultime due cose nel file non sono affatto funzioni. Sono semplici istruzioni
in fondo allo script: costruisci il campo stellare, poi chiama `startGame` per
preparare le rocce e azzerare il punteggio. Vengono eseguite una volta, in
ordine, mentre lo script si carica, e devono venire dopo le funzioni che usano.
`overlapping` sono i quattro confronti che, come diceva la settima lezione,
potevi scrivere da te invece di chiamare `collideRects`, messi per esteso in una
funzione con gli stessi otto argomenti. Due rettangoli non si toccano se uno dei due sta completamente a sinistra, a destra, sopra o sotto
l'altro; se nessuna di queste quattro cose è vera, si sovrappongono.
L'undicesima lezione la sostituisce con qualcosa che fornisce il motore, e
spiega perché il rettangolo è la forma sbagliata per un'astronave.
Un'ultima nota per quando leggerai il codice Singe di altre persone. Ogni
chiamata agli sprite in questo libro prende l'handle *per primo*. Singe 2.10 lo
metteva per ultimo, e un gioco può chiedere al motore di continuare a fare così,
quindi il codice che trovi online potrebbe essere `spriteDraw(x, y, ship)`. Non
copiarlo. L'handle per primo è l'ordine attuale, e quello documentato dal
Riferimento.
[#_try_it_9]
=== Prova tu
. *Centra l'astronave sulla sua posizione.* Aggiungi `true` come quarto
argomento allo `spriteDraw` dell'astronave. Guarda dove salta l'astronave, e
dalla distanza di cui si è spostata deduci che cosa ha fatto davvero il quarto
argomento.
. *Più rocce.* Cambia `ROCK_COUNT` in 20. Poi in 200. Da qualche parte tra quei
due valori il gioco smette di essere giocabile, e da qualche parte molto più in
là il gioco comincia a rallentare. Trovali tutti e due.
. *Fai crescere l'astronave.* Aggiungi `spriteScale(shipSprite, 2)` proprio in
fondo al file, sulla riga sopra `startGame()`. Avvialo e guida contro il bordo
destro: l'astronave è grande il doppio ma si ferma ancora dove si fermava
quella piccola, e sporge oltre il bordo destro e oltre il fondo. Scopri
perché, poi sposta quell'unica riga sopra le chiamate `spriteGetWidth` e
guarda le cose sistemarsi. La voce di `spriteScale` nel Riferimento spiega che cosa
fa la scalatura alle misure.
. *Metti dentro chi cammina.* Carica `walk.png` nel gioco, e disegna la figura
che cammina lungo la parte alta dello schermo come scenografia. Ti servirà il
conteggio dei fotogrammi dello script della camminata, e dovrai decidere in
che punto di `onOverlayUpdate` va il disegno -- davanti alle stelle, dietro le
rocce.
. *Scarica qualcosa che stai ancora usando.* Aggiungi `spriteUnload(starSprite)`
in fondo al file, dopo `startGame()`, ed eseguilo. Leggi che cosa ottieni.
[#_break_it_on_purpose_8]
=== Rompilo apposta
Le maiuscole contano, ed è con la grafica che mordono più forte. Cambia il
caricamento dell'astronave perché usi una S maiuscola:
[source,lua]
----
local shipSprite = spriteLoad(DIR .. "art/Spaceship.png")
----
Il gioco si rifiuta di partire:
----
10:spriteLoad: Couldn't open rocks/art/Spaceship.png: No such file or directory
----
Leggilo come l'errore della prima lezione, ma nota che la forma è un po'
diversa. `10` è la riga. `spriteLoad` è la funzione del motore che si è arresa,
invece del nome di un file -- quando la lamentela viene da Singe invece che da
Lua, ottieni questo. Poi il motivo, che qui è formulato dal tuo sistema
operativo e cambierà un po' da macchina a macchina.
`No such file or directory` è il computer che è preciso, non scortese. Non
esiste davvero nessun file chiamato `Spaceship.png`; ce n'è uno chiamato
`spaceship.png`, e per una macchina Linux sono due nomi diversi. Su Windows, e
sulla maggior parte dei Mac, avrebbe funzionato, il che è peggio, perché vuol dire che il bug viaggia
fino alla macchina di qualcun altro e compare lì per la prima volta.
L'altra metà di questo errore che incontrerai è la cartella. Se hai messo il kit
di grafica nel posto sbagliato, o hai avviato Singe dalla cartella sbagliata, il
messaggio è esattamente lo stesso e la correzione no. Il percorso nel messaggio
aiuta: è il nome completo che Singe ha provato, contato dalla cartella da cui
l'hai avviato, e il `rocks/` davanti è la parte che ha aggiunto `DIR`. Controlla
tre cose, in quest'ordine: se il file si chiama esattamente come l'hai scritto, se sta nella
cartella `art` e se quella cartella `art` sta accanto al tuo script.
[#_what_you_learned_9]
=== Cosa hai imparato
* Un'immagine disegnata sull'overlay è uno sprite, e si carica con
`spriteLoad`.
* `spriteLoad` restituisce un handle: un numero che per il motore vuol dire
«quell'immagine», e che tieni in una variabile.
* `DIR` è la cartella in cui si trova il tuo script, e ogni file che il tuo
gioco carica andrebbe indicato a partire da lì.
* `spriteDraw(handle, x, y)` lo disegna, dal suo angolo in alto a sinistra, e
solo dentro `onOverlayUpdate`. Un quarto argomento `true` indica invece il
centro.
* Carica una volta, in cima al file. Caricare dentro `onOverlayUpdate` si mangia
la memoria finché il gioco muore.
* `spriteUnload` restituisce un'immagine, di solito da `onShutdown`, e dopo
l'handle è morto.
* Il PNG porta con sé la trasparenza, ed è per questo che gli angoli
dell'astronave non sono riquadri neri.
* `spriteGetWidth` e `spriteGetHeight` misurano uno sprite come verrebbe
disegnato ora, così non devi mai scrivere la sua dimensione nel programma.
* Gli sprite si coprono a vicenda. L'ordine in cui disegni va dal fondo verso il
davanti.
* `spriteLoadFrames(count, file)` taglia una striscia in fotogrammi, e
`spriteDrawFrame(handle, x, y, frame)` ne disegna uno, contando da 1.
[#_next_time_9]
=== La prossima volta
Il gioco ha l'aspetto giusto e non ha nessun suono. Nella decima lezione i colpi
ricevono un rumore, le rocce un'esplosione, e sotto a tutto suona qualcosa -- e
incontri la tua quinta callback, quella che il motore usa per dirti che un suono
è finito.