lua和unity如何交互_【Lua与C#交互④】如何让Lua打印到Unity控制台

今天要讲的如何让Lua打印到Unity控制台?

相信用过tolua或者xlua的人都知道,在lua脚本里面只要写一行print就能打印到unity控制台效果类似Debug.Log。 如下:

print("This is a script from a utf8 file")

print("tolua: 你好! こんにちは! 안녕하세요!")

那么它们背后的原理是什么呢?如果自己实现一个类似的函数替换功能又该如何实现?

首先来看核心代码如下:

_L = LuaDLL.luaL_newstate();

LuaDLL.luaL_openlibs( _L );

LuaDLL.lua_pushcfunction( _L, Print );

LuaDLL.lua_setglobal( _L, "print" );

1. luaL_newstate

新建一个lua状态机,没什么好说的

2. luaL_openlibs

打开lua标准库,把库里的函数放到全局变量里

什么是lua标准库?

lua标准库指的是一些lua源码自带的库函数,包括debug、package、string、math等。相关代码在lua源码里的linit.c文件。如果没有这一步,后面调用setglobal时就会失败,因为在全局里面没有print这个函数。

3. lua_pushcfunction

将c#里的方法压入栈顶。这个方法在lua源码里是一个宏。

#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0)

所以如果直接build lua源码的dll,是调用不了这个方法的。在c#端需要做以下处理,或者自行修改lua源码。

[DllImport( LUADLL, CallingConvention = CallingConvention.Cdecl )]

public static extern void lua_pushcclosure( IntPtr L, LuaCSFunction f, int n );

public static void lua_pushcfunction( IntPtr L, LuaCSFunction f )

{

lua_pushcclosure( L, f, 0 );

}

LuaCSFuncton是一个参数为Intptr,返回值为int的委托。

我们需要把c#端另外实现一个Print方法,并且把lua端的print替换掉。

Print

[MonoPInvokeCallbackAttribute( typeof( LuaCSFunction ) )]

private static int Print( IntPtr L )

{

try

{

int n = LuaDLL.lua_gettop( L );

var sb = new StringBuilder();

//获得当前运行的函数的上一个调用层的信息,返回行数,把调用层的名称入栈 int line = LuaDLL.jlua_where( L, 1 );

string filename = LuaDLL.lua_tostring( L, -1 );

LuaDLL.lua_settop( L, n );

int offset = filename[0] == '@' ? 1 : 0;

sb.Append( '[' ).Append( filename, offset, filename.Length - offset ).Append( ':' ).Append( line ).Append( "]:" );

for( int i = 1; i <= n; i++ )

{

if( i > 1 ) sb.Append( " " );

if( LuaDLL.lua_isstring( L, i ) == 1 )

{

sb.Append( LuaDLL.lua_tostring( L, i ) );

}

else if( LuaDLL.lua_isnil( L, i ) )

{

sb.Append( "nil" );

}

else if( LuaDLL.lua_isboolean( L, i ) )

{

sb.Append( LuaDLL.jlua_toboolean( L, i ) ? "true" : "false" );

}

else

{

IntPtr p = LuaDLL.lua_topointer( L, i );

if( p == IntPtr.Zero )

{

sb.Append( "nil" );

}

else

{

sb.Append( LuaDLL.luaL_typename( L, i ) ).Append( ":0x" ).Append( p.ToString( "X" ) );

}

}

}

Debug.Log( sb.ToString() );

return 0;

}

catch(Exception e )

{

throw e;

}

}

Print 函数需要打印出lua的调用栈信息。如一开始展示的那样,它能够打印出是哪个lua脚本的哪一行调用的print。

这里我的实现方式基本照抄tolua。唯一的区别是我使用的stringbuilder进行字符串拼接,tolua使用的CString,效果是一样的。

(1) 首先调用lua_gettop知道当前lua栈里有多少层

(2) 接着调用了一个lua_where,这是tolua在c端自己写的方法,我也模仿(照抄)了一个,源码如下:

LUA_API int jlua_where(lua_State *L, int level) {

lua_Debug ar;

if (lua_getstack(L, level, &ar)) {

lua_getinfo(L, "Sl", &ar);

if (ar.currentline > 0) {

lua_pushstring(L, ar.source);

return ar.currentline;

}

}

lua_pushliteral(L, "");

return -1;

}

这里有涉及到几个lua的API:int lua_getstack (lua_State L, int level, lua_Debugar):获取栈信息,level表示第几层

int lua_getinfo (lua_State L, const charwhat, lua_Debug *ar):根据what字符串的内容给ar填充信息'n': 填充 name 及 namewhat 域;

'S': 填充 source , short_src , linedefined , lastlinedefined ,以及 what 域;

'l': 填充 currentline 域;

't': 填充 istailcall 域;

'u': 填充 nups, nparams,及 isvararg 域; -

'f': 把正在运行中指定层次处函数压栈;

'L': 将一张表压栈,这张表中的整数索引用于描述函数中哪些行是有效行。 (有效行指有实际代码的行,即你可以置入断点的行。 无效行包括空行和只有注释的行。)

const char lua_pushliteral (lua_StateL, const char *s): 等同于pushstring

在Print里面还有一个方法lua_tostring也值得一讲。lua_tostring在lua源码里也是一个宏。不能被打到dll里面。

#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len){...}

在c#端需要这么处理:

[DllImport( LUADLL, CallingConvention = CallingConvention.Cdecl )]

public static extern IntPtr lua_tolstring( IntPtr luaState, int index, out int len );

public static string lua_tostring( IntPtr luaState, int index )

{

int len = 0;

IntPtr str = lua_tolstring( luaState, index, out len );

if( str != IntPtr.Zero )

{

string result = Marshal.PtrToStringAnsi( str, len );

if( result == null )

{

byte[] buffer = new byte[len];

Marshal.Copy( str, buffer, 0, len );

return Encoding.UTF8.GetString( buffer );

}

return result;

}

return null;

}

lua_tolstring之所以需要out是因为lua源码里对应的方法会给len赋值。还有在lua_tostring将指针Intptr转成了字符串是因为c里面的char类型不能直接转成c#里的string类型。

(3)调用lua_settop把调用lua_where的时候往栈里放的东西扔掉。lua_settop是一个非常常用的API,作用是把第n层往上的栈里存的东西都舍弃。

(4)遍历,判断一个栈里的从1到n层存的都是什么数据类型

(5)异常处理,我这里因为偷懒,直接抛出异常。在tolua里面有一个luaExection类专门用来处理lua端的异常的。

4. lua_setglobal

最后一步调用lua_setglobal替换全局函数print就大功告成了。

总结,这次依旧讲的的lua的api调用以及lua和c#端进行交互的问题。并且从这次开始不再使用tolua等别人打包好的dll了。而是我自己拿lua源码打包。这些知识属于比较底层的东西,可能很多人觉得没有什么用,我之前也是这么觉得的。别人都已经造好轮子了,我还去研究轮子怎么造有用吗?最近也是因为工作需要不得不研究lua才发现,自己以前懂的连皮毛都算不上,都是停留在tolua,xlua怎么用的程度,一旦脱离了这些工具,自己什么也不会了。

单纯的使用工具并不能提高自己的技术。因为工具总是会变的,只有掌握好底层,才能以不变应万变。

关于作者:水曜日鸡,简称水鸡,ACG宅。曾参与索尼中国之星项目研发,具有2D联网多人动作游戏开发经验。

游戏行业交流学习群:891809847

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值