[#_lesson_30_shipping_it] == Lezione 30: Distribuirlo image::learn/30-shipping.png[La lezione finita, 480] Hai una cartella. Dentro ci sono uno script, qualche immagine, un paio di suoni e il `games.dat` che hai scritto nella tredicesima lezione. Funziona, perché tutto quello che le serve è dove l'hai lasciato e tu sai dov'è. Questa lezione trasforma quella cartella in un unico file che qualcuno che non ti ha mai conosciuto può copiare sulla sua macchina e giocare. Quasi tutto si riduce a due righe di comando. Il resto è la parte che nessuno insegna: che cosa va nel file, il lavoro di chi c'è dentro oltre al tuo, e come scoprire se funziona anche da qualche altra parte oltre che qui. [#_one_file] === Un solo file Un gioco Singe finito si distribuisce come un unico file `.game`. Per impacchettarne uno: ---- Singe --pack MyGame MyGame.game ---- Il primo nome è la cartella del tuo gioco, il secondo è il file da scrivere. Singe legge ogni file della cartella dentro un database e dice che cosa ha fatto: ---- >>> Packed 42 files (3819204 bytes) from MyGame into MyGame.game ---- Lo avvii come avvii qualsiasi altra cosa: ---- Singe MyGame.game ---- Così parte la prima voce del `games.dat` che contiene; `--entry=2` avvia la seconda, per un file che contiene più di un gioco. Per installarlo, il giocatore copia `MyGame.game` accanto al programma Singe. Il menu incluso lo trova e lo elenca. Non c'è altro: niente programma di installazione, niente archivio da estrarre, niente cartella da mettere nel posto giusto. [#_what_packing_does_to_every_file_name] === Che cosa fa l'impacchettamento a ogni nome di file Per essere impacchettato, nel tuo gioco non deve cambiare niente, e vale la pena sapere perché, perché è la cosa che ha più probabilità di sorprenderti. Ogni nome che il tuo gioco usa -- `dofile`, `require`, `io.open`, `spriteLoad`, `soundLoad`, `fontLoad`, `videoLoad`, un framefile, un `controls.cfg` -- passa per un'unica ricerca. Per una cartella sciolta, quella ricerca è il normale file system. Per un gioco impacchettato, il nome viene cercato in tre posti e vince il primo che lo trova: . una cartella sciolta con il nome del file senza estensione -- `MyGame/` accanto a `MyGame.game`; . la cartella dei dati del gioco, sotto `files/`, che è dove è finito tutto ciò che il gioco scrive; . il database stesso. Dentro il database, i nomi vengono confrontati senza badare alle maiuscole, con l'uno o l'altro tipo di barra, e a partire dalla radice del gioco. Un nome di cartella iniziale uguale a quello del gioco viene ignorato, quindi `MyGame/art/spaceship.png`, `art/spaceship.png` e `DIR .. "art/spaceship.png"` trovano tutti lo stesso file. Ne derivano due regole, e se hai seguito il libro le rispetti già tutte e due. Usa `DIR` per tutto quello che carichi, così un nome non dipende mai da dove è stato avviato Singe. E scrivi solo sotto `singeGetDataPath()`, che è dove `saveSet` mette già le cose -- un gioco impacchettato viene aperto in sola lettura e non ci si scrive mai, quindi un gioco che salva accanto a sé funziona da sciolto e fallisce da impacchettato. Due nomi vanno altrove di proposito. Tutto ciò che comincia con `Singe/` resta sul file system vero, così `dofile("Singe/Framework.singe")` continua a funzionare, e lo stesso vale per la cartella dei dati. E un nome con dentro `..` che cerca di arrampicarsi fuori dal gioco non porta da nessuna parte: `io.open` restituisce `nil` e un messaggio che dice che il nome esce dal gioco, e `spriteLoad` e i suoi parenti segnalano il file come mancante. [#_the_loose_folder_still_wins] === La cartella sciolta vince ancora Il primo posto dell'elenco è quello che userai tutti i giorni. Una cartella con il nome del file impacchettato, che gli sta accanto, ha la meglio sulle copie impacchettate, file per file. ---- MyGame.game il gioco impacchettato MyGame/ una cartella con dentro un file modificato ---- Mettici dentro un solo `art/spaceship.png` corretto e il gioco impacchettato parte con la tua nuova immagine e con tutto il resto preso dal database. Per provare una modifica non bisogna reimpacchettare niente, e puoi tenere lì tutta la tua cartella di lavoro mentre finisci il gioco. È anche il modo in cui un giocatore ci fa una mod, e tu puoi considerarla o no una funzionalità. Ne seguono due cose. Prova il gioco almeno una volta con quella cartella rinominata, o distribuirai un file che funziona solo sulla macchina che ha la cartella. E sappi che impacchettare non nasconde niente: `--unpack` è un'opzione documentata e chiunque può usarla. Impacchetta il gioco perché diventa un solo file, non perché diventa un file chiuso a chiave. [#_patching_a_released_game] === Applicare una patch a un gioco pubblicato Qualcuno ha il tuo gioco, c'è un bug in uno script, e non vuoi mandargli di nuovo quaranta megabyte. ---- Singe --patch MyGame.game fixes ---- `fixes` è una cartella organizzata come il gioco -- gli stessi nomi negli stessi posti -- che contiene solo i file cambiati. I file che la patch non nomina restano come sono. L'intera modifica è un'unica transazione, quindi una patch interrotta lascia il gioco esattamente com'era invece che sostituito a metà. Una patch può viaggiare come file a sé, creato con l'impacchettatore: ---- Singe --pack fixes MyGame-1.0.2.patch Singe --patch MyGame.game MyGame-1.0.2.patch ---- Un `.patch` non ha bisogno di un `games.dat`, non si può avviare, e il menu lo ignora. Può anche portare una tabella `removed(path)` che nomina i file da cancellare, per la grafica che hai sostituito con qualcosa che ha un nome diverso. In un caso o nell'altro, dopo Singe controlla il gioco aggiornato nello stesso modo in cui controlla un gioco nuovo, quindi una patch non può lasciare il tuo `games.dat` a puntare a un file che non c'è più. E per tirare fuori di nuovo tutto: ---- Singe --unpack MyGame.game MyGameUnpacked ---- [#_what_the_packer_refuses] === Che cosa rifiuta l'impacchettatore L'impacchettatore non costruisce un gioco con qualunque cosa, e ogni rifiuto riguarda qualcosa che più avanti si romperebbe. Rifiuta una cartella senza un `games.dat` alla radice. Rifiuta un `Framework.singe`, un `controls.dat`, qualunque cosa finisca in `exe`, `sh`, `bat` o `cmd`, e un file senza estensione il cui nome comincia con `singe` -- in altre parole, una copia del motore o dei suoi file di supporto finita per sbaglio nella cartella del gioco. Rifiuta due file i cui nomi differiscono solo per le maiuscole, perché la ricerca non sa distinguerli. Rifiuta una voce al primo livello con lo stesso nome della cartella. E controlla il tuo `games.dat` prima di confermare: ogni voce ha bisogno di uno `SCRIPT`, e ogni `SCRIPT`, `VIDEO`, `CABINET`, `MARQUEE` e `ATTRACT` che nomina deve essere un file che c'è davvero. Un percorso sbagliato viene segnalato con il titolo della voce, e non viene scritto niente. Ogni lamentela nomina il file, e ricevi l'elenco di tutte invece che solo della prima, così un solo tentativo ti dice tutto quello che c'è da correggere. [#_what_to_include_and_what_to_leave_out] === Che cosa includere e che cosa lasciare fuori Guarda nella cartella del gioco. Una parte di quello che c'è dentro è il gioco, e un'altra parte è il modo in cui il gioco è stato fatto. Da distribuire: lo script e tutto quello che carica, il `games.dat`, la grafica che mostra il menu (`CABINET`, `MARQUEE` e la clip `ATTRACT`), i tuoi file dei crediti e delle licenze, e un leggimi se ne hai uno. Da lasciare fuori: i disegni da cui hai esportato la grafica, le registrazioni dei suoni che non hai usato, i tuoi appunti, gli script di prova, gli screenshot, qualunque cosa si chiami `old` o `backup`, la cartella `data` con dentro i tuoi salvataggi e i tuoi punteggi record, e qualunque cosa non vorresti far leggere a uno sconosciuto. Si impacchetta tutto benissimo, ed è proprio questo il problema: esce insieme al gioco, e chiunque ci arriva con un `--unpack`. Il modo affidabile per scoprire che cosa hai distribuito davvero è estrarlo in una cartella vuota e guardare. Fallo una volta per ogni versione che pubblichi. Ci vuole un minuto, ed è l'unico controllo che vede quello che hai dimenticato invece di quello che ricordi. [#_credits_and_licenses] === Crediti e licenze Se una qualunque immagine, suono, musica o font del tuo gioco l'ha fatta qualcun altro, stai distribuendo il suo lavoro, e le condizioni a cui te l'ha dato sono un obbligo, non una cortesia. Non è una formalità e non è una questione di avvocati: una persona ha fatto quella cosa e in cambio ha chiesto una cosa precisa, di solito il suo nome. Tieni un registro mentre lavori, non alla fine. Un file di testo semplice nella cartella del gioco con una riga per ogni elemento: che file è, da dove viene, chi l'ha fatto, quale licenza e che cosa richiede quella licenza. Scriverlo man mano richiede pochi secondi. Ricostruirlo la sera prima dell'uscita, dalla cronologia del browser, è il modo in cui i crediti vengono dimenticati. Alcune cose che vale la pena sapere prima che ti servano. «Scaricabile gratuitamente» non è una licenza: cerca le condizioni vere. Creative Commons `BY` vuol dire che devi dare il credito indicato, e non c'è nessuna eccezione per i giochi. `CC0` e il pubblico dominio non chiedono niente, e citare l'autore comunque ti costa una riga. I font hanno le loro licenze, e alcune ne permettono l'incorporazione mentre altre no. La musica presa da un video non è quasi mai tua da usare. Tutto ciò che è generato da uno strumento è pulito solo quanto le condizioni di quello strumento. Poi metti i crediti in un posto dove un giocatore possa raggiungerli, dentro il gioco. Non solo in un file di testo lì accanto -- un gioco impacchettato è un solo file e il file di testo è là dentro, dove nessuno guarderà. Lo script alla fine di questa lezione è quella schermata. Il `Singe/LICENSES` di Singe stesso è il modello da copiare: una riga per ogni libreria, con la sua versione, la sua licenza e da dove viene. L'elenco del tuo gioco sarà più corto, e il lavoro è lo stesso. [#_a_version_number_that_means_something] === Un numero di versione che significa qualcosa Il tuo gioco avrà una seconda versione. La prima resterà in giro finché durerà internet, e qualcuno ti manderà una segnalazione di bug su di essa senza avere idea di quale versione abbia. Quindi scegli un numero, mettilo dove un giocatore possa leggerlo, e non riusarne mai uno. La forma comune è in tre parti -- `1.0.2` -- e che cosa significano le parti conta meno del fatto che salgano sempre e soltanto. Alza l'ultima per una correzione, quella in mezzo per qualcosa di nuovo, la prima quando hai fatto un gioco diverso. Qualunque cosa tu scelga, scrivila nel tuo leggimi, così la rispetterà anche il te del futuro. `games.dat` non ha una chiave per la versione, quindi il numero te lo devi portare dietro tu. Mettilo nello script come costante, mostralo nella schermata dei crediti, e mettilo nel nome di quello che spedisci (`MyGame-1.0.2.game`, `MyGame-1.0.2.patch`). Così la segnalazione del bug riporta la versione sullo schermo, e tu sai di quali file si parla. [#_testing_on_a_machine_that_is_not_yours] === Provarlo su una macchina che non è la tua La tua macchina è il posto peggiore in assoluto per provare una versione da pubblicare. Ha i font che hai installato, la cartella che stavi modificando, il file di salvataggio con dentro le tue impostazioni, e il motore nello stato in cui l'ha lasciato un mese di lavoro. La prova minima, in ordine: . Estrai la versione in una cartella vuota e leggi l'elenco dei file. . Copia *solo* il file `.game` in un'altra cartella, con una copia nuova di Singe e nient'altro, e avvialo dal menu come farà un giocatore. . Cancella la cartella `data` di quella cartella e avvialo di nuovo. È un primo avvio su una macchina nuova: niente impostazioni salvate, niente punteggi record, nessun account connesso. Un gioco che funziona solo al secondo avvio è un bug comune, ed è così che lo trovi. . Dallo a una persona che non l'ha mai visto, e guarda senza aiutare. Poi prova la macchina oltre al gioco. Avrà uno schermo con proporzioni diverse, quindi guarda il gioco in finestra e a schermo intero. Potrebbe non avere nessun dispositivo 3D, ed è per questo che il menu ha un secondo renderer. La pagina *System Information* degli strumenti di servizio elenca su una sola schermata la versione del motore, il sistema operativo, il processore, il renderer, i decoder e la cartella dei dati; una foto di quella pagina risponde a quasi tutta una segnalazione di bug, e dirlo ai tuoi giocatori nel leggimi ti risparmierà una settimana di congetture. [#_the_script_2] === Lo script Una schermata dei crediti e della versione. È una cosa piccola da scrivere, ed è la parte della distribuzione che la maggior parte dei giochi sbaglia. [source,lua] ---- dofile("Singe/Framework.singe") local GAME_NAME = "Rock Dodger" local GAME_VERSION = "1.0.2" local CREDITS = { "Written and drawn by ", "", "Music \"Night Drive\" by A. Composer", " Creative Commons BY 4.0", " example.org/night-drive", "", "Explosion freesound.org user \"thud\"", " Creative Commons CC0", "", "Font FreeSansBold", " GPL with the font exception", "", "Engine Singe " .. SINGE_VERSION_STRING, " GPL version 3", " See Singe/LICENSES for the", " libraries Singe is built on.", "", "Thank you for playing.", } local FIRST_ROW = 4 local ROWS = 10 local scroll = 0 local function scrollBy(lines) local most = #CREDITS - ROWS if most < 0 then most = 0 end scroll = scroll + lines if scroll < 0 then scroll = 0 end if scroll > most then scroll = most end end function onInputPressed(what) if what == SWITCH_UP then scrollBy(-1) elseif what == SWITCH_DOWN then scrollBy(1) elseif what == SWITCH_BUTTON1 then scroll = 0 end end function onOverlayUpdate() local line = nil overlayClear() overlayPrint(2, 0, GAME_NAME .. " version " .. GAME_VERSION) overlayPrint(2, 1, "Singe " .. SINGE_VERSION_STRING .. " id " .. (singeGetGameId() or "none")) for row = 0, ROWS - 1 do line = CREDITS[scroll + row + 1] if line then overlayPrint(2, FIRST_ROW + row, line) end end overlayPrint(2, FIRST_ROW + ROWS + 1, "Saves go to:") overlayPrint(2, FIRST_ROW + ROWS + 2, singeGetDataPath()) overlayPrint(2, 17, "Up and down scroll. Button 1 goes back to the top.") return OVERLAY_UPDATED end ---- Sostituisci ogni riga di `CREDITS` con la verità sul tuo gioco. I segnaposto sono lì per mostrare la forma: che cos'è, chi l'ha fatto, quale licenza e da dove viene. [#_what_just_happened_25] === Cos'è appena successo [source,lua] ---- local GAME_VERSION = "1.0.2" ---- Un solo posto, in cima, in maiuscolo perché non cambia mai mentre il gioco gira. Tutto ciò che mostra la versione legge questo, quindi alzarla è una modifica sola invece di una ricerca. [source,lua] ---- "Music \"Night Drive\" by A. Composer", ---- Quelle barre rovesciate sono la sequenza di escape che hai incontrato nella prima lezione: una virgoletta doppia che fa parte del testo invece di chiudere la stringa. I titoli contengono virgolette, ed è per questo che un elenco di crediti ci si imbatte già alla prima riga. [source,lua] ---- for row = 0, ROWS - 1 do line = CREDITS[scroll + row + 1] if line then ---- Dieci righe di schermo che mostrano dieci righe di un elenco più lungo di dieci. `scroll` dice quanto è scesa la finestra lungo l'elenco. Il `+ 1` c'è perché Lua conta un elenco a partire da uno e le righe dello schermo contano da zero, e l’`if line then` c'è perché la fine dell'elenco è `nil` -- leggere oltre la fine di una tabella in Lua non è un errore, ti dà semplicemente niente, e stampare niente lo sarebbe. [source,lua] ---- overlayPrint(2, 1, "Singe " .. SINGE_VERSION_STRING .. " id " .. (singeGetGameId() or "none")) ---- Tre fatti di cui una segnalazione di bug ha bisogno e che un giocatore non può trovare altrimenti: la tua versione sulla riga sopra, la versione del motore e l'id del gioco. `SINGE_VERSION_STRING` è una variabile globale che il motore imposta prima che il tuo script parta. `or "none"` copre il caso in cui il gioco non ha una voce in `games.dat`, così la riga viene stampata invece di andare in crash su un `nil`. [source,lua] ---- overlayPrint(2, FIRST_ROW + ROWS + 2, singeGetDataPath()) ---- Dove scrive questo gioco. In un gioco impacchettato è l'unico posto in cui viene scritto qualcosa, ed è la cartella che un giocatore cancella per ripartire da zero. Mostrarla trasforma «dove sono i miei salvataggi» in una frase a cui non devi mai rispondere. Se quel percorso esce dal bordo destro dello schermo, non è rotto: `overlayPrint` scarta i caratteri che andrebbero oltre il bordo. L'overlay predefinito è largo sessanta celle di carattere. [#_try_it_30] === Prova tu . *Impacchettalo.* Metti questo script nella cartella del tuo gioco, esegui `--pack` e avvia il file `.game`. Poi estrailo in una cartella vuota e leggi l'elenco di quello che è uscito. . *Sostituisci un file.* Accanto al file impacchettato, crea una cartella con il suo nome senza l'estensione, e mettici dentro un file modificato. Avvia il gioco impacchettato e guarda il tuo file sciolto vincere. . *Crea una patch.* Cambia la versione in `1.0.3`, metti solo lo script in una cartella `fixes`, impacchettala come `.patch` e applicala al gioco che hai già creato. Controlla la versione sullo schermo. . *Rompi l'impacchettatore.* Rinomina per un momento il tuo `games.dat` e prova a impacchettare. Poi rimettilo a posto, fai puntare uno dei suoi nomi `SCRIPT` a un file che non c'è, e riprova. Due rifiuti diversi, entrambi prima che venga scritto qualunque cosa. . *Scrivi i crediti veri.* Passa la cartella del gioco file per file e scrivi una riga per ognuno che non hai fatto tu. Se non riesci a ricordare da dove viene qualcosa, quella è la risposta alla domanda se dovresti distribuirlo. [#_break_it_on_purpose_29] === Rompilo apposta Avvia Singe una volta da *dentro* la cartella del gioco, così il motore ci estrae i suoi file di supporto, e poi impacchettala: ---- Singe --pack MyGame MyGame.game ---- ---- !!! MyGame has Framework.singe: Singe/Framework.singe !!! MyGame has sh file: Menu.sh ---- Non viene scritto niente. Ogni riga è un file che non accetta e il perché, e potresti riceverle nell'altro ordine o riceverne anche altre. Dentro il tuo gioco c'è una copia della cartella di supporto del motore, e lo script launcher che il motore ha scritto lì accanto, perché a un certo punto hai avviato Singe da lì dentro. Un gioco non deve mai portarsi dietro né l'una né l'altro: i file di supporto appartengono al Singe che sta usando il giocatore, qualunque sia, non al tuo gioco, e un `Framework.singe` impacchettato verrebbe caricato al posto del suo. Su Windows la seconda riga nomina invece `Menu.bat`. Cancella la cartella `Singe` e `Menu.sh` (o `Menu.bat`) da dentro il gioco, tieni la cartella del gioco come una cartella accanto al programma Singe invece che attorno a esso, e impacchetta di nuovo. È la stessa struttura descritta nella tredicesima lezione, e questo errore è quello che succede quando la si perde di vista. [#_what_you_learned_30] === Cosa hai imparato * `Singe --pack folder name.game` trasforma la cartella del gioco in un solo file, e copiare quel file accanto al programma Singe lo installa. * Ogni nome di file dentro un gioco impacchettato viene cercato prima in una cartella sciolta, poi nella cartella dei dati, poi nel database. * Una cartella sciolta con il nome del file impacchettato lo sostituisce file per file, ed è così che continui a lavorare -- e così che pubblichi una versione rotta se ti dimentichi che è lì. * Carica con `DIR` e scrivi solo sotto `singeGetDataPath()`, o il tuo gioco funzionerà da sciolto e fallirà da impacchettato. * `--patch` sostituisce dei file in un gioco pubblicato con un'unica transazione; `--unpack` riscrive fuori tutto quanto. * L'impacchettatore rifiuta i file del motore, i nomi in conflitto e un `games.dat` che punta a file che non ha. * Estrai la tua versione e leggi che cosa c'è dentro prima di mandarla da qualunque parte. * La grafica e i suoni di qualcun altro arrivano con delle condizioni, e rispettarle vuol dire avere una schermata dei crediti dentro il gioco. * Un numero di versione sale sempre e soltanto, e il suo posto è sullo schermo. * Prova su una macchina che non è la tua, partendo senza nessuna cartella `data`. [#_next_time_30] === La prossima volta Un solo file, installato, con i crediti e con il suo numero. L'ultima lezione lo mette in una cassa di legno con una fessura per i gettoni e senza tastiera, il che cambia del tuo gioco più di quanto penseresti.