【游戏开发】Excel表格批量转换成lua的转表工具

时间:2022-08-23 20:52:33

一、简介

  在上篇博客《【游戏开发】Excel表格批量转换成CSV的小工具》 中,我们介绍了如何将策划提供的Excel表格转换为轻便的CSV文件供开发人员使用。实际在Unity开发中,很多游戏都是使用Lua语言进行开发的。如果要用Lua直接读取CSV文件的话,又要写个对应的CSV解析类,不方便的同时还会影响一些加载速度,牺牲游戏性能。因此我们可以直接将Excel表格转换为lua文件,这样就可以高效、方便地在Lua中使用策划配置的数据了。在本篇博客中,马三将会和大家一起,用C#语言实现一个Excel表格转lua的转表工具——Xls2Lua,并搭配一个通用的ConfigMgr来读取lua配置文件。

二、开发环境准备

  由于要使用C#来读取Excel表格文件,所以我们需要使用一些第三方库。针对C#语言,比较好用的Excel库有NPOI和CSharpJExcel 这两个,其实无论哪个库都是可以用的,我们只是用它来读取Excel表格中的数据罢了。马三在本篇博客中使用的是CSharpJExcel库,因为它相对来说更轻便一些。下面附上NPOI和CSharpJExcel库的下载链接:

三、转表工具

1.思路分析

  一切准备就绪,可以开始我们的开发任务了。首先我们来大致地说一下转表工具的思路:

  1. 读取Excel表格文件的数据,依次读取配置目录下的Excel文件,然后逐个读取表里面Sheet的内容;
  2. 根据Excel表格中配置的字段类型,对数据进行校验,判断数据是否合法;
  3. 将通过校验的数据转为lua文件,一个Sheet切页对应一个lua配置文件;
  4. 使用通用的ConfigMgr对转出来的lua配置文件进行读取操作;

2.目录结构

  项目整体的目录结构如下图所示:

  【游戏开发】Excel表格批量转换成lua的转表工具

  图1:转表工具整体目录结构

  ConfigMgr存放我们的ConfigMgr.lua,它是一个工具类,用来读取并管理转出来的Lua配置文件,兼具缓存数据的功能。Excel目录存放我们需要进行转换的Excel表格文件。LuaData目录存放转出来的Lua配置文件。Xls2Lua目录也就是我们的转表工具的目录了,它包含源代码和可直接运行的转表工具。

  转表工具的设计结构如下图所示:

  【游戏开发】Excel表格批量转换成lua的转表工具

  图2:转表工具设计结构

  FileExporter类专门用来读取Excel文件和导出lua配置文件;GlobalDef类中定义了一些通用的数据结构和枚举等信息;XlsTransfer类即为我们的转表工具核心类,大部分数据都是在这里进行校验处理的。

  下面我们就可以按照之前分析出来的思路编写具体的代码了,首先放上来的是我们主程序的入口,我们有一个名为config.ini的配置文件,程序运行的时候会先去这个配置信息中读取Excel的目录和输出目录,然后调用FileExporter.ExportAllLuaFile函数进行转表操作。

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Xls2Lua
{
class Program
{
private static string inDir;
private static string outDir;
private static readonly string configPath = "./config.ini"; static void Main(string[] args)
{
ReadConfig();
FileExporter.ExportAllLuaFile(inDir, outDir);
} private static void ReadConfig()
{
StreamReader reader = new StreamReader(configPath, Encoding.UTF8);
inDir = reader.ReadLine().Split(',')[];
inDir = Path.GetFullPath(inDir);
outDir = reader.ReadLine().Split(',')[];
outDir = Path.GetFullPath(outDir);
reader.Close();
}
}
}

  下面是FileExporter.cs的代码,在这里我们用到了之前提及的CSharpJExcel库,我们需要先把它加到我们工程的引用项中,然后在代码里调用即可。在这部分代码中,我们首先会调用ClearDirectory函数,清空之前转出来的lua配置文件。然后遍历Excel目录下的所有Excel文件,对其依次执行ExportSingleLuaFile函数。在ExportSingleLuaFile函数中主要做的是打开每一张Excel表格,并且依次遍历里面的Sheet文件,对其中命名合法的Sheet切页进行导出(sheet名称前带有#的为导出的表格,不带#的会被自动忽略掉,通过这个规则可以方便*地控制导出规则,决定哪些Sheet导出,哪些Sheet不导出)。

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CSharpJExcel.Jxl; namespace Xls2Lua
{
/// <summary>
/// 负责最终文件的输出保存等操作类
/// </summary>
public class FileExporter
{ /// <summary>
/// 清空某个DIR下的内容
/// </summary>
/// <param name="dir"></param>
public static void ClearDirectory(string dir)
{
if (!Directory.Exists(dir))
{
return;
}
Console.WriteLine("清空目录:" + dir);
DirectoryInfo directoryInfo = new DirectoryInfo(dir);
FileSystemInfo[] fileSystemInfos = directoryInfo.GetFileSystemInfos(); foreach (var info in fileSystemInfos)
{
if (info is DirectoryInfo)
{
DirectoryInfo subDir = new DirectoryInfo(info.FullName);
try
{
subDir.Delete(true);
}
catch (Exception e)
{
Console.WriteLine("警告:目录删除失败 " + e.Message);
}
}
else
{
try
{
File.Delete(info.FullName);
}
catch (Exception e)
{
Console.WriteLine("警告:文件删除失败 " + e.Message);
}
}
}
} /// <summary>
/// 导出所有的Excel配置到对应的lua文件中
/// </summary>
/// <param name="inDir"></param>
/// <param name="outDir"></param>
public static void ExportAllLuaFile(string inDir, string outDir)
{
ClearDirectory(outDir);
List<string> allXlsList = Directory.GetFiles(inDir, "*.xls", SearchOption.AllDirectories).ToList();
Console.WriteLine("开始转表...");
foreach (var curXlsName in allXlsList)
{
ExportSingleLuaFile(curXlsName, outDir);
}
Console.WriteLine("按任意键继续...");
Console.ReadKey();
} public static void ExportSingleLuaFile(string xlsName, string outDir)
{
if (".xls" != Path.GetExtension(xlsName).ToLower())
{
return;
} Console.WriteLine(Path.GetFileName(xlsName)); //打开文件流
FileStream fs = null;
try
{
fs = File.Open(xlsName, FileMode.Open);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw;
}
if (null == fs) return;
//读取xls文件
Workbook book = Workbook.getWorkbook(fs);
fs.Close();
//循环处理sheet
foreach (var sheet in book.getSheets())
{
string sheetName = XlsTransfer.GetSheetName(sheet);
if (string.IsNullOrEmpty(sheetName)) continue;
sheetName = sheetName.Substring(, sheetName.Length - );
Console.WriteLine("Sheet:" + sheetName);
string outPath = Path.Combine(outDir, sheetName + ".lua");
string content = XlsTransfer.GenLuaFile(sheet);
if (!string.IsNullOrEmpty(content))
{
File.WriteAllText(outPath, content);
}
}
}
}
}

  下面是GloablDef.cs的代码,我们主要在里面定义了一些字段类型的枚举和一些通用数据结构,其中的ColoumnDesc类用来存储表格数据:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Xls2Lua
{
/// <summary>
/// 表格字段类型的枚举
/// </summary>
public enum FieldType : byte
{
c_unknown,
c_int32,
c_int64,
c_bool,
c_float,
c_double,
c_string,
c_uint32,
c_uint64,
c_fixed32,
c_fixed64,
c_enum,
c_struct
} /// <summary>
/// 表头字段描述
/// </summary>
public class ColoumnDesc
{
public int index = -;
public string comment = "";
public string typeStr = "";
public string name = "";
public FieldType type;
public bool isArray = false;
}
}

  最后压轴出场的是我们的核心类:XlsTransfer,其核心代码如下: 

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CSharpJExcel.Jxl; namespace Xls2Lua
{ /// <summary>
/// Xls表格转换处理核心类
/// </summary>
public class XlsTransfer
{
/// <summary>
/// 分割字符串的依据
/// </summary>
private static readonly char[] splitSymbol = { '|' }; /// <summary>
/// 根据字符串返回对应字段类型
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static FieldType StringToFieldType(string str)
{
str = str.Trim();
str = str.ToLower();
if ("int32" == str)
return FieldType.c_int32;
else if ("int64" == str)
return FieldType.c_int64;
else if ("bool" == str)
return FieldType.c_bool;
else if ("float" == str)
return FieldType.c_float;
else if ("double" == str)
return FieldType.c_double;
else if ("string" == str)
return FieldType.c_string;
else if ("uint32" == str)
return FieldType.c_uint32;
else if ("uint64" == str)
return FieldType.c_uint64;
else if ("fixed32" == str)
return FieldType.c_fixed32;
else if ("fixed64" == str)
return FieldType.c_fixed64;
return FieldType.c_unknown;
} /// <summary>
/// 根据字段类型,返回对应的字符串
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string FieldTypeToString(FieldType type)
{
if (type == FieldType.c_int32)
{
return "int32";
}
else if (type == FieldType.c_int64)
{
return "int64";
}
else if (type == FieldType.c_bool)
{
return "bool";
}
else if (type == FieldType.c_float)
{
return "float";
}
else if (type == FieldType.c_double)
{
return "double";
}
else if (type == FieldType.c_string)
{
return "string";
}
else if (type == FieldType.c_uint32)
{
return "uint32";
}
else if (type == FieldType.c_uint64)
{
return "uint64";
}
else if (type == FieldType.c_fixed32)
{
return "fixed32";
}
else if (type == FieldType.c_fixed64)
{
return "fixed64";
}
return "";
} /// <summary>
/// 获取表格的列数,表头碰到空白列直接中断
/// </summary>
public static int GetSheetColoumns(Sheet sheet)
{
int coloum = sheet.getColumns();
for (int i = ; i < coloum; i++)
{
string temp1 = sheet.getCell(i, ).getContents();
string temp2 = sheet.getCell(i, ).getContents();
if (string.IsNullOrWhiteSpace(temp1) || string.IsNullOrWhiteSpace(temp2))
{
return i;
}
}
return coloum;
} /// <summary>
/// 获取表格行数,行开头是空白直接中断
/// </summary>
/// <param name="sheet"></param>
/// <returns></returns>
public static int GetSheetRows(Sheet sheet)
{
int rows = sheet.getRows();
for (int i = ; i < sheet.getRows(); i++)
{
if (i >= )
{
if (string.IsNullOrEmpty(sheet.getCell(, i).getContents()))
{
return i;
}
}
}
return rows;
} /// <summary>
/// 获取当前Sheet切页的表头信息
/// </summary>
/// <param name="sheet"></param>
/// <returns></returns>
public static List<ColoumnDesc> GetColoumnDesc(Sheet sheet)
{
int coloumnCount = GetSheetColoumns(sheet);
List<ColoumnDesc> coloumnDescList = new List<ColoumnDesc>();
for (int i = ; i < coloumnCount; i++)
{
string comment = sheet.getCell(i, ).getContents().Trim();
comment = string.IsNullOrWhiteSpace(comment) ? comment : comment.Split('\n')[];
string typeStr = sheet.getCell(i, ).getContents().Trim();
string nameStr = sheet.getCell(i, ).getContents().Trim(); bool isArray = typeStr.Contains("[]");
typeStr = typeStr.Replace("[]", "");
FieldType fieldType;
if (typeStr.ToLower().StartsWith("struct-"))
{
typeStr = typeStr.Remove(, );
fieldType = FieldType.c_struct;
}
else if (typeStr.ToLower().StartsWith("enum-"))
{
typeStr.Remove(, );
fieldType = FieldType.c_enum;
}
else
{
fieldType = StringToFieldType(typeStr);
}
ColoumnDesc coloumnDesc = new ColoumnDesc();
coloumnDesc.index = i;
coloumnDesc.comment = comment;
coloumnDesc.typeStr = typeStr;
coloumnDesc.name = nameStr;
coloumnDesc.type = fieldType;
coloumnDesc.isArray = isArray;
coloumnDescList.Add(coloumnDesc);
}
return coloumnDescList;
} /// <summary>
/// 生成最后的lua文件
/// </summary>
/// <param name="coloumnDesc"></param>
/// <param name="sheet"></param>
/// <returns></returns>
public static string GenLuaFile(Sheet sheet)
{
List<ColoumnDesc> coloumnDesc = GetColoumnDesc(sheet); StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("--[[Notice:This lua config file is auto generate by Xls2Lua Tools,don't modify it manually! --]]\n");
if (null == coloumnDesc || coloumnDesc.Count <= )
{
return stringBuilder.ToString();
}
//创建索引
Dictionary<string, int> fieldIndexMap = new Dictionary<string, int>();
for (int i = ; i < coloumnDesc.Count; i++)
{
fieldIndexMap[coloumnDesc[i].name] = i + ;
}
//创建数据块的索引表
stringBuilder.Append("local fieldIdx = {}\n");
foreach (var cur in fieldIndexMap)
{
stringBuilder.Append(string.Format("fieldIdx.{0} = {1}\n", cur.Key, cur.Value));
} //创建数据块
stringBuilder.Append("local data = {");
int rows = GetSheetRows(sheet);
int validRowIdx = ;
//逐行读取并处理
for (int i = validRowIdx; i < rows; i++)
{
StringBuilder oneRowBuilder = new StringBuilder();
oneRowBuilder.Append("{");
//对应处理每一列
for (int j = ; j < coloumnDesc.Count; j++)
{
ColoumnDesc curColoumn = coloumnDesc[j];
var curCell = sheet.getCell(curColoumn.index, i);
string content = curCell.getContents(); if (FieldType.c_struct != curColoumn.type)
{
FieldType fieldType = curColoumn.type;
//如果不是数组类型的话
if (!curColoumn.isArray)
{
content = GetLuaValue(fieldType, content);
oneRowBuilder.Append(content);
}
else
{
StringBuilder tmpBuilder = new StringBuilder("{");
var tmpStringList = content.Split(splitSymbol, StringSplitOptions.RemoveEmptyEntries);
for (int k = ; k < tmpStringList.Length; k++)
{
tmpStringList[k] = GetLuaValue(fieldType, tmpStringList[k]);
tmpBuilder.Append(tmpStringList[k]);
if (k != tmpStringList.Length - )
{
tmpBuilder.Append(",");
}
} oneRowBuilder.Append(tmpBuilder);
oneRowBuilder.Append("}");
}
}
else
{
//todo:可以处理结构体类型的字段
throw new Exception("暂不支持结构体类型的字段!");
} if (j != coloumnDesc.Count - )
{
oneRowBuilder.Append(",");
}
} oneRowBuilder.Append("},");
stringBuilder.Append(string.Format("\n{0}", oneRowBuilder));
}
//当所有的行都处理完成之后
stringBuilder.Append("}\n");
//设置元表
string str =
"local mt = {}\n" +
"mt.__index = function(a,b)\n" +
"\tif fieldIdx[b] then\n" +
"\t\treturn a[fieldIdx[b]]\n" +
"\tend\n" +
"\treturn nil\n" +
"end\n" +
"mt.__newindex = function(t,k,v)\n" +
"\terror('do not edit config')\n" +
"end\n" +
"mt.__metatable = false\n" +
"for _,v in ipairs(data) do\n\t" +
"setmetatable(v,mt)\n" +
"end\n" +
"return data";
stringBuilder.Append(str);
return stringBuilder.ToString();
} /// <summary>
/// 处理字符串,输出标准的lua格式
/// </summary>
/// <param name="fieldType"></param>
/// <param name="value"></param>
/// <returns></returns>
private static string GetLuaValue(FieldType fieldType, string value)
{
if (FieldType.c_string == fieldType)
{
if (string.IsNullOrWhiteSpace(value))
{
return "\"\"";
} return string.Format("[[{0}]]", value);
}
else if (FieldType.c_enum == fieldType)
{
//todo:可以具体地相应去处理枚举型变量
string enumKey = value.Trim();
return enumKey;
}
else if (FieldType.c_bool == fieldType)
{
bool isOk = StringToBoolean(value);
return isOk ? "true" : "false";
}
else
{
return string.IsNullOrEmpty(value.Trim()) ? "" : value.Trim();
}
} /// <summary>
/// 字符串转为bool型,非0和false即为真
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static bool StringToBoolean(string value)
{
value = value.ToLower().Trim();
if (string.IsNullOrEmpty(value))
{
return true;
} if ("false" == value)
{
return false;
} int num = -;
if (int.TryParse(value, out num))
{
if ( == num)
{
return false;
}
} return true;
} /// <summary>
/// 获取当前sheet的合法名称
/// </summary>
/// <param name="sheet"></param>
/// <returns></returns>
public static string GetSheetName(Sheet sheet)
{
var sheetName = sheet.getName();
return ParseSheetName(sheetName);
} /// <summary>
/// 检测Sheet的名称是否合法,并返回合法的sheet名称
/// </summary>
/// <param name="sheetName"></param>
/// <returns></returns>
private static string ParseSheetName(string sheetName)
{
sheetName = sheetName.Trim();
if (string.IsNullOrEmpty(sheetName))
{
return null;
}
//只有以#为起始的sheet才会被转表
if (!sheetName.StartsWith("#"))
{
return null;
} return sheetName;
}
}
}

  还记得上文提到的FileExporter类嘛,它会遍历每一张Sheet,然后调用XlsTransfer的GenLuaFile函数,把表格数据转为字符串,然后再把字符串导出为lua配置文件。在GenLuaFile函数中,将先对传入的sheet进行GetSheetColoumns处理,获取该Sheet中的每一个格子的信息(包括第几列Index,表格中的内容,对应的索引字段的名字,数据类型枚举,是否是数组标志位等等信息)。拿到这些信息以后,我们逐一对其进行进一步的处理,如果不是数组的话,我们将其直接添加到StringBuilder里面;如果是数组的话,我们根据字符"|",将其分解为n个单独的数据字段,然后存储为Lua中的table结构。在处理的过程中,会利用StringBuilder将数据自动化地格式为元表和table的lua数据结构,方便Lua端读取数据,具体操作可以看代码,这里就不再赘述。

四、读取Lua配置文件

  经过上面的一系列操作,我们得到了转换后的Lua配置文件,它长成下面这个样子:

 --[[Notice:This lua config file is auto generate by Xls2Lua Tools,don't modify it manually! --]]
local fieldIdx = {}
fieldIdx.id =
fieldIdx.text =
local data = {
{,[[测试文字1]]},
{,[[测试文字2]]},}
local mt = {}
mt.__index = function(a,b)
if fieldIdx[b] then
return a[fieldIdx[b]]
end
return nil
end
mt.__newindex = function(t,k,v)
error('do not edit config')
end
mt.__metatable = false
for _,v in ipairs(data) do
setmetatable(v,mt)
end
return data
 --[[Notice:This lua config file is auto generate by Xls2Lua Tools,don't modify it manually! --]]
local fieldIdx = {}
fieldIdx.id =
fieldIdx.path =
fieldIdx.resType =
fieldIdx.resLiveTime =
local data = {
{,[[Arts/Gui/Prefabs/uiLoginPanel.prefab]],,},
{,[[Arts/Gui/Textures/airfightSheet.prefab]],,-},}
local mt = {}
mt.__index = function(a,b)
if fieldIdx[b] then
return a[fieldIdx[b]]
end
return nil
end
mt.__newindex = function(t,k,v)
error('do not edit config')
end
mt.__metatable = false
for _,v in ipairs(data) do
setmetatable(v,mt)
end
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的代码如下所示:

 require "Class"

 ConfigMgr = {
--实例对象
_instance = nil,
--缓存表格数据
_cacheConfig = {},
--具有id的表的快速索引缓存,结构__fastIndexConfig["LanguageCfg"][100]
_quickIndexConfig = {},
}
ConfigMgr.__index = ConfigMgr
setmetatable(ConfigMgr,Class) -- 数据配置文件的路径
local cfgPath = "../LuaData/%s.lua" -- 构造器
function ConfigMgr:new()
local self = {}
self = Class:new()
setmetatable(self,ConfigMgr)
return self
end -- 获取单例
function ConfigMgr:Instance()
if ConfigMgr._instance == nil then
ConfigMgr._instance = ConfigMgr:new()
end
return ConfigMgr._instance
end -- 获取对应的表格数据
function ConfigMgr:GetConfig(name)
local tmpCfg = self._cacheConfig[name]
if nil ~= tmpCfg then
return tmpCfg
else
local fileName = string.format(cfgPath,name)
--print("----------->Read Config File"..fileName)
-- 读取配置文件
local cfgData = dofile(fileName) -- 对读取到的配置做缓存处理
self._cacheConfig[name] = {}
self._cacheConfig[name].items = cfgData;
return self._cacheConfig[name]
end
return nil
end -- 获取表格中指定的ID项
function ConfigMgr:GetItem(name,id)
if nil == self._quickIndexConfig[name] then
local cfgData = self:GetConfig(name)
if cfgData and cfgData.items and cfgData.items[] then
-- 如果是空表的话不做处理
local _id = cfgData.items[].id
if _id then
-- 数据填充
self._quickIndexConfig[name] = {}
for _,v in ipairs(cfgData.items) do
self._quickIndexConfig[name][v.id]= v
print("---->"..v.id)
end
else
print(string.format("Config: %s don't contain id: %d!",name,id))
end
end
end
if self._quickIndexConfig[name] then
return self._quickIndexConfig[name][id]
end
return nil
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中实战检验一下上面一系列的操作是否成功: 

 require "Class"
require "ConfigMgr" function Main()
local configMgr = ConfigMgr:Instance()
local lang = configMgr:GetConfig("Language")
print(lang.items[].id .. " " .. lang.items[].text)
local myText = configMgr:GetItem("Language",).text
print(myText)
end Main()

  其执行结果如下图所示:

  【游戏开发】Excel表格批量转换成lua的转表工具

  图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

作者:马三小伙儿
出处:https://www.cnblogs.com/msxh/p/8539108.html 
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

【游戏开发】Excel表格批量转换成lua的转表工具的更多相关文章

  1. 【游戏开发】Excel表格批量转换成CSV的小工具

    一.前言 在工作的过程中,我们有时可能会面临将Excel表格转换成CSV格式文件的需求.这尤其在游戏开发中体现的最为明显,策划的数据文档大多是一些Excel表格,且不说这些表格在游戏中读取的速度,但就 ...

  2. C&num; Unity游戏开发——Excel中的数据是如何到游戏中的 (二)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (一) 上个帖子主要是讲了如何读取Excel,本帖主要是讲述读取的Excel数据是如何序列化成二进制的,考虑到现在在手游中 ...

  3. C&num; Unity游戏开发——Excel中的数据是如何到游戏中的 (三)

    本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二) 前几天有点事情所以没有继续更新,今天我们接着说.上个帖子中我们看到已经把Excel数据生成了.bin的文件,不过其 ...

  4. C&num; Unity游戏开发——Excel中的数据是如何到游戏中的 (四)2018&period;4&period;3更新

    本帖是延续的:C# Unity游戏开发--Excel中的数据是如何到游戏中的 (三) 最近项目不算太忙,终于有时间更新博客了.关于数据处理这个主题前面的(一)(二)(三)基本上算是一个完整的静态数据处 ...

  5. Excel将秒转换成标准的时间格式HH&colon;MM&colon;SS

    Excel将秒转换成标准的时间格式HH:MM:SS 比如120秒,转换成00:02:00 Excel公式为: =TEXT(A1/86400,"[hh]:mm:ss") A1为秒数据 ...

  6. word ppt excel文档转换成pdf

    1.把word文档转换成pdf (1).添加引用 using Microsoft.Office.Interop.Word; 添加引用 (2).转换方法 /// <summary> /// ...

  7. Linux下将UTF8编码批量转换成GB2312编码的方法

    Linux下将UTF8编码批量转换成GB2312编码的方法 在sqlplus中导入UTF8编码的sql脚本就会出现乱码错误,这时就需要将UTF8编码转换成GB2312编码,下面为大家介绍下在Linux ...

  8. 将psd格式批量转换成jpg或png格式(C&num;自制软件)

    此项目基于.net framework 4.0 将psd格式批量转换成jpg或png格式. 链接:https://pan.baidu.com/s/16IEjX0sHaY9H3Ah7mv6IPQ 提取码 ...

  9. 把ANSI格式的TXT文件批量转换成UTF-8文件类型

    把ANSI格式的TXT文件批量转换成UTF-8文件类型 Posted on 2010-08-05 10:38 moss_tan_jun 阅读(3635) 评论(0) 编辑 收藏 #region 把AN ...

随机推荐

  1. paramiko 的使用

    paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作 SSHClient 用于远程连接机器执行基本命令,也可以执行shell脚本 基于用户名密码连接: def ssh_connect ...

  2. Car的旅行路线(codevs 1041)

    题目描述 Description 又到暑假了,住在城市A的Car想和朋友一起去城市B旅游.她知道每个城市都有四个飞机场,分别位于一个矩形的四个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第I ...

  3. BZOJ2883 &colon; gss2加强版

    首先离散化颜色 设pre[x]表示与x颜色相同的点上一次出现的位置,对于每种颜色开一个set维护 修改时需要修改x.x修改前的后继.x修改后的后继 询问[l,r]等价于询问[l,r]内pre[x]&l ...

  4. POJ 3617 Best Cow Line &lpar;贪心&rpar;

    题意:给定一行字符串,让你把它变成字典序最短,方法只有两种,要么从头部拿一个字符,要么从尾部拿一个. 析:贪心,从两边拿时,哪个小先拿哪个,如果一样,接着往下比较,要么比到字符不一样,要么比完,也就是 ...

  5. &ast;&lbrack;topcoder&rsqb;LittleElephantAndString

    http://community.topcoder.com/stat?c=problem_statement&pm=12854&rd=15709 这道题DIV1 250的,还有点意思. ...

  6. DB2&lowbar;001&lowbar;MQT

    MQT stands for Materialed Query Table.它的定义是建立在查询结果之上的,把动态查询的结果放到表中,表中的数据随着基础表中数据的变化而变化.当基础表中的数据变化时,M ...

  7. 老李推荐:第6章2节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-获取命令字串

    老李推荐:第6章2节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-获取命令字串   从上一节的描述可以知道,MonkeyRunner发送给Monkey的命令 ...

  8. vue2数组更新视图未更新的情况

    以选中列表为例 <p @click="selectGoods(item, index)" :class="{'selected': item.isActive}&q ...

  9. VirtualBox 使用物理硬盘

    /******************************************************************************* * VirtualBox 使用物理硬盘 ...

  10. 【window】Windows10下为PHP安装redis扩展

    操作: 步骤1:D:\wamp\bin\apache\apache2.4.9\bin/php.ini中添加 ; php_redis extension=php_igbinary.dll extensi ...