UH2S3L3——toLua解析器自定义解析方式

本章代码关键字

1
2
3
AddSearchPath()                    //添加解析路径
luaState.RemoveSeachPath() //移除指定读取的路径
LuaFileUtils.ReadFile() //如果要自定义toLua解析器的解析方法,需要在继承LuaFileUtils的类里重写ReadFile()方法

自定义toLua解析路径

我们自己编写的Lua脚本,默认只能找到Assets/Lua文件夹下的,放到其子文件夹下或者其他路径下需要去指定路径
指定解析路径有以下的方式:

  • 如果该lua脚本在Assets/Lua文件夹下的文件夹内,则可以加父目录

    1
    luaState.Require("CSharpCallLua/L2_Loader");
  • 使用LuaState​中的方法AddSearchPath()

    1
    2
    luaState.AddSearchPath(Application.dataPath + "/Lua/CSharpCallLua");
    luaState.Require("L2_Loader");

移除指定路径

移除一个搜索路径,很少使用

1
luaState.RemoveSeachPath(Application.dataPath + "/Lua/CSharpCallLua");

自定义toLua解析方式

目前Lua脚本的解析路径是基于Application.dataPath​,但是打包后我们不能使用这个路径,而且我们还要去解析AB包中的Lua脚本
这就要求我们自定义toLua解析器的解析方式

分析toLua解析Lua脚本逻辑

通过对Lua解析器的require()​断点查看其逻辑的执行,Lua脚本字符串的读取是在LuaFileUtils​的ReadFile()​方法执行的
ReadFile()​是虚方法,这意味着我们可以继承LuaFileUtils​并重写ReadFile()​方法,进而自定义toLua的解析方式

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
namespace LuaInterface
{
public class LuaFileUtils
{
//...
//读取Lua脚本文件的方法
public virtual byte[] ReadFile(string fileName)
{
if (!beZip)
{
string path = FindFile(fileName); //查找路径
byte[] str = null; //声明返回值

if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
#if !UNITY_WEBPLAYER
str = File.ReadAllBytes(path); //读取文件,返回文件的字节数组
#else
throw new LuaException("can't run in web platform, please switch to other platform");
#endif
}

return str;
}
else
{
return ReadZipFile(fileName);
}
}
//...
}
}

同时,LuaFileUtils​是一个单例模式的类,且LuaFileUtils​子类可以修改唯一单例instance
构造函数LuaFileUtils()​内执行instance = this​,因此如果子类实例化,则LuaFileUtils​的唯一单例会被赋值为实例化出来的子类对象

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
namespace LuaInterface
{
public class LuaFileUtils
{
public static LuaFileUtils Instance
{
get
{
if (instance == null)
{
instance = new LuaFileUtils();
}
return instance;
}
//protected set意味着子类可以修改该类的唯一单例
protected set
{
instance = value;
}
}
//...
protected static LuaFileUtils instance = null;
//当子类实例化时,会执行这里的构造函数,因此实例化子类时,该类的唯一单例会赋值为实例化出来的子类对象
public LuaFileUtils()
{
instance = this;
}
//...
}
}

从以上分析结果可知,想要自定义toLua的解析方式,
需要继承LuaFileUtils​并重写ReadFile()​方法,再new​一次继承LuaFileUtils​的类
接下来toLua解析脚本时就会执行子类里重写的ReadFile()​方法
重写的ReadFile()​方法需要返回Lua脚本文件的byte[]​数组

自定义Lua脚本解析方式

根据以上分析结果,声明一个LuaCustomLoader​并继承LuaFileUtils​,然后重写ReadFile()​方法
对于打包之后的Unity程序,我们需要添加从Resources文件夹和AB包内加载Lua脚本文件的逻辑

我们最好优先从AB包加载Lua脚本,因为AB包用于热更新,而AB包内的Lua脚本往往是会更新的上层逻辑脚本
然后再读取Resources文件夹下Lua脚本,他们一般是toLua自带的Lua脚本或者不需要热更新的脚本

对于Resources和AB包内的Lua脚本,因为读取方法只支持.txt或者.bytes等文件,因此需要在脚本后加上
Resource和AB包的读取方法就需要通过xxx.lua​的这种脚本名读取脚本,因此我们还需要提前检测.lua是否加上了

  • 快速将Assets/Lua文件夹下的Lua脚本拷贝到Resources下的操作

    点击工具栏的Lua - Copy Lua files to Resources选项,会将Lua文件夹下以及toLua自带的文件一并拷贝到Resources文件夹下
    同时会添加.bytes后缀名,以便于Resources.Load​方法读取

    image

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
using LuaInterface;
using UnityEngine;

public class LuaCustomLoader : LuaFileUtils
{
public override byte[] ReadFile(string fileName)
{
//传入的文件名检测是否有.lua后缀,没有就加上,确保读取方法可以通过该方法读取到文件
if (!fileName.EndsWith(".lua"))
fileName += ".lua";
byte[] buffer = null;

// 优先从AB包中加载Lua文件,因为AB包内的Lua文件是热更新文件,一般是自己写的上层Lua逻辑代码
string[] strs = fileName.Split('/'); // 因为有可能传入如:xxx/xxx.lua,而AB包加载不需要前面的内容,因此需要拆分取最后一段
TextAsset luaCode = ABManager.Instance.LoadRes<TextAsset>("lua", strs[strs.Length - 1]);
if (luaCode != null)
{
buffer = luaCode.bytes;
Resources.UnloadAsset(luaCode);
}
// 如果AB包内加载到了内容就直接返回
if (buffer != null)
return buffer;

// 从Resources文件夹中加载Lua文件,toLua自带的逻辑和类一般从这里加载
string path = "Lua/" + fileName;
TextAsset text = Resources.Load<TextAsset>(path);
if (text != null)
{
buffer = text.bytes;
Resources.UnloadAsset(text); // 卸载使用后的文本资源
}

#if UNITY_EDITOR
buffer ??= base.ReadFile(fileName); // 当处于编辑环境下时,若以上方法没有找到脚本,就是使用基类方法去查找内容
#endif
return buffer;
}
}