找回密码
 立即注册
查看: 244|回复: 0

【unity游戏开发】tolua学习-C#和Lua的交互细节

[复制链接]
发表于 2023-4-9 10:31 | 显示全部楼层 |阅读模式
前言用了2年多的Lua了,然而,却不知道unity的C#是怎么调用到Lua的细节和底层原理,特此学习一下。
先讲讲toluatolua#是Unity静态绑定lua的一个解决方案,它通过C#提供的反射信息分析代码并生成包装的类。它是一个用来简化在C#中集成lua的插件,可以自动生成用于在Lua中访问Unity的绑定代码,并把C#中的常量、变量、函数、属性、类以及枚举暴露给Lua。
它是从cstolua衍变而来。从它的名字可以看出,它是集成了原来的tolua代码通过二次封装写了一个C#与tolua(c)的一个中间层。
下面详细介绍一下C#和Lua的交互细节
一、C#调用Lua
[img][/img]


C# 调用Lua文件
1.1 编译tolua.dll
先上一下unity C#和lua交互的流程图,我们项目用的是tolua,unity C#先是生成tolua C#(warp文件),然后通过这些自动生成的tolua C#和tolua C交互。
tolua C 起到承上启下的作用,是C#和lua的中间层,在和C#交互方面,作为非c#托管代码,会提供一些函数让c# DllImport,c#会通过Marshal等与非托管代码交互
还有一些lua的扩展库,比如 cjson、LuaSocket、sqlite3、lpeg、bit、pbc等手机游戏常用库,这些库扩展了lua的能力,具体编译过程见 如何编译各平台使用的库-以编译tolua为例 这边不足赘述。windows平台叫做tolua.dll,
android叫做libtolua.so,
mac平台叫tolua.bundle,
而iOS平台由于不允许使用动态库,所以会编译成静态库libtolua.a。
编译出tolua.dll,复制到unity的Plugins文件夹下,然后就可以在C#层以DllImport导入接口,然后就可以在C#层调用到C层的方法了。
1.2 C#调用Lua文件
//C# 层调用 lua层 LuaState m_LuaState= new LuaState();
//手动添加一个lua文件搜索地址 m_LuaState.AddSearchPath(string.Format("{0}/{1}", Application.dataPath, "Lua"));
//读取mian.lua文件 m_LuaState.DoFile("main");
//找到mian方法并调用 LuaFunction main = m_LuaState.GetFunction("main");
main.Call();
main.Dispose();
main = null;
//①DoFile函数:载入文件 public void DoFile(string fileName)
{
byte[] buffer = LoadFileBuffer(fileName);
fileName = LuaChunkName(fileName);
LuaLoadBuffer(buffer, fileName);
}
protected void LuaLoadBuffer(byte[] buffer, string chunkName)
{
LuaDLL.tolua_pushtraceback(L);
int oldTop = LuaGetTop();
if (LuaLoadBuffer(buffer, buffer.Length, chunkName) == 0)
{
if (LuaPCall(0, LuaDLL.LUA_MULTRET, oldTop) == 0)
{
LuaSetTop(oldTop - 1);
return;
}
}
string err = LuaToString(-1);
LuaSetTop(oldTop - 1);
throw new LuaException(err, LuaException.GetLastError());
}
public int LuaLoadBuffer(byte[] buff, int size, string name)
{
//把一段缓存加载为一个 Lua 代码块。 //这个函数使用 lua_load 来加载 buff 指向的长度为 sz 的内存区。 return LuaDLL.luaL_loadbuffer(L, buff, size, name);
}
//②GetFunction 函数,找到lua文件的指定函数 public LuaFunction GetFunction(string name, bool beLogMiss = true)
二、Lua调用C#ToLua是通过方法名绑定的方式来实现这个映射的,
首先构造一个Lua虚拟机(LuaState state = new LuaState();),
在虚拟机启动后对所需的方法进行绑定(LuaBinder.Bind(state)),
在虚拟机运行时可以在Lua中调用特定方法,
虚拟机变相地实现了一个解释器的功能,在Lua调用特定方法和对象时,虚拟机会在已绑定的方法中找到对应的C#方法和对象进行操作
2.1 生成wrap文件首先将自己写的类添加到CustomSettings.cs文件的customTypeList数组里,
然后点击unity菜单栏Lua-GenerateAll按钮,就会生成自己写的类对应的wrap文件。具体的generate过程,下面这篇文章有具体分析,tolua#是Unity静态绑定lua的一个解决方案
2.2 lua调用Wrap文件,访问C#接下来,以常见的写法gameobj.transform.position = pos进行分析,看lua层写下这一行代码,发生了什么。像这样的写法,在unity中是再常见不过的事情,但是在lua中,大量使用这种写法是非常糟糕的。为什么呢?
因为短短一行代码,却发生了非常非常多的事情,为了更直观一点,我们把这行代码调用过的关键luaapi以及ulua相关的关键步骤列出来(以ulua+cstolua导出为准,gameobj是GameObject类型,pos是Vector3):
第一步:.transform.position
GameObjectWrap.get_transform lua想从gameobj拿到transform,对应gameobj.transform
LuaDLL.luanet_rawnetobj 把lua中的gameobj变成c#可以辨认的id
ObjectTranslator.TryGetValue 用这个id,从ObjectTranslator中获取c#的gameobject对象
gameobject.transform 准备这么多,这里终于真正执行c#获取gameobject.transform了
ObjectTranslator.AddObject 给transform分配一个id,这个id会在lua中用来代表这个transform,
transform要保存到ObjectTranslator供未来查找
LuaDLL.luanet_newudata 在lua分配一个userdata,把id存进去,用来表示即将返回给lua的transform
LuaDLL.lua_setmetatable 给这个userdata附上metatable,让你可以transform.position这样使用它
LuaDLL.lua_pushvalue 返回transform,后面做些收尾
LuaDLL.lua_rawseti
LuaDLL.lua_remove
第二步:= pos
TransformWrap.set_position lua想把pos设置到transform.position
LuaDLL.luanet_rawnetobj 把lua中的transform变成c#可以辨认的id
ObjectTranslator.TryGetValue 用这个id,从ObjectTranslator中获取c#的transform对象
LuaDLL.tolua_getfloat3 从lua中拿到Vector3的3个float值返回给c#
lua_getfield + lua_tonumber 3次 拿xyz的值,退栈
lua_pop
transform.position = new Vector3(x,y,z) 准备了这么多,终于执行transform.position = pos赋值了
这大概就是我当时刚开始写lua,天天被主程锤的主要原因吧!随便一个.transform就有如此多的操作,所以我们封装了一个LuaHelper中间层,用来处理lua和C#的交互,一些lua想要调用C#层的东西都会封装到这个类里,供lua层使用,比如这个position,我们就封装了
public static void SetLocalPositionEx(this Component cmpt, float x, float y, float z)
{
cmpt.transform.localPosition = new Vector3(x, y, z);
}
这样就实力将第一步的漫长步骤砍掉了,砍掉的还有 lua Vector3的3个float值返回给c#,下面的文章有实际的测试,可以减少66%的时间 用好Lua+Unity,让性能飞起来——Lua与C#交互篇
2.3 OjbectTranslator深入了解简单来说就是C#中的对象在传给lua时并不是直接把对象暴露给了lua,而是在这个OjbectTranslator里面注册并返回一个索引(可以理解为windows编程中的句柄),并把这个索引包装成一个userdata传递给lua,并且设置元表
// tolua# 代码static void PushUserData(IntPtr L, object o, int reference)
{
int index;
ObjectTranslator translator = ObjectTranslator.Get(L);
if(translator.Getudata(o, out index))
{
if(LuaDLL.tolua_pushudata(L, index))
{
return;
}
translator.Destroyudata(index);
}
index = translator.AddObject(o);
LuaDLL.tolua_pushnewudata(L, reference, index);
}
// tolua++ 代码LUALIB_API void tolua_pushnewudata(lua_State* L, int metaRef, int index)
{
lua_getref(L, LUA_RIDX_UBOX);
tolua_newudata(L, index);
lua_getref(L, metaRef);
lua_setmetatable(L, -2);
lua_pushvalue(L, -1);
lua_rawseti(L, -3, index);
lua_remove(L, -2);
}
参考:
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-15 07:46 , Processed in 0.088637 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表