singe/docs/it/lessons/21-subtitles.adoc

565 lines
23 KiB
Text

[#_lesson_21_subtitles]
== Lezione 21: Sottotitoli
image::learn/21-subtitles.png[La lezione finita, 480]
Qualcuno che non sente giocherà al tuo gioco.
Se il tuo gioco ha del parlato e niente sottotitoli, non potrà farlo. Non nel
senso di «fa più fatica» -- arriverà alla prima scena in cui un personaggio dice
la cosa che spiega al giocatore che cosa fare, e si fermerà lì. Tutto qui, ed è
il motivo per cui questa lezione esiste invece di essere un paragrafo della
diciannovesima.
L'altro motivo è pratico. Aggiungere i sottotitoli a un gioco finito è una
tortura. Devi trovare ogni frase parlata, capire in quale fotogramma si trova,
e scoprire che metà delle tue tempistiche sono sbagliate perché il video è stato
ricodificato a metà del progetto. Fatto mentre scrivi il gioco, è un
pomeriggio. Singe ti dà un sistema che fa la parte difficile, e questa lezione
spiega come usarlo.
[#_it_is_not_ordinary_text_drawing]
=== Non è il solito testo disegnato
Potresti fare i sottotitoli da solo. Tieni un elenco di frasi con il tempo di
inizio e di fine, controlla l'orologio in `onOverlayUpdate`, e disegna quella
corrente con `fontToSprite`. C'è chi l'ha fatto. Ecco perché tu non dovresti.
I sottotitoli sono sincronizzati con *il disco*, non con un orologio. Quando
`srtLoad` legge il tuo file converte ogni marca temporale in un numero di
fotogramma del disco, lì per lì, usando la frequenza dei fotogrammi del disco
caricato. Da quel momento il motore non guarda più nessun orologio; a ogni
fotogramma disegnato chiede quale battuta copre il fotogramma su cui si trova
il disco.
Quella sola decisione ti dà la cosa che un sistema fatto in casa non azzecca
mai: *un sottotitolo sopravvive a un salto*. Manda il disco dove vuoi --
`discSearch`, `discSkipForward`, un passo, una diramazione verso un altro
segmento -- e la frase giusta compare al fotogramma disegnato successivo, in
avanti o all'indietro, senza ricaricare niente e senza azzerare nessun
orologio. In un gioco a diramazioni come quello della sedicesima lezione, dove
le scelte del giocatore fanno saltare il disco di continuo, è la differenza tra
funzionare e quasi funzionare.
Si disegna anche nel posto giusto. Il documento dei sottotitoli sta sopra
l'immagine, sopra il tuo overlay, sotto qualsiasi GUI nello spazio dello
schermo, e segue l'immagine ovunque la mettano le impostazioni del giocatore --
un'immagine rimpicciolita, una spostata, un cabinato ruotato, un'apertura in un
bezel. Non riceve input dal mouse, quindi un colpo di pistola ottica gli passa
dritto attraverso. Sistemare tutto questo da solo è una settimana che non avevi
messo in conto.
[#_write_the_file]
=== Scrivi il file
I sottotitoli vanno in un file SubRip, che finisce in `.srt` ed è testo semplice
che puoi scrivere con qualsiasi editor. È uno dei formati più semplici che
esistano. Crea un file chiamato `subtitles.srt` accanto al tuo script:
----
1
00:00:01,000 --> 00:00:04,000
They told us the cave was empty.
2
00:00:04,200 --> 00:00:08,000
They were wrong about
a great many things.
3
00:00:08,500 --> 00:00:13,000
Keep your light on the floor.
----
Una battuta è fatta di quattro cose. Un numero su una riga tutta sua. Una riga
con due marche temporali e, tra le due, una freccia fatta di due trattini e un
segno di maggiore. Una o più righe di testo. Una riga vuota per finire.
Una marca temporale è `HH:MM:SS,mmm` -- ore, minuti e secondi separati da due
punti, poi una virgola, poi i millisecondi. La virgola non è un errore di
battitura, e un punto non va bene. `00:00:04,200` vuol dire quattro secondi e
un quinto dall'inizio.
I numeri delle battute vengono letti e buttati via, quindi non devono essere in
ordine e nemmeno giusti. Numerale comunque; prima o poi ti ritroverai a cercare
la battuta quarantuno.
La battuta due mostra com'è fatta una battuta su due righe. Le righe vengono
mostrate come le hai scritte, quindi sei *tu* a decidere dove va a capo una
riga lunga, il che conta più di quanto penseresti ed è spiegato più avanti.
Il caricamento è volutamente tollerante con le cose che vanno storte in un file
scritto a mano. I fine riga di Windows si caricano senza problemi, e gli spazi
all'inizio e alla fine di ogni riga vengono tolti. Una battuta le cui marche
temporali non si riescono a leggere viene saltata e il resto del file continua
a caricarsi, il che è gentile fino al momento in cui ti chiedi dov'è finita la
riga dodici. Alcune cose non sono supportate: i tag `<i>` e `<b>` che certi
strumenti scrivono vengono mostrati come testo invece di essere applicati, e le
coordinate di posizione che certi strumenti aggiungono alla riga dei tempi
vengono ignorate. La sezione sui sottotitoli del Riferimento ha l'elenco completo.
[#_load_it_and_switch_it_on]
=== Caricalo e accendilo
Tre chiamate, e l'ordine conta.
[source,lua]
----
if srtLoad(DIR .. "subtitles.srt") then
srtPosition(80)
srtEnable(true)
end
----
`srtLoad` legge il file e risponde `true` o `false`. Risponde `false`, senza
aver caricato niente, in tre casi: il file non si può leggere, non c'è niente al
suo interno che si riesca a leggere, oppure *non c'è un disco*. In tutti e tre i
casi scrive nella console un avviso con il nome del file. È una buona scelta di
progetto e dovresti sfruttarla: un file di sottotitoli mancante non vale la
morte di un gioco, ma distribuire senza accorgertene un gioco senza sottotitoli
è una cosa che vale la pena notare.
Il caso «nessun disco» è quello in cui cadrai. Le marche temporali devono
diventare numeri di fotogramma alla frequenza dei fotogrammi del disco, e
finché non c'è un disco aperto non c'è nessuna frequenza da usare. Quindi
carica i sottotitoli *dopo* il disco, il che in uno script normale vuol dire dal
corpo dello script, dove gira tutto il resto che hai scritto al livello più
esterno. Ne segue che un `.srt` preparato per un video a una certa frequenza dei
fotogrammi finisce nel posto sbagliato su un video a un'altra. Sincronizza il
tuo file con il video che distribuirai davvero.
`srtEnable(true)` accende le battute caricate. Uno script parte con i
sottotitoli spenti, sempre, quindi un gioco che li carica deve accenderli.
Spegnerli di nuovo toglie quello che si vede, e riaccenderli mostra quello che
copre il fotogramma su cui si trova il disco in quel momento -- niente ricerche,
nessuno stato da tenere.
Tenere i sottotitoli accesi è un'impostazione, non una decisione che prendi al
posto del giocatore. Mettila nella tua pagina delle opzioni della ventesima
lezione, salvala con il `saveSet` della dodicesima, e passa a `srtEnable`
quello che il giocatore ha scelto l'ultima volta.
[#_where_they_sit]
=== Dove stanno
[source,lua]
----
srtPosition(70)
----
`srtPosition` è una percentuale dell'altezza dell'immagine, contata dall'alto,
da `1` a `95`, e il valore predefinito è `80`. Ha effetto sulla battuta che si
vede in quel momento e su tutte quelle successive. Un numero fuori
dall'intervallo viene ignorato invece di essere riportato nei limiti, il che
vuol dire che un errore di battitura lascia la posizione esattamente dov'era e
non dice niente, quindi controlla i tuoi numeri.
L'ottanta per cento è in basso, ed è in basso apposta: la parte bassa
dell'immagine di solito è la meno interessante dell'inquadratura. Spostali più
su quando la parte bassa *non* è noiosa -- quando il tuo gioco disegna un
tabellone segnapunti lungo il bordo inferiore, o quando il mirino della pistola
ottica del giocatore sta laggiù.
[#_taking_one_down]
=== Toglierne uno
[source,lua]
----
srtClear()
----
`srtClear` toglie subito dallo schermo quello che c'è, e lascia stare le
battute caricate e lo stato di accensione. La frase che si vedeva resta spenta
finché il disco non raggiunge la battuta successiva. Usalo quando una scena
finisce prima del previsto e non vuoi che l'ultima frase del dialogo resti
appesa sopra l'inquadratura successiva.
C'è un'altra chiamata che condivide il meccanismo dei sottotitoli, e forse l'hai
già usata:
[source,lua]
----
overlayBanner("Extra life!", 20)
----
`overlayBanner` mostra un breve messaggio sopra l'immagine per circa trenta
fotogrammi disegnati e poi lo toglie. Viene disegnato dallo stesso documento dei
sottotitoli, quindi un banner sembra un sottotitolo, e mentre è visibile prende
il posto di qualsiasi sottotitolo, che torna quando il banner scade. Il suo
argomento per l'altezza è separato da `srtPosition` e se non lo dai vale `47`,
il centro dell'immagine. I messaggi più lunghi di sessanta caratteri vengono
ignorati del tutto, quindi uno lungo sembra proprio non funzionare.
[#_subtitles_already_inside_the_video]
=== Sottotitoli già dentro il video
Alcuni file video portano con sé le proprie tracce di sottotitoli. Se il tuo lo
fa, non ti serve affatto un `.srt`:
[source,lua]
----
if discGetSubtitleTracks() > 0 then
srtLoadTrack(0)
else
srtLoad(DIR .. "subtitles.srt")
end
srtEnable(true)
----
`srtLoadTrack` prende un numero di traccia da `0` a
`discGetSubtitleTracks() - 1`, e dopo il caricamento tutto è identico.
`discGetSubtitleLanguage(track)` ti dice con quale lingua è etichettata una
traccia, come codice di tre lettere tipo `eng` o `fra`, così un gioco può
offrire al giocatore tutto quello che il suo video si trova ad avere.
Due avvertenze. Leggere una traccia percorre tutto il file video una volta,
senza decodificare immagini, quindi un disco lungo quanto un film richiede un
momento: fallo mentre sullo schermo c'è qualcos'altro, non tra due fotogrammi di
gioco. E le tracce di sottotitoli che contengono *immagini* delle parole invece
delle parole -- il tipo che usano i DVD e i Blu-ray -- non si possono leggere.
Vengono contate, così la numerazione corrisponde a quella che mostrano gli altri
lettori multimediali, ma per loro `srtLoadTrack` risponde `false`.
[#_the_whole_script_3]
=== Lo script completo
Questo script ha bisogno di un video. Ce l'hai: `Singe/menuBackground.mkv`, che
il motore ha estratto nella tua cartella di lavoro la prima volta che hai
eseguito qualcosa. Di' a Singe di usarlo come disco con `--framefile`, che
accende anche il disco:
----
Singe --framefile=Singe/menuBackground.mkv subtitles
----
[source,lua]
----
dofile("Singe/Framework.singe")
overlaySetResolution(discGetWidth(), discGetHeight())
local HUD_X = 16
local STEP = 5
local TOP_LIMIT = 10
local LOW_LIMIT = 95
local hudFont = fontLoad("Singe/FreeSansBold.ttf", 18)
local loaded = false
local showing = false
local height = 80
if discGetSubtitleTracks() > 0 then
loaded = srtLoadTrack(0)
end
if not loaded then
loaded = srtLoad(DIR .. "subtitles.srt")
end
if loaded then
srtPosition(height)
showing = true
srtEnable(true)
end
discPlay()
function onInputPressed(what)
if not loaded then
return
end
if what == SWITCH_BUTTON1 then
showing = not showing
srtEnable(showing)
if showing then
overlayBanner("Subtitles on", 20)
else
overlayBanner("Subtitles off", 20)
end
elseif what == SWITCH_UP then
height = math.max(TOP_LIMIT, height - STEP)
srtPosition(height)
elseif what == SWITCH_DOWN then
height = math.min(LOW_LIMIT, height + STEP)
srtPosition(height)
elseif what == SWITCH_LEFT then
srtClear()
discSearch(0)
discPlay()
end
end
function onOverlayUpdate()
overlayClear()
fontSelect(hudFont)
colorForeground(255, 255, 255)
if loaded then
if showing then
fontPrint(HUD_X, 16, "Frame " .. discGetFrame() .. " subtitles ON at " .. height .. "%")
else
fontPrint(HUD_X, 16, "Frame " .. discGetFrame() .. " subtitles OFF")
end
fontPrint(HUD_X, 40, "Fire toggles them, up and down move them, left rewinds")
else
fontPrint(HUD_X, 16, "No subtitles loaded.")
fontPrint(HUD_X, 40, "Run with --framefile and check subtitles.srt is beside the script.")
end
return OVERLAY_UPDATED
end
function onShutdown()
fontUnload(hudFont)
end
----
Premi fuoco per accenderli e spegnerli, su e giù per spostare le righe, e
sinistra per riavvolgere fino all'inizio. Il riavvolgimento è quello da tenere
d'occhio: il disco salta indietro al fotogramma zero e la battuta giusta compare
proprio al fotogramma successivo, senza che tu faccia niente.
[#_what_just_happened_17]
=== Cos'è appena successo
[source,lua]
----
if discGetSubtitleTracks() > 0 then
loaded = srtLoadTrack(0)
end
if not loaded then
loaded = srtLoad(DIR .. "subtitles.srt")
end
----
Prendi i sottotitoli del video quando ce li ha, ripiega sul file accanto allo
script quando non li ha, e alla fine sai quale dei due -- se uno dei due -- ha
funzionato. Entrambe le chiamate rispondono con un booleano, quindi il ripiego
sono due istruzioni `if` e nessun caso speciale. `menuBackground.mkv` non ha
nessuna traccia di sottotitoli, quindi quello che otterrai davvero è il file che
hai scritto.
[source,lua]
----
function onInputPressed(what)
if not loaded then
return
end
----
`return` senza niente dopo esce subito dalla funzione. Senza sottotitoli
caricati nessuno di questi tasti ha qualcosa da fare, e uscire subito è più
chiaro che avvolgere tutto il corpo in un `if`. Chiamare `srtEnable` senza
niente di caricato non fa danni, ma scrivere codice che ha senso solo quando non
fa niente è un'abitudine che conviene non prendere.
[source,lua]
----
elseif what == SWITCH_LEFT then
srtClear()
discSearch(0)
discPlay()
----
La chiamata a `srtClear` non è necessaria. Il motore mostrerà comunque la
battuta giusta per il fotogramma zero al fotogramma disegnato successivo. È lì
perché tu possa cancellarla e guardare che cosa succede: la vecchia frase resta
su per una frazione di secondo mentre il disco arriva dove deve andare. Con un
salto lento quel lampo della frase sbagliata si vede, e pulire prima è il modo
per evitarlo.
[#_making_text_readable_over_a_moving_picture]
=== Rendere leggibile il testo sopra un'immagine in movimento
Il sistema mette le parole sullo schermo. Se qualcuno riesce a leggerle è un
problema tuo, e vale per ogni riga di testo che disegni sopra un video -- una
richiesta della diciottesima lezione, un punteggio, una scelta di diramazione --
non solo per i sottotitoli.
*Stai lontano dai bordi.* I televisori tagliano i bordi delle immagini da
settant'anni, e i monitor da sala giochi sono peggio, non meglio. Parti dal
presupposto che il cinque per cento più esterno dell'immagine, su ogni lato,
sullo schermo di qualcuno potrebbe proprio non esserci. Tieni lì dentro tutto
quello che il giocatore deve leggere. È per questo che `srtPosition` si ferma a
`95` e non a `100`.
*Il contrasto non è una scelta di colore, è una garanzia.* Un testo bianco
sopra un'immagine è un testo bianco sopra qualunque cosa l'immagine stia
facendo, e fra due secondi potrebbe essere un campo di neve. Lo stile dei
sottotitoli fornito con il motore lo risolve come fanno i sottotitoli dei film:
un pannello scuro che avvolge il testo, più un'ombra di un pixel. Se disegni tu
del testo sopra un video, fai l'una o l'altra cosa -- un `overlayBox` dietro, o
una copia scura del testo spostata di un pixel. Non contare sull'immagine.
*Vai a capo tu.* Due righe corte si leggono più in fretta di una lunga, e una
riga che va a capo dove non l'hai deciso tu di solito si spezza nel punto
sbagliato. Punta a una quarantina di caratteri per riga e non più di due righe
alla volta. Vai a capo in una pausa naturale:
`They were wrong about / a great many things` si legge; `They
were wrong / about a great many things` inciampa.
*Lasciali su abbastanza a lungo.* Una velocità di lettura comoda è intorno ai
quindici caratteri al secondo, quindi una riga di quaranta caratteri vuole più
o meno tre secondi. Guarda la battuta uno nel file qui sopra: trentadue
caratteri, tre secondi. Se una frase del dialogo è breve e rapida, tenere la
battuta su un attimo dopo la fine del parlato è più gentile che farla sparire di
colpo.
*Mettili dove non c'è l'azione.* Se la cosa importante dell'inquadratura è in
basso nel fotogramma, sposta i sottotitoli più su per quella scena con
`srtPosition`. Puoi chiamarla tutte le volte che vuoi, e il giocatore non si
accorgerà mai che l'hai fatto.
[#_styling_them]
=== Lo stile dei sottotitoli
Le righe vengono disegnate da un documento, dello stesso tipo che hai incontrato
nella ventesima lezione, e due file nella tua cartella `Singe` decidono che
aspetto hanno:
* `Singe/subtitle.rml` -- il documento. Contiene un solo elemento, `#slot`, in
cui il motore scrive la battuta o il banner del momento.
* `Singe/subtitle.rcss` -- lo stile. `.cue` è il blocco che porta l'altezza
chiesta da `srtPosition`, e `.cue .text` è la barra di testo al suo interno:
bianca, in grassetto, su un pannello scuro con gli angoli arrotondati che
avvolge il testo, con un'ombra.
Apri `subtitle.rcss` e leggilo -- sono una quarantina di righe. Poi lascialo
stare, perché una modifica lì non dura. Ogni volta che Singe parte, rimette la
propria copia di qualsiasi cosa nella cartella `Singe` sia stata cambiata, e lo
dice:
----
>>> Updated File: Singe/subtitle.rcss
----
Quella cartella è del motore. Il tuo gioco porta invece una copia sua. Copia
entrambi i file fuori da `Singe` e dentro la tua cartella `subtitles`, accanto
allo script. Poi apri la tua copia di `subtitle.rml` e cambia l'unica riga del
suo `<head>` che collega lo stile, in modo che diventi:
[source,html]
----
<link type="text/rcss" href="subtitle.rcss"/>
----
Un nome dentro un documento parte dalla cartella del documento stesso, tranne
un nome che comincia con `Singe/`, che vuol dire sempre la cartella del motore.
Il collegamento così come l'hai copiato, `Singe/subtitle.rcss`, prende ancora lo
stile del motore. Quello nuovo prende il tuo.
Quando Singe trova un `subtitle.rml` accanto al tuo script, disegna i
sottotitoli con quello invece che con il proprio. Aggiungi `-p` al comando e
Singe descrive quello che sta facendo man mano, una riga alla volta:
----
Singe -p --framefile=Singe/menuBackground.mkv subtitles
----
Quando compare la prima battuta, una di quelle righe è questa:
----
Using the game's own subtitles/subtitle.rml
----
Ora cambia `font-size`, `color`, `background-color`, `border-radius` o
`font-effect` nel tuo `subtitle.rcss`, salva ed esegui di nuovo. Il motore
imposta sempre e solo il contenuto di `#slot` e l'altezza di `.cue`, quindi
tutto il resto dei tuoi due file è tuo, e niente di quello che Singe fa
all'avvio li toccherà. Se `-p` dice che sta usando i tuoi e l'aspetto non è
cambiato, rileggi il collegamento: una copia che dice ancora `Singe/` sta usando
in silenzio lo stile del motore.
Siccome `overlayBanner` usa lo stesso documento, cambiare lo stile dei
sottotitoli cambia anche quello dei tuoi banner, il che di solito è quello che
vuoi, ed è meglio saperlo prima di chiederti perché.
Un limite: il documento dei sottotitoli è un documento, e i documenti hanno
bisogno di una GPU. Su una macchina che non ce l'ha, `srtLoad` legge comunque il
tuo file e risponde comunque il vero, e non viene disegnato niente. Un avviso lo
dice una volta.
[#_try_it_21]
=== Prova tu
. *Aggiungi una quarta battuta* a `subtitles.srt`, tra gli undici e i tredici
secondi. Salva ed esegui. Poi mettila fuori ordine, come battuta `99`, per
prima nel file, e verifica che funzioni ancora.
. *Dai a una battuta una marca temporale sbagliata* -- scrivi `00:00:05.500` con
un punto al posto della virgola -- ed esegui. Guarda quale riga sparisce, e
nota che nessuno ti dice niente. Un file che si carica non è un file giusto.
. *Cambia il loro stile.* Nel tuo `subtitle.rcss` -- la copia accanto al tuo
script, non quella in `Singe` -- cambia `.cue .text` in testo giallo su uno
sfondo del tutto trasparente, poi aggiungi un'ombra più marcata. Poi guardalo sopra il video e decidi onestamente se si legge ancora.
. *Rompi apposta la tempistica.* Cambia ogni marca temporale del file in modo
che sia in ritardo di due secondi, e guarda un sistema che funziona a dovere
consegnare sottotitoli inutili. È questo che ti fa un video ricodificato.
. *Metti il comando in un menu.* Prendi la pagina di pausa della ventesima
lezione, aggiungi una casella di controllo, e collegala a `srtEnable`. Poi
salva la scelta con `saveSet`, così la volta dopo è ancora attiva. Questa è la
versione che i tuoi giocatori vogliono davvero.
[#_break_it_on_purpose_20]
=== Rompilo apposta
Esegui lo script senza l'opzione `--framefile`:
----
Singe subtitles
----
La finestra si apre, nera, con sopra il tuo messaggio di ripiego, e la console
dice:
----
Warning: No subtitles were loaded from subtitles/subtitles.srt.
----
Nessun crash e nessuna finestra di errore, perché `srtLoad` ha risposto `false`
e il tuo script ne ha tenuto conto.
È il fallimento più prezioso di questa lezione, perché è quello che succede
senza che nessuno se ne accorga. Non c'è un disco, quindi non c'è una frequenza
dei fotogrammi, quindi non c'è niente in cui trasformare le marche temporali,
quindi non si carica niente. Se avessi scritto `srtLoad(...)` da solo e avessi
buttato via la risposta, il tuo gioco sarebbe andato alla perfezione senza
mostrare nessun sottotitolo, e l'avresti scoperto quando qualcuno te l'avesse
detto.
Usa la risposta. Ogni volta.
[#_what_you_learned_21]
=== Cosa hai imparato
* Un gioco con del parlato e senza sottotitoli è un gioco a cui alcune persone
non possono proprio giocare.
* Inserire i sottotitoli strada facendo è un pomeriggio; aggiungerli dopo no.
* I sottotitoli sono un file `.srt`: un numero, due marche temporali con `-->`
in mezzo, il testo e una riga vuota.
* `srtLoad` trasforma le marche temporali in numeri di fotogramma del disco al
momento del caricamento, ed è per questo che una battuta sopravvive a
qualsiasi salto.
* I sottotitoli vanno caricati dopo il disco, e il caricamento risponde
`false` quando non riesce, anche quando non c'è un disco. Controlla la
risposta.
* `srtEnable` li accende, e uno script parte sempre con i sottotitoli spenti.
* `srtPosition` li colloca a una percentuale dell'altezza dell'immagine,
contata dall'alto, da `1` a `95`.
* `srtClear` toglie quello che si vede, e `overlayBanner` condivide lo stesso
documento.
* `srtLoadTrack` legge i sottotitoli dal file video stesso, quando li contiene.
* Singe riscrive la sua cartella `Singe` a ogni avvio, quindi per cambiare lo
stile dei sottotitoli copi `subtitle.rml` e `subtitle.rcss` accanto al tuo
script, fai puntare il collegamento della copia al tuo foglio, e cambi quello.
* Il testo sopra un'immagine in movimento ha bisogno di un margine di
sicurezza, di un contrasto suo, di righe che hai spezzato tu e del tempo per
essere letto.
[#_next_time_21]
=== La prossima volta
Con questo il testo è finito. La quarta parte prosegue senza che tu debba
leggerla in ordine, e la prossima lezione apre una porta rimasta chiusa per
venti lezioni: `overlayGetWidth` e `overlayGetHeight` sono due numeri, e una
scena ne ha tre. La ventiduesima lezione parla del terzo.