元宇宙链游系统开发搭建技术
META FORCE 系统属于我们的社区,开发需求及分析:Congge420 并且是完全的去中心化,这意味着它是透明的,安全的,技术 Congge420 整理发布,并且能够抵抗外部影响。
三个步骤:
加载 lua 代码到 vm 中,对应 api-luaL_loadbuffer
luaL_loadbuffer 会同时在栈上压入代码块的指针
执行 lua 代码,对应 api-lua_pcall
lua_pcall 会从栈上依次弹出{nargs}个数据作为函数参数,再弹出函数进行执行,并将结果压入栈
如果 lua 代码有返回值,那么通过 lua_toXXX 相关 api 从栈上获取结果
完整的代码如下:
private bool DoLuaCode(System.IntPtr L,string luaCode){
//加载 lua 代码
if(Lua.luaL_loadbuffer(L,luaCode,"")==0){
//执行栈顶的函数
if(Lua.lua_pcall(L,0,1,0)==0){
//函数执行完成后,返回值会依次依次押入栈
return true;
}else{关于区块链技术开发唯:Congge420
Debug.LogError("pcall failed!");
return false;
}
}else{
Debug.LogError("load buffer failed");
return false;
}
}
假如我们有一段 lua 代码:
return'hello,i am from lua'
这段 lua 仅仅返回一段字符串,那么利用 DoLuaCode 去执行就是:
//lua 代码
string luaCode="return'hello,i am from lua'";
if(DoLuaCode(L,luaCode)){
Debug.Log(Lua.lua_tostring(L,-1));
//lua_toXXX 不会出栈,需要 lua_pop 才能出栈
Lua.lua_pop(L,1);
}
由于此处 lua 代码返回的是字符串,因此使用 lua_tostring(L,-1)来将栈顶的元素转为字符串并返回,相应的我们还能看到有 lua_tonumber,lua_toboolean 等等.
4.c#调用 lua 全局函数
接下来的例子将说明一下 c#端如何执行 lua 中的全局函数。
假设现在我们有一段 lua 代码如下:
function addSub(a,b)
return a+b,a-b;
end
通过 DoLuaCode 来运行以上的 lua 代码,就得到了一个全局的 addSub 函数,这个函数会返回 a,b 相加和相减的结果。
为了在 c#端执行以上的 lua 函数,需要按以下步骤进行:
将全局函数压入栈中,对应 api-lua_getglobal
将函数所需的参数依次压入栈中,对应 api-lua_pushnumber
执行栈中函数,对应 api-lua_pcall
获取函数返回结果,对应 api-lua_tonumber
完整 c#代码如下:
//从全局表里读取 addSub 函数,并压入栈
Lua.lua_getglobal(L,"addSub");
//压入参数 a
Lua.lua_pushnumber(L,101);
//压入参数 b
Lua.lua_pushnumber(L,202);
//2 个参数,2 个返回值
Lua.lua_pcall(L,2,2,0);
//pcall 会让参数和函数指针都出栈
//pcall 执行完毕后,会将结果压入栈
Debug.Log(Lua.lua_tonumber(L,-2));
Debug.Log(Lua.lua_tonumber(L,-1));
Lua.lua_pop(L,2);
5.lua 注册并调用 c#静态函数
首先,想要被 Lua 调用的 c#函数,都必须满足以下的格式:
public delegate int LuaCSFunction(System.IntPtr luaState);
同时需要加上特性:
MonoPInvokeCallback(typeof(LuaCSFunction))
我们可以通过以下方式,将一个 LuaCSFunction 注册到 lua 中:
static void RegisterCSFunctionGlobal(System.IntPtr L,string funcName,LuaCSFunction func){
//将 LuaCSFunction 压入栈中
Lua.lua_pushcfunction(L,func);
//lua_setglobal 会弹出栈顶元素,并按给定的名字作为 key 将其加入到全局表
Lua.lua_setglobal(L,funcName);
}
那么,当我们在 lua 中执行 c#注册的函数时,其交互过程如下:
LuaVM 会临时分配一个局部栈结构(这里要区分开始通过 luaL_newstate 创建的全局栈,两者是独立的)
LuaVM 会将 lua 侧的函数参数压入这个临时栈,然后将栈指针传给 LuaCSFunction
LuaCSFunction 在实现上需要从这个栈中读取 lua 侧压入的参数,然后执行真正的相关逻辑,并将最终结果压入栈中
LuaCSFunction 需要返回一个 int 值,表示往栈中压入了多少个返回值
Lua 从栈中获取 C#侧压入的 0/1/多个返回值
官方说明文档可以参考-Calling C from Lua
接下来要将演示如何将一个 c#静态函数 Print 注入到 lua 中,实现 lua 中调用 c#端的日志输出功能。
我们定义一个 c#静态函数如下:
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Print(System.IntPtr localL){
//获取栈中元素个数
var count=Lua.lua_gettop(localL);
System.Text.StringBuilder s=new System.Text.StringBuilder();
for(var i=1;i<=count;i++){
//依次读取 print 的每个参数,合并成一个 string
s.Append(Lua.lua_tostring(localL,i));
s.Append('');
}
Debug.Log(s);
//print 函数没有返回值
return 0;
}
lua_gettop 可以获取栈中的元素个数,此处代表了 lua 端压入栈中的函数参数个数
然后我们通过以下方式将这个 c#侧的 Print 注册到 lua 中,命名为 print。
//将 LuaCSFunction 压入栈中
Lua.lua_pushcfunction(L,Print);
//lua_setglobal 会弹出栈顶元素,并按给定的名字作为 key 将其加入到全局表
Lua.lua_setglobal(L,"print");
接下来我们执行以下的 lua 代码:
print('hello','csharp')
就能看到编辑器中输出
hello csharp
6.lua 注册 c#类型
通常我们使用 lua 中的 table 来模拟 c#中的类。一般类的注册思路如下:
在 lua 中创建一个与 c#类同名的表
将 c#类的静态函数都注册到 lua 的这个同名表里
下面演示一下如何将 Unity 中的 Debug 类注册到 lua 中:
Lua.lua_createtable(L,0,1);
Lua.lua_setglobal(L,"Debug");
其实很简单:
lua_createtable 会创建一个 table,压入栈顶
lua_setglobal 会弹出栈顶元素,并将其加到全局表里
这样我们在 lua 里就有了一个名为 Debug 的表可供全局访问。但目前这个表是空空如也的,我们还需要为其添加静态函数。(tips:实际上完整的设计中,还需要为 class table 设置 metatable,增加一些限制性,但这里先不表)
6.1 注入类的静态函数
首先我们定义一个符合 LuaCSFunction 形式的 c#函数如下:
[MonoPInvokeCallback(typeof(LuaCSFunction))]
private static int Debug_Log(System.IntPtr L){
string msg=Lua.lua_tostring(L,1);
Debug.Log(msg);
return 0;
}
这个 c#函数是对 Debug.Log 的一个封装。
然后可以通过以下方式将这个 c#函数注册到 lua 中的 Debug 表中:
Lua.lua_createtable(L,0,1);
//往栈中压入字符串'Log'
Lua.lua_pushstring(L,"Log");
//往栈中压入函数 Debug_Log
Lua.lua_pushcfunction(L,Debug_Log);
//从栈中弹出一个元素作为 key,再弹出一个元素作为 value,作为 pair 赋值到 index 指定的 table
Lua.lua_settable(L,1);
Lua.lua_setglobal(L,"Debug");
评论