|
简单原理
ToLua 框架主要是通过静态绑定来实现 C# 与 Lua 之间的交互的,基本原理是通过建立一个 Lua 虚拟机来映射 C# 脚本,然后再通过这个虚拟机来运行 Lua 脚本,Lua 脚本在运行时可以通过虚拟机反过来调用 C# 脚本里注册过的物体,虚拟机变相地实现了一个解释器的功能,这种方式的优势在于比起使用反射的 uLua 来说效率更高。
ToLua 框架下可以将实现分成三大部分:普通的 Unity+C# 部分、ToLua 虚拟机部分和 Lua 脚本部分,结构见下图:
下载和安装
Github:GitHub - topameng/tolua: The fastest unity lua binding solution 进入以后点击下载 zip,完成后解压到自己需要的目录,再用 Unity 打开即可。
第一次打开工程时会提示是否需要自动生成注册文件,新手可以选择直接生成,若选择了取消,也可以在编辑器lua 菜单中手动注册。
C# 调用 Lua 代码
创建虚拟机 LuaState state = new LuaState() 和启动虚拟机 state.Start() 之后,就可以调用 Lua 代码。
执行一段 Lua 代码
- state.DoString(string chunk, string chunkName = "LuaState.cs"):比较少用这种方式加载代码,无法避免代码重复加载覆盖等,需调用者自己保证。第二个参数用于调试信息,或者 error 消息,用于提示出错代码所在文件名称。
- state.DoFile(string fileName):加载一个 lua 文件,fileName 需要扩展名,可反复执行,后面的变量会覆盖之前的 DoFile 加载的变量。
- state.Require(string fileName):同 lua require(modname)。
使用 DoFile 和 Require 方法时,要用 state.AddSearchPath(string fullPath) 手动给目标文件添加一个文件搜索位置。
示例:
LuaState state = new LuaState();
state.Start();
string hello =
@"
print('hello tolua#')
";
state.DoString(hello, "HelloWorld.cs");
//如果移动了ToLua目录,需要自己手动,这里只是例子就不做配置了
string fullPath = Application.dataPath + "/ToLua/Examples/02_ScriptsFromFile";
state.AddSearchPath(fullPath);
state.DoFile("ScriptsFromFile.lua");
state.Require("ScriptsFromFile");
// 垃圾回收, 对于被自动 gc 的 LuaFunction, LuaTable, 以及委托减掉的 LuaFunction, 延迟删除的 Object 之类
// 等等需要延迟处理的回收, 都在这里自动执行
state.Collect();
// 检查是否堆栈是否平衡,一般放于 update 中,c# 中任何使用 lua 堆栈操作,
// 都需要调用者自己平衡堆栈(参考 LuaFunction 以及 LuaTable 代码),
// 当 CheckTop 出现警告时其实早已经离开了堆栈操作范围,这时需自行 review 代码
state.CheckTop();
// 释放LuaState 以及其资源
state.Dispose();
state= null;
调用 Lua 变量/函数
state[string]:通过 LuaState [string] 的形式就可以直接获取到,也可以通过这个表达式来直接赋值,取到的是 object 类型。
ToLua table 方法
这里完全可以将 table 看一个 LuaState 来进行操作,ToLua 对 table 的数据结构进行了解析,实现了非常多的方法:
- state.GetTable(string):从 state 中获取一个 table, 可以串式访问比如 state.GetTable("varTable.map.name") 等于 varTable->map->name(table["map.name"] 不支持串式访问,"map.name" 只是一个key,不是table->map->name)。
- table.GetMetaTable():可以获取当前 table 的 metatable。
- table.ToArray():获取表中的数组部分。
- table.GetTable(key):获取 table[key] 值,类似于 lua_gettable。
- table.SetTable(key, value):等价于 table[k] = v,类似于lua_settable。
- table.RawGet(key):获取 table[key]值,类似于 lua_rawget。
- table.RawSet(key, value):等价于 table[k] = v,类似于lua_rawset。
示例:
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
";
void Start ()
{
LuaState state = new LuaState();
state.Start();
state["Objs2Spawn"] = 5;
state.DoString(script);
//通过LuaState访问
Debugger.Log("Read var from lua: {0}", state["var2read"]);
Debugger.Log("Read table var from lua: {0}", state["varTable.default"]); //LuaState 拆串式table
//cache成LuaTable进行访问
LuaTable table = state.GetTable("varTable");
Debugger.Log("Read varTable from lua, default: {0} name: {1}", table["default"], table["map.name"]);
table["map.name"] = "new"; //table 字符串只能是key
Debugger.Log("Modify varTable name: {0}", table["map.name"]);
table.AddTable("newmap");
LuaTable table1 = (LuaTable)table["newmap"];
table1["name"] = "table1";
Debugger.Log("varTable.newmap name: {0}", table1["name"]);
table1.Dispose();
table1 = table.GetMetaTable();
if (table1 != null)
{
Debugger.Log("varTable metatable name: {0}", table1["name"]);
}
object[] list = table.ToArray();
for (int i = 0; i < list.Length; i++)
{
Debugger.Log(&#34;varTable[{0}], is {1}&#34;, i, list);
}
table.Dispose();
state.CheckTop();
state.Dispose();
}
调用 Lua coroutine
待续。
调用 Lua 的函数
- state.Invoke:临时调用一个 lua function 并返回一个值,这个操作并不缓存 lua function,适合频率非常低的函数调用,可带参数。
- state.Call:看起来和 Invoke 类似,只是无返回值。
- state.GetFunction(string): 获取并缓存一个 lua 函数,string 支持串式操作。
- LuaFunction.Call:不需要返回值的函数调用操作。
- LuaFunction.Invoke:有一个返回值的函数调用操作。
- LuaFunction.BeginPCall():开始函数调用。
- LuaFunction.Push:压入函数调用需要的参数。
- LuaFunction.PCall():调用 lua 函数。
- LuaFunction.CheckNumber():提取函数返回值, 并检查返回值为 lua number 类型。
- LuaFunction.EndPCall():结束 lua 函数调用,清楚函数调用造成的堆栈变化。
- luaFunc.ToDelegate:创建一个委托,后续直接调用委托即可。
- LuaFunction.Dispose():释放 LuaFunction,递减引用计数,如果引用计数为 0,则从 _R 表删除该函数。
示例:
private string script =
@&#34; function luaFunc(num)
return num + 1
end
&#34;;
void Start ()
{
LuaState state = new LuaState();
state.Start();
DelegateFactory.Init();
state.DoString(script);
state.Call<int>(&#34;luaFunc&#34;, 123456, false);
Debugger.Log(&#34;state.Call&#34;);
int num = state.Invoke<int, int>(&#34;luaFunc&#34;, 123456, false);
Debugger.Log(&#34;state.Invoke: {0}&#34;, num);
LuaFunction luaFunc = state.GetFunction(&#34;luaFunc&#34;);
if (luaFunc != null)
{
luaFunc.Call<int>(123456);
Debugger.Log(&#34;LuaFunction.Call&#34;);
num = luaFunc.Invoke<int, int>(123456);
Debugger.Log(&#34;LuaFunction.Invoke: {0}&#34;, num);
luaFunc.BeginPCall();
luaFunc.Push(123456);
luaFunc.PCall();
num = (int)luaFunc.CheckNumber();
luaFunc.EndPCall();
Debugger.Log(&#34;LuaFunction.PCall: {0}&#34;, num);
Func<int, int> func = luaFunc.ToDelegate<Func<int, int>>();
num = func(123456);
Debugger.Log(&#34;LuaFunction.ToDelegate: {0}&#34;, num);
}
state.CheckTop();
luaFunc.Dispose();
luaFunc = null;
state.Dispose();
state = null;
}
Lua 调用 C# 方法/变量
从 C# 或者其他语言中来调用 Lua 都不算太困难,只需要提前约定特定方法名然后载入脚本即可,但 C# 是需要提前编译的,怎么通过一段解释器来调用 C# 中的对象就是主要的难点了,ToLua实现的就是这两方面的功能。
大多数人会想到的最直接的实现思路大概都是通过反射来实现,uLua 也是通过反射来实现的,但是反射的效率非常低,虽然确实可以实现,但问题还是非常明显。
ToLua 是通过方法名绑定的方式来实现这个映射的,首先构造一个 Lua 虚拟机,在虚拟机启动后对所需的方法进行绑定,在虚拟机运行时可以在 Lua 中调用特定方法,虚拟机变相地实现了一个解释器的功能,在 Lua 调用特定方法和对象时,虚拟机会在已绑定的方法中找到对应的 C# 方法和对象进行操作,并且 ToLua 已经自动实现了一些绑定的方法。
LuaBinder.Bind()
LuaBinder 由 GenLuaBinder() 自动生成,主要分为以下几个部分:
- LuaState.BeginModule/EndModule():用于绑定命名空间,可以逐层嵌套。
- wrap.Register():注册相应的 wrap 文件,每个 wrap 文件都是对一个 c# 类的包装,实现了将变量/方法绑定进入虚拟机中。在 lua 中,通过对 wrap 类中的函数调用,间接的对 c# 实例进行操作。
- LuaState.BeginPreLoad/AddPreLoad/EndPreLoad():预加载相关(未深入了解)。
wrap 文件
参考 & 引用 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|