【Unity】【Localization & Addressables】ローカライズリソースのロードとダウンロード・メモリ管理の仕組みをちゃんと理解する

UnityのLocalizationパッケージにおけるローカライズリソースのロードとダウンロードの仕組みをまとめます。

Unity2020.3.15f2
Localization 1.1.1

はじめに

この記事ではUnityのLocalizationパッケージにおけるロードとダウンロードの仕組みについてまとめます。
Localizationパッケージの基礎知識については以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

基本はLocale初期化時にプリロード

特に何も設定をしないと、ローカライズリソースはLocaleの初期化時に全てダウンロードとロードが行われます。
Localeの初期化時とはすなわち、アプリ起動時と、ロケールを切り替えたタイミングを指します。

なおこの初期化処理は非同期で行われます。
完了を待機するには以下のようにLocalizationSettings.InitializationOperationを使います。
初期化が完了するまでロード画面を表示しておきたい場合などにはこれを使って制御します。

yield return LocalizationSettings.InitializationOperation;

プリロードしない場合には必要になったときにロードされる

さて前節の挙動では、初期化時にそのLocaleの全てのリソースがメモリ上に常駐してしまうことになります。
これでは困るケースでは、プリロードを無効にすることもできます。

プリロードを無効にするにはWindow > Asset Management > Localization Tablesを開き、Edit Table Collectionタブから以下の画像のように操作を行いチェックボックスからチェックを外します。

f:id:halya_11:20220303180927p:plain
Edit Table Collection

プリロードをオフにした場合には、必要になったときに初めてそのテーブルがダウンロード及びロードされます。
ただしこちらも初期化と同様非同期で行われるため、ダウンロードやロードが完了する間はそのリソースが取得できていない状態となってしまいます。
つまり画像であればその間はデフォルトの画像が見えてしまうということになり、細かい制御は難しそうです。

手動でプリロードする

以下のように記述すると、プリロードのタイミングをLocalization任せではなく、自身で制御できます。

yield return LocalizationSettings.AssetDatabase.PreloadTables("テーブル名");

基本的にメモリに常駐していいリソースはプリロードにチェックを入れ、それ以外はこのように手動でプリロードしてメモリをしっかり管理するのがいいのではないかと思います。

メモリの解放タイミング

さてメモリ管理といえば次に気になるのがメモリの解放タイミングです。

まず基本的な仕様として、Localeを変更したときには変更前のLocaleに関連するリソースは全てアンロードされます。
つまり日本語から英語に切り替えた場合、日本語のリソースはアンロードされます。

これとは別に、以下のように書くことで強制的にテーブルをリリースすることもできます。

LocalizationSettings.AssetDatabase.ReleaseTable("テーブル名");

前節のように自身でプリロードタイミングを決めたリソースは解放のタイミングも制御したいと思うので、これを使うことになりそうです。
なおこのメソッドは他でリソースを使用していても強制解放されるので管理は厳密に行う必要があります。

ちなみにですが、PreloadTables()の戻り値はAsyncOperationHandleなので以下のように解放できるんじゃないかと思いましたが、これはできませんでした。

// ダメな例です
var op = LocalizationSettings.AssetDatabase.PreloadTables("テーブル名");
yield return op;
Addressables.Release(op); // 解放できなかった

Localeを変えてもメモリを解放したくない場合

さて前節の通り、Localeを変えると変更前のLocaleのリソースはメモリから解放されます。
これは自身でプリロードを行なったテーブルについても例外ではありません。

もしLocaleが切り替わってもメモリを解放したくない場合には、以下のように記述します。

var op = LocalizationSettings.AssetDatabase.PreloadTables("ExampleAssetTable");
yield return op;

// テーブルへの参照を保持して解放されないようにする
Addressables.ResourceManager.Acquire(op);

コンテンツアップデートのやり方

ここまででLocalizationパッケージにおけるロードやダウンロード及びメモリ管理についてまとめました。

次に気になるのがコンテンツアップデートのフローです。
つまり、アクティブなLocaleのダウンロードリソースが更新されたときに、アプリを起動したままそれを更新する方法です。

LocalizationパッケージはAddressableアセットシステムを使っているので、基本的な考え方はAddressablesに準拠します。

light11.hatenadiary.com

スクリプトとしては例えば以下のようにすれば実現できそうです。
Unique Bundle IDを使わず、メモリ上のアセットバンドルを一度クリアにして更新する例です。

using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Localization.Settings;
using UnityEngine.UI;

namespace LocalizationTest
{
    public class LocalizationContentsUpdateTest : MonoBehaviour
    {
        [SerializeField] private Button _button;
        [SerializeField] private Text _text;
        [SerializeField] private string _stringTableName = "ExampleStringTable";
        [SerializeField] private string _stringEntryName = "ExampleStringEntry";

        public void Awake()
        {
            _button.onClick.AddListener(OnClicked);
        }

        public void OnDestroy()
        {
            _button.onClick.RemoveListener(OnClicked);
        }

        private void OnClicked()
        {
            LogEntryValue();
        }

        private void LogEntryValue()
        {
            StartCoroutine(LogEntryValueRoutine());
        }

        private IEnumerator LogEntryValueRoutine()
        {
            // Update catalog.
            var checkUpdatesHandle = Addressables.CheckForCatalogUpdates(false);
            yield return checkUpdatesHandle;
            var updates = checkUpdatesHandle.Result;
            Addressables.Release(checkUpdatesHandle);
            if (updates.Count >= 1)
                Addressables.UpdateCatalogs();

            // Reset initialization state. (Unload all tables)
            LocalizationSettings.Instance.ResetState();
            
            // Initialize LocalizationSettings.
            yield return LocalizationSettings.InitializationOperation;

            // Load table.
            var handle = LocalizationSettings.StringDatabase.PreloadTables(_stringTableName);
            yield return handle;

            // Get entry.
            var entry = LocalizationSettings.StringDatabase.GetTableEntry(_stringTableName, _stringEntryName).Entry;

            if (_text != null)
                _text.text = entry.Value;
        }
    }
}

しかしこれが想定しているワークフローなのかちょっと怪しそうだったので、Unityのフォーラムで聞いてみました。
結論、現時点のやり方としては合ってそうだが今後のアップデートでもっといいやり方を提供してくれるかもしれないとのことでした。

forum.unity.com

その頃にはこの記事のAPIも変更される部分がありそうな気がしてるので、もしこの情報が古そうだったら教えてください。

関連

light11.hatenadiary.com

light11.hatenadiary.com

参考

forum.unity.com