在使用Unity開(kāi)發(fā)游戲的過(guò)程中,本地化是必不可少的。網(wǎng)絡(luò)上也有很多的本地化工具,本次我介紹的是Unity官方提供的Localization插件,大家可以在Package Manager進(jìn)行安裝
?一、語(yǔ)言配置,本地化表創(chuàng)建
在Project Setting中找到Localization,(需要先創(chuàng)建這個(gè)Localization Setting文件)點(diǎn)擊Locale Generator選擇需要本地化的語(yǔ)言。創(chuàng)建好后會(huì)得到這些文件,這些文件可以用于切換語(yǔ)言(后面做切換語(yǔ)言界面時(shí)會(huì)用),先把英語(yǔ)拖入作為默認(rèn)語(yǔ)言。
?打開(kāi)本地化表工具,創(chuàng)建本地化表
?創(chuàng)建UILocalization和ScriptLocalization兩個(gè)本地化表,分別用作UI和代碼本地化。會(huì)得到以下文件
?可以在表格中添加一些需要本地化的文本(這里只是先試試,先不要添加太多,后面會(huì)用Excel進(jìn)行管理),注意最左側(cè)的Key值,這個(gè)key值后續(xù)用來(lái)確定文本
?
二、使用Localize String Event進(jìn)行本地化
?給UI上的文本掛載LocalizeStringEvent腳本,點(diǎn)擊其中最上面的String Reference搜索之前表格中創(chuàng)建的本地化文本。搜索Key值或者本地化的文本都可以搜得到。
注:如果顯示不完整,可以調(diào)節(jié)右下角的小球
然后將LocalizeStringEvent的Update String調(diào)整為T(mén)ext.text,就是要刷新的腳本。任何的string都可以刷新,Text Mesh Pro也是可以的
?運(yùn)行游戲查看效果,可以臨時(shí)先使用右上角的下拉框調(diào)節(jié)本地化語(yǔ)言,界面上的文本會(huì)實(shí)時(shí)刷新。
?
三、UI本地化
?我先將本地化的需求暫時(shí)分成兩類(lèi)
1,UI本地化:UI界面上面固定不變的文本
2,Script本地化:代碼中實(shí)時(shí)更改的文本,包括(String.Format+數(shù)值),動(dòng)態(tài)彈出的提示語(yǔ),NPC對(duì)話,物品名字等
為了后續(xù)教程,我們暫時(shí)約定在制作界面時(shí),UI本地化的內(nèi)容正常命名,Script本地化的對(duì)象以$開(kāi)頭命名,例如:
首先,需要知道有哪些文本需要本地化,并給它們自動(dòng)掛載上LocalizeStringEvent腳本??梢栽贓ditor目錄下創(chuàng)建一個(gè)擴(kuò)展腳本實(shí)現(xiàn)此功能。
using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using UnityEngine.Events; using UnityEngine.Localization; using UnityEngine.Localization.Components; using UnityEngine.UI; public class LocalizeTextEditor : EditorWindow { private string outputFilePath = "LocalizedText.txt"; // 指定的txt文件路徑 private List<string> localizedTextEntries = new List<string>(); [MenuItem("Custom/Localize Text")] private static void ShowWindow() { GetWindow<LocalizeTextEditor>("Localize Text"); } private void OnGUI() { GUILayout.Label("Localize Text Editor", EditorStyles.boldLabel); if (GUILayout.Button("Localize Selected GameObjects")) { LocalizeSelectedGameObjects(); } if (GUILayout.Button("Save Localized Text to File")) { SaveLocalizedTextToFile(); } if (GUILayout.Button("Print Script Localize Selected GameObjects")) { PrintScriptLocalize(); } } private void LocalizeSelectedGameObjects() { localizedTextEntries.Clear(); GameObject[] selectedObjects = Selection.gameObjects; foreach (GameObject selectedObject in selectedObjects) { Text[] textComponents = selectedObject.GetComponentsInChildren<Text>(true); foreach (Text textComponent in textComponents) { if (!textComponent.name.StartsWith("$")) { // 需要本地化的Text if(textComponent.gameObject.GetComponent<LocalizeStringEvent>() == null) { LocalizeStringEvent localizeEvent = textComponent.gameObject.AddComponent<LocalizeStringEvent>(); // 標(biāo)記對(duì)象為“已修改” EditorUtility.SetDirty(selectedObject); } // 添加到列表 string entry = $"{selectedObject.name}\t{textComponent.name}\t{textComponent.text}"; localizedTextEntries.Add(entry); } } } } private void SaveLocalizedTextToFile() { if(!File.Exists(outputFilePath)) { File.Create(outputFilePath).Dispose(); } using (StreamWriter writer = new StreamWriter(outputFilePath, true)) { foreach (string entry in localizedTextEntries) { writer.WriteLine(entry); } } Debug.Log($"Localized text entries saved to {outputFilePath}"); } private void PrintScriptLocalize() { GameObject[] selectedObjects = Selection.gameObjects; foreach (GameObject selectedObject in selectedObjects) { Text[] textComponents = selectedObject.GetComponentsInChildren<Text>(true); foreach (Text textComponent in textComponents) { if (textComponent.name.StartsWith("$")) { Debug.Log($"{selectedObject.name}\t{textComponent.name}\t{textComponent.text}"); } } } } }
我本來(lái)還想自動(dòng)給LocalizeStringEvent的Update String賦值,但是未能實(shí)現(xiàn)。如果哪位高人有辦法可以在評(píng)論區(qū)指出
?全選需要本地化的UI預(yù)制體,依次點(diǎn)擊擴(kuò)展窗口的第1和第2個(gè)按鈕,可以給非$開(kāi)頭的Text(你們?nèi)绻?guī)則不一樣,請(qǐng)自行修改腳本)自動(dòng)掛載LocalizeStringEvent腳本
這些Text中的內(nèi)容會(huì)輸出到txt中,可以查看(注:txt默認(rèn)應(yīng)該在項(xiàng)目根路徑)
?
?四、使用Excel表格管理本地化文本
?右鍵點(diǎn)擊本地化表的選項(xiàng)卡,選擇導(dǎo)出CSV
?如果使用Office,導(dǎo)出時(shí)選擇UTF-8即可。如果和我一樣使用WPS,請(qǐng)另存為xlsx格式,不然部分語(yǔ)言會(huì)亂碼。
將之前txt中的內(nèi)容復(fù)制到表格中,由于我輸出的是\t,內(nèi)容會(huì)自動(dòng)分布到表格的不同列,大家可以把根據(jù)預(yù)制體名字來(lái)給Key起名。
丟給AI或者翻譯軟件翻譯之后,對(duì)于沒(méi)有空格的語(yǔ)言,可以使用alt+回車(chē)在適當(dāng)位置輸入換行,例如:中文,日語(yǔ)等。Unity的自適應(yīng)換行只會(huì)在有空格的地方換行
對(duì)于WPS用戶,我們需要將xlsx轉(zhuǎn)回CSV才能在Unity中讀取,這里使用python的pandas庫(kù)進(jìn)行處理。
import pandas as pd # 讀取 Excel 文件 df = pd.read_excel("UILocalization.xlsx", sheet_name="UILocalization") # 將 DataFrame 寫(xiě)入 CSV 文件 df.to_csv('dataUI.csv', encoding='utf-8', index=False) df2 = pd.read_excel("ScriptLocalization.xlsx", sheet_name="ScriptLocalization") df2.to_csv('dataScript.csv', encoding='utf-8', index=False)
這里還處理了后面的Script本地化文件,大家還沒(méi)看完后續(xù)教程的,可以先注釋后兩行代碼
在本地化表導(dǎo)出CSV的上面還有導(dǎo)入CSV,這里就不截圖了,導(dǎo)入之后就可以得到我們剛才在Office或WPS中編輯的表格
?
五、Script本地化
還是全選所有UI預(yù)制體,這次點(diǎn)擊第三個(gè)按鈕,會(huì)在控制臺(tái)中輸出所有$開(kāi)頭的Text。這些Text都是在代碼中更改內(nèi)容的,只能對(duì)其查找所有引用,然后逐個(gè)更改。
那么,如何在代碼中獲取本地化的內(nèi)容呢,我們需要?jiǎng)?chuàng)建一個(gè)單例的LocalizationManager,提供一個(gè)獲取本地化內(nèi)容的函數(shù)。
?以防止有編程小白,這里介紹一下單例模式,單例模式就是類(lèi)最多只有一個(gè)對(duì)象,并且有一個(gè)指向該對(duì)象的靜態(tài)指針。在Unity中可以用以下代碼實(shí)現(xiàn)一個(gè)簡(jiǎn)單的單例模式
using UnityEngine; public class Singleton<T> : MonoBehaviour where T : Singleton<T> { protected static T _instance; public static T Ins { get { return _instance; } } protected virtual void Awake() { _instance = (T)this; } protected virtual void OnDestroy() { if (_instance == this) { _instance = null; } } }
在場(chǎng)景中創(chuàng)建一個(gè)(DontDestroyOnLoad)不會(huì)銷(xiāo)毀的對(duì)象,并掛載LocalizationManager腳本
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.ResourceManagement.AsyncOperations; namespace UI { public class LocalizationManager : Singleton<LocalizationManager> { private StringTable ScriptStringTable; //代碼本地化表 void Start() { if (LocalizationSettings.AvailableLocales.Locales.Count > 0) { GetLocalizationTable(); } else { LocalizationSettings.InitializationOperation.Completed += OnLocalizationInitialized; } LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged; } protected override void OnDestroy() { base.OnDestroy(); LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged; } /// <summary> /// 本地化初始化完成 /// </summary> private void OnLocalizationInitialized(AsyncOperationHandle<LocalizationSettings> handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { Debug.Log("Localization initialized successfully!"); GetLocalizationTable(); } else { Debug.LogError("Localization initialization failed."); } } /// <summary> /// 切換語(yǔ)言 /// </summary> private void OnLocaleChanged(Locale newLocale) { GetLocalizationTable(); } /// <summary> /// 獲取本地化表 /// </summary> public void GetLocalizationTable() { ScriptStringTable = LocalizationSettings.StringDatabase.GetTable("ScriptLocalization"); //Debug.LogWarning(ScriptStringTable.GetEntry("CommonTip_NoItem").GetLocalizedString()); } /// <summary> /// 獲取本地化文本 /// </summary> public string GetLocalizedString(string key) { return ScriptStringTable.GetEntry(key).GetLocalizedString(); } } }
這里我們注冊(cè)了兩個(gè)事件,一個(gè)是LocalizationSettings.InitializationOperation.Completed,這個(gè)是LocalizationSettings初始化完成時(shí)調(diào)用。由于本地化插件是異步初始化,代碼運(yùn)行到start時(shí)不一定初始化完成,此處通過(guò)判斷可用語(yǔ)言是否大于0來(lái)判斷有沒(méi)有初始化完成。
另一個(gè)事件是LocalizationSettings.SelectedLocaleChanged,這個(gè)是LocalizationSettings切換語(yǔ)言是調(diào)用。這兩個(gè)事件都會(huì)執(zhí)行獲取本地化表的操作。對(duì)于之前在代碼中更改的Text,可用調(diào)用GetLocalizedString來(lái)獲取本地化文本。
例如:
物品名字要進(jìn)行本地化,key值是GameItem_ID,每個(gè)物品ID不同
和之前使用表格管理UILocalization表類(lèi)似,我們也使用表格管理ScriptLocalization表。
?
六、語(yǔ)言切換界面
創(chuàng)建一個(gè)這樣的UI界面,我設(shè)計(jì)的是每個(gè)按鈕都能點(diǎn)。當(dāng)前使用的語(yǔ)言是綠的。
給每個(gè)按鈕拖上一個(gè)Locale,就是之前最開(kāi)始創(chuàng)建的用于切換語(yǔ)言的
/// <summary> /// 刷新按鈕狀態(tài) /// </summary> public void RefreshItemChooseState() { Locale currentLocale = LocalizationSettings.SelectedLocale; foreach (var item in languageItems) { item.SetChooseState(currentLocale == item.locale); } } /// <summary> /// 設(shè)置語(yǔ)言 /// </summary> public void SetLanguage(Locale locale) { if(locale == LocalizationSettings.SelectedLocale) { return; } LocalizationSettings.Instance.SetSelectedLocale(locale); Client.Ins.Player.PlayerLanguage = locale.LocaleName; RefreshItemChooseState(); }
獲取當(dāng)前語(yǔ)言:LocalizationSettings.SelectedLocale,獲取之后和每個(gè)按鈕上面拖入的Locale比對(duì),一樣的就是當(dāng)前語(yǔ)言
設(shè)置語(yǔ)言:LocalizationSettings.Instance.SetSelectedLocale(locale);把拖入的Locale設(shè)置進(jìn)去,就可以切換語(yǔ)言。
?
以下為拓展內(nèi)容:
設(shè)置完語(yǔ)言之后,最好能夠保存下來(lái),用戶下次啟動(dòng)自動(dòng)使用。這里把locale.LocaleName,也就是語(yǔ)言的名字保存下來(lái)。
大家可以用任意方式保存一個(gè)string,我這邊用的json,這里就涉及到游戲存檔設(shè)計(jì)了,超出了本文的討論范圍。大家可以自行設(shè)計(jì)游戲存檔,反正能夠存取當(dāng)前語(yǔ)言名字(public string PlayerLanguage)就行了
?這里仍然使用了之前提到的LocalizationSettings.InitializationOperation.Completed事件,在LocalizationSettings初始化完成之后設(shè)置為保存的語(yǔ)言。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-747652.html
/// <summary> /// 本地化初始化完成 /// </summary> private void OnLocalizationInitialized(AsyncOperationHandle<LocalizationSettings> handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { Debug.Log("Localization initialized successfully!"); UseSaveLanguage(); } else { Debug.LogError("Localization initialization failed."); } } /// <summary> /// 使用保存的語(yǔ)言 /// </summary> private void UseSaveLanguage() { var locales = LocalizationSettings.AvailableLocales.Locales; foreach (var locale in locales) { if (locale.LocaleName == PlayerLanguage) { LocalizationSettings.Instance.SetSelectedLocale(locale); break; } } }
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-747652.html
到了這里,關(guān)于使用Unity Localization插件進(jìn)行項(xiàng)目本地化實(shí)戰(zhàn)詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!