[#_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 `` e `` 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 `` che collega lo stile, in modo che diventi: [source,html] ---- ---- 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.