前言


上一篇实现了C#的代码生成和数据生成,本篇就来完成Lua的代码生成并进行功能测试。


代码生成


因为Lua是脚本语言,所以excel导出的数据直接转换成LuaTable就可以了并不需要存储到其他文件。对于excel来说,一行数据就是一个LuaTable,而整张excel也可以看成是一个LuaTable,转换出来的结果就类似下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
local a  = {--整张excel
[1] = {--第一行数据
b = 1,--第一行第一列
c = 2,--第一行第二列
d = 3,--第一行第三列
}
[2] = {--第二行行数据
b = 4,--第二行第一列
c = 5,--第二行第二列
d = 6,--第二行第三列
}
}

return a

有了这个转换格式,那转换的思路就很清晰了:

  1. 在开始遍历前插入"local a = {"这样的字符串

  2. 逐行逐列遍历excel

  3. 在每列的遍历开始前插入"[row-3] = {"这样的字符串

  4. 在每列遍历中,根据行列号读取出该条数据的字段名和值,插入"key = value,"这样的字符串

  5. 每列遍历结束后插入一个反大括号"}"

  6. 整个遍历结束后,插入一个反大括号"}"

这就是整个转换过程。而json的转换和前面C#的实现方式是一样的:先把Json字符串反序列化为JsonData,然后判断是否为数组,是数组就遍历这个数组并进行递归,不是数组就读出键值对并生成代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
protected override void ExportData(DataTable dt, string excelName, string sheetName)
{
StringBuilder sb = new StringBuilder();
sb.Append("local data = {\n");//1.在开始遍历前插入"local a = {"这样的字符串

for (int i = 4; i < dt.Rows.Count; i++)//2.逐行逐列遍历excel
{
sb.AppendFormat("\t[{0}] = ", i - 3);//3.在每列的遍历开始前插入"[row-3] = {"这样的字符串
sb.Append("{\n");

for (int j = 1; j < dt.Columns.Count; j++)
{
string fieldName = dt.Rows[0][j].ToString().Trim();
string fieldType = dt.Rows[1][j].ToString().Trim();
string fieldValue = dt.Rows[i][j].ToString().Trim();

string fieldStr = GetFieldStr(fieldName, fieldValue, fieldType);//4.在每列遍历中,根据行列号读取出该条数据的字段名和值,插入"key = value,"这样的字符串

if (!string.IsNullOrEmpty(fieldStr))
{
sb.AppendFormat("\t\t{0}\n", fieldStr);
}
}

sb.Append("\t},\n");//5.每列遍历结束后插入一个反大括号"}"
}

sb.Append("}\n");//6.整个遍历结束后,插入一个反大括号"}"
sb.AppendFormat("--excelName = {0}\n", excelName);
sb.AppendFormat("--sheetName = {0}\n", sheetName);
sb.Append("return data");

File.WriteAllText(string.Format("{0}/Lua/{1}Data.lua", m_ExportPath, dt.Rows[1][0].ToString()), sb.ToString());
}

private string GetFieldStr(string fieldName, string fieldValue, string fieldType)
{
if (fieldType.Equals("string"))
{
return string.Format("{0} = \"{1}\",", fieldName, fieldValue);
}
else if (fieldType.Equals("json"))
{
if (!string.IsNullOrEmpty(fieldValue))
{
JsonData jsonData = LitJson.JsonMapper.ToObject(fieldValue);

if (jsonData != null)
{
StringBuilder jsonSB = new StringBuilder();
ParseJson(jsonData, jsonSB);
return string.Format("{0} = {1}\n{2}\t\t{3},", fieldName, "{", jsonSB.ToString(), "}");
}
}

return string.Format("{0} = nil,", fieldName);
}

if (string.IsNullOrEmpty(fieldValue))
{
fieldValue = fieldType.Contains("bool") ? "false" : "nil";
}
else if (fieldType.Contains("[]"))
{
string result = fieldName + " = {\n\t\t\t";
string fieldValueTemp = fieldValue.Replace(" ", "").Replace(",", ",\n\t\t\t");

if (fieldType.Contains("string"))
{
fieldValueTemp = "\"" + fieldValue.Replace(" ", "").Replace(",", "\",\n\t\t\t\"") + "\"";
}
else if (fieldType.Contains("bool"))
{
fieldValueTemp = fieldValueTemp.ToLower();
}

return result + fieldValueTemp + ",\n\t\t},";
}
else if (fieldType.Contains("Vector"))
{
string[] vectorValues = fieldValue.Split(',');
string[] vectorFieldName = { "x", "y", "z" };
string result = fieldName + " = {";

for (int i = 0; i < vectorValues.Length; i++)
{
result += string.Format("{0} = {1}", vectorFieldName[i], vectorValues[i]);

if(i < vectorValues.Length - 1)
{
result += ",";
}
}


return result + "},";
}

return string.Format("{0} = {1},", fieldName, fieldValue);
}

private void ParseJson(JsonData jsonData, StringBuilder sb, int tCount = 3)
{
if (jsonData.IsArray)
{
for (int i = 0; i < jsonData.Count; i++)
{
if (!JsonFieldIsBaseValueType(string.Format("[{0}]", i + 1), jsonData[i], tCount, sb))
{
for (int j = 0; j < tCount; j++)
{
sb.Append("\t");
}

sb.AppendFormat("[{0}] = ", i + 1);
sb.Append("{\n");

ParseJson(jsonData[i], sb, tCount + 1);

for (int j = 0; j < tCount; j++)
{
sb.Append("\t");
}

sb.Append("},\n");
}
}
}
else if (jsonData.Keys.Count > 0)
{
foreach (KeyValuePair<string, LitJson.JsonData> kvp in jsonData)
{
string key = kvp.Key;
JsonData val = kvp.Value;

if (!JsonFieldIsBaseValueType(key, val, tCount, sb))
{
for (int i = 0; i < tCount; i++)
{
sb.Append("\t");
}

sb.AppendFormat("{0} = ", key);
sb.Append("{\n");

ParseJson(val, sb, tCount + 1);

for (int i = 0; i < tCount; i++)
{
sb.Append("\t");
}

sb.Append("},\n");
}
}
}
}

private bool JsonFieldIsBaseValueType(string fieldName, JsonData jsonData, int tCount, StringBuilder sb)
{
string fieldValueStr = jsonData.ToString();
string fieldType = string.Empty;

if (jsonData.IsInt)
{
fieldType = "int";
}
else if (jsonData.IsLong)
{
fieldType = "long";
}
else if (jsonData.IsDouble)
{
fieldType = "double";
}
else if (jsonData.IsBoolean)
{
fieldType = "bool";
fieldValueStr = fieldValueStr.ToLower();
}
else if (jsonData.IsString)
{
fieldType = "string";
}

if (string.IsNullOrEmpty(fieldType))
{
return false;
}

for (int i = 0; i < tCount; i++)
{
sb.Append("\t");
}

sb.AppendFormat("{0}\n", GetFieldStr(fieldName, fieldValueStr, fieldType));
return true;
}

以上就是整个Lua代码的生成逻辑了。


导出测试

首先配置一下excel的存放路径和导出路径。

配置

然后点击读取按钮测试是否能读出文件列表。

读取

成功的读取出了文件列表。




选择C#选项卡,点击导出。

C#导出
C#导出
C#导出
C#导出
数据和代码都成功的生成了,json也成功的解析并还原了对应的类和对象。




选择Lua选项卡,点击导出。Lua导出
Lua导出
Lua导出
生成了一个完整的LuaTable且格式和json解析都是正确的。


读取测试


把生成的C#代码复制到工程中,并把C#的.bytes文件存放到一个单独的目录,这里我是放在工程中的ConfigData目录下。创建一个Test脚本并在Start()函数中写测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void Start()
{
RoleConfigData[] roleConfigDatas = ConfigDataHelper.LoadConfigData<RoleConfigData>("ConfigData/", "RoleConfigData");

StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < roleConfigDatas.Length; i++)
{
RoleConfigData roleConfigData = roleConfigDatas[i];
stringBuilder.AppendFormat("=======================第{0}条数据=======================\n", (i + 1).ToString());
stringBuilder.AppendFormat("{0} = {1}\n", "Id", roleConfigData.id);
stringBuilder.AppendFormat("{0} = {1}\n", "name", roleConfigData.name);
stringBuilder.AppendFormat("{0} = {1}\n", "assetName", roleConfigData.assetName);
stringBuilder.AppendFormat("{0} = {1}\n", "hitEffect", roleConfigData.hitEffect);
stringBuilder.AppendFormat("{0} = {1}\n", "headIcon", roleConfigData.headIcon);
stringBuilder.AppendFormat("{0} = {1}\n", "attackSpeed", roleConfigData.attackSpeed);
stringBuilder.AppendFormat("{0} = {1}\n", "moveSpeed", roleConfigData.moveSpeed);
stringBuilder.AppendFormat("{0} = {1}\n", "jumpForce", roleConfigData.jumpForce);
stringBuilder.AppendFormat("{0} = {1}\n", "attactIds", roleConfigData.attactIds);
stringBuilder.AppendFormat("{0} = {1}\n", "jumpAttackIds", roleConfigData.jumpAttackIds);
stringBuilder.AppendFormat("{0} = {1}\n", "catchAttackId", roleConfigData.catchAttackId);
stringBuilder.AppendFormat("{0} = {1}\n", "throwAttackId", roleConfigData.throwAttackId);
stringBuilder.AppendFormat("{0} = {1}\n", "weaponAttackId", roleConfigData.weaponAttackId);
stringBuilder.AppendFormat("{0} = {1}\n", "throwWeaponId", roleConfigData.throwWeaponId);
stringBuilder.AppendFormat("{0} = {1}\n", "skillIds", roleConfigData.skillIds);
stringBuilder.AppendFormat("{0} = {1}\n", "attackWait", roleConfigData.attackWait);
stringBuilder.AppendFormat("{0} = {1}\n", "attackNextTime", roleConfigData.attackNextTime);
stringBuilder.AppendFormat("{0} = {1}\n", "isCatchControl", roleConfigData.isCatchControl);
stringBuilder.AppendFormat("{0} = {1}\n", "behaviourTreeIds", roleConfigData.behaviourTreeIds);
stringBuilder.AppendFormat("{0} = {1}\n", "hurtAnims", roleConfigData.hurtAnims);
stringBuilder.AppendFormat("{0} = {1}\n", "isBoss", roleConfigData.isBoss);
}

Debug.Log(stringBuilder.ToString());
}

点击运行

测试

可以看到,输出的数据都是正确的。




Lua数据的测试需要引入tolua或者xLua,具体怎么用这里不进行阐述可以自行学习。把生成的Lua代码复制到Lua/ConfigData路径,并在Main.lua中编写测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
--主入口函数。从这里开始lua逻辑
function Main()
local data = require("ConfigData/RoleData")
local str = ""

for i = 1,#data do
str = str..string.format("====================第%s条数据===============\n",tostring(i))
str = str..string.format("%s = %s\n", "Id", tostring(data[i].id))
str = str..string.format("%s = %s\n", "name", data[i].name)
str = str..string.format("%s = %s\n", "assetName", data[i].assetName)
str = str..string.format("%s = %s\n", "hitEffect", data[i].hitEffect)
str = str..string.format("%s = %s\n", "headIcon", data[i].headIcon)
str = str..string.format("%s = %s\n", "attackSpeed", data[i].attackSpeed)
str = str..string.format("%s = %s\n", "moveSpeed", data[i].moveSpeed)
str = str..string.format("%s = %s\n", "jumpForce", data[i].jumpForce)
str = str..string.format("%s = %s\n", "attactIds", data[i].attactIds)
str = str..string.format("%s = %s\n", "jumpAttackIds", data[i].jumpAttackIds)
str = str..string.format("%s = %s\n", "catchAttackId", data[i].catchAttackId)
str = str..string.format("%s = %s\n", "throwAttackId", data[i].throwAttackId)
str = str..string.format("%s = %s\n", "weaponAttackId", data[i].weaponAttackId)
str = str..string.format("%s = %s\n", "throwWeaponId", data[i].throwWeaponId)
str = str..string.format("%s = %s\n", "skillIds", data[i].skillIds)
str = str..string.format("%s = %s\n", "attackWait", data[i].attackWait)
str = str..string.format("%s = %s\n", "attackNextTime", data[i].attackNextTime)
str = str..string.format("%s = %s\n", "isCatchControl", data[i].isCatchControl)
str = str..string.format("%s = %s\n", "behaviourTreeIds", data[i].behaviourTreeIds)
str = str..string.format("%s = %s\n", "hurtAnims", data[i].hurtAnims)
str = str..string.format("%s = %s\n", "isBoss", data[i].isBoss)
end

print(str)
end

测试

启动Unity之后print函数成功输出信息,数据都是正确的,那么到这里Lua的导出也完成了,这个工具基本就算做完了。


结语


这个工具,其实我是先做好了东西才开始写博客,很多功能的实现我都不知道如何用文字去描述,也把握不好要怎样逐步的去拆解功能的代码贴到博客中才能更简单易懂,而且代码中很多东西是集成在我自己的框架中,不好改,改了又不好写,非常纠结。但是不管写的好坏,这个工具又写了三篇博客,总算是又完成了一个内容,期待下一个篇章。