在使用ToLua的LuaFramework作为代码热更框架,进行AssetBundle打包的时候,有时会莫名其妙出现Lua文件读不出来的问题,即require了某个Lua文件,且文件确实存在,却报出该文件的全局变量为nil的error。

问题分析

经过仔细的查看和debug,发现是同一个文件夹下存在两个Lua文件的文件名发生了尾部包含的关系,例如文件b.luaaaaaaab.lua,当require文件b.lua时,实际获取出来的却是aaaaaab.lua

原因所在

发现了这个问题之后(鬼知道我经历了什么才查出来……),就去翻了一下LuaFramework的代码,发现在ToLua/Core/LuaFileUtils.cs中,读取bundle中的文件的函数是这样写的:

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
byte[] ReadZipFile(string fileName)
{
……

if (pos > 0)
{
zipName = fileName.Substring(0, pos);
zipName = zipName.Replace('/', '_');
zipName = string.Format("Lua_{0}", zipName);
fileName = fileName.Substring(pos + 1);
}

zipMap.TryGetValue(zipName.ToLower(), out zipFile);

if (zipFile != null)
{
#if UNITY_5
string[] names = zipFile.GetAllAssetNames();
for (int i = 0; i < names.Length; i++) {
if (names[i].EndsWith(fileName.ToLower() + ".bytes")) {
fileName = names[i];
break;
}
}
TextAsset luaCode = zipFile.LoadAsset<TextAsset>(fileName);
#else
TextAsset luaCode = zipFile.Load(fileName, typeof(TextAsset)) as TextAsset;
#endif

……

return buffer;
}

注意中间的for循环,使用了names[i].EndsWith(fileName.ToLower() + “.bytes”)。
首先要明确一点,LuaFramework在对Lua文件进行打包时,会把每个文件夹打成一个bundle,因此上述代码中的zipFile就是某个文件夹的bundle。在对names和fileName进行匹配时,使用了EndsWith,而bundle内的文件是按字母表顺序排列的。因此require "b.lua"时,EndsWith(“b.lua”),就读到了aaaaaab.lua

解决方案

一,这个问题存在于LuaFramework 1.0.5.203之前的版本,最新的版本已经修改了Lua文件的打包和读取的方式,所以直接升级框架版本即可解决这个巨坑。

二,升级框架版本本身可能存在隐患,导致代码不稳定。既然已经定位到问题,其实直接修改就可以了。

虽然不是很清楚为什么使用EndsWith来做文件的匹配,大概可以猜到,应该是为了适配多平台导致的bundle内部文件路径不统一的问题。以下修改代码暂时只在PC和Android平台做了测试:

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
byte[] ReadZipFile(string fileName)
{
……

if (pos > 0)
{
zipName = fileName.Substring(0, pos);
zipName = zipName.Replace('/', '_');
zipName = string.Format("Lua_{0}", zipName);
//fileName = fileName.Substring(pos + 1); //需要读取整体路径
}

if (!fileName.EndsWith(".lua"))
{
fileName += ".lua";
}

//bundle内部文件读取需要从assets开始读起
//LuaFramework 1.0.1版本在打bundle时先copy到了LuaTemp文件夹下再进行打包
//因此此处我的路径需要加上"assets/luatemp/"
//具体的路径可以自行debug获取和修改
fileName = "assets/luatemp/" + fileName;

zipMap.TryGetValue(zipName.ToLower(), out zipFile);

if (zipFile != null)
{
#if UNITY_5
fileName = fileName.ToLower() + ".bytes";
TextAsset luaCode = zipFile.LoadAsset<TextAsset>(fileName);
#else
TextAsset luaCode = zipFile.Load(fileName, typeof(TextAsset)) as TextAsset;
#endif

……

return buffer;
}

小结

LuaFramework本身提供了Lua和C#交互的基础功能,并且扩展了热更新、诸多管理器、消息分发器等功能,当然作为一款开源工具,仍存在些许问题,我踩过其中一些坑,这篇总结的坑连踩了两次,本着事不过三的原则,记录下来,也是一劳永逸地解决了这个问题。