Gérer plusieurs langues dans votre jeu avec Unity 3D

Cet article est obsolète, je vous encourage vivement à essayer ma nouvelle méthode qui est beaucoup plus facile et rapide à mettre en place !

Lorsque l’on créé un jeu il arrive le moment où il faut ajouter une interface graphique avec inévitablement du texte. Là vous avez trois cas de figure. Le premier cas que l’on retrouve très souvent est un jeu totalement en Anglais car c’est une langue très utilisée à travers le monde, dans ce cas il n’y a pas trop à se poser de questions. Il arrive aussi que le développeur choisisse d’utiliser sa langue maternelle, c’est une bonne idée au début car la diffusion pour l’entourage est facile, cependant le diffusion à l’international est plus compliquée. Enfin le dernier cas est le jeu avec plusieurs langues prisent en charge, et là ça se complique un peu car il est préférable de penser à cette étape au début du développement, histoire de ne pas avoir 50 fichiers à modifier par la suite. Je vous propose de réaliser un système de gestion des langues fonctionnel avec Unity, vous pouvez le modifier pour l’utiliser avec n’importe quel autre moteur. Dans ce tutoriel nous aurons besoin de deux scripts externes. SimpleJSON dans sa version la plus light (sans SharpZipLib) qui va nous permettre de lire le contenu des fichiers de langages. La classe Singleton<T> qui permet d’avoir un composant statique sur tout le projet. Pour ma part, j’ai placé ces deux scripts dans un dossier Utils. Nous commencerons par créer la classe Lang, elle devra hériter de Singleton. Les fichiers de traduction seront stockés dans le dossier Resources, que vous devez impérativement créer, ils seront au format json (car nous sommes des gens bien).

Le fichier de définitition

La première étape conscite à placer deux fichiers texte dans le dossier Resources. On utilise ce dossier car nous pouvons charger dynamiquement les assets qui s’y trouvent. Unity ne prend pas nativement en charge le format json mais il peut charger des fichiers texte (format txt) en tant que TextAsset. Comme le JSON est un format libre et ouvert, nous placerons son contenu dans le fichier texte, mais il sera traité dans le code comme un JSON (au final ce n’est qu’une longue chaîne de caractères).

Structure du fichier lang.fr.txt

[
    	["game.title", "Mon super jeu"],
	    ["game.new", "Nouveau"],
	    ["game.settings", "Paramètres"],
	    ["game.exit", "Quitter"]
]

Structure du fichier lang.en.txt

[
    	["game.title", "My super game"],
	    ["game.new", "New"],
	    ["game.settings", "Settings"],
	    ["game.exit", "Exit"]
]

Chaque element du fichier est un tableau de deux valeurs, avec une clé d’identification de la chaine et la chaine correspondante. Vous pouvez utiliser n’importe quoi pour vos clés mais le but est de rester explicite et lisible.

Le gestionnaire de langage

La classe Lang va hériter de Singleton, la détection de la langue utilisateur ainsi que le chargement du fichier correspondant sera fait ici. Nous aurons en plus une méthode Get qui nous renverra la bonne chaine. Enfin toutes les clés/valeurs seront stockées dans une collection de type Dictionary<string, string>.

using UnityEngine;
using System.Collections.Generic;
using SimpleJSON;

public class Lang : Singleton
{
    private Dictionary<string, string> _gameTexts;

    // Initialisation du gestionnaire de langues
    void Awake()
    {
        string lang = Application.systemLanguage.ToString();
        string jsonString = string.Empty;

        if (lang == "French")
            jsonString = Resources.Load<TextAsset>("lang.fr").text;
        else
            jsonString = Resources.Load<TextAsset>("lang.en").text;

        JSONNode json = JSON.Parse(jsonString);
        int size = json.Count;

        _gameTexts = new Dictionary<string, string>(size);

        JSONArray array;
        for (int i = 0; i < size; i++)
        {
            array = json[i].AsArray;
            _gameTexts.Add(array[0].Value, array[1].Value);
        }
    }

    // Alias court à Lang.Instance.Get(key)
    public static string Get(string key)
    {
        return Instance.GetText(key);
    }

    // Récupère la chaine ayant pour clé key.
    public string GetText(string key)
    {
        if (_gameTexts.ContainsKey(key))
            return _gameTexts[key];

        return key;
    }
}

On utilise la méthode Awake() pour initialiser le script car nous voulons que le chargement des langues soit réalisé le plus rapidement possible. La propriété systemLanguage nous renvoie la langue du système de l’utilisateur, cela permet de charger le bon fichier. Comme les fichiers contenant les langues sont au format texte, qu’ils sont dans le dossier resources et que Unity les voient comme des TextAsset, on peut les charger dynamiquement avec la méthode Resource.Load<TextAsset>(filename). Vous noterez qu’on ne spécifie pas d’extension. Une fois que le fichier est chargé, il est parsé avec SimpleJSON et le dictionnaire statique est initialisé. Nous n’avons presque pas de code pour gérer cette partie car la structure d’un fichier json est simple. Enfin la méthode Get et son alias statique permettent de récupérer la valeur désirée. Vous noterez que si la clé n’existe pas alors on renvoie tout simplement cette clé. Cela permet de voir plus rapidement un texte manquant.

Utilisation

Je vous invite à créer une scène vide et d’ajouter un script sur la caméra avec un label et trois boutons. L’utilisation est relativement simple.

using UnityEngine;
using System.Collections;

public class demo : MonoBehaviour 
{
    	private Rect _boxRect;
	    private Rect _areaRect;

	    void Start()
	    {  	 
		        _boxRect = new Rect(
            Screen.width / 2 - 125,
            	Screen.height / 2 - 75, 
            250, 
            150);

       	_areaRect = new Rect(
            _boxRect.x + 5, 
            _boxRect.y + 40, 
            _boxRect.width - 10, 
            _boxRect.height - 10);
	    }

    	void OnGUI() 
	    {
    		    GUI.Box(_boxRect, Lang.Get("game.title"));
		        GUILayout.BeginArea(_areaRect);
		        GUILayout.BeginVertical();
		        GUILayout.Button(Lang.Get("game.new"));
		        GUILayout.Space(10);
		        GUILayout.Button(Lang.Get("game.settings"));
		        GUILayout.Space(10);
		        GUILayout.Button(Lang.Get("game.exit"));
		        GUILayout.Space(10);
		        GUILayout.EndHorizontal();
		        GUILayout.EndArea();
    	}
}

Comme vous pouvez le constater, l’utilisation du gestionnaire est ultra simple, il n’y a aucunement besoin de l’ajouter à un GameObject ou quoi que ce soit d’autre, tout ce que vous avez à faire est d’appeler la méthode Lang.Get(« votre.cle ») et celle-ci sera renvoyée automatiquement. Nous pouvons écrire cela grâce à la classe Singleton<T> qui garde une instance d’un composant entre toutes nos scènes.

Un menu en Français
Un menu en Français
Un menu en Anglais
Un menu en Anglais

Dans la classe Lang, vous pouvez supprimer le teste sur la langue pour ne charger que l’Anglais ou le Français afin de tester et valider le fonctionnement. Je vous recommande aussi d’utiliser les UserPrefs afin d’y stocker la langue préférée de l’utilisateur car ce n’est pas forcément la langue par défaut sur son système.

Conclusion

Cette méthode est efficace pour des jeux avec peu de texte car le système de clé remplira parfaitement son rôle et le code restera lisible. Pour un jeu avec plus de texte,  une solution type gettext sera nettement plus pratique (comme un RPG par exemple). Cependant c’est la méthode que j’ai mis en place dans un de mes jeu car elle est rapide, aussi bien à mettre en place, qu’à utiliser. Ces scripts sont relativement basiques, mise à part la classe Singleton, vous pouvez réutiliser ce système dans tout autres jeux exploitant le langage C#. Les sources de ce tutoriel sont disponibles à cette adresse.