Unity3D甚のScriptableObjectベヌスのロヌカラむズ

はじめに


読者の皆様、ご挚拶。 この蚘事では、 Unity3D環境で䜜成されたアプリケヌションのロヌカリれヌションシステムの䜜成に぀いお説明したす。このシステムは、 ScriptableObjectクラスの䜿甚に基づいおおり、テキストだけでなく音声や画像もロヌカラむズし、倖郚からそのようなデヌタを読み蟌むこずができたす。

䌝統によれば、詳现の説明に進む前に、ロヌカラむズずは䜕か、なぜロヌカラむズが必芁なのかに぀いお説明したす。

非垞に頻繁に、そしおほずんどの堎合、ゲヌム開発およびその他のアプリケヌションは耇数の垂堎に焊点を圓おおいたす。 各垂堎は独自の蚀語グルヌプによっお特城付けられおいるため、開発者はこれを考慮に入れる必芁がありたす。なぜなら、ゲヌムをロシア語のみで䜜成するず、英語を話すナヌザヌは単に䜕も理解できないからです。 どうする そうです、ゲヌム内の耇数の蚀語のサポヌトを提䟛する必芁がありたす。 ほずんどの堎合、テキストデヌタのみが翻蚳され、これにはGoogleスプレッドシヌトなどがよく䜿甚されたす。 テヌブルからのむンポヌトは難しくないため、非垞にシンプルで柔軟です。 しかし、すべおが䞀芋バラ色に芋えるわけではありたせん。 ゲヌムに倚くの音声ガむダンスがある堎合はどうなりたすか たたは、テキストは蚀語ごずに異なるフォントを䜿甚する必芁がありたすか 最埌に、画像内の蚀語に䞀意性を必芁ずするテキストや䜕かがありたすか これらの堎合、テヌブルはもはや十分ではありたせん。

それで、あなたは䜕を尋ねたすかもちろん、あなたがすでに答えを知っおいるのでない限り ScriptableObjectずAssetBundleのナヌスケヌスを 思い぀きたした 。 1぀目はデヌタをAssetの圢匏で保存する機胜を提䟛し、2぀目はこのデヌタを倖郚からロヌドしお保存する機胜を提䟛したす。

提案されたアプロヌチが䜕であるかをより詳现に怜蚎したしょう。

デヌタを保存する方法


たず、䜕をどの圢匏で保存する必芁があるかを刀断したす。そのために、䞀般から特定に移行したす。 ロヌカリれヌションシステムから取埗する必芁がある基本デヌタは、サポヌトされおいる蚀語のリストです。

泚 蚘事を読み進めながら、必芁なクラスを䜜成しお説明したす。 したがっお、蚀語

public class LocalizationData : ScriptableObject { public List<LanguageData> Languages; } [Serializable] public class LanguageData { public string Name { get { return _name; } } [SerializeField] private string _name; } 

サポヌトされおいる蚀語の名前は、ロヌカラむズされた圢匏で䜿甚し、むンタヌフェむスでの出力に䜿甚できたす。 ご芧のずおり、 LocalizationDataはScriptableObjectの埌継であり、実際、このクラスはメむンデヌタりェアハりスであり、これはAssetの圢匏でプロゞェクトに含たれたす。

次は そしお、各蚀語に぀いお、アプリケヌションたたはゲヌムで䜿甚される最終デヌタであるリ゜ヌスのセットを保存する必芁がありたす。 たず、䜿甚するリ゜ヌスのタむプを決定し、列挙したす enum 

 public enum LocalizationResourceType { Text, Image, Texture, Audio } 

画像は、Unity GUIベヌスのむンタヌフェむスたたは2Dゲヌムで䜿甚するスプラむトです。 なぜTextureず分離されおいるのですか 利䟿性のためだけに。

ここで、リ゜ヌスぞのリンクを盎接保存する堎所を決定したす。
 [Serializable] public class LocalizationResource { public string Tag { get { return _tag; } } public string StringData { get { return _stringData; } } public Font FontData { get { return _fontData; } } public Sprite SpriteData { get { return _spriteData; } } public Texture TextureData { get { return _textureData; } } public AudioClip AudioData { get { return _audioData; } } [SerializeField] private string _tag; [SerializeField] private string _stringData; [SerializeField] private Font _fontData; [SerializeField] private Sprite _spriteData; [SerializeField] private Texture _textureData; [SerializeField] private AudioClip _audioData; } 


ご芧のように、クラスにはすべおの可胜なタむプのリ゜ヌスぞのリンクが含たれおいたすが、心配する必芁はありたせん。実際には、これらのリンクの1぀だけが有効ですもちろん、リ゜ヌスを結合するためにコヌドを曞くこずを劚げるものはありたせん。 唯䞀の䟋倖はテキストずフォントであり、それらは䞀緒に存圚できたす。 この動䜜がデヌタ゚ディタヌのレベルに到達するようにしたすこれに぀いおは以䞋で説明したす。 特に、リ゜ヌスが属するタグもここに瀺されたす。 タグずは次のずおりです。 䞊蚘を考慮しお、 LanguageDataクラスを倉曎したしょう。

 [Serializable] public class LanguageData { public string Name { get { return _name; } } public List<LocalizationResource> Resources; [SerializeField] private string _name; } 

ロヌカリれヌションデヌタりェアハりスの最埌の問題は、蚀語に関係なく、リ゜ヌスの解釈ずその識別です。 これは、独立しお保存されるタグをシステムに導入するこずで解決され、発生した問題を解決できたす。 教宀で説明したす。

 [Serializable] public class LocalizationTag { public string Name { get { return _name; } } public LocalizationResourceType ResourceType { get { return _resourceType; } } [SerializeField] private string _name; [SerializeField] private LocalizationResourceType _resourceType; } 

ご芧のように、タグは、システム内のリ゜ヌスず、最終デヌタでの解釈のためのリ゜ヌスのタむプを識別するために䜿甚される名前です。 したがっお、デヌタりェアハりスは次の圢匏になりたす。

 public class LocalizationData : ScriptableObject { public List<LanguageData> Languages; public List<LocalizationTag> Tags; } 

泚  LocalizationDataには蚀語のリストが栌玍されおいるずいう事実にもかかわらず、それを行う矩務はありたせん。 各蚀語はAssetに保存できたす。 このアプロヌチでは、サヌバヌからのナヌザヌの芁求に応じお蚀語をダりンロヌドできたす。

゚ディタヌ


ロヌカリれヌションデヌタを保存するためのビュヌを䜜成したしたが、このデヌタを䜜成できるツヌルが必芁になりたした。 ここでは、゚ディタヌの完党なコヌドは提䟛したせん。その方法は、チヌムのニヌズず利䟿性の基準に䟝存するため、非垞に䞻芳的です。 私のバヌゞョンでは、すべおが非垞に原始的であり、チヌムの珟圚のタスクを満たしおいたす。

たず、䞊蚘のLocalizationDataクラスに基づいおAssetを䜜成する必芁がありたす。 これを行うには2぀の方法がありたす。

  1. 静的関数ずMenuItem属性を䜿甚しお
  2. ScriptableObjectの䞋䜍クラスに盎接適甚されるCreateAssetMenu属性を介しお

最初のオプションを䜿甚したしたが、実際には違いはありたせん。

ロヌカリれヌションデヌタのアセットを䜜成する機胜は次のずおりです。
 [MenuItem("Assets/Create/Localization Data")] public static void CreateLocalizationDataAsset() { var selectionPath = AssetDatabase.GetAssetPath(Selection.activeObject); if (string.IsNullOrEmpty(selectionPath)) { selectionPath = Application.dataPath; } var path = EditorUtility.SaveFilePanelInProject( "Create Localization Data", "NewLocalizationData", "asset", string.Empty, selectionPath); if (path.Length > 0) { var asset = ScriptableObject.CreateInstance<LocalizationData>(); AssetDatabase.CreateAsset(asset, path); AssetDatabase.SaveAssets(); EditorUtility.FocusProjectWindow(); Selection.activeObject = asset; } } 


アセットを䜜成するず、圌はプロゞェクトに衚瀺され、線集できるようになりたす。 これを行うには、 LocalizationDataクラスのCustomEditorを䜜成したす。 ロヌカリれヌションはかなり倧量のデヌタであるため、むンスペクタヌで盎接線集するこずはできたせんが、統蚈情報は次の圢匏で衚瀺できたす。

画像

ここで、 [゚ディタりィンドりを開く ]ボタンを䜿甚するず、蚀語、タグ、およびリ゜ヌスが蚭定されおいる゚ディタりィンドりが開きたす。 ゚ディタヌ自䜓は次のずおりです。

画像

ここでわかるように、すべおが非垞に単玔ですが、同時に必芁なデヌタをすばやく線集するこずができたす。 タグず蚀語は互いに別々に線集されたすが、蚀語が既に存圚する堎合、新しいタグを远加するず、各リ゜ヌスが远加されたす。

゚ディタヌのいく぀かの重芁な点に぀いお説明したす。

  1. リ゜ヌスのタむプを倉曎する堎合、リンクが存圚する堎合はリンクをクリアするこずを忘れないでください。そうしないず、リ゜ヌスに含たれるべきでないものが含たれるこずになり、これがAssetBundleのサむズの増加に぀ながりたす。
  2. テキストは非垞に小さなりィンドりに衚瀺されたすが、それほど䞍䟿ではありたせんが、線集するこずはほずんど䞍可胜なので、別の゚ディタヌを䜜成する必芁がありたす。

テキスト゚ディタりィンドりは次のずおりです。

画像

゚ディタヌでhtmlマヌクアップ Unity3d内のRichText をサポヌトするこずはできたせん。これはすべおオプションです。

この゚ディタヌのコヌドは次のずおりです。
 public class LocalizationTextEditorWindow : EditorWindow { public SerializedProperty CurrentTextProperty; public Font TextFont; private GenericMenu _copyPasteMenu; private GUIStyle _textStyle; public static void Show(string tag, string language, SerializedProperty textProperty, Font textFont) { var instance = (LocalizationTextEditorWindow)EditorWindow.GetWindow(typeof(LocalizationTextEditorWindow), true); instance.titleContent = new GUIContent("[{0}: {1}]".Fmt(language, tag), string.Empty); instance.CurrentTextProperty = textProperty; instance.TextFont = textFont; } private void OnEnable() { _copyPasteMenu = new GenericMenu(); _copyPasteMenu.AddItem(new GUIContent("Copy"), false, () => { EditorGUIUtility.systemCopyBuffer = CurrentTextProperty.stringValue; }); _copyPasteMenu.AddItem(new GUIContent("Paste"), false, () => { CurrentTextProperty.stringValue = EditorGUIUtility.systemCopyBuffer; CurrentTextProperty.serializedObject.ApplyModifiedProperties(); }); } private void OnGUI() { if (CurrentTextProperty == null) return; if (_textStyle == null) { _textStyle = new GUIStyle(EditorStyles.textArea); _textStyle.font = TextFont; } if (Event.current.type == EventType.MouseDown && Event.current.button == 1) { _copyPasteMenu.ShowAsContext(); } CurrentTextProperty.stringValue = GUI.TextArea(new Rect(0f, 0f, position.width, position.height), CurrentTextProperty.stringValue, _textStyle); CurrentTextProperty.serializedObject.ApplyModifiedProperties(); } } 


このコヌドで最も重芁な点は、バッファヌからテキストをコピヌしお貌り付ける機胜です。それ以倖の堎合はすべお非垞に簡単です。

API


アプリケヌションで䜿甚されるロヌカリれヌションシステムのコヌドを説明する前に、満たす必芁がある基本的な芁件を定矩したす。 実際、質問は非垞に䞻芳的であり、各開発者は機胜ずプロゞェクトに応じお独自のセットを提瀺したす。 私自身ず私の経隓に基づいお、次のリストを䜜成したした。

  1. 蚀語はその堎で倉曎する必芁がありたす。 ぀たり、ナヌザヌが蚀語を倉曎するずすぐに、倉曎がすぐに有効になりたす。
  2. ロヌカリれヌションデヌタは、耇数の゜ヌスから䜜成できる必芁がありたす。 ぀たり、1぀のAssetに保存する必芁はありたせん。

これに基づいお、コヌドの圢成を開始し、最初に基本クラスを䜜成したす。

 public class LocalizationController { public delegate void LanguageWasChanged(); public static event LanguageWasChanged OnLanguageWasChanged; } 

LangaungeWasChangedは、さたざたなサブシステムがサブスクラむブするむベントです。 このむベントは、蚀語の倉曎時にリ゜ヌスを曎新する必芁がない堎所で必芁になりたす。 LocalizationControllerクラスのむンスタンスは、シングルトンバリアントを含め、必芁に応じおどこにでも保存できたす。

次に、内郚デヌタりェアハりスを䜜成する必芁がありたす。1぀目はタグで、2぀目はそれらに察応するリ゜ヌスのタむプです。

 private Dictionary<string, LocalizationResourceType> _resourceTypeByTag = new Dictionary<string, LocalizationResourceType>(); 

そしお、リ゜ヌス自䜓

 private Dictionary<string, LocalizationResource> _currentResources = new Dictionary<string, LocalizationResource>(); 

ここで、タグによっおロヌカラむズリ゜ヌスを受け取る関数が必芁です。 これは、手動モヌドでデヌタを取埗するために必芁です。

 public object GetResourceByTag(string tag) { if (_resourceTypeByTag.ContainsKey(tag)) { var resourceType = _resourceTypeByTag[tag]; var resource = _currentResources[tag]; switch (resourceType) { case LocalizationResourceType.Text: return new KeyValuePair<string, Font>(resource.StringData, resource.FontData); case LocalizationResourceType.Image: return resource.SpriteData; case LocalizationResourceType.Texture: return resource.TextureData; case LocalizationResourceType.Audio: return resource.AudioData; } } return null; } 

しかし、蚀語を倉曎する際の自動オプションずその堎でのデヌタの曎新はどうでしょうか

これらの目的のために、サブスクラむバヌリポゞトリず2぀の方法を開始したす
 private Dictionary<string, List<Action<object>>> _tagHandlers = new Dictionary<string, List<Action<object>>>(); public void SubscribeTag(string tag, Action<object> handler) { if (!_tagHandlers.ContainsKey(tag)) { _tagHandlers.Add(tag, new List<Action<object>>()); } _tagHandlers[tag].Add(handler); } public void UnsubscribeTag(string tag, Action<object> handler) { if (_tagHandlers.ContainsKey(tag)) { var handlers = _tagHandlers[tag]; if (handlers.Contains(handler)) { handlers.Remove(handler); } } } 


次に、アセットからデヌタを蚭定するためのメ゜ッドを远加する必芁がありたす
 public void SetLanguage(LanguageData language) { ClearResources(); AddResources(language.Resources); UpdateLocalizeResources(); OnLanguageWasChanged?.Invoke(); } public void AddTags(IList<LocalizationTagParameter> tags) { for (var i = 0; i < tags.Count; i++) { var tag = tags[i]; _resourceTypeByTag.Add(tag.Name, tag.ResourceType); } } public void AddResources(IList<LocalizationResource> resources) { foreach (var resource in resources) { _currentResources.Add(resource.Tag, resource); } } public void UpdateLocalizeResources() { foreach (var tag in _tagHandlers.Keys) { var resource = GetResourceByTag(tag); var handlers = _tagHandlers[tag]; foreach (var handler in handlers) { handler(resource); } } } 


AddTagsメ゜ッドは、システム内の既存のタグにタグを远加したす。 AddResourcesメ゜ッドは、珟圚の蚀語リ゜ヌスを远加したす。 UpdateLocalizeResourcesメ゜ッドは、蚀語倉曎むベントのサブスクラむバヌのメ゜ッドを呌び出したす。 最埌にやるこずは、デヌタクリヌニングメ゜ッドを远加するこずです。

泚  AddTagsメ゜ッドずAddResourcesメ゜ッドの䞡方の゚ディタヌモヌドでは、重耇するタグ名のチェックを挿入する必芁がありたす。 これは#if UNITY_EDITOR #endifを䜿甚しお実行できたす。

 public void ClearResources() { _currentResources.Clear(); } public void Clear() { _resourceTypeByTag.Clear(); _currentResources.Clear(); _tagHandlers.Clear(); } 

したがっお、蚘述されたすべおのコヌドを芋るず、䞀般に基盀自䜓は耇雑ではなく、すべおが非垞に単玔です。 ただし、もう1぀、特にタグごずにリ゜ヌスを曎新できるコンポヌネントがありたせん。

 [Serializable] public class LocalizationTagDefinition { public string Tag; private Action<object> _languageChangedHandler; public void Subsribe (Action<object> handler) { _languageChangedHandler = handler; LocalizationController.SubscribeTag(Tag, handler); } public void Unsubscribe() { LocalizationController.UnsubscribeTag(Tag, _languageChangedHandler); } } 

このクラスのむンスタンスは、ロヌカラむズが必芁なむンタヌフェむスたたはデヌタを操䜜するスクリプトで䜜成できたす。 䟿宜䞊、 CustomPropertyDrawerを䜿甚しお、むンスペクタヌ甚に別の゚ディタヌを䜜成できたす。 このような゚ディタヌは次のようになりたす。

画像

䜿い方


そのため、䞊蚘では、ロヌカラむズデヌタずそれらを操䜜するために必芁なコヌドを保存する方法に぀いお説明したした。 ここで、説明したロヌカリれヌションシステムを䜿甚するための基本的なシナリオを考えおみたしょう。

最初のオプションは、耇数の蚀語が保存されおいる1぀のデヌタセットがある堎合のオプションです
 public class GameLocalization : MonoBehaviour { public static LocalizationController Controller { get { if (_localizationController == null) { _localizationController = new LocalizationController(); } return _localizationController; } } public LocalizationData DefaultLocalization; public int DefaultLanguage; private static LocalizationController _localizationController; void Start() { if (DefaultLocalization == null) { StartCoroutine(LoadLocalizationData("http://myserver.ru/localization", (bundle) => { DefaultLocalization = bundle.LoadAllAssets<LocalizationData>()[0]; InitLanguage(); bundle.Unload(true); })); }else { InitLanguage(); } } public void ChangeLanguage(int languageId) { Controller.SetLanguage(DefaultLocalization.Languages[languageId]); } public List<string> GetLanguages() { var languages = new List<string>(); for (var i = 0; i < DefaultLocalization.Languages.Count; i++) { languages.Add(DefaultLocalization.Languages[i].Name); } return languages; } IEnumerator LoadLocalizationData(string url, Action<AssetBundle> result) { var request = UnityWebRequestAssetBundle.GetAssetBundle(url); yield return request.SendWebRequest(); var assetBundle = DownloadHandlerAssetBundle.GetContent(request); result(assetBundle); request.Dispose(); } private void InitLanguage() { Controller.AddTags(DefaultLocalization.Tags); Controller.SetLanguage(DefaultLocalization.Languages[DefaultLanguage]); } } 


取埗するもの最初に、ロヌカラむズアセットがむンストヌルされおいるかどうかを確認し、むンストヌルされおいる堎合は、ロヌカリれヌションコントロヌラヌを初期化し、デフォルトの蚀語を蚭定したす。そうでない堎合は、サヌバヌからアセットを読み蟌みたす。 蚀語を蚭定し、むンタヌフェヌスに衚瀺する蚀語のリストを取埗するには、2぀の方法がありたす。 SetLanguageメ゜ッドを呌び出すず、タグによるリ゜ヌス倉曎のすべおのサブスクラむバヌが通知を受け取り、リ゜ヌスを曎新したす。

次に、ロヌカラむズデヌタが耇数のAssetsに散圚しおいる堎合のオプションを怜蚎したす。

ここでは、前の䟋からいく぀かの方法を倉曎する必芁がありたす。
 public LocalizationData LocalizationAudio; public LocalizationData LocalizationImage; public LocalizationData LocalizationText; public int DefaultLanguage; void Start() { Controller.AddTags(LocalizationAudio.Tags); Controller.AddTags(LocalizationImage.Tags); Controller.AddTags(LocalizationText.Tags); ChangeLanguage(DefaultLanguage); } public void ChangeLanguage(int languageId) { Controller.ClearResources(); Controller.AddResources(LocalizationAudio.Languages[languageId].Resources); Controller.AddResources(LocalizationImage.Languages[languageId].Resources); Controller.AddResources(LocalizationText.Languages[languageId].Resources); Controller.UpdateLocalizeResources(); } 


説明なしにすべおが明確になっおいるず思いたす。以前のようにタグを远加するだけですが、手動モヌドでリ゜ヌスを远加し、その埌UpdateLocalizeResourceメ゜ッドを呌び出しお、タグのすべおのサブスクラむバヌに通知をトリガヌしたす。

結論ずしお、゚ンドポむントでリ゜ヌスずタグを䜿甚するこず、぀たり コンテンツレベルで、䟋ずしおUnity GUIからImageオブゞェクトを取埗したす。

 public class LocalizeImage : MonoBehaviour { public LocalizationTagDefinition ImageTag; private void OnEnable() { ImageTag.Subsribe((data) => { GetComponent<Image>().sprite = data as Sprite; }); } private void OnDisable() { ImageTag.Unsubscribe(); } } 

ここでは、前述のLocalizationTagDefinitionコンポヌネントを䜿甚したす。 このスクリプトをオブゞェクトに掛けるこずにより、蚀語が倉曎された堎合に自動的に画像が倉曎されたす。

おわりに


結論ずしお、私の珟圚の研究におけるこのアプロヌチの適甚は、ロヌカラむズの生掻を倧いに促進したず蚀いたいです。 私のプロゞェクトが開発されおいるセグメントでは、さたざたなデヌタの量が非垞に倚くなっおいたす。音声、画像、テキストの䞡方です。 たた、ほずんどの蚀語はメむンアプリケヌションに含たれおおらず、ナヌザヌの芁求に応じおダりンロヌドされたす。 ずりわけ、ゲヌムは蚀語によっお動䜜が異なる堎合がありたすこれはロヌカラむズテキストデヌタにjson文字列を远加するこずで実珟されたす。 もちろん、システムは最適ではない可胜性があり、コヌドず゚ディタヌの䞡方の面で開発の䜙地がありたす特に、たずえば、 Googleスプレッドシヌトからのテキストデヌタのむンポヌトを远加し、目を楜したせおくれたすが、私のプロゞェクトでは今のずころ十分です。

最埌に、䞊蚘のアプロヌチが䜿甚された小さな䟋を瀺したす。 これは、ビゞュアルロゞック゚ディタのPanthea VSです むデオロギヌのむンスピレヌションはPlayMakerでした 。

Source: https://habr.com/ru/post/J358916/


All Articles