えんじにあ雑記!

開発していて学んだことをまとめていきます!

Unityで多言語対応を実装してみた

f:id:flat-M_M:20200413114127j:plain

先日、カジュアルゲームを全世界に向けてリリースしました!

全世界でユーザを抱えるゲームを作ってみたいという思いから、日本語、英語、中国語、韓国語に対応したのでそのときの実装方法をまとめます。

はじめに

作ったゲームはこんな感じです。


カジュアルスマホゲーム「1びょうさきがみえる」

ゲームの根幹はなんと言っても「1秒先が見える」こと!

操作はシンプルな避けゲーと一緒でタップのみの簡単操作ですが、思わぬタイミングでゲームオーバになったりするクセになる楽しさを感じてもらえればと思っています!!☺️

インストールは下記のリンクから出来るので、もし良ければプレイしてみてください😍

1 second ahead

1 second ahead

  • Shunsuke Saito
  • Games
  • Free
apps.apple.com

play.google.com

そして、このアプリは日本語だけでなく、英語、韓国語、中国語に対応しています。

この記事ではどうやってローカライズを実装していったかの説明をしていこうと思います。

使い方

まずは全体像を簡単に説明します。

Localizeオブジェクトを配置

LocalizeのManagerとなるプレハブを作成しているので、それをゲームの最初にロードされるシーンに配置します。

またSingletonMonoBehaviourを継承しており、DontDestroyOnLoadを実行しているため、今後のシーンでは常にLocalizeが参照できる状態を作っておきます。

エクセルでローカライズデータを作成

今回はエクセルを利用しました。まずは、画像のようにエクセルシートにローカライズデータを記入します。

f:id:flat-M_M:20200413222332p:plain
エクセルで作成したローカライズデータ

特定の文言を取り出す際のキーとなる文字列と、各言語に翻訳された文言を記入していきます。

実際に文言を取り出す際は、

Localize.Get("<取り出したい文字列のキー>");

という感じで取り出せるようにします。

CSV形式に書き出す

エクセルで作成したローカライズデータをCSV形式で書き出します。

これはエクセルの標準機能でCSV形式で書き出すものがあるのでそちらを使います。

メニューから「編集」→「名前を付けて保存」を選択します。

f:id:flat-M_M:20200413222752p:plain

次に書き出し形式をCSVに設定します。

f:id:flat-M_M:20200413222857p:plain

Localize.csvをプロジェクトに導入

Resourcesフォルダ直下に、CSV形式に書き出したファイルを配置します。

あとは取り出すだけ

あとは、ローカライズされた文言を取り出したい場所で下記のようにコードを書いて取り出せるようになります。

Localize.Get("<取り出したい文字列のキー>");

内部実装

CSVデータの読み込み

まずは、Resourcesフォルダ直下のLocalize.csvからデータを読み取るコードは下記のように実装しています。

念のためですが、データを重複して読み込むことを防ぐため_localizeDataに既にデータがある場合は一旦削除しています。

// データが残ってる場合は削除
if (_localizeData.Count != 0)
{
    _localizeData.Clear();
}

Resourcesフォルダ直下に配置したCSVファイルを読み込みます

string localizeCSV = Resources.Load<TextAsset>(_localizeCSVPath).ToString();

そして、一行ごとに切り分けていきます

// 一行ごとに切り分ける
string[] textlines = localizeCSV.Split(new char[]{'\r', '\n'});

CSV形式の一行目は各国を示すキーとなるため別の変数に格納します。

// 最初の一行はキーとして利用するため、先に取得しておく
var firstRow = textlines[0].Split(',');

あとは、残りのデータを順々にDictionary形式で追加していきます。

// _localizeDataに追加処理
for (var i = 1; i < textlines.Length; i++)
{
    var row = textlines[i].Split(',');
    _localizeData[row[0]] = new Dictionary<string, string>();
    for (var j = 1; j < row.Length; j++)
    {
        // キーに該当し、国コードが指定された場所に文字列をセットする
        _localizeData[row[0]][firstRow[j]] = row[j];
    }
}

CSV形式のデータを読み込む全体のコードは次の通りになります。

public class Localize : SingletonMonoBehaviour<Localize>
{
    /// <summary>
    /// Localize用のCSVファイルがあるパスをさす
    /// </summary>
    private const string _localizeCSVPath = "Localize";

    private static Dictionary<string, Dictionary<string, string>> _localizeData = new Dictionary<string, Dictionary<string, string>>();

    private static void _LoadLocalizeData()
    {
        // データが残ってる場合は削除
        if (_localizeData.Count != 0)
        {
            _localizeData.Clear();
        }
        
        string localizeCSV = Resources.Load<TextAsset>(_localizeCSVPath).ToString();
        
        // 一行ごとに切り分ける
        string[] textlines = localizeCSV.Split(new char[]{'\r', '\n'});
        
        // 最初の一行はキーとして利用するため、先に取得しておく
        var firstRow = textlines[0].Split(',');
        
        // _localizeDataに追加処理
        for (var i = 1; i < textlines.Length; i++)
        {
            var row = textlines[i].Split(',');
            _localizeData[row[0]] = new Dictionary<string, string>();
            for (var j = 1; j < row.Length; j++)
            {
                // キーに該当し、国コードが指定された場所に文字列をセットする
                _localizeData[row[0]][firstRow[j]] = row[j];
            }
        }
    }
}

ローカライズされた文言の取り出し方

ローカライズされた文言を取得する際には、まずキーを指定した上で国指定の文字列をキーにローカライズされた文言を取り出す、という実装方法になっています。

/// <summary>
/// ローカライズされた単語を取得する
/// </summary>
public static string Get(string key)
{
    if (_localizeData.TryGetValue(key, out var pair))
    {
        return pair.TryGetValue(_localizeKey, out var text) ? text : key;
    }
    return key;
}

Localizeクラスの全コード

以下にLocalizeのコード全文を記載しておきます。

このままコピペして空のGameObjectにアタッチした上でフォントを適切に設定してあげればローカライズ対応はできるようになります!👏

using System.Collections;
using System.Collections.Generic;
using Mummibrary;
using UnityEngine;

namespace Mummibrary.Localize
{

    public class Localize : SingletonMonoBehaviour<Localize>
    {
        public const string JP = "JAPANESE";
        public const string EN = "ENGLISH";
        public const string CI = "CHINESE";
        public const string KO = "KOREAN";

        [SerializeField] private Font JPFont;
        [SerializeField] private Font ENFont;
        [SerializeField] private Font CIFont;
        [SerializeField] private Font KOFont;

        /// <summary>
        /// ローカライズ対応の国を示すキーが入る
        /// </summary>
        private static string _localizeKey = "";
        
        /// <summary>
        /// Localize用のCSVファイルがあるパスをさす
        /// </summary>
        private const string _localizeCSVPath = "Localize";

        private static Dictionary<string, Dictionary<string, string>> _localizeData =
            new Dictionary<string, Dictionary<string, string>>();
        
        protected override void Awake()
        {
            base.Awake();
            _LoadLocalizeData();
        }

        private static void _LoadLocalizeData()
        {
            // データが残ってる場合は削除
            if (_localizeData.Count != 0)
            {
                _localizeData.Clear();
            }
            
            string localizeCSV = Resources.Load<TextAsset>(_localizeCSVPath).ToString();
            
            // 一行ごとに切り分ける
            string[] textlines = localizeCSV.Split(new char[]{'\r', '\n'});
            
            // 最初の一行はキーとして利用するため、先に取得しておく
            var firstRow = textlines[0].Split(',');
            
            // _localizeDataに追加処理
            for (var i = 1; i < textlines.Length; i++)
            {
                var row = textlines[i].Split(',');
                _localizeData[row[0]] = new Dictionary<string, string>();
                for (var j = 1; j < row.Length; j++)
                {
                    // キーに該当し、国コードが指定された場所に文字列をセットする
                    _localizeData[row[0]][firstRow[j]] = row[j];
                }
            }
        }

        public static void SetLocalizeKey(string key)
        {
            _localizeKey = key;
        }

        public Font GetLocalizeFont()
        {
            switch (_localizeKey)
            {
                case JP:
                    return JPFont;
                case EN:
                    return ENFont;
                case CI:
                    return CIFont;
                case KO:
                    return KOFont;
                default:
                    return JPFont;
            }
        }

        /// <summary>
        /// ローカライズされた単語を取得する
        /// </summary>
        /// <param name="key">国指定用のコード</param>
        public static string Get(string key)
        {
            if (_localizeData.TryGetValue(key, out var pair))
            {
                return pair.TryGetValue(_localizeKey, out var text) ? text : key;
            }
            return key;
        }
    }
}

さいごに

全世界でたくさん遊んでもらえるゲームになればいいなぁと思っていますが、現状そんなに簡単には行かないなという感じです。

それでも4/13現在で、20人程度のユーザが国外で使用してもらえてる事実は、ローカライズ対応してよかったなと思っています。

アプリが起動された端末の設定言語を取得する方法は、「Unityで起動時に端末の設定言語を取得する方法」で解説しているので参考にしてください。