问题及难点
相信做海外游戏代理的同学一定会遇到需要做本地化的问题,其中资源可以通过替换合图来处理。而文本是其中一个比较难处理的问题。其中主要难点在于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一哈)