以前捣鼓过一个经过csv配置游戏多语言支持的小工具,可是发现使用过程当中,经过notepad++去进行转码很不方便,而且直接将配置的csv不加密的放在游戏中内心感受不是很踏实
因而乎~~数组
1.在PC/MAC平台上解析多语言配置,也就是editor运行环境中解析csv或者excel
2.经过在Editor运行过程当中生成多个语言对象,而后序列化并加密存盘
3.在使用端(移动端)经过resources加载加密以后的文件
4.读取对应的语言序列化文件并实例化加载到游戏中进行使用ide
因为不想让csv文件被打包入游戏工程中,因此选择与Assets文件夹并列的路径文件:
PS:固然路径能够本身指定
而后里面存放多语言csv
而后代码中是调用是这样的:
工具
using Newtonsoft.Json; using System; using System.IO; using System.Security.Cryptography; using System.Text; public static class SaveHelper { private const string M_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; public static bool IsFileExist(string filePath) { return File.Exists(filePath); } public static bool IsDirectoryExists(string filePath) { return Directory.Exists(filePath); } public static void CreateFile(string fileName, string content) { StreamWriter streamWriter = File.CreateText(fileName); streamWriter.Write(content); streamWriter.Close(); } public static void CreateDirectory(string filePath) { if (IsDirectoryExists(filePath)) { return; } Directory.CreateDirectory(filePath); } private static string SerializeObject(object pObject) { string serializedString = string.Empty; serializedString = JsonConvert.SerializeObject(pObject); return serializedString; } private static object DeserializeObject(string pString, Type pType) { object deserializedObject = null; deserializedObject = JsonConvert.DeserializeObject(pString, pType); return deserializedObject; } private static string RijndaelEncrypt(string pString, string pKey) { byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey); byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(pString); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateEncryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Convert.ToBase64String(resultArray, 0, resultArray.Length); } private static String RijndaelDecrypt(string pString, string pKey) { byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey); byte[] toEncryptArray = Convert.FromBase64String(pString); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateDecryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return UTF8Encoding.UTF8.GetString(resultArray); } public static void SaveData(string fileName, object pObject) { // 若是文件已存在,则删除 if (File.Exists(fileName)) { File.Delete(fileName); } string toSave = SerializeObject(pObject); toSave = RijndaelEncrypt(toSave, M_KEY); StreamWriter streamWriter = File.CreateText(fileName); streamWriter.Write(toSave); streamWriter.Close(); } public static object ReadData(string str, Type pType, bool isFile = true) { string data; if (isFile) { // 若是文件不存在,则返回空 if (!File.Exists(str)) { return null; } StreamReader streamReader = File.OpenText(str); data = streamReader.ReadToEnd(); streamReader.Close(); } else { data = str; } data = RijndaelDecrypt(data, M_KEY); return DeserializeObject(data, pType); } }
using System.Collections.Generic; using System.IO; using System.Text; // col是竖行,row是横排,防止我忘了 public class LTCSVLoader { private TextReader inStream = null; private List<string> vContent; private List<List<string>> table; /// <summary> /// 只支持GBK2312的编码(WPS直接保存的编码支持,仅提供给Windows使用) /// </summary> /// <param name="fileName"></param> public void ReadFile(string fileName) { inStream = new StreamReader(fileName, Encoding.GetEncoding("GBK")); table = new List<List<string>>(); List<string> temp = this.getLineContentVector(); while (null != temp) { List<string> tempList = new List<string>(); for (int i = 0; i < temp.Count; ++i) { tempList.Add(temp[i]); } table.Add(tempList); temp = this.getLineContentVector(); } } /// <summary> /// 目前只支持UTF-8的编码(WPS直接保存的编码不支持) /// </summary> /// <param name="str"></param> public void ReadMultiLine(string str) { inStream = new StringReader(str); table = new List<List<string>>(); List<string> temp = this.getLineContentVector(); while (null != temp) { List<string> tempList = new List<string>(); for (int i = 0; i < temp.Count; ++i) { tempList.Add(temp[i]); } table.Add(tempList); temp = this.getLineContentVector(); } } private int containsNumber(string parentStr, string parameter) { int containNumber = 0; if (parentStr == null || parentStr.Equals("")) { return 0; } if (parameter == null || parameter.Equals("")) { return 0; } for (int i = 0; i < parentStr.Length; i++) { i = parentStr.IndexOf(parameter, i); if (i > -1) { i = i + parameter.Length; i--; containNumber = containNumber + 1; } else { break; } } return containNumber; } private bool isQuoteAdjacent(string p_String) { bool ret = false; string temp = p_String; temp = temp.Replace("\"\"", ""); if (temp.IndexOf("\"") == -1) { ret = true; } return ret; } private bool isQuoteContained(string p_String) { bool ret = false; if (p_String == null || p_String.Equals("")) { return false; } if (p_String.IndexOf("\"") > -1) { ret = true; } return ret; } private string[] readAtomString(string lineStr) { string atomString = "";// 要读取的原子字符串 string orgString = "";// 保存第一次读取下一个逗号时的未经任何处理的字符串 string[] ret = new string[2];// 要返回到外面的数组 bool isAtom = false;// 是不是原子字符串的标志 string[] commaStr = lineStr.Split(new char[] { ',' }); while (!isAtom) { foreach (string str in commaStr) { if (!atomString.Equals("")) { atomString = atomString + ","; } atomString = atomString + str; orgString = atomString; if (!isQuoteContained(atomString)) { // 若是字符串中不包含引号,则为正常,返回 isAtom = true; break; } else { if (!atomString.StartsWith("\"")) { // 若是字符串不是以引号开始,则表示不转义,返回 isAtom = true; break; } else if (atomString.StartsWith("\"")) { // 若是字符串以引号开始,则表示转义 if (containsNumber(atomString, "\"") % 2 == 0) { // 若是含有偶数个引号 string temp = atomString; if (temp.EndsWith("\"")) { temp = temp.Replace("\"\"", ""); if (temp.Equals("")) { // 若是temp为空 atomString = ""; isAtom = true; break; } else { // 若是temp不为空,则去掉先后引号 temp = temp.Substring(1, temp.LastIndexOf("\"")); if (temp.IndexOf("\"") > -1) { // 去掉先后引号和相邻引号以后,若temp还包含有引号 // 说明这些引号是单个单个出现的 temp = atomString; temp = temp.Substring(1); temp = temp.Substring(0, temp.IndexOf("\"")) + temp.Substring(temp.IndexOf("\"") + 1); atomString = temp; isAtom = true; break; } else { // 正常的csv文件 temp = atomString; temp = temp.Substring(1, temp.LastIndexOf("\"")); temp = temp.Replace("\"\"", "\""); atomString = temp; isAtom = true; break; } } } else { // 若是不是以引号结束,则去掉前两个引号 temp = temp.Substring(1, temp.IndexOf('\"', 1)) + temp.Substring(temp.IndexOf('\"', 1) + 1); atomString = temp; isAtom = true; break; } } else { // 若是含有奇数个引号 if (!atomString.Equals("\"")) { string tempAtomStr = atomString.Substring(1); if (!isQuoteAdjacent(tempAtomStr)) { // 这里作的缘由是,若是判断前面的字符串不是原子字符串的时候就读取第一个取到的字符串 // 后面取到的字符串不计入该原子字符串 tempAtomStr = atomString.Substring(1); int tempQutoIndex = tempAtomStr.IndexOf("\""); // 这里既然有奇数个quto,因此第二个quto确定不是最后一个 tempAtomStr = tempAtomStr.Substring(0, tempQutoIndex) + tempAtomStr.Substring(tempQutoIndex + 1); atomString = tempAtomStr; isAtom = true; break; } } } } } } } // 先去掉以前读取的原字符串的母字符串 if (lineStr.Length > orgString.Length) { lineStr = lineStr.Substring(orgString.Length); } else { lineStr = ""; } // 去掉以后,判断是否以逗号开始,若是以逗号开始则去掉逗号 if (lineStr.StartsWith(",")) { if (lineStr.Length > 1) { lineStr = lineStr.Substring(1); } else { lineStr = ""; } } ret[0] = atomString; ret[1] = lineStr; return ret; } private bool readCSVNextRecord() { // 若是流未被初始化则返回false if (inStream == null) { return false; } // 若是结果向量未被初始化,则初始化 if (vContent == null) { vContent = new List<string>(); } // 移除向量中之前的元素 vContent.Clear(); // 声明逻辑行 string logicLineStr = ""; // 用于存放读到的行 StringBuilder strb = new StringBuilder(); // 声明是否为逻辑行的标志,初始化为false bool isLogicLine = false; while (!isLogicLine) { string newLineStr = inStream.ReadLine(); if (newLineStr == null) { strb = null; vContent = null; isLogicLine = true; break; } if (newLineStr.StartsWith("#")) { // 去掉注释 continue; } if (!strb.ToString().Equals("")) { strb.Append("\r\n"); } strb.Append(newLineStr); string oldLineStr = strb.ToString(); if (oldLineStr.IndexOf(",") == -1) { // 若是该行未包含逗号 if (containsNumber(oldLineStr, "\"") % 2 == 0) { // 若是包含偶数个引号 isLogicLine = true; break; } else { if (oldLineStr.StartsWith("\"")) { if (oldLineStr.Equals("\"")) { continue; } else { string tempOldStr = oldLineStr.Substring(1); if (isQuoteAdjacent(tempOldStr)) { // 若是剩下的引号两两相邻,则不是一行 continue; } else { // 不然就是一行 isLogicLine = true; break; } } } } } else { // quotes表示复数的quote string tempOldLineStr = oldLineStr.Replace("\"\"", ""); int lastQuoteIndex = tempOldLineStr.LastIndexOf("\""); if (lastQuoteIndex == 0) { continue; } else if (lastQuoteIndex == -1) { isLogicLine = true; break; } else { tempOldLineStr = tempOldLineStr.Replace("\",\"", ""); lastQuoteIndex = tempOldLineStr.LastIndexOf("\""); if (lastQuoteIndex == 0) { continue; } if (tempOldLineStr[lastQuoteIndex - 1] == ',') { continue; } else { isLogicLine = true; break; } } } } if (strb == null) { // 读到行尾时为返回 return false; } // 提取逻辑行 logicLineStr = strb.ToString(); if (logicLineStr != null) { // 拆分逻辑行,把分离出来的原子字符串放入向量中 while (!logicLineStr.Equals("")) { string[] ret = readAtomString(logicLineStr); string atomString = ret[0]; logicLineStr = ret[1]; vContent.Add(atomString); } } return true; } private List<string> getLineContentVector() { if (this.readCSVNextRecord()) { return this.vContent; } return null; } private List<string> getVContent() { return this.vContent; } public int GetRow() { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } return table.Count; } public int GetCol() { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } if (table.Count == 0) { throw new System.Exception("table内容为空"); } return table[0].Count; } public int GetFirstIndexAtCol(string str, int col) { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } if (table.Count == 0) { throw new System.Exception("table内容为空"); } if (col >= table[0].Count) { throw new System.Exception("参数错误:col大于最大行"); } for (int i = 0; i < table.Count; ++i) { if (table[i][col].Equals(str)) { return i; } } return -1; } public int GetFirstIndexAtRow(string str, int row) { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } if (table.Count == 0) { throw new System.Exception("table内容为空"); } if (row >= table.Count) { throw new System.Exception("参数错误:cow大于最大列"); } int tempCount = table[0].Count; for (int i = 0; i < tempCount; ++i) { if (table[row][i].Equals(str)) { return i; } } return -1; } public int[] GetIndexsAtCol(string str, int col) { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } if (table.Count == 0) { throw new System.Exception("table内容为空"); } if (col >= table[0].Count) { throw new System.Exception("参数错误:col大于最大行"); } List<int> tempList = new List<int>(); for (int i = 0; i < table.Count; ++i) { if (table[i][col].Equals(str)) { // 增长 tempList.Add(i); } } return tempList.ToArray(); } public int[] GetIndexsAtRow(string str, int row) { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } if (table.Count == 0) { throw new System.Exception("table内容为空"); } if (row >= table.Count) { throw new System.Exception("参数错误:cow大于最大列"); } int tempCount = table[0].Count; List<int> tempList = new List<int>(); for (int i = 0; i < tempCount; ++i) { if (table[row][i].Equals(str)) { tempList.Add(i); } } return tempList.ToArray(); } public string GetValueAt(int col, int row) { if (null == table) { throw new System.Exception("table还没有初始化,请检查是否成功读取"); } if (table.Count == 0) { throw new System.Exception("table内容为空"); } if (row >= table.Count) { throw new System.Exception("参数错误:row大于最大列"); } if (col >= table[0].Count) { throw new System.Exception("参数错误:col大于最大行"); } return table[row][col]; } }
using UnityEngine; using System.Collections.Generic; public class LTLocalization { public const string LANGUAGE_ENGLISH = "EN"; public const string LANGUAGE_CHINESE = "CN"; public const string LANGUAGE_JAPANESE = "JP"; public const string LANGUAGE_FRENCH = "FR"; public const string LANGUAGE_GERMAN = "GE"; public const string LANGUAGE_ITALY = "IT"; public const string LANGUAGE_KOREA = "KR"; public const string LANGUAGE_RUSSIA = "RU"; public const string LANGUAGE_SPANISH = "SP"; private const string KEY_CODE = "KEY"; private const string FILE_PATH = "LTLocalization/localization"; private SystemLanguage language = SystemLanguage.Chinese; private Dictionary<string, string> textData = new Dictionary<string, string>(); private static LTLocalization mInstance; private LTLocalization() { } private static string GetLanguageAB(SystemLanguage language) { switch (language) { case SystemLanguage.Afrikaans: case SystemLanguage.Arabic: case SystemLanguage.Basque: case SystemLanguage.Belarusian: case SystemLanguage.Bulgarian: case SystemLanguage.Catalan: return LANGUAGE_ENGLISH; case SystemLanguage.Chinese: case SystemLanguage.ChineseTraditional: case SystemLanguage.ChineseSimplified: return LANGUAGE_CHINESE; case SystemLanguage.Czech: case SystemLanguage.Danish: case SystemLanguage.Dutch: case SystemLanguage.English: case SystemLanguage.Estonian: case SystemLanguage.Faroese: case SystemLanguage.Finnish: return LANGUAGE_ENGLISH; case SystemLanguage.French: return LANGUAGE_FRENCH; case SystemLanguage.German: return LANGUAGE_GERMAN; case SystemLanguage.Greek: case SystemLanguage.Hebrew: case SystemLanguage.Icelandic: case SystemLanguage.Indonesian: return LANGUAGE_ENGLISH; case SystemLanguage.Italian: return LANGUAGE_ITALY; case SystemLanguage.Japanese: return LANGUAGE_JAPANESE; case SystemLanguage.Korean: return LANGUAGE_KOREA; case SystemLanguage.Latvian: case SystemLanguage.Lithuanian: case SystemLanguage.Norwegian: case SystemLanguage.Polish: case SystemLanguage.Portuguese: case SystemLanguage.Romanian: return LANGUAGE_ENGLISH; case SystemLanguage.Russian: return LANGUAGE_RUSSIA; case SystemLanguage.SerboCroatian: case SystemLanguage.Slovak: case SystemLanguage.Slovenian: return LANGUAGE_ENGLISH; case SystemLanguage.Spanish: return LANGUAGE_SPANISH; case SystemLanguage.Swedish: case SystemLanguage.Thai: case SystemLanguage.Turkish: case SystemLanguage.Ukrainian: case SystemLanguage.Vietnamese: case SystemLanguage.Unknown: return LANGUAGE_ENGLISH; } return LANGUAGE_CHINESE; } private static string GetWinReadPath(string fileName) { return Application.dataPath + "/../" + fileName + ".csv"; } private static string GetWinSavePath(string fileName) { return Application.dataPath + "/Resources/LTLocalization/" + fileName + ".txt"; } private void ReadData() { #if UNITY_EDITOR // 在Windows平台下读取语言配置文件 string CSVFilePath = GetWinReadPath(FILE_PATH); LTCSVLoader loader = new LTCSVLoader(); loader.ReadFile(CSVFilePath); // 将配置文件序列化为多个语言类 int csvRow = loader.GetRow(); int csvCol = loader.GetCol(); Debug.Log("row:" + csvRow + "col:" + csvCol); for (int tempCol = 1; tempCol < csvCol; ++tempCol) { LTLocalizationData languageData = new LTLocalizationData(); // 获取第一行数据(语言类型) languageData.LanguageType = loader.GetValueAt(tempCol, 0); // 遍历生成变量 languageData.LanguageData = new Dictionary<string, string>(); for (int tempRow = 1; tempRow < csvRow; ++tempRow) { languageData.LanguageData.Add(loader.GetValueAt(0, tempRow), loader.GetValueAt(tempCol, tempRow)); } // 将语言对象序列化存档 SaveHelper.SaveData(GetWinSavePath(languageData.LanguageType), languageData); if (GetLanguageAB(language).Equals(languageData.LanguageType)) { textData = languageData.LanguageData; } } #else // 读取对应的语言对象 TextAsset tempAsset = (TextAsset)Resources.Load("LTLocalization/" + GetLanguageAB(language), typeof(TextAsset)); if (null == tempAsset) { tempAsset = (TextAsset)Resources.Load("LTLocalization/" + "EN", typeof(TextAsset)); } if (null == tempAsset) { Debug.LogError("未检测到语言配置文件"); } else { string saveData = tempAsset.text; LTLocalizationData currentLanguageData = (LTLocalizationData)SaveHelper.ReadData(saveData, typeof(LTLocalizationData), false); textData = currentLanguageData.LanguageData; } #endif } private void SetLanguage(SystemLanguage language) { this.language = language; } public static void Init() { mInstance = new LTLocalization(); mInstance.SetLanguage(Application.systemLanguage); mInstance.ReadData(); } public static void ManualSetLanguage(SystemLanguage setLanguage) { if (null == mInstance) { mInstance = new LTLocalization(); } mInstance.SetLanguage(setLanguage); mInstance.ReadData(); } public static string GetText(string key) { if (null == mInstance) { Init(); } if (mInstance.textData.ContainsKey(key)) { return mInstance.textData[key]; } return "[NoDefine]" + key; } }
using UnityEngine; using System.Collections.Generic; public class LTLocalizationData { public string LanguageType; public Dictionary<string, string> LanguageData; public override string ToString() { string result = "LanguageType:" + LanguageType; List<string> tempKeys = new List<string>(LanguageData.Keys); for (int i = 0; i < tempKeys.Count; ++i) { result += "\nKey:[" + tempKeys[i] + "]|Value:[" + LanguageData[tempKeys[i]] + "]"; } return result; } }
感受比上一个版本好了不少
1.不用考虑csv编码的问题了
2.反序列化速度比读取csv更快
3.加了密更可靠
4.不一样语言分开读取,占用内存更小
缺点嘛,暂时以为还不错~~继续先用着,有问题再改ui