在上篇博客《【遊戲開發】Excel表格批量轉換成CSV的小工具》 中,咱們介紹瞭如何將策劃提供的Excel表格轉換爲輕便的CSV文件供開發人員使用。實際在Unity開發中,不少遊戲都是使用Lua語言進行開發的。若是要用Lua直接讀取CSV文件的話,又要寫個對應的CSV解析類,不方便的同時還會影響一些加載速度,犧牲遊戲性能。所以咱們能夠直接將Excel表格轉換爲lua文件,這樣就能夠高效、方便地在Lua中使用策劃配置的數據了。在本篇博客中,馬三將會和你們一塊兒,用C#語言實現一個Excel表格轉lua的轉表工具——Xls2Lua,並搭配一個通用的ConfigMgr來讀取lua配置文件。html
因爲要使用C#來讀取Excel表格文件,因此咱們須要使用一些第三方庫。針對C#語言,比較好用的Excel庫有NPOI和CSharpJExcel 這兩個,其實不管哪一個庫都是能夠用的,咱們只是用它來讀取Excel表格中的數據罷了。馬三在本篇博客中使用的是CSharpJExcel庫,由於它相對來講更輕便一些。下面附上NPOI和CSharpJExcel庫的下載連接:git
一切準備就緒,能夠開始咱們的開發任務了。首先咱們來大體地說一下轉表工具的思路:github
項目總體的目錄結構以下圖所示:數據庫
圖1:轉表工具總體目錄結構json
ConfigMgr存放咱們的ConfigMgr.lua,它是一個工具類,用來讀取並管理轉出來的Lua配置文件,兼具緩存數據的功能。Excel目錄存放咱們須要進行轉換的Excel表格文件。LuaData目錄存放轉出來的Lua配置文件。Xls2Lua目錄也就是咱們的轉表工具的目錄了,它包含源代碼和可直接運行的轉表工具。api
轉表工具的設計結構以下圖所示:數組
圖2:轉表工具設計結構緩存
FileExporter類專門用來讀取Excel文件和導出lua配置文件;GlobalDef類中定義了一些通用的數據結構和枚舉等信息;XlsTransfer類即爲咱們的轉表工具核心類,大部分數據都是在這裏進行校驗處理的。安全
下面咱們就能夠按照以前分析出來的思路編寫具體的代碼了,首先放上來的是咱們主程序的入口,咱們有一個名爲config.ini的配置文件,程序運行的時候會先去這個配置信息中讀取Excel的目錄和輸出目錄,而後調用FileExporter.ExportAllLuaFile函數進行轉表操做。數據結構
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace Xls2Lua 9 { 10 class Program 11 { 12 private static string inDir; 13 private static string outDir; 14 private static readonly string configPath = "./config.ini"; 15 16 static void Main(string[] args) 17 { 18 ReadConfig(); 19 FileExporter.ExportAllLuaFile(inDir, outDir); 20 } 21 22 private static void ReadConfig() 23 { 24 StreamReader reader = new StreamReader(configPath, Encoding.UTF8); 25 inDir = reader.ReadLine().Split(',')[1]; 26 inDir = Path.GetFullPath(inDir); 27 outDir = reader.ReadLine().Split(',')[1]; 28 outDir = Path.GetFullPath(outDir); 29 reader.Close(); 30 } 31 } 32 }
下面是FileExporter.cs的代碼,在這裏咱們用到了以前說起的CSharpJExcel庫,咱們須要先把它加到咱們工程的引用項中,而後在代碼裏調用便可。在這部分代碼中,咱們首先會調用ClearDirectory函數,清空以前轉出來的lua配置文件。而後遍歷Excel目錄下的全部Excel文件,對其依次執行ExportSingleLuaFile函數。在ExportSingleLuaFile函數中主要作的是打開每一張Excel表格,而且依次遍歷裏面的Sheet文件,對其中命名合法的Sheet切頁進行導出(sheet名稱前帶有#的爲導出的表格,不帶#的會被自動忽略掉,經過這個規則能夠方便自由地控制導出規則,決定哪些Sheet導出,哪些Sheet不導出)。
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using CSharpJExcel.Jxl; 8 9 namespace Xls2Lua 10 { 11 /// <summary> 12 /// 負責最終文件的輸出保存等操做類 13 /// </summary> 14 public class FileExporter 15 { 16 17 /// <summary> 18 /// 清空某個DIR下的內容 19 /// </summary> 20 /// <param name="dir"></param> 21 public static void ClearDirectory(string dir) 22 { 23 if (!Directory.Exists(dir)) 24 { 25 return; 26 } 27 Console.WriteLine("清空目錄:" + dir); 28 DirectoryInfo directoryInfo = new DirectoryInfo(dir); 29 FileSystemInfo[] fileSystemInfos = directoryInfo.GetFileSystemInfos(); 30 31 foreach (var info in fileSystemInfos) 32 { 33 if (info is DirectoryInfo) 34 { 35 DirectoryInfo subDir = new DirectoryInfo(info.FullName); 36 try 37 { 38 subDir.Delete(true); 39 } 40 catch (Exception e) 41 { 42 Console.WriteLine("警告:目錄刪除失敗 " + e.Message); 43 } 44 } 45 else 46 { 47 try 48 { 49 File.Delete(info.FullName); 50 } 51 catch (Exception e) 52 { 53 Console.WriteLine("警告:文件刪除失敗 " + e.Message); 54 } 55 } 56 } 57 } 58 59 /// <summary> 60 /// 導出全部的Excel配置到對應的lua文件中 61 /// </summary> 62 /// <param name="inDir"></param> 63 /// <param name="outDir"></param> 64 public static void ExportAllLuaFile(string inDir, string outDir) 65 { 66 ClearDirectory(outDir); 67 List<string> allXlsList = Directory.GetFiles(inDir, "*.xls", SearchOption.AllDirectories).ToList(); 68 Console.WriteLine("開始轉表..."); 69 foreach (var curXlsName in allXlsList) 70 { 71 ExportSingleLuaFile(curXlsName, outDir); 72 } 73 Console.WriteLine("按任意鍵繼續..."); 74 Console.ReadKey(); 75 } 76 77 public static void ExportSingleLuaFile(string xlsName, string outDir) 78 { 79 if (".xls" != Path.GetExtension(xlsName).ToLower()) 80 { 81 return; 82 } 83 84 Console.WriteLine(Path.GetFileName(xlsName)); 85 86 //打開文件流 87 FileStream fs = null; 88 try 89 { 90 fs = File.Open(xlsName, FileMode.Open); 91 } 92 catch (Exception e) 93 { 94 Console.WriteLine(e.Message); 95 throw; 96 } 97 if (null == fs) return; 98 //讀取xls文件 99 Workbook book = Workbook.getWorkbook(fs); 100 fs.Close(); 101 //循環處理sheet 102 foreach (var sheet in book.getSheets()) 103 { 104 string sheetName = XlsTransfer.GetSheetName(sheet); 105 if (string.IsNullOrEmpty(sheetName)) continue; 106 sheetName = sheetName.Substring(1, sheetName.Length - 1); 107 Console.WriteLine("Sheet:" + sheetName); 108 string outPath = Path.Combine(outDir, sheetName + ".lua"); 109 string content = XlsTransfer.GenLuaFile(sheet); 110 if (!string.IsNullOrEmpty(content)) 111 { 112 File.WriteAllText(outPath, content); 113 } 114 } 115 } 116 } 117 }
下面是GloablDef.cs的代碼,咱們主要在裏面定義了一些字段類型的枚舉和一些通用數據結構,其中的ColoumnDesc類用來存儲表格數據:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Xls2Lua 8 { 9 /// <summary> 10 /// 表格字段類型的枚舉 11 /// </summary> 12 public enum FieldType : byte 13 { 14 c_unknown, 15 c_int32, 16 c_int64, 17 c_bool, 18 c_float, 19 c_double, 20 c_string, 21 c_uint32, 22 c_uint64, 23 c_fixed32, 24 c_fixed64, 25 c_enum, 26 c_struct 27 } 28 29 /// <summary> 30 /// 表頭字段描述 31 /// </summary> 32 public class ColoumnDesc 33 { 34 public int index = -1; 35 public string comment = ""; 36 public string typeStr = ""; 37 public string name = ""; 38 public FieldType type; 39 public bool isArray = false; 40 } 41 }
最後壓軸出場的是咱們的核心類:XlsTransfer,其核心代碼以下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using CSharpJExcel.Jxl; 7 8 namespace Xls2Lua 9 { 10 11 /// <summary> 12 /// Xls表格轉換處理核心類 13 /// </summary> 14 public class XlsTransfer 15 { 16 /// <summary> 17 /// 分割字符串的依據 18 /// </summary> 19 private static readonly char[] splitSymbol = { '|' }; 20 21 /// <summary> 22 /// 根據字符串返回對應字段類型 23 /// </summary> 24 /// <param name="str"></param> 25 /// <returns></returns> 26 public static FieldType StringToFieldType(string str) 27 { 28 str = str.Trim(); 29 str = str.ToLower(); 30 if ("int32" == str) 31 return FieldType.c_int32; 32 else if ("int64" == str) 33 return FieldType.c_int64; 34 else if ("bool" == str) 35 return FieldType.c_bool; 36 else if ("float" == str) 37 return FieldType.c_float; 38 else if ("double" == str) 39 return FieldType.c_double; 40 else if ("string" == str) 41 return FieldType.c_string; 42 else if ("uint32" == str) 43 return FieldType.c_uint32; 44 else if ("uint64" == str) 45 return FieldType.c_uint64; 46 else if ("fixed32" == str) 47 return FieldType.c_fixed32; 48 else if ("fixed64" == str) 49 return FieldType.c_fixed64; 50 return FieldType.c_unknown; 51 } 52 53 /// <summary> 54 /// 根據字段類型,返回對應的字符串 55 /// </summary> 56 /// <param name="type"></param> 57 /// <returns></returns> 58 public static string FieldTypeToString(FieldType type) 59 { 60 if (type == FieldType.c_int32) 61 { 62 return "int32"; 63 } 64 else if (type == FieldType.c_int64) 65 { 66 return "int64"; 67 } 68 else if (type == FieldType.c_bool) 69 { 70 return "bool"; 71 } 72 else if (type == FieldType.c_float) 73 { 74 return "float"; 75 } 76 else if (type == FieldType.c_double) 77 { 78 return "double"; 79 } 80 else if (type == FieldType.c_string) 81 { 82 return "string"; 83 } 84 else if (type == FieldType.c_uint32) 85 { 86 return "uint32"; 87 } 88 else if (type == FieldType.c_uint64) 89 { 90 return "uint64"; 91 } 92 else if (type == FieldType.c_fixed32) 93 { 94 return "fixed32"; 95 } 96 else if (type == FieldType.c_fixed64) 97 { 98 return "fixed64"; 99 } 100 return ""; 101 } 102 103 /// <summary> 104 /// 獲取表格的列數,表頭碰到空白列直接中斷 105 /// </summary> 106 public static int GetSheetColoumns(Sheet sheet) 107 { 108 int coloum = sheet.getColumns(); 109 for (int i = 0; i < coloum; i++) 110 { 111 string temp1 = sheet.getCell(i, 1).getContents(); 112 string temp2 = sheet.getCell(i, 2).getContents(); 113 if (string.IsNullOrWhiteSpace(temp1) || string.IsNullOrWhiteSpace(temp2)) 114 { 115 return i; 116 } 117 } 118 return coloum; 119 } 120 121 /// <summary> 122 /// 獲取表格行數,行開頭是空白直接中斷 123 /// </summary> 124 /// <param name="sheet"></param> 125 /// <returns></returns> 126 public static int GetSheetRows(Sheet sheet) 127 { 128 int rows = sheet.getRows(); 129 for (int i = 0; i < sheet.getRows(); i++) 130 { 131 if (i >= 5) 132 { 133 if (string.IsNullOrEmpty(sheet.getCell(0, i).getContents())) 134 { 135 return i; 136 } 137 } 138 } 139 return rows; 140 } 141 142 /// <summary> 143 /// 獲取當前Sheet切頁的表頭信息 144 /// </summary> 145 /// <param name="sheet"></param> 146 /// <returns></returns> 147 public static List<ColoumnDesc> GetColoumnDesc(Sheet sheet) 148 { 149 int coloumnCount = GetSheetColoumns(sheet); 150 List<ColoumnDesc> coloumnDescList = new List<ColoumnDesc>(); 151 for (int i = 0; i < coloumnCount; i++) 152 { 153 string comment = sheet.getCell(i, 0).getContents().Trim(); 154 comment = string.IsNullOrWhiteSpace(comment) ? comment : comment.Split('\n')[0]; 155 string typeStr = sheet.getCell(i, 1).getContents().Trim(); 156 string nameStr = sheet.getCell(i, 2).getContents().Trim(); 157 158 bool isArray = typeStr.Contains("[]"); 159 typeStr = typeStr.Replace("[]", ""); 160 FieldType fieldType; 161 if (typeStr.ToLower().StartsWith("struct-")) 162 { 163 typeStr = typeStr.Remove(0, 7); 164 fieldType = FieldType.c_struct; 165 } 166 else if (typeStr.ToLower().StartsWith("enum-")) 167 { 168 typeStr.Remove(0, 5); 169 fieldType = FieldType.c_enum; 170 } 171 else 172 { 173 fieldType = StringToFieldType(typeStr); 174 } 175 ColoumnDesc coloumnDesc = new ColoumnDesc(); 176 coloumnDesc.index = i; 177 coloumnDesc.comment = comment; 178 coloumnDesc.typeStr = typeStr; 179 coloumnDesc.name = nameStr; 180 coloumnDesc.type = fieldType; 181 coloumnDesc.isArray = isArray; 182 coloumnDescList.Add(coloumnDesc); 183 } 184 return coloumnDescList; 185 } 186 187 /// <summary> 188 /// 生成最後的lua文件 189 /// </summary> 190 /// <param name="coloumnDesc"></param> 191 /// <param name="sheet"></param> 192 /// <returns></returns> 193 public static string GenLuaFile(Sheet sheet) 194 { 195 List<ColoumnDesc> coloumnDesc = GetColoumnDesc(sheet); 196 197 StringBuilder stringBuilder = new StringBuilder(); 198 stringBuilder.Append("--[[Notice:This lua config file is auto generate by Xls2Lua Tools,don't modify it manually! --]]\n"); 199 if (null == coloumnDesc || coloumnDesc.Count <= 0) 200 { 201 return stringBuilder.ToString(); 202 } 203 //建立索引 204 Dictionary<string, int> fieldIndexMap = new Dictionary<string, int>(); 205 for (int i = 0; i < coloumnDesc.Count; i++) 206 { 207 fieldIndexMap[coloumnDesc[i].name] = i + 1; 208 } 209 //建立數據塊的索引表 210 stringBuilder.Append("local fieldIdx = {}\n"); 211 foreach (var cur in fieldIndexMap) 212 { 213 stringBuilder.Append(string.Format("fieldIdx.{0} = {1}\n", cur.Key, cur.Value)); 214 } 215 216 //建立數據塊 217 stringBuilder.Append("local data = {"); 218 int rows = GetSheetRows(sheet); 219 int validRowIdx = 4; 220 //逐行讀取並處理 221 for (int i = validRowIdx; i < rows; i++) 222 { 223 StringBuilder oneRowBuilder = new StringBuilder(); 224 oneRowBuilder.Append("{"); 225 //對應處理每一列 226 for (int j = 0; j < coloumnDesc.Count; j++) 227 { 228 ColoumnDesc curColoumn = coloumnDesc[j]; 229 var curCell = sheet.getCell(curColoumn.index, i); 230 string content = curCell.getContents(); 231 232 if (FieldType.c_struct != curColoumn.type) 233 { 234 FieldType fieldType = curColoumn.type; 235 //若是不是數組類型的話 236 if (!curColoumn.isArray) 237 { 238 content = GetLuaValue(fieldType, content); 239 oneRowBuilder.Append(content); 240 } 241 else 242 { 243 StringBuilder tmpBuilder = new StringBuilder("{"); 244 var tmpStringList = content.Split(splitSymbol, StringSplitOptions.RemoveEmptyEntries); 245 for (int k = 0; k < tmpStringList.Length; k++) 246 { 247 tmpStringList[k] = GetLuaValue(fieldType, tmpStringList[k]); 248 tmpBuilder.Append(tmpStringList[k]); 249 if (k != tmpStringList.Length - 1) 250 { 251 tmpBuilder.Append(","); 252 } 253 } 254 255 oneRowBuilder.Append(tmpBuilder); 256 oneRowBuilder.Append("}"); 257 } 258 } 259 else 260 { 261 //todo:能夠處理結構體類型的字段 262 throw new Exception("暫不支持結構體類型的字段!"); 263 } 264 265 if (j != coloumnDesc.Count - 1) 266 { 267 oneRowBuilder.Append(","); 268 } 269 } 270 271 oneRowBuilder.Append("},"); 272 stringBuilder.Append(string.Format("\n{0}", oneRowBuilder)); 273 } 274 //當全部的行都處理完成以後 275 stringBuilder.Append("}\n"); 276 //設置元表 277 string str = 278 "local mt = {}\n" + 279 "mt.__index = function(a,b)\n" + 280 "\tif fieldIdx[b] then\n" + 281 "\t\treturn a[fieldIdx[b]]\n" + 282 "\tend\n" + 283 "\treturn nil\n" + 284 "end\n" + 285 "mt.__newindex = function(t,k,v)\n" + 286 "\terror('do not edit config')\n" + 287 "end\n" + 288 "mt.__metatable = false\n" + 289 "for _,v in ipairs(data) do\n\t" + 290 "setmetatable(v,mt)\n" + 291 "end\n" + 292 "return data"; 293 stringBuilder.Append(str); 294 return stringBuilder.ToString(); 295 } 296 297 /// <summary> 298 /// 處理字符串,輸出標準的lua格式 299 /// </summary> 300 /// <param name="fieldType"></param> 301 /// <param name="value"></param> 302 /// <returns></returns> 303 private static string GetLuaValue(FieldType fieldType, string value) 304 { 305 if (FieldType.c_string == fieldType) 306 { 307 if (string.IsNullOrWhiteSpace(value)) 308 { 309 return "\"\""; 310 } 311 312 return string.Format("[[{0}]]", value); 313 } 314 else if (FieldType.c_enum == fieldType) 315 { 316 //todo:能夠具體地相應去處理枚舉型變量 317 string enumKey = value.Trim(); 318 return enumKey; 319 } 320 else if (FieldType.c_bool == fieldType) 321 { 322 bool isOk = StringToBoolean(value); 323 return isOk ? "true" : "false"; 324 } 325 else 326 { 327 return string.IsNullOrEmpty(value.Trim()) ? "0" : value.Trim(); 328 } 329 } 330 331 /// <summary> 332 /// 字符串轉爲bool型,非0和false即爲真 333 /// </summary> 334 /// <param name="value"></param> 335 /// <returns></returns> 336 private static bool StringToBoolean(string value) 337 { 338 value = value.ToLower().Trim(); 339 if (string.IsNullOrEmpty(value)) 340 { 341 return true; 342 } 343 344 if ("false" == value) 345 { 346 return false; 347 } 348 349 int num = -1; 350 if (int.TryParse(value, out num)) 351 { 352 if (0 == num) 353 { 354 return false; 355 } 356 } 357 358 return true; 359 } 360 361 /// <summary> 362 /// 獲取當前sheet的合法名稱 363 /// </summary> 364 /// <param name="sheet"></param> 365 /// <returns></returns> 366 public static string GetSheetName(Sheet sheet) 367 { 368 var sheetName = sheet.getName(); 369 return ParseSheetName(sheetName); 370 } 371 372 /// <summary> 373 /// 檢測Sheet的名稱是否合法,並返回合法的sheet名稱 374 /// </summary> 375 /// <param name="sheetName"></param> 376 /// <returns></returns> 377 private static string ParseSheetName(string sheetName) 378 { 379 sheetName = sheetName.Trim(); 380 if (string.IsNullOrEmpty(sheetName)) 381 { 382 return null; 383 } 384 //只有以#爲起始的sheet纔會被轉表 385 if (!sheetName.StartsWith("#")) 386 { 387 return null; 388 } 389 390 return sheetName; 391 } 392 } 393 }
還記得上文提到的FileExporter類嘛,它會遍歷每一張Sheet,而後調用XlsTransfer的GenLuaFile函數,把表格數據轉爲字符串,而後再把字符串導出爲lua配置文件。在GenLuaFile函數中,將先對傳入的sheet進行GetSheetColoumns處理,獲取該Sheet中的每個格子的信息(包括第幾列Index,表格中的內容,對應的索引字段的名字,數據類型枚舉,是不是數組標誌位等等信息)。拿到這些信息之後,咱們逐一對其進行進一步的處理,若是不是數組的話,咱們將其直接添加到StringBuilder裏面;若是是數組的話,咱們根據字符"|",將其分解爲n個單獨的數據字段,而後存儲爲Lua中的table結構。在處理的過程當中,會利用StringBuilder將數據自動化地格式爲元表和table的lua數據結構,方便Lua端讀取數據,具體操做能夠看代碼,這裏就再也不贅述。
通過上面的一系列操做,咱們獲得了轉換後的Lua配置文件,它長成下面這個樣子:
1 --[[Notice:This lua config file is auto generate by Xls2Lua Tools,don't modify it manually! --]] 2 local fieldIdx = {} 3 fieldIdx.id = 1 4 fieldIdx.text = 2 5 local data = { 6 {10000,[[測試文字1]]}, 7 {10001,[[測試文字2]]},} 8 local mt = {} 9 mt.__index = function(a,b) 10 if fieldIdx[b] then 11 return a[fieldIdx[b]] 12 end 13 return nil 14 end 15 mt.__newindex = function(t,k,v) 16 error('do not edit config') 17 end 18 mt.__metatable = false 19 for _,v in ipairs(data) do 20 setmetatable(v,mt) 21 end 22 return data
1 --[[Notice:This lua config file is auto generate by Xls2Lua Tools,don't modify it manually! --]] 2 local fieldIdx = {} 3 fieldIdx.id = 1 4 fieldIdx.path = 2 5 fieldIdx.resType = 3 6 fieldIdx.resLiveTime = 4 7 local data = { 8 {100,[[Arts/Gui/Prefabs/uiLoginPanel.prefab]],0,20}, 9 {2001,[[Arts/Gui/Textures/airfightSheet.prefab]],0,-2},} 10 local mt = {} 11 mt.__index = function(a,b) 12 if fieldIdx[b] then 13 return a[fieldIdx[b]] 14 end 15 return nil 16 end 17 mt.__newindex = function(t,k,v) 18 error('do not edit config') 19 end 20 mt.__metatable = false 21 for _,v in ipairs(data) do 22 setmetatable(v,mt) 23 end 24 return data
其實它們都是一段lua代碼,所以能夠直接執行,而沒必要再去解析,因此會節省很多性能。先來讓咱們看一下它的結構。首先第一行是一行註釋說明,表示該配置文件是由軟件自動生成的,請不要隨意更改!而後定義了一個名爲fieldIdx的table,顧名思義,他就是用來把字段名和對應的列的index創建起索引關係的一個數據結構。例如id字段對應第一列,path字段對應第二列,以此類推。那麼咱們定義這個table的用處是什麼呢?別急,咱們立刻就會用到它,先接着往下看。咱們在fieldIdx後面緊接着定義了名爲data的table,從上述配置文件中,咱們能夠很明顯地看到data纔是真正存儲着咱們數據的結構。按照行、列的順序和數據類型,咱們將Excel表格中的數據依次存在了data結構裏面。再接着,定義了一個名爲mt的table,他重寫了__index、__newindex、__metatable這樣幾個方法。經過設置mt.__metatable = false關閉它的元表,而後在重寫的__newindex中咱們輸出一個error信息,表示配置文件不能夠被更改,這樣就保證了咱們的配置文件的安全,使得它不能再運行時隨意的增刪字段。而後咱們把__index指向了一個自定義函數function(a,b),其中第一參數是待查找的table,b表示的是想要索引的字段。(__index方法除了能夠是一個表,也能夠是一個函數,若是是函數的話,__index方法被調用時會返回該函數的返回值)在這個函數中,咱們會先去以前定義的fieldIdx中,獲取字段名所對應的index,而後再去data表中拿index對應的值。而這個值就是咱們最後須要的值了。最後別忘了,在整段代碼的最後,遍歷data,將裏面每一個子table的元表設置爲mt。這樣就能夠根據Lua查找表元素的機制方便地獲取到咱們須要的字段對應的值了。(對lua的查找表元素過程和元表、元方法等概念不熟悉的讀者能夠先去看一下這篇博客《【遊戲開發】小白學Lua——從Lua查找表元素的過程看元表、元方法》)
好了,咱們的配置文件也成功獲取到了,下面該去讀取配置文件中的內容了。爲了方便讀取而且提升效率,我作了一個名ConfigMgr的類,它封裝了一些函數,能夠根據id獲取對應的一行的數據或者根據表名獲取該表的全部配置,而且兼具緩存功能,對已經加載過的配置文件直接作返回數據處理,不用屢次加載讀取,提升性能。ConfigMgr的代碼以下所示:
1 require "Class" 2 3 ConfigMgr = { 4 --實例對象 5 _instance = nil, 6 --緩存表格數據 7 _cacheConfig = {}, 8 --具備id的表的快速索引緩存,結構__fastIndexConfig["LanguageCfg"][100] 9 _quickIndexConfig = {}, 10 } 11 ConfigMgr.__index = ConfigMgr 12 setmetatable(ConfigMgr,Class) 13 14 -- 數據配置文件的路徑 15 local cfgPath = "../LuaData/%s.lua" 16 17 -- 構造器 18 function ConfigMgr:new() 19 local self = {} 20 self = Class:new() 21 setmetatable(self,ConfigMgr) 22 return self 23 end 24 25 -- 獲取單例 26 function ConfigMgr:Instance() 27 if ConfigMgr._instance == nil then 28 ConfigMgr._instance = ConfigMgr:new() 29 end 30 return ConfigMgr._instance 31 end 32 33 -- 獲取對應的表格數據 34 function ConfigMgr:GetConfig(name) 35 local tmpCfg = self._cacheConfig[name] 36 if nil ~= tmpCfg then 37 return tmpCfg 38 else 39 local fileName = string.format(cfgPath,name) 40 --print("----------->Read Config File"..fileName) 41 -- 讀取配置文件 42 local cfgData = dofile(fileName) 43 44 -- 對讀取到的配置作緩存處理 45 self._cacheConfig[name] = {} 46 self._cacheConfig[name].items = cfgData; 47 return self._cacheConfig[name] 48 end 49 return nil 50 end 51 52 -- 獲取表格中指定的ID項 53 function ConfigMgr:GetItem(name,id) 54 if nil == self._quickIndexConfig[name] then 55 local cfgData = self:GetConfig(name) 56 if cfgData and cfgData.items and cfgData.items[1] then 57 -- 若是是空表的話不作處理 58 local _id = cfgData.items[1].id 59 if _id then 60 -- 數據填充 61 self._quickIndexConfig[name] = {} 62 for _,v in ipairs(cfgData.items) do 63 self._quickIndexConfig[name][v.id]= v 64 print("---->"..v.id) 65 end 66 else 67 print(string.format("Config: %s don't contain id: %d!",name,id)) 68 end 69 end 70 end 71 if self._quickIndexConfig[name] then 72 return self._quickIndexConfig[name][id] 73 end 74 return nil 75 end
在這裏咱們先定義了_cacheConfig和_quickIndexConfig這樣兩個字段,_cacheConfig用來緩存配置文件名對應的數據,而_quickIndexConfig用來緩存配置文件名+id對應的數據,這樣雖然稍稍多佔用了一些內存空間,可是極大地提高了咱們訪問數據的速度。爲了方便調用ConfigMgr,我將其作成了單例類,在須要的地方調用一下Instance()方法,就能夠獲取到ConfigMgr的實例了。
在ConfigMgr中主要有兩個供外界訪問的接口:GetConfig(name)和GetItem(name,id)。在GetConfig(name)函數中,首先根據name去緩存中查看是否有緩存數據,若是有緩存數據則直接返回,若是沒有加載過該配置文件,則會把配置文件的根目錄和配置文件名拼接成一個完整的配置文件路徑,而後調用dofile方法,把這個數據加載進來,而且緩存進_cacheConfig表中,以便下次快速訪問。在GetItem(name,id)函數中,首先會判斷_quickIndexConfig緩存中是否有name對應的數據存在。若是有,則直接返回self._quickIndexConfig[name][id],也就是id對應的那一行的配置數據。若是沒有,則調用上面的GetConfig(name)函數,把對應的名稱的數據文件先加載進來,而後按照對應的name和id把數據一一緩存起來。
最後,讓咱們在Main.lua中實戰檢驗一下上面一系列的操做是否成功:
1 require "Class" 2 require "ConfigMgr" 3 4 function Main() 5 local configMgr = ConfigMgr:Instance() 6 local lang = configMgr:GetConfig("Language") 7 print(lang.items[1].id .. " " .. lang.items[1].text) 8 local myText = configMgr:GetItem("Language",10000).text 9 print(myText) 10 end 11 12 Main()
其執行結果以下圖所示:
圖3:最後的執行結果
能夠看到,咱們成功地取到了表格中的數據而且輸出了出來,由於lua編碼的緣由,中文變成了亂碼,不過這並不影響咱們在Unity開發中使用配置文件。
在本篇博客中,咱們一塊兒學習瞭如何使用C#製做一款簡潔的轉表工具,從而提高咱們的工做效率。最後仍是要推薦一款優秀的成熟的轉表工具XlsxToLua。它是由tolua的開發者爲廣大的Unity開發人員製做的一款能夠將Excel表格數據導出爲Lua table、csv、json形式的工具,兼帶數據檢查功能以及導出、導入MySQL數據庫功能。除此以外,還支持GUI界面等不少實用的功能,你們感興趣的話能夠到Github去查看該項目的具體內容:https://github.com/zhangqi-ulua/XlsxToLua
本篇博客中的全部代碼已經託管到Github,開源地址:https://github.com/XINCGer/Unity3DTraining/tree/master/XlsxTools/Xls2Lua
做者:馬三小夥兒
出處:http://www.javashuo.com/article/p-wzjoisrz-b.html 請尊重別人的勞動成果,讓分享成爲一種美德,歡迎轉載。另外,文章在表述和代碼方面若有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!