lua与c#交互,lua c#

lua与c#交互,lua c#,ToLua框架下C#与Lua代码的互调操作

本文主要介绍了ToLua框架下C#与Lua代码之间的互调操作,具有很好的参考价值。希望对大家有帮助。来和边肖一起看看吧。

Lua是目前国内使用最广泛的语言,有很多基于Lua的热更新框架。最近学习了ToLua的热更新框架,主要问题是C#和Lua的互调,我就把我的学习做个记录,以备将来参考。

所谓“互调”当然包括两个方面,一个是通过C#调用Lua代码,一个是通过Lua代码调用C#脚本,第二点也包括C#脚本中注册的Unity对象。

1. ToLua的简单实现原理

ToLua框架主要通过静态绑定实现C#和Lua的交互。基本原理是构建一个Lua虚拟机来映射C#脚本,然后通过这个虚拟机运行Lua脚本。Lua脚本运行时,可以通过虚拟机依次调用C#脚本中注册的对象。这种方法的优点是比使用反射uLua更有效。

在ToLua框架下,实现可以分为三个部分:通用Unity C#部分、ToLua虚拟机部分和Lua脚本部分。结构见下图:

甲苯结构

目前国内手游需要更新的主要逻辑框架和组件功能一般都是用C#实现的,而具体的功能和调用都放在Lua中。因为C#不能打包成AssetBundle,所以不能通过AssetBundle修改代码。但是,Lua是一种即时编译语言,可以打包成AssetBundle。当需要修改简单函数时,可以通过AssetBundle更新Lua代码。

2. ToLua的下载的安装

首先是下载地址:

托卢阿

这是作者的github地址。进入后,点击下载Zip。完成后解压到需要的目录,然后用Unity打开。

点击压缩文件下载。

第一次打开项目时,会提示您是否需要自动生成注册文件。新手可以选择直接生成。如果选择取消,您也可以在编辑器菜单中手动注册它。3354这是一个很重要的操作,后面会提到。

开始下面关于用法的文字。

3. ToLua的基本使用

ToLua的基本实现前面已经提到了,这里可以进一步细化:创建虚拟机——绑定数据——,调用Lua代码。这组步骤在框架附带的示例中也非常清楚。

首先,除了示例之外,实现这三个步骤。

ToLua虚拟机的创建非常简单,只需要创建一个新的LuaState即可。我们可以构建一个C#脚本作为入口,引用LuaInterface命名空间,输入以下代码,将文件挂载到场景中的一个空对象上。

使用LuaInterface

使用UnityEngine

公共类LuaScene : MonoBehaviour

{

字符串luaString=@ '

Print(“这是一个使用DoString的Lua程序”)

string luaFile=' LuaStudy

LuaState州;

无效开始()

{

state=new Lua state();//创建Lua虚拟机

状态。start();//启动虚拟机

//用字符串调用Lua

状态。do string(Lua string);

//用文件调用Lua

//手动添加lua文件搜索地址

string scene file=application . data path '/Lua study ';

状态。AddSearchPath(scene file);

状态。DoFile(Lua file);

状态。require(Lua file);

状态。dispose();//使用后回收虚拟机

调试。LogFormat('当前虚拟机状态:{0} ',null==state);//验证虚拟机状态

}

}

这里使用的Lua脚本也很简单。

打印(“这是一个使用DoFile的Lua程序”)

Lua挂载

ToLua直接调用Lua代码有两种方式,一种是DoString,一种是DoFile;此外,还有一个必需方法。这种方法不同于前两种方法。ToLua会将调用的Lua文件加载到Lua栈中,而前两个方法只运行一次,运行后保存在缓存中。虽然以后可以调用它们,但是它们会的。

请注意,在上面的代码中,当使用DoFile和Require方法时,您需要手动将文件搜索位置添加到目标文件中。

运行结果如下:

Lua运行结果

最后,用完后记得清理虚拟机。我用null==state来判断,最后输出“true”,表示调用LuaState后虚拟机已经被清理了。处置()。

4. C#中调用Lua变量/函数

上面我们实现了C#调用Lua文件和字符串。其实对于ToLua来说,直接调用string和file没有本质区别,最终都会转换成byte[]进行加载。

接下来,实现调用ToLua来指定Lua变量和函数。这里,Lua代码是通过一个文件导入的。

首先是我们的Lua代码。这个Lua代码有一个公共变量,一个带函数的表,一个不带参数的函数和一个带参数的函数。它的功能很简单,这段代码里不调用。

num=0

mytable={1,2,3,4}

mytable.tableFunc=function()

Print('调用table func ');

结束

函数计数()

数字=数字1

打印('计数器1,当前计数器是'.数字)

退货数量;

结束

函数输入值(参数)

Print ('[Call: ]InputValue方法在Lua中传入了参数:'.tostring( param))

结束

然后就是C#代码,还是一样的套路。首先,创建一个虚拟机并读入Lua文件。普通变量、无参数函数、无参数函数、表的调用将依次讲解。

注意:如果有本地的logo,在C#中是无法直接获取的。

普通变量

普通变量的调用很简单。文件加载后,可以直接以LuaState[string]的形式获取,也可以直接用这个表达式赋值。

无参函数

调用的函数有两种方式,一种是在调用之前缓存为LuaFunction类型,另一种是直接调用Call方法。

有参函数

参数调用和参数调用的区别在于参数的传递。在ToLua中,重载了大量的参数调用函数,与参数调用的调用方法相同。参数调用也有两种调用方式。这里详细解释了传递参数的不同方式。

传入参和调用分离。

一般这种方法需要先将函数缓存为LuaFunction,然后用BeginPcall方法标记函数,再用Push或PushArgs方法将参数传入函数,最后调用Pcall,或者调用EndPcall标记结束。

//将参数传入方法

LuaFunction valueFunc=state。GetFunction(' input value ');

valueFuncbegin call();

ValueFuncPush(' - push push方法从C#-')传入参数;

valueFuncPCall();

调用时,直接传入参数。

这是最符合逻辑的操作方式,但是当你看实现代码的时候,你会发现它其实只是一组封装在LuaFunction中的实现,本质和前面的一样。

4.table

是表lua里的百宝箱。一切都可以过去。表中可以有普通的变量、表和方法。

在ToLua中,分析了表的数据结构,并实现了多种方法。在这里,table可以作为LuaState来操作,两者没有区别。

以下是完整的C#代码,并附上运行结果。

使用LuaInterface

使用UnityEngine

公共类LuaAccess : MonoBehaviour

{

string luaFile=' LuaAccess

LuaState州;

无效开始()

{

state=new Lua state();

状态。start();

//用文件调用Lua

//手动添加lua文件搜索地址

string scene file=application . data path '/Lua study ';

状态。AddSearchPath(scene file);

状态。require(Lua file);//加载文件

//获取Lua变量

调试。Log('获取文件中的变量:' state[' num ']);

state[' num ']=10;

调试。Log('设置文件中的变量是:' state[' num ']);

//调用Lua方法

luaFunc=状态。GetFunction(' Count ');

luaFunc。call();

调试。Log('C#调用LuaFunc,函数返回值:' state[' num ']);

调试。Log('C#直接调用Count方法。);

状态。Call('Count ',false);

//将参数传入方法

LuaFunction valueFunc=state。GetFunction(' input value ');

valueFuncbegin call();

ValueFuncPush(' - push push方法从C#-')传入参数;

valueFuncPCall();

valueFuncendp call();

ValueFuncCall(' -直接调用方法从C#-')传入参数;

//获取LuaTable

LuaTable table=state。GetTable(' my table ');

桌子。调用(' table func ');

LuaFunction tableFunc=table。getluafunch(' table func ');

调试。Log('C#调用表中的func ');

tableFunc。call();

调试。Log('获取table中的num值:' table[' num ']);

//直接通过下标获取

for(int I=0;我表。长度;我)

{

调试。Log('获取table的值:' table[I]);

}

//转换为LuaDictTable

表。todict table();

foreach(可口述的变量项)

{

调试。LogFormat('遍历表:{0}-{1} ',item.key,item . value);

}

状态。dispose();

}

}

Lua访问变量

5. Lua中调用C#方法/变量

之前在@ Rorschach L的文章里看过一个关于lua调用C#的笔记,但是总觉得少了点什么,所以自己做笔记的时候特别注意具体的实现。

在@ Rorschach L的文章中,将一个C#对象作为参数传入列表,然后直接在Lua代码中运行对应的方法名。少了几个关键步骤。如果只进行这些步骤,Lua中的引用是无法实现的。

先说实现原理。在文章的第一部分,我提到了ToLua的基本实现思想,并将这个系统分为三个部分。在这三个部分中,ToLua作为沟通Lua脚本和C#的桥梁。我们知道Lua的本质是通过字节码封装C,特点是即时编译。从C#或其他语言调用Lua并不太难。你只需要提前约定好一个具体的方法名,然后加载脚本,但是C#需要提前编译。如何通过解释器调用C#中的对象是主要难点。ToLua实现了这两个功能。

从这方面来说,我觉得大多数人会想到的最直接的实现思路,大概都是通过反思来实现的,uLua也是通过反思来实现的,但是反思的效率很低。虽然可以实现,但是问题还是很明显的。

ToLua通过绑定方法名来实现这种映射。首先构造一个Lua虚拟机,虚拟机启动后绑定需要的方法。当虚拟机运行时,可以在Lua中调用特定的方法。虚拟机实现了伪装的解释器的功能。当Lua调用特定的方法和对象时,虚拟机在绑定的方法中找到对应的C#方法和对象进行操作,ToLua已经自动实现了一些绑定的方法。

基本原理大概了解以后,我们就可以来看看它的具体实现了。

第一步是设置并启动虚拟机。为了实现Lua对C#的调用,首先我们要调用binding方法,所以我们的代码变成如下。正如你所看到的,这里和之前唯一的不同是添加了LuaBinder。绑定(状态)方法。这个方法里面其实是很多定义好的方法的绑定,也就是上面说的绑定方法。

使用LuaInterface

使用UnityEngine

公共类CSharpAccess : MonoBehaviour

{

私有字符串luaFile=' LuaCall

LuaState州;

无效开始()

{

state=new Lua state();

状态。start();

string scene file=application . data path '/Lua study ';

状态。AddSearchPath(scene file);

//注册方法调用

卢阿宾德。Bind(状态);

状态。require(Lua file);//加载文件

}

}

然后我们添加一个变量和一个方法。我们要实现的是在Lua中完成对这个方法和变量的调用。

公共字符串AccessVar='这是初始值';

公共void PrintArg(字符串参数)

{

调试。Log('C#输出变量值:' arg ');

}

有了目标方法之后,我们将把这个变量和方法绑定到虚拟机中。

查看LuaState的实现代码可以发现,绑定有三种方法:RegFunction、RegVar和RegConstant,分别用于绑定函数/委托、变量和常量。在这里,ToLua通过一个委托实现方法的映射。这个委托需要传入一个IntPtr类型的luaState变量。这个变量的本质是一个句柄。在实际操作中,虚拟机将作为变量传入。

公共委托int LuaCSFunction(int ptr Lua state);

public void RegFunction(字符串名,LuaCSFunction func);

public void RegVar(字符串名,LuaCSFunction get,LuaCSFunction set);

public void RegConstant(字符串名,双d);

public void RegConstant(字符串名,bool标志);

总结一下几个方法的特点:

这些方法都需要传入一个字符串名,这个字符串名就是稍后在Lua中要调用的变量或方法名。

RegConstant方法很简单,只需传入一个名称,然后传入一个常数;

函数和RegVar都是由LuaCSFunction类型的委托实现的;

正则函数需要一个LuaCSFunction委托,这个委托需要对原方法重新进行一次实现;

雷瓦尔除了名字之外,还需要两个LuaCSFunction委托,可以理解为一个变量的获取/设置方法,如果只有得到或设置,另一个留空即可。

接下来我们对AccessVar和PrintArg方法进行一下LuaCSFunction形式的实现。

private int PrintCall(System .IntPtr L)

{

尝试

{

托卢阿CheckArgsCount(L,2);//对参数进行校验

csharp access obj=(csharp access)ToLua .CheckObject(L,1,类型为(csharp访问));//获取目标对象并转换格式

string arg0=ToLua .CheckString(L,2);//获取特定值

obj .PrintArg(arg 0);//调用对象方法

返回1;

}

接住(系统。例外情况e)

{

返回Lua dll。total _ exception(L,e);

}

}

private int GetAccesVar(System .IntPtr L)

{

对象o=空

尝试

{

o=托卢阿ToObject(L,1);//获得变量实例

cs harp access obj=(cs harp access)o;//转换目标格式

字符串ret=obj .AccessVar//获取目标值

托卢阿Push(L,ret);//将目标对象传入虚拟机

返回1;

}

接住(系统。例外情况e)

{

回报鲁阿德。tol ual _ exception(L,e,o,’尝试在零值上索引访问var’);

}

}

private int SetAccesVar(System .IntPtr L)

{

对象o=空

尝试

{

o=托卢阿ToObject(L,1);//获得变量实例

cs harp access obj=(cs harp access)o;//转换目标格式

obj .AccessVar=ToLua .ToString(L,2);//将要修改的值进行设定,注意这里如果是值类型可能会出现拆装箱

返回1;

}

接住(系统。例外情况e)

{

回报鲁阿德。tol ual _ exception(L,e,o,’尝试在零值上索引访问var’);

}

}

可以看到这三个方法的格式都是一致的,通用的步骤如下:

使用托卢阿中的方法对L句柄进行校验,出现异常则抛出,本例中使用托卢阿CheckArgsCount方法;

获得目标类的实例,并转换格式,具体转换方法较多,可以根据需要在托卢阿类中选择,本例中使用了托卢阿。检查对象和托卢阿。对象等方法;

调用对应方法,不同的方法调用略有区别。

值得注意的是,在托卢阿的ToObjectQuat、ToObjectVec2等获取值类型的方法中,会出现拆装箱的情况。

下一步将几个方法注册进左上臂虚拟机。

注意这里有两对方法,分别是开始模块\结束模块和BeginClass\EndClass,BeginModule\EndModule用于绑定命名空间,可以逐层嵌套;而开始类\结束类用于开启具体的类型空间,具体的方法和变量绑定必须在这成对的方法之中,否则会导致托卢阿崩溃(百试百灵,别问我怎么知道的)。

私有空的绑定(卢阿塞特L)

{

长度开始模块(空);

长度BeginClass(typeof(CSharpAccess),typeof(UnityEngine .MonoBehaviour));

状态RegFunction('Debug ',打印调用);

状态RegVar('AccessVar ',GetAccesVar,SetAccesVar);

长度end class();

长度EndModule();

}

最后是我们的左上臂代码,非常简单,注意调试和AccessVar调用的区别。

打印('-进入左上臂调用- ')

本地go=UnityEngine .游戏对象。查找(' LuaScene ')

本地访问=go:get组件(“csharp访问”)

访问:调试(' Lua调用C#方法)

访问AccessVar=' -这是修改值- '

打印('- Lua调用结束- ')

完整C#代码

使用Lua接口

使用联合工程

公共类CSharpAccess : MonoBehaviour

{

私有字符串luaFile=' LuaCall

卢阿塞特州;

无效开始()

{

state=new Lua state();

状态. start();

字符串场景文件=应用程序。数据路径'/Lua研究';

状态AddSearchPath(场景文件);

//注册方法调用

卢阿宾德绑定(状态);

绑定(状态);

调试。日志('访问变量初始值:' access var);

状态. require(Lua文件);//载入文件

调试。日志(' C#查看:' access var);

状态dispose();

}

私有空的绑定(卢阿塞特L)

{

长度开始模块(空);

长度BeginClass(typeof(CSharpAccess),typeof(UnityEngine .MonoBehaviour));

状态RegFunction('Debug ',打印调用);

状态RegVar('AccessVar ',GetAccesVar,SetAccesVar);

长度end class();

长度EndModule();

}

private int PrintCall(System .IntPtr L)

{

尝试

{

托卢阿。CheckArgsCount(L,2);//检查参数

csharp access obj=(csharp access)ToLua。CheckObject(L,1,type of(csharp access));//获取目标对象并转换格式

string arg0=ToLua。CheckString(L,2);//获取特定值

obj。PrintArg(arg 0);//调用对象方法

返回1;

}

catch(系统。例外情况e)

{

返回Lua dll . total _ exception(L,e);

}

}

公共void PrintArg(字符串参数)

{

调试。Log('C#输出变量值:' arg ');

}

【系统。未序列化]

公共字符串AccessVar='这是初始值';

private int GetAccesVar(System。IntPtr L)

{

对象o=null

尝试

{

o=托卢阿。ToObject(L,1);//获取变量实例

csharp access obj=(csharp access)o;//转换目标格式

字符串ret=obj。AccessVar//获取目标值

托卢阿。Push(L,ret);//将目标对象传入虚拟机

返回1;

}

catch(系统。例外情况e)

{

return Lu adll . tol ual _ exception(L,e,o,'尝试在零值上索引access var ');

}

}

private int SetAccesVar(System。IntPtr L)

{

对象o=null

尝试

{

o=托卢阿。ToObject(L,1);//获取变量实例

csharp access obj=(csharp access)o;//转换目标格式

obj。AccessVar=ToLua。ToString(L,2);//设置要修改的值。

返回1;

}

catch(系统。例外情况e)

{

return Lu adll . tol ual _ exception(L,e,o,'尝试在零值上索引access var ');

}

}

}

运行结果

Lua调用C#

最后,让我们回到这一节的开头。@罗夏L的文章哪里出错了?

我在lua中添加了一行access:PrintArg('PrintArg ')调用方法,发现Unity报错了这个:

直接调用方法名时出错。png

说明不能简单的这么做就直接调用方法。仔细读完文章,我发现他提到了这样一件事:

首先要做的是把自己的类放到CustomSettings中,这个类是CallLuafunction。

BindType[]自定义类型列表

放在这个数组里注册给lua使用。

他在这里没有详细说明吗?我找到了这个课,发现这个课里面记录了很多Unity自己的课。这让我想起了第一次启动Lua时的提示,心中生出一个疑问:这些数据是用来自动注册生成类的吗?

//在此添加要导出并注册到lua的类型列表。

公共静态绑定类型[]自定义类型列表=

{

_ GT(type of(luaeinjectionstation)),

_GT(typeof(InjectType)),

_GT(调试器类型)。SetNameSpace(null),

.以下部分省略。

沿着调用链,我找到了这个变量的引用,果然,最多的数据是用于类型注册的。

我把这个类放在数组末尾,点击清除换行文件,完成后马上弹出自动生成数据的对话框。点击确定,

重新生成注册

自动生成

接下来,我重新运行lua脚本:

打印('-输入Lua调用-')

本地go=UnityEngine。GameObject.Find('LuaScene ')

local access=go:get component(' csharp access ')

访问:调试(“Lua调用C#方法”)

访问。AccessVar=' -这是修改后的值-'

打印('Lua调用结束-')

访问:PrintArg('PrintArg ')

运行成功。

成功的运行表明,ToLua已经实现了一套完整的绑定方案,只需要配置需要的内容即可。

6.总结

本来只是想写一个简单的调用方法,最后写了一篇很长的文章,但是从计划开始到整篇文章结束用了将近一整天的时间。

即便如此,收获还是很大的,使用这套工具的熟练程度已经上了一个台阶,以后要加强总结。

以上ToLua框架下C#与Lua代码的互调操作,就是边肖分享的全部内容。希望给大家一个参考,多多支持我们。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • 设计一个简单的C#控制台应用程序,C#控制台程序,C# 创建控制台应用程序
  • 深入解析windows第8版,深入解析C#(第4版)
  • 数组代码,c# 数组操作,C# 数组实例介绍(图文)
  • 学会C#要多久,学会c#要多久,c#学习之30分钟学会XAML
  • 回溯法01背包问题c,回溯法求解01背包问题伪代码,C#使用回溯法解决背包问题实例分析
  • xml文件转义字符,xml转意字符,C# XML中的转义字符操作
  • winform 进度条控件,c# 进度条使用
  • winform 进度条控件,c# 进度条使用,C#使用winform实现进度条效果
  • winform backgroundworker,c# isbackground
  • winform backgroundworker,c# isbackground,C# BackgroundWorker用法详解
  • lua与c#交互,lua c#,ToLua框架下C#与Lua代码的互调操作
  • linq c#,linq原理 c#
  • linq c#,linq原理 c#,c#中LINQ的基本用法实例
  • java decimal保留两位小数,sql中decimal函数保留2位小数,C#中decimal保留2位有效小数的实现方法
  • com组件初始化失败,c#开发com组件
  • 留言与评论(共有 条评论)
       
    验证码: