【Unity】【Localization】あらゆる種類のアセットをローカライズできるようにする方法まとめ

UnityのLocalizationパッケージであらゆるアセットをローカライズ対応する方法をまとめます。

Unity2020.3.15f2
Localization 1.2.1

はじめに

Localizationパッケージでは、文字列に加えてテクスチャやスプライトなどのアセットをローカライズすることができます。
しかしながらアセットの種類によってはそのままだとローカライズできないものもあります。
本記事ではこのようなアセットをローカライズするための対応についてまとめます。

Localizationパッケージの基礎知識については以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

Component Localizerでローカライズできるか確認する

さてLocalizationパッケージを使ってローカライズする方法には、Component Localizerを使う方法とProperty Variantを使う方法の2通りの方法があります。

light11.hatenadiary.com

まずこのうち、ローカライズしたいアセットがComponent Localizerローカライズできるかを確認します。

上の記事に記載している通り、Component Localizerに対応している場合には、Context MenuからLocalizeを選択できます。
例えばAudio Sourceの Context Menuを開くと、Localizeメニューが存在していることを確認できます。

ComponentLocalizerに対応している

つまりAudio SourceComponent Localizerによりローカライズできます。

次にMesh FilterのContext Menuを開いてみます。
こちらはLocalizeメニューが存在していないことがわかります。

Component Localizerに対応していない

したがって、Mesh FilterはこのままではComponent Localizerによりローカライズすることができません。

Component Localizerでローカライズできるようにする

それでは次に、前節のMesh Filterを例にとり、メッシュのローカライズをComponent Localizerを使って行う方法についてまとめます。

LocalizedAssetEventを継承したクラスを作成する

まず以下のように以下の三つのクラスを継承したクラスを作成します。

  • LocalizedAsset
  • UnityEvent
  • LocalizedAssetEvent
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Localization;
using UnityEngine.Localization.Components;

[Serializable]
public class LocalizedMesh : LocalizedAsset<Mesh>
{
}

[Serializable]
public class UnityEventMesh : UnityEvent<Mesh>
{
}

[AddComponentMenu("Localization/Asset/Localize Mesh Event")]
public class LocalizeMeshEvent : LocalizedAssetEvent<Mesh, LocalizedMesh, UnityEventMesh>
{
}

あとはGameObjectにLocalizeMeshEventをアタッチし、ローカライズ設定を行ない、下部のイベント部分でMeshFilterを更新するように設定したらローカライズできるようになります。

Component Localizerによるローカライズ

Context Menuに追加する

さて前節では手動でMeshFilterを更新するイベントを登録しました。
これは面倒なので、Audio SourceのようにContext MenuからLocalizeを選択した時にこの辺りのセットアップが自動的に完了するようにします。

Context Menuを追加したスクリプトが以下です。

using System;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Events;
#endif
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Localization;
using UnityEngine.Localization.Components;

[Serializable]
public class LocalizedMesh : LocalizedAsset<Mesh>
{
}

[Serializable]
public class UnityEventMesh : UnityEvent<Mesh>
{
}

[AddComponentMenu("Localization/Asset/Localize Mesh Event")]
public class LocalizeMeshEvent : LocalizedAssetEvent<Mesh, LocalizedMesh, UnityEventMesh>
{
#if UNITY_EDITOR
    [MenuItem("CONTEXT/MeshFilter/Localize")]
    private static void AttachAndSetupForMeshFilter(MenuCommand command)
    {
        var target = (MeshFilter)command.context;
        AttachAndSetupForMeshFilter(target);
    }

    public static LocalizeMeshEvent AttachAndSetupForMeshFilter(MeshFilter target)
    {
        var localizeEvent = (LocalizeMeshEvent)Undo.AddComponent(target.gameObject, typeof(LocalizeMeshEvent));
        
        // イベント発生時にsharedMeshが変更されるように
        var setMethod = typeof(MeshFilter).GetProperty("sharedMesh")?.GetSetMethod();
        if (setMethod != null)
        {
            var methodDelegate = (UnityAction<Mesh>)Delegate.CreateDelegate(typeof(UnityAction<Mesh>), target, setMethod);
            UnityEventTools.AddPersistentListener(localizeEvent.OnUpdateAsset, methodDelegate);
            localizeEvent.OnUpdateAsset.SetPersistentListenerState(0, UnityEventCallState.EditorAndRuntime);
        }
        
        return localizeEvent;
    }
#endif
}

UnityEventへの登録部分が若干ややこしいですが、これに関しては以下の記事にまとめていますので必要に応じて参照してください。

light11.hatenadiary.com

これで、Mesh FilterのContext Menuから簡単にローカライズを行えるようになりました。

Context Menuからローカライズ

Property Variantに対応しているか調べる

さてローカライズを行うワークフローとして、Component Localizerとは別に、Property Variantを使う方法あります。

light11.hatenadiary.com

デフォルトでは、Property Variantローカライズを行えるプロパティはJsonUtilityシリアライズが可能なものと、TransformRectTransformに限られます。

実際にプロパティがローカライズできるかどうかを確認するには、以下の手順によって実際にローカライズしてみるのが手っ取り早いです。

  1. Window > Asset Management > Localization Scene Controls を開く
  2. Active LocaleをNone以外に設定する
  3. Track Changesにチェックを入れる
  4. ローカライズしたいプロパティの値を変更する

この手順の結果として、プロパティフィールドの背景が緑色になりアイコンが表示されたらそのプロパティはProperty Variantに対応しています。
以下は上記の手順でTransformのPosition.XとPosition.Yを変更した例です。

Transformをローカライズ

背景が緑色になりアイコンが表示されているため、Transoform.positionProperty Variantによりローカライズできることがわかります。

次に同様の手順でMesh FilterのMeshを変更してみます。
こちらはプロパティを変更しても背景が変わりません。

対応していない

したがって、Mesh FilterはこのままではProperty Variantによりローカライズすることができないと言えます。

Property Variantに対応する

それでは最後にMesh FilterProperty Variantに対応させる方法についてまとめます。

Property Variantに対応させるには、以下のようにLocalizedAssetを継承したクラスと、TrackedObjectを継承したクラスを作成します。
細かい実装の説明はソースコードを見た方早いと思うのでコメントを参照してください。

using System;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.PropertyVariants;
using UnityEngine.Localization.PropertyVariants.TrackedObjects;
using UnityEngine.Localization.PropertyVariants.TrackedProperties;
using UnityEngine.ResourceManagement.AsyncOperations;

[Serializable]
public class LocalizedMesh : LocalizedAsset<Mesh>
{
}

[Serializable]
[DisplayName("Mesh Filter")]
[CustomTrackedObject(typeof(MeshFilter), false)]
public sealed class TrackedMeshFilter : TrackedObject
{
    public override AsyncOperationHandle ApplyLocale(Locale variantLocale, Locale defaultLocale)
    {
        var sharedMeshProperty = GetTrackedProperty("m_Mesh"); // ローカライズしたいプロパティパスを指定
        if (sharedMeshProperty == null)
            return default;

        // アセットテーブルに保存
        if (sharedMeshProperty is LocalizedAssetProperty localizedAssetProperty &&
            localizedAssetProperty.LocalizedObject is LocalizedMesh localizedMesh)
        {
            localizedMesh.LocaleOverride = variantLocale;
            var loadHandle = localizedMesh.LoadAssetAsync();
            if (loadHandle.IsDone)
                OnAssetLoaded(loadHandle);
            else
            {
                loadHandle.Completed += OnAssetLoaded;
                return loadHandle;
            }
        }
        // ローカル(GameObject)に保存
        else if (sharedMeshProperty is UnityObjectProperty localAssetProperty)
        {
            if (localAssetProperty.GetValue(variantLocale.Identifier, defaultLocale.Identifier, out var mesh))
                SetAsset((Mesh)mesh);
        }

        return default;
    }

    private void OnAssetLoaded(AsyncOperationHandle<Mesh> loadHandle)
    {
        SetAsset(loadHandle.Result);
    }

    private void SetAsset(Mesh mesh)
    {
        var meshFilter = (MeshFilter)Target;
        meshFilter.sharedMesh = mesh;
    }

    public override bool CanTrackProperty(string propertyPath)
    {
        return propertyPath == "m_Mesh";
    }
}

これで、Property Variantを使ってMesh Filterをローカライズできるようになりました。
なおCustomTrackedObjectを使う場合には、ローカライズしたプロパティの背景が緑色になったりアイコンがついたりはしないようなのでその点ご注意ください。

Property Variantに対応

関連

light11.hatenadiary.com

light11.hatenadiary.com

docs.unity3d.com