UGF框架本地化在使用时,有三个问题,第一个是策划一般会用表格编辑本地化语言,而框架解析的是Xml文件,缺一个Excel转xml的工具。第二个问题是,编辑器编辑情况下,UI预制件的文本是key值,看不到文字显示后的效果,只有运行时才会动态改变文字,调试起来比较麻烦。第三个是文本的字体不能批量修改。本文就是为了解决这几个问题,做的扩展。
Excel读表插件下载链接,下载后跟框架的ICSharpCode.SharpZipLib插件会冲突,可以删除框架的。

1、Excel转Xml

主要有两个脚本:ExcelConfig.cs,ExcelBuild.cs。语言表的形式如下配置了四种语言,跟框架是一致的。

Unity实现表格效果 unity 表格插件_excel


脚本中路径有所改动,与框架路径不一致,使用时需注意路径。

ExcelConfig.cs脚本如下:

using System.Collections.Generic;
using System.Data;
using System.IO;
using Excel;
using UnityEngine;

public class ExcelConfig
{
    /// <summary>
    /// 存放excel表文件夹的的路径,本例Excel表放在了"Board/配置表/"当中,Board路径与Assets路径同级
    /// </summary>
    public static readonly string excelsFolderPath = Application.dataPath + "/../Board/配置表/MK_Language.xlsx";

    /// <summary>
    /// 存放Excel转化后文件的文件夹路径
    /// </summary>
    public static string assetPath(string language) 
    {
        return $"Assets/Scripts/Data/Localization/{language}/Dictionaries/Default.xml";
    }
}

/// <summary>
/// 处理Excel工具类
/// </summary>
public class ExcelTool
{
    /// <summary>
    /// 读取表数据,生成对应的数组
    /// </summary>
    /// <param name="filePath">excel文件全路径</param>
    /// <returns>Item数组</returns>
    public static ExcelItem[] CreateItemArrayWithExcel(string filePath)
    {
        //获得表数据
        int columnNum = 0, rowNum = 0;
        Debug.Log(filePath);
        DataRowCollection collect = ReadExcelContext(filePath, ref columnNum, ref rowNum);
        Debug.Log("读取到数据表 列数 columnNum : " + columnNum + " ,行数 rowNum: " + rowNum);
        // 第一行是标题(头)
        //for (int i = 0; i < columnNum; i++)
        //{ 
        //    rowTitleList.Add(collect[0][i].ToString());
        //}
        //第二行开始才是数据
        List<ExcelItem> array = new List<ExcelItem>();
        ExcelItem title = new ExcelItem();
        //解析标题数据
        title.key = collect[1][0].ToString();
        title.Language = new string[4];
        title.Language[0] = collect[1][1].ToString();
        title.Language[1] = collect[1][2].ToString();
        title.Language[2] = collect[1][3].ToString();
        title.Language[3] = collect[1][4].ToString();
        array.Add(title);
        //解析内容数据
        for (int i = 4; i < rowNum; i++)
        {
            if (collect[i][0].ToString()==null|| collect[i][0].ToString() == "")
            {
                continue;
            }
            ExcelItem item = new ExcelItem();
            //解析每列的数据
            item.key = collect[i][0].ToString();
            item.Language = new string[4];
            item.Language[0] = collect[i][1].ToString();
            item.Language[1] = collect[i][2].ToString();
            item.Language[2] = collect[i][3].ToString();
            item.Language[3] = collect[i][4].ToString();
            array.Add(item);
        }
        return array.ToArray();
    }

    /// <summary>
    /// 读取excel文件内容
    /// </summary>
    /// <param name="filePath">文件路径</param>
    /// <param name="columnNum">行数</param>
    /// <param name="rowNum">列数</param>
    /// <returns></returns>
    static DataRowCollection ReadExcelContext(string filePath, ref int columnNum, ref int rowNum)
    {
        FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        //Debug.Log(stream == null);
        IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
        DataSet result = excelReader.AsDataSet();
        // Tables[0] 下标0表示excel文件中第一张表的数据
        columnNum = result.Tables[0].Columns.Count;
        rowNum = result.Tables[0].Rows.Count;
        return result.Tables[0].Rows;
    }
}

/// <summary>
/// 数据序列化类 -- 和Excel列一一对应
/// </summary>
[System.Serializable]
public class ExcelItem
{
    public string key;
    public string[] Language;
}

/// <summary>
/// 表数据管理器 -- 存最后的数据集
/// </summary>
public class ExcelManager : ScriptableObject
{
    public ExcelItem[] dataArray;
}

ExcelBuild.cs脚本如下

using System.Xml;
using UnityEditor;
// <summary>
/// 编辑器扩展 将xlsx文件转换Xml
/// </summary>
public class ExcelBuild : Editor
{
    /// <summary>
    /// 转换为 Asset
    /// </summary>
    [MenuItem("Tools/CreateLocalizationXML")]
    public static void CreateLocalizationXML()
    {
        //创建表格管理器
        ExcelManager excelManager = CreateInstance<ExcelManager>();
        //获取表格的值
        excelManager.dataArray = ExcelTool.CreateItemArrayWithExcel(ExcelConfig.excelsFolderPath);
        // 文件保存路径
        string[] filePath = new string[excelManager.dataArray[0].Language.Length];
        for (int i = 0; i < excelManager.dataArray[0].Language.Length; i++)
        {
            filePath[i] = ExcelConfig.assetPath(excelManager.dataArray[0].Language[i]);
        }
        for (int language = 0; language < filePath.Length; language++)
        {
            XmlDocument xml = new XmlDocument();
            //创建声明信息
            XmlDeclaration Declaration = xml.CreateXmlDeclaration("1.0", "utf-8", null);
            //设置声明信息
            xml.AppendChild(Declaration);
            //创建根节点
            XmlElement Dictionaries = xml.CreateElement("Dictionaries");
            // 设置根节点
            xml.AppendChild(Dictionaries);
            // 一级子节点
            XmlElement Dictionary = xml.CreateElement("Dictionary");
            Dictionary.SetAttribute("Language", excelManager.dataArray[0].Language[language]);
            Dictionaries.AppendChild(Dictionary);
            for (int i = 0; i < excelManager.dataArray.Length; i++)
            {
                //创建数据子节点   
                XmlElement itemId = xml.CreateElement("String");
                itemId.SetAttribute("Key", excelManager.dataArray[i].key);
                itemId.SetAttribute("Value", excelManager.dataArray[i].Language[language]);
                // 设置节点间关系
                Dictionary.AppendChild(itemId);
            }
            // 保存到本地
            xml.Save(filePath[language]);
        }
        AssetDatabase.Refresh();
    }
}

2、实时更新文本显示

功能实现:

1、修改key或切换语言时,实时替换文本内容。

2、修改字体时,替换所有使用同样字体的文本。

3、编辑器创建Text,不用手动挂脚本。

该模块有三个脚本,UIFont.cs,LocalizationText.cs,LocalizationMenuExtension.cs

UIFont.cs字体脚本,脚本做成预制件,预制件只有一个字体属性

Unity实现表格效果 unity 表格插件_Unity实现表格效果_02


脚本内容如下:

using UnityEngine;
public class UIFont : MonoBehaviour {
    public Font UseFont;
}

LocalizationText.cs该脚本挂在Text组件所载对象上,主要用途是实时修改text文本显示,和修改字体,设置字体时,只需拖动预制件即可。替换字体时,不用每个text修改,只需修改预制件的字体即可。

Unity实现表格效果 unity 表格插件_游戏引擎_03


代码如下:

using UnityEngine.UI;
using UnityEngine;
using System.Xml;
using UnityGameFramework.Runtime;
namespace StarForce
{
    /// <summary>
    /// Custom Text Control used for localization text.
    /// </summary>
    [AddComponentMenu("UI/Text_Local", 10)]
    [RequireComponent(typeof(Text))]
    public class LocalizationText : MonoBehaviour
    {
        [HideInInspector]
        public Text T;
        protected void Awake()
        {
            T = transform.GetComponent<Text>();
            if (CustomFont != null)
            {
                T.font = CustomFont.UseFont;
            }
        }
        /// <summary>
        /// 文本的key
        /// </summary>
        public string KeyString;

        /// <summary>
        /// 自定义字体,方便后期替换
        /// </summary>
        public UIFont CustomFont;

        /// <summary>
        /// 是否开启自身的本地化
        /// </summary>
        public bool IsOpenLocalize = true;

        /// <summary>
        /// 重新本地化,用于游戏内切换语言时调用
        /// </summary>
        public void OnLocalize()
        {
            if (IsOpenLocalize)
            {
                text = GameEntry.Localization.GetString(KeyString);
            }
        }

        public string text
        {
            get
            {
                if (IsOpenLocalize)
                {
                    T.text = GameEntry.Localization.GetString(KeyString);
                    return T.text;
                }
                else
                {
                    return T.text;
                }
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    if (string.IsNullOrEmpty(T.text))
                        return;
                    T.text = "";
                    T.SetVerticesDirty();
                }
                else
                {
                    T.text = value;
                    T.SetVerticesDirty();
                    T.SetLayoutDirty();
                }
            }
        }
#if UNITY_EDITOR
        private void OnValidate()
        {
            if (string.IsNullOrEmpty(KeyString))
            {
                T.text = KeyString;
            }
            else
            {
                string txt = GetKey(KeyString);
                if (string.IsNullOrEmpty(txt))
                {
                    T.text ="<NoKey>"+ KeyString;
                }
                else
                {
                    T.text = txt;
                }
                if (CustomFont != null)
                {
                    T.font = CustomFont.UseFont;
                }
            }
        }
        private string GetKey(string KeyString)
        {
            string currentLanguage = FindObjectOfType<BaseComponent>().EditorLanguage.ToString();
            string dictionaryAssetName = AssetUtility.GetLocalXmlAsset(currentLanguage, "Default", false);
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(dictionaryAssetName);
            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;
                    if (key == KeyString)
                    {
                        return xmlNodeString.Attributes.GetNamedItem("Value").Value;
                    }
                }
            }
            return null;
        }
#endif
    }
}

LocalizationMenuExtension.cs编辑器创建Text_Local文本对象

Unity实现表格效果 unity 表格插件_Unity实现表格效果_04

public class LocalizationMenuExtension : MonoBehaviour {
    public const string kUILayerName = "UI";
    [MenuItem("GameObject/UI/Text_Local", false, 2000)]
    static public void AddText(MenuCommand menuCommand)
    {
        GameObject go = new GameObject("Text");
        LocalizationText txt = go.AddComponent<LocalizationText>();
        InitValue(txt);
    }
    /// <summary>
    /// 初始化值为了和Text的初始值保持一致
    /// </summary>
    /// <param name="txt"></param>
    public static void InitValue(LocalizationText txt)
    {
        txt.T.color = new Color(50f / 255f, 50f / 255f, 50f / 255f);
        RectTransform contentRT = txt.GetComponent<RectTransform>();
        contentRT.sizeDelta = new Vector2(160f, 30f);
        txt.gameObject.layer = LayerMask.NameToLayer(kUILayerName);
    }
}

3、框架辅助修改

UGuiForm方法OnInit初始化中读取的text,直接修改文本,加了本地化脚本后,改为直接读取LocalizationText脚本。

protected override void OnInit(object userData)
    {
        base.OnInit(userData);

        m_CachedCanvas = gameObject.GetOrAddComponent<Canvas>();
        m_CachedCanvas.overrideSorting = true;
        OriginalDepth = m_CachedCanvas.sortingOrder;

        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;

        gameObject.GetOrAddComponent<GraphicRaycaster>();

        LocalizationText[] texts = GetComponentsInChildren<LocalizationText>(true);
        for (int i = 0; i < texts.Length; i++)
        {
            if (!string.IsNullOrEmpty(texts[i].KeyString))
            {
                texts[i].text = GameEntry.Localization.GetString(texts[i].KeyString);
            }
        }
    }

注意,UI预制件编辑时,专门放在一个场景里。修改编辑器模式下的语言配置,对应的场景中的UI预制件都会更新文本显示。所用到的UI预制件需放到场景中。

Unity实现表格效果 unity 表格插件_excel_05