前言
此模块是用于语言本地化,可以让开发出的游戏走向国际化,接下就来看看Gameframework是如何实现语言切换的。
1.本地化原理
首先看一下作者提供的Star Force工程里,可以看到有两个地方出现了解析本地化配置文件,在游戏最先启动流程里(ProcedureLaunch)还有一个是游戏预制体加载流程里(ProcedurePreload),如图所示:
函数目的都是解析xml文件然后按照键值对的形式添加到Dictionary<string,string>,它会调用LocalizationComponent组件下ParseDictionary或者LoadDictionary去解析文件,LocalizationComponent下有默认本地文件解析助手,默认提供的解析文件助手支持的格式是回车表示分组,Table按键表示Key和Value分开,具体的判断标识如下:
private static readonly string[] RowSplitSeparator = new string[] { "\r\n", "\r", "\n" };
private static readonly string[] ColumnSplitSeparator = new string[] { "\t" };
//资源格式
key1 value1
key2 value2
StarForce工程里的本地化配置文件是Xml格式,所以扩展出了一个XmlLocalizationHelper辅助器脚本用来解析xml配置文件,找到本地化组件(LocalizationComponent)去选择XmlLocalizationHelper脚本即可,如图所示:
以后需要扩展json解析器怎么办呢?所以观察一下本地化辅助器脚本(XmlLocalizationHelper)是如何扩展出来的,具体代码如下:
using System;
using System.Xml;
using UnityGameFramework.Runtime;
namespace StarForce
{
/// <summary>
/// XML 格式的本地化辅助器。
/// </summary>
public class XmlLocalizationHelper : DefaultLocalizationHelper
{
/// <summary>
/// 解析字典。
/// </summary>
/// <param name="text">要解析的字典文本。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否解析字典成功。</returns>
public override bool ParseDictionary(string text, object userData)
{
try
{
string currentLanguage = GameEntry.Localization.Language.ToString();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(text);
XmlNode xmlRoot = xmlDocument.SelectSingleNode("Dictionaries");
XmlNodeList xmlNodeDictionaryList = xmlRoot.ChildNodes;
for (int i = 0; i < xmlNodeDictionaryList.Count; i++)
{
XmlNode xmlNodeDictionary = xmlNodeDictionaryList.Item(i);
if (xmlNodeDictionary.Name != "Dictionary")
{
continue;
}
string language = xmlNodeDictionary.Attributes.GetNamedItem("Language").Value;
if (language != currentLanguage)
{
continue;
}
XmlNodeList xmlNodeStringList = xmlNodeDictionary.ChildNodes;
for (int j = 0; j < xmlNodeStringList.Count; j++)
{
XmlNode xmlNodeString = xmlNodeStringList.Item(j);
if (xmlNodeString.Name != "String")
{
continue;
}
string key = xmlNodeString.Attributes.GetNamedItem("Key").Value;
string value = xmlNodeString.Attributes.GetNamedItem("Value").Value;
if (!AddRawString(key, value))
{
Log.Warning("Can not add raw string with key '{0}' which may be invalid or duplicate.", key);
return false;
}
}
}
return true;
}
catch (Exception exception)
{
Log.Warning("Can not parse dictionary '{0}' with exception '{1}'.", text, exception.ToString());
return false;
}
}
}
}
只是重写解析字典函数,读取文件格式确实可以解析xml格式的,所以就可以按照同样的方式去扩展出json解析器,继承DefaultLocalizationHelper重写解析函数,其他函数或者属性需要调整的也可以重写,解析器设置成功以后,会通过反射在本地化管理器下的辅助器创建成XmlLocalizationHelper对象,接下来就是通过Key获取到字典的Value,所以这里盲目猜测所有预制体只要存在文本组件的都应该设置成字典的Key,随便查看一下主界面预制体,具体如图所示:
确实设置成字典的Key了,至于这个字典对应着什么value,找到Assets\GameMain\Localization路径去看一下配置文件具体是什么,再查看一下框架具体何时通过key获取到value的,可以看到UGuiForm脚本的OnInit初始化函数具体如下:
protected override void OnInit(object userData)
#else
protected internal override void OnInit(object userData)
#endif
{
base.OnInit(userData);
m_CanvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
RectTransform transform = GetComponent<RectTransform>();
transform.anchorMin = Vector2.zero;
transform.anchorMax = Vector2.one;
transform.anchoredPosition = Vector2.zero;
transform.sizeDelta = Vector2.zero;
Text[] texts = GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
texts[i].font = s_MainFont;
if (!string.IsNullOrEmpty(texts[i].text))
{
texts[i].text = GameEntry.Localization.GetString(texts[i].text);
}
}
}
获取到所有Test组件,通过GameEntry.Localization.GetString(texts[i].text)把原本的key替换成value了,本地化的基本原理差不多就讲述完了。
2.为何不能合并解析步骤
可以看到小节一里,把解析语言配置步骤出现在2个地方,有没有想过为什么需要把它拆解成两个步骤去解析配置文件呢?假设全部在启动流程里直接全部解析一次会怎么样呢?这样本地化配置文件资源热更新就会失效,很明显的问题,因为先执行的启动流程之后才是开始版本热更新(热更新完成以后会没有如何反应,重启一次才可以),先把没有更新的资源加载到字典里后面资源更新了也没有去刷新它,这样就在资源版本热更新以后再去进行解析一次本地化配置文件,大家可能忘记了在资源版本检查热更新之前的那些流程可能就需要用到本地化组件的功能,比如启动界面或者资源版本检查界面都需要进行本地化,但是它们的顺序全部在资源热更新之前,你们现在是不是就一个想法。
所以在ProcedureLaunch加载的本地化配置文件是不支持热更新的,你可能会想有没有什么解决方案去解决这个问题,嗯...解决方案我还真的想不到,除非在资源版本更新之前都不用本地化组件的功能,比如资源版本更新界面不提示文本,就提供一个进度条或者文本显示定死(比如Loading 80%)。