問題及難點
相信做海外遊戲代理的同學一定會遇到需要做本地化的問題,其中資源可以通過替換合圖來處理。而文本是其中一個比較難處理的問題。其中主要難點在於UGUI本身沒有提供相應的插件,而對於一開始沒規劃的項目來說,你需要去找出其中所有的外語文本,找到後再將對應Text
控件替換成你自定義的控件,替換後再需要填入對應的Key值。這對於策劃來說簡直是惡夢~
工具的基本原理
LocalizationText控件
UGUI中的Text
控件只提供了text屬性接口供你進行對文本的設置,那麼要實現本地化有下面兩種做法:
- 1.在
Text
控件初始化後使用代碼將其text
屬性修改,這樣的話,我們需要在所有的有Text
控件的地方添加相應的修改代碼,這顯然是不符合可持續發展的。 - 2.自定義新的
Text
控件,在編輯器階段完成對Key值的設置。這種方案需要我們繼承Text
控件,並修改其輸入接口,從修改text
改爲修改Key
,通過Key來得到相應語言的文本。
綜上,我們採取第二種方案來實現我們的LocalizationText
控件,通過對Key的設置得到文本,如圖:
有了LocaliztionText
我們只需要輸入Key**Help**而不需要直接填寫幫助二字,通過爲不同語言建立映射表我們就可得到對應語言的Help的文本。
相關代碼實現
這裏我主要是先去看了UGUI源碼,根據其中Text的實現做了修改。
using System;
using UnityEngine.UI;
using UnityEngine;
namespace Localization
{
public class LocalizationText : Text
{
#region Param
[Header("Localization")]
[SerializeField]
protected string m_KeyString;
public string keyString {
get {return this.m_KeyString; }
set { this.m_KeyString = value; }
}
#endregion
#region Override Part
public override string text
{
get
{
if (!ClientString.LocalizationDict.ContainsKey(keyString))
{
m_Text = string.Format("[{0}]",m_KeyString);
}
else
{
m_Text = ClientString.LocalizationDict[keyString];
}
return m_Text;
}
set
{
if (String.IsNullOrEmpty(value))
{
if (String.IsNullOrEmpty(m_Text))
return;
m_Text = "";
SetVerticesDirty();
}
else if (m_Text != value)
{
m_Text = value;
SetVerticesDirty();
SetLayoutDirty();
}
}
}
#endregion
}
}
這部分的代碼其實比較簡單,主要是進行了一個Key/Value的映射,然後設置對應的Text。其中的KeyString則是多語言文本映射用的Key值。
總的來說,此控件的原理是比較簡單的,Key->Value->Set Text。但對於一個完整的方案來說這是遠遠不夠的,我們還需要有自動化修改的工具。
控件替換工具
這個工具簡直是那些用UGUI的Text控件開發了很久後,突然想做多語言的項目的救星。因爲如果你通過將項目中的Text
控件刪除後再AddComponent< LocalizationText >的話會出現一個很災難性的問題,那就是MonoBehaviour
中對相應Text
控件的引用都會丟失,因爲Unity是通過GUID和FileID來找到對應類型控件的,而這樣的處理方式會讓GUID和FileID改變從而導致嚴重的後果。所以這個控件替換工具就非常重要了~
原理
上面說到Unity中用GUID和FileID來找對應類型控件的,那麼只需要找到初始Text
控件的GUID和FileID,然後更改爲LocaliztionText
控件的GUID和FileID則可以達到我們的目的。下面是一個場景文件(.unity)文件的內容,我們在場景文件中添加了一個物體名叫Normal Text上面掛着Text
控件。
中間的圖片表示的是Normal Text這個物體的結構,他有三個m_Component
其中有一個是Text
,Text控件的結構如圖,所以在右邊Text Component部分的m_Script
處,只要把FileID和GUID修改爲Localization Text對應的FileID和GUID即可,這樣就避免了上面提到的引用丟失問題。那麼怎麼得到Localiztion Text的GUID和FileID呢。最簡單的方法是添加一個Localization Text物體(也就是現在場景中的Localization Text),然後去場景文件內容中查看。
所以此工具的基本原理就是將Text控件的FileID和GUID換成LocaliztionText控件的FileID和GUID。
具體代碼如下(基本就是一個文本級別的操作):
public static void UpgradeToLocalizationText(string assetPath, string textCompGUID, string textCompFileID, string localizeCompGUID, string localizeCompFileID, List<string> jpTextList)
{
ClearConsole();
string formatedGUID = string.Format("guid: {0},", localizeCompGUID);
string fullPath = Path.Combine(Application.dataPath.Substring(0, Application.dataPath.LastIndexOf('/')), assetPath);
string[] lines = System.IO.File.ReadAllLines(fullPath);
string fullText = System.IO.File.ReadAllText(fullPath);
for (int i = 0; i < lines.Length; i++)
{
if(lines[i].Contains(textCompFileID)) {
int scriptLineIdx = i; //往下遍歷直到找到m_Text
while(i < lines.Length) {
if(lines[i].Contains("--- !u!")) //發現下一個控件時跳出
break;
if (JpUtil.PlainText_IsContainsJapanese(lines[i]))
{
string jpText = Regex.Unescape(lines[i].Split('\"')[1]).Replace("\n", "\\n");
jpTextList.Add(jpText);
//發現了FileID相同 但GUID不同的情況,所以直接把FileID後面的GUID替換
lines[scriptLineIdx] = Regex.Replace(lines[scriptLineIdx], guidReplacePattern, formatedGUID);
lines[scriptLineIdx] = lines[scriptLineIdx].Replace(textCompFileID, localizeCompFileID);
break;
}
i++;
}
}
}
string text = string.Join("\n", lines);
System.IO.File.WriteAllText(fullPath, text);
AssetDatabase.Refresh();
}
優化方案
上面的控件及工具解決了這一方案中的兩個難點,但是我們還可以進行優化,就是對有需要變化(例如內容爲日文或者英文)的控件才進行轉化,這其中的處理就是
JpUtil.PlainText_IsContainsJapanese(lines[i])
這一處理會判斷文本中是否有日文,有的話纔對Text控件進行升級,所以你需要爲不同的語言做不同的文本判斷,我們項目爲日文所以我做了日文的判斷。
除此之外在策劃將對應的翻譯都處理好後需要來設置LocaliztionText
的Key值,我新增了一個自動設置Key值的功能,原理是將其中的內容進行比對,用Value找到Key值,然後填入。
這個工具已經上傳到GitHub:Localization Text Tool (麻煩Star一哈)