【遊戲開發】Excel表格批量轉換成lua的轉表工具

1、簡介

  在上篇博客《【遊戲開發】Excel表格批量轉換成CSV的小工具》 中,咱們介紹瞭如何將策劃提供的Excel表格轉換爲輕便的CSV文件供開發人員使用。實際在Unity開發中,不少遊戲都是使用Lua語言進行開發的。若是要用Lua直接讀取CSV文件的話,又要寫個對應的CSV解析類,不方便的同時還會影響一些加載速度,犧牲遊戲性能。所以咱們能夠直接將Excel表格轉換爲lua文件,這樣就能夠高效、方便地在Lua中使用策劃配置的數據了。在本篇博客中,馬三將會和你們一塊兒,用C#語言實現一個Excel表格轉lua的轉表工具——Xls2Lua,並搭配一個通用的ConfigMgr來讀取lua配置文件。html

2、開發環境準備

  因爲要使用C#來讀取Excel表格文件,因此咱們須要使用一些第三方庫。針對C#語言,比較好用的Excel庫有NPOI和CSharpJExcel 這兩個,其實不管哪一個庫都是能夠用的,咱們只是用它來讀取Excel表格中的數據罷了。馬三在本篇博客中使用的是CSharpJExcel庫,由於它相對來講更輕便一些。下面附上NPOI和CSharpJExcel庫的下載連接:git

3、轉表工具

1.思路分析

  一切準備就緒,能夠開始咱們的開發任務了。首先咱們來大體地說一下轉表工具的思路:github

  1. 讀取Excel表格文件的數據,依次讀取配置目錄下的Excel文件,而後逐個讀取表裏面Sheet的內容;
  2. 根據Excel表格中配置的字段類型,對數據進行校驗,判斷數據是否合法;
  3. 將經過校驗的數據轉爲lua文件,一個Sheet切頁對應一個lua配置文件;
  4. 使用通用的ConfigMgr對轉出來的lua配置文件進行讀取操做;

2.目錄結構

  項目總體的目錄結構以下圖所示:數據庫

  

  圖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 }
View Code

  下面是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 }
View Code

   下面是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 }
View Code

   最後壓軸出場的是咱們的核心類: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 }
View Code

  還記得上文提到的FileExporter類嘛,它會遍歷每一張Sheet,而後調用XlsTransfer的GenLuaFile函數,把表格數據轉爲字符串,而後再把字符串導出爲lua配置文件。在GenLuaFile函數中,將先對傳入的sheet進行GetSheetColoumns處理,獲取該Sheet中的每個格子的信息(包括第幾列Index,表格中的內容,對應的索引字段的名字,數據類型枚舉,是不是數組標誌位等等信息)。拿到這些信息之後,咱們逐一對其進行進一步的處理,若是不是數組的話,咱們將其直接添加到StringBuilder裏面;若是是數組的話,咱們根據字符"|",將其分解爲n個單獨的數據字段,而後存儲爲Lua中的table結構。在處理的過程當中,會利用StringBuilder將數據自動化地格式爲元表和table的lua數據結構,方便Lua端讀取數據,具體操做能夠看代碼,這裏就再也不贅述。

4、讀取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
View Code
 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
View Code

  其實它們都是一段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
View Code

  在這裏咱們先定義了_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()
View Code

  其執行結果以下圖所示:

  

  圖3:最後的執行結果

  能夠看到,咱們成功地取到了表格中的數據而且輸出了出來,由於lua編碼的緣由,中文變成了亂碼,不過這並不影響咱們在Unity開發中使用配置文件。

5、總結

  在本篇博客中,咱們一塊兒學習瞭如何使用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 請尊重別人的勞動成果,讓分享成爲一種美德,歡迎轉載。另外,文章在表述和代碼方面若有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

相關文章
相關標籤/搜索