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