Traduzione e internazionalizzazione con GameMaker
Ciao gente!
Benvenuti su GameMakerItalia. In questo articolo tecnico andremo a coprire un aspetto a volte sottovalutato dello sviluppo di un videogioco ma che nasconde alcune insidie: il supporto al multilingua.
Sicuramente scrivere i testi e i contenuti del proprio gioco in inglese può "risolvere" alla radice il problema della gestione della traduzione. Ma se è necessario supportare il multilingua o aggiungere una lingua secondaria senza perdere il contenuto già scritto in precedente questo articolo ci può venire sicuramente in nostro aiuto.
Basta chiacchiere e vediamo subito un pò di... teoria
i18n
Citando direttamente wikipedia, possiamo capire che:
In computing, internationalization and localization (American) or internationalisation and localisation (British English), often abbreviated i18n and L10n, are means of adapting computer software to different languages, regional peculiarities and technical requirements of a target locale.
In particolare possiamo dividere il processo in due macro categorie:
- L'internazionalizzazione (i18n) è il processo in un software (e quindi in un gioco) di adattarsi e supportare diverse tipologie di lingue e regioni.
- La localizzazione (l10n) invece è l'adattamento dell'interno dell'internazionalizzazione di supportare specifici varianti, come potrebbero essere i dialetti per l'italiano ma anche l'inglese americano/inglese britannico.
A noi in questo momento ci interessa rimanere concentrati sull'internazionalizzazione, quindi supportare tante lingue. Vediamo come.
Il nostro dizionario
Partiamo da un caso semplice: supportare il nostro gioco sia in italiano ed in inglese, partendo dai contenuti in italiano. Primissima cosa da fare è scrivere due "Database", uno per lingua. Scrivo Database tra apici per intenderci di cosa stiamo parlando ma tecnicamente possono essere banalmente due file testuali, scritti in json.
Tutti i file json dovranno avere la stessa struttura, con ognuno il valore di ogni parola o frase tradotta.
Per essere più chiari con un esempio, facciamo finta che dobbiamo tradurre questa pagina, che consiste nella schermata principale di un menu ( gioco sviluppato con GameMaker, ovviamente :D )
Troviamo subito le seguenti parole/frasi da tradurre
- Menu principale
- Competizione
- Time attack
- Classifica
- Crediti
- Opzioni
- Esci
- Frecce - seleziona
- ENTER - scegli
Il nostro json lo potremmo strutturare in questo modo:
Essendo il json relativo alle risorse in italiano possiamo rinominarlo in IT.json e metterlo subito nella cartella IncludesFiles del nostro progetto Game Maker.
La struttura del file json è fondamentale: dividendo correttamente gli oggetti e le varie chiavi possiamo facilmente ritrovare una risorsa tramite una semplice navigazione del file. Mettere tutte le chiavi e le risorse in piano senza oggetti è sicuramente più semplice da scrivere ma sei sicuro che poi nell'atto pratico non ti perderai tra i vari nomi?
Per esempio se volessi ottenere il valore tradotto per il pulsante opzioni la chiave sarebbe:
"menuPrincipale.azioni.opzioni"
Tip: un eccessiva organizzazione del file json secondo la mia esperienza è peggio tanto quanto una poca organizzazione. Il mio consiglio personale è tenere la profondità della chiave non più di 5-6 elementi
Possiamo pensare dopo alla creazione degli altri dizionari per le altre lingue, per ora andiamo avanti.
Caricamento del dizionario
Nel nostro progetto dovremmo inserire il seguente snippet:
Tip questo codice va inserito in un punto iniziale del flusso del gioco, prima di qualsiasi altra cosa. Un oggetto globale istanziato per primo o prima di avviare qualsiasi schermata di gioco potrebbe andar bene.
os_get_language permette di capire la lingua corrente del sistema operativo. ritorna un codice che possiamo usare per capire quale dizionario caricare. A questo punto possiamo effettivamente caricare il nostro dizionario.
Creiamo uno script e ci andiamo ad incollare il seguente codice:
in base alla chiave passata va a leggere tra gli include files il corretto dizionario e ritorna una struct valorizzata correttamente, che andremmo a salvare in una variabile globale.
Utilizzo del dizionario
Tutto bello... ma come si fa a leggere la giusta chiave? Leggere la variabile globale language in quanto struct non è complesso ma sicuramente possiamo scrivere una funzione di questo tipo:
Questa funzione, posizionata in uno script, possiamo usarla liberamente in un oggetto qualsiasi, per esempio nel create dell'oggetto menu:
in questo modo la funzione accetta come ingresso unicamente la chiave definita precedentemente nel dizionario, spezza la stringa per il carattere punto e itera tutte le sotto chiavi finché non trova la risorsa voluta restituendola, altrimenti restituisce la chiave stessa
La seconda lingua
A questo punto siamo pronti per inserire un altro dizionario, per esempio la lingua inglese
Creiamo un altro file json, da inserire nella stessa cartella del file it.json. Lo possiamo scrivere partendo dalla stessa struttura di quello già esistente, così non possiamo sbagliare:
Tip i file json devono essere uguali nella struttura, come ho già spiegato. Ma nel caso ci sia un errore la funzione getLanguageValue non va in errore, restituendo la chiave al posto del valore non trovato. Utile!
Risorse dinamiche con placeholder
A volte è necessario avere dei valori un pò dinamici nelle traduzioni, per visualizzare nel gioco sia un testo fissato sia un testo che cambia dinamicamente
Il testo evidenziato nel rettangolo in alto a sinistra mostra il numero di giro corrente in questo gioco di corse. La scritta è chiaramente composta da due parti:
- "Giro: " che corrisponde in una parte statica, da tradurre
- "2/10" che corrisponde in una parte dinamica, una variabile di gioco non da tradurre
Come fare?
Possiamo ampliare il nostro dizionario quanto segue
e la controparte in inglese in questo modo
Tip: Non mi stuferò mai di dirlo, aggiornate sempre tutti i dizionari!
A questo punto possiamo andare nel nostro oggetto relativo a disegnare durante la partita l'ui e scrivere una cosa del genere
obUI.CREATE
labelGiroRaw = loadDictionaryValue("game.ui.giroCorrente")
obUI.DRAW
labelGiro = string(labelGiroRaw, giroCorrente, giroTotale)
In questo modo possiamo sostituire i nostri placeholder ( {0} e {1} ) con le relative variabili direttamente in game e sfruttare la risorsa tradotta nel modo corretto.
Inoltre, spezzando il codice nel Create e nel Draw ( e non mettendo tutto nel Draw con una singola variabile ) separo le logiche, caricando dal dizionario il valore "grezzo" una volta sola e potendolo utilizzare quanto voglio sfruttando la variale d'appoggio labelGiroRaw
Cambiare lingua
L'azione di cambiare lingua a questo punto risulta molto semplice. Basta cambiare chiave e ricaricare il giusto dizionario.
Per esempio nell'evento click del nostro oggetto pulsante:
obLanguageITButtonMenu.MOUSE_PRESS_EVENT
global.language = "it";
global.languageDict = loadDictionary(global.language)
obLanguageENButtonMenu.MOUSE_PRESS_EVENT
global.language = "en";
global.languageDict = loadDictionary(global.language)
Links risorse
Qui di sotto lascio un po' di links e risorse utili, fatemi sapere se avete qualche consiglio su come migliorare la tecnica o se avete domande, vi risponderò sicuramente sul canale Discord di GMI!