ToLua框架使用

ToLua框架使用

下载和安装

从Github拉取ToLua框架代码。Github地址:https://github.com/topameng/tolua

可以看到下载拉下来的目录结构如下:

image-20250314004612856

创建一个空的Unity工程,然后把Assets目录里的内容复制到Demo工程的Assets目录下,会弹出下面的提示框:

image-20250314004716791

因为是第一次尝试,所以我选择取消,为的是能尽可能熟悉这个菜单下面的过程。

image-20250314004913036

Source/Generate目录下,有两个文件,一个是DelegateFactory,另一个是LuaBinder,暂时不知道有什么用,先保留。

image-20250314012610330

生成Wrap Files

先来看菜单Lua/Gen Lua Wrap Files,这个步骤的代码实现如下:

image-20250314010006048

通过阅读这段代码,和它所调用的函数,可以知道这个步骤从CustomSettings.customTypeList中获得要导出的类型,如果有自定义的类型需要导出,需要在这里手动添加。GenBindTypes函数是对typeList进行过滤,过滤掉以下几种类型:

  • 重复的类型(出现重复会抛异常,中断流程)
  • ToLuaMenu.dropType中添加的类型(不需要的类型)
  • ToLuaMenu.baseType中添加的类型(自定义的list不需要添加,下一行代码会加回来)

整理完之后,会把整理的结果丢到ToLuaExport.allTypes里。然后逐个导出,最后清理。默认的保存路径定义在CustomSettings.saveDir这个变量内,为Assets/Source/Generate

点击生成,可以看到Assets/Source/Generate下生成了很多C#的Wrap代码。

image-20250314011209998

生成Lua Delegates

除了导出的类型,还需要导出Delegate,导出列表定义在CustomSettings.customDelegatesList中,并且从CustomSettings.customTypeList中整理出方法,函数和定义的内部Delegate等等一并导出。

最后归并到一个集合内,批量导出。最后将结果写到Source/Generate/DelegateFactory.cs中:

image-20250314012347407

生成 Lua Binder

生成Lua Binder的过程比较复杂,总的来说也是根据先前导出的类型,构建命名空间树,然后生成Binder,将结果写到Source/Generate/LuaBinder.cs中。

从下图可以看到,LuaBinder就是将类型注册到LuaState中。

image-20250314013612957

至此生成完毕,Generate All的代码,就是把这三个步骤按顺序调用,不过不一定是我写的顺序,可以看看代码:

image-20250314013759835

使用例子说明

在ToLua中,已经给出了一些使用示例,通过阅读这些代码,可以很好熟悉ToLua的使用,下面我就捡一些主要的样例来逐个说明。

image-20250316191122132

从C#中调用Lua

在C#中执行Lua代码,是通过LuaState类来进行的,在使用之前,需要先调用LuaStateStart方法,然后通过DoFile来导入指定的Lua文件中的代码,再使用Call方法来调用指定的函数,C#这一边的示例如下:

image-20250316014212889

它执行的是Assets/Lua/Main.lua文件,改文件的内容如下:

image-20250316014327246

LuaState默认的SearchPath就是Assets/Lua,所以这里不用加路径也可以找到Main

设置和访问Lua中定义的变量

在这里通过DoString把样例代码加载到LuaState中,然后就可以像字典一样设置和访问里面的变量、表,以及访问定义的函数。

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
using LuaInterface;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
LuaState state;

private string script =
@"
print('Objs2Spawn is: '..Objs2Spawn)
var2read = 42
varTable = {1,2,3,4,5}
varTable.default = 1
varTable.map = {}
varTable.map.name = 'map'

meta = {name = 'meta'}
setmetatable(varTable, meta)

function TestFunc(strs)
print('get func by variable')
end
";

// Start is called before the first frame update
void Start()
{
state = new LuaState();
state.Start();
state["Objs2Spawn"] = 5;
state.DoString(script);

Debugger.Log("Read var from lua: " + state["var2read"]);
Debugger.Log("Read table var from lua: " + state["varTable.default"]);

LuaFunction func = state.GetFunction("TestFunc");
func.Call();
func.Dispose();

LuaTable table = state["varTable"] as LuaTable;

LuaTable map = table["map"] as LuaTable;

Debugger.Log($"Read varTable from lua, default: {table["default"]} map name: {map["name"]}");

map["name"] = "New";
Debugger.Log("Modify varTable name: {0}", map["name"]);
map.Dispose();

table.AddTable("newmap");

LuaTable newmap = table["newmap"] as LuaTable;
newmap["name"] = "newmap";

Debugger.Log($"varTable.newmap name {newmap["name"]}");
newmap.Dispose();

LuaTable metaTable = table.GetMetaTable();

if (metaTable != null)
{
Debugger.Log("varTable metatable name: {0}", metaTable["name"]);
}

object[] list = table.ToArray();

for (int i = 0; i < list.Length; i++)
{
Debugger.Log("varTable[{0}], is {1}", i, list[i]);
}

table.Dispose();
}

private void OnDestroy()
{
state.CheckTop();
state.Dispose();
}
}

函数调用、参数和返回值

调用函数有三种形式,一种是调用LuaFuncCall函数,支持无参函数和多个参数的调用形式,但是没有返回值。第二种是PCall的调用形式,这种调用形式比较麻烦,调用语句和获取返回值要写的代码比较多,另一种是LazyCall的形式,支持可变参数的传递并返回多个返回值,其实是对PCall的封装,由于有GC的影响,已经不推荐使用。

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
using LuaInterface;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
LuaState state;

private string script =
@"
function TestArray(array)
local len = array.Length

for i = 0, len - 1 do
print('Array: '..tostring(array[i]))
end

local iter = array:GetEnumerator()

while iter:MoveNext() do
print('iter: '..iter.Current)
end

local t = array:ToTable()

for i = 1, #t do
print('table: '.. tostring(t[i]))
end

local pos = array:BinarySearch(3)
print('array BinarySearch: pos: '..pos..' value: '..array[pos])

pos = array:IndexOf(4)
print('array indexof bbb pos is: '..pos)

return 1, '123', true
end
";

// Start is called before the first frame update
void Start()
{
state = new LuaState();
state.Start();
state.DoString(script);

LuaFunction func = state.GetFunction("TestArray");

int[] array = { 1, 2, 3, 4, 5 };

// 开始调用
func.BeginPCall();

// 设置参数
func.Push(array);

func.PCall();

// 获取返回值
double arg1 = func.CheckNumber();
string arg2 = func.CheckString();
bool arg3 = func.CheckBoolean();
Debugger.Log("return is {0} {1} {2}", arg1, arg2, arg3);

// 结束调用
func.EndPCall();

func.Dispose();


}

private void OnDestroy()
{
state.CheckTop();
state.Dispose();
}
}

自定义加载器

在ToLua的例子中,有一个例子是说明如何定义自己的加载器。例子中创建了一个TestCustomLoader类,并集成自LuaClient,重写InitLoader方法,该方法返回自己定义的修改器LuaResLoader

1
2
3
4
5
6
7
8
9
public class TestCustomLoader : LuaClient 
{
string tips = "Test custom loader";

protected override LuaFileUtils InitLoader()
{
return new LuaResLoader();
}
}

一开始我还以为LuaResLoaderToLua提供的一些Helper类,阅读其中的代码才知道,它是继承自LuaFileUtils的类,重写ReadFile方法。这可以很方便地去管理Lua文件的加载路径。甚至可以想象这样一种场景:在网络延迟开销影响不大的情况下,通过HTTP URL访问的方式去获取Lua文件的内容并加载到运行环境中。

image-20250316234029870


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件