singe/docs/it/lessons/30-shipping.adoc

501 lines
21 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#_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 <your name here>",
"",
"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.