Baste 发表于 2022-9-27 07:37

【学习笔记】从零开始学ToLua(二)ToLua导入与样例

ToLua

tolua是一个可以大大简化c#代码与Lua集成的工具。基于cleand版本的头文件,tolua自动生成绑定代码以从Lua访问c#功能。使用Lua API和标记方法工具,tolua将c#常量,外部变量,函数,类和方法映射到Lua。
Tolua集成主要分为两部分,一部分是运行时需要的代码包括一些手写的和自动生成的绑定代码,另一部分是编辑器相关代码,主要提供代码生成、编译lua文件等操作,具体就是Unity编辑器中提供的功能。

下载与导入

官网下载 https://github.com/topameng/tolua
解压后将「Assets」、「Unity5.x」、「Luajit64」、「Luajit」四个文件夹复制到我们的工程文件夹中。

工程文件夹介绍

Editor
Editor下Custom/CustomSettings.cs 自定义配置文件,用于定义哪些类作为静态类型、哪些类需要导出、哪些附加委托需要导出等。我们可以在这里导入Tolua没有导入的类型或是自己的类型。导入之后点击Lua菜单上的Generate All即可生成对应的wrap文件。
Source
在Source文件夹中有Generate文件夹及LuaConst.cs脚本,Generate中主要是生成用于交互的绑定代码wrap脚本,LuaConst.cs是一些lua路径等配置文件。 若在CustomSettings中做了修改,需要在菜单栏的Lua选项中,重新生成新的Wrap文件。
Tolua
BaseType: 一些基础类型的绑定代码Core: 提供的一些核心功能,包括封装的「LuaFunction」「LuaTable」 「LuaThread」「LuaState」「LuaEvent」、调用tolua原生代码等等Examples: Tolua示例

Misc: 杂项,包含LuaClient,LuaCoroutine(协程),LuaLooper(用于tick),LuaResLoader(用于加载lua文件)Reflection: 反射相关
我们可以根据Examples里的内容对ToLua的使用进行了解。以下内容最好和example中的样例对照着看。

ToLua的初始化和加载文件

调用ToLua命名空间 using LuaInterface。new LuaResLoader() :自定义加载器加载lua文件。调用lua脚本需要先创建一个lua的虚拟机,创建的方法是LuaState lua = new LuaState();, 创建一个LuaState型的对象,这个就是lua的虚拟机,通过它我们可以与C#代码进行交互。使用完lua虚拟机之后记得要销毁,具体操作如下:先进行lua虚拟机栈的判空,使用lua.CheckTop(),如果栈空返回true, 然后就是析构掉lua虚拟机,具体方法为lua.Dispose()。调用lua.Start()方法完成lua虚拟机的一些基础初始化,里面的内容主要包括一些环境的配置和一些lua的基本库的加载,默认一般工程中创建该虚拟机时都要初始化。直接运行一段lua脚本 lua.DoString(),我们将要执行的脚本字符串作为参数传入。记得在脚本字符串前+@用来忽略转义字符。lua.AddSearchPath() ,通过此方法添加lua文件的路径,只有添加了文件路径之后,在该路径上的lua文件才可以被读取。读取到路径之后我们就可以通过lua.DoFile , lua.Require 来加载Lua文件。Require 读取文件是会检查该文件是否被加载过,如果被加载过,则直接返回一个索引,否则则加载并返回一个索引,而Dofile则是每次调用都会重新加载使用,相对来说对性能等的消耗都会大一些。当我们要使用协程时需要添加looper组件并且绑定LuaState,looper = gameObject.AddComponent<LuaLooper>();looper.luaState = lua;当我们要用到被wrap的文件时,使用LuaBinder.Bind(LuaState)向Lua虚拟机注册Wrap类

ToLua函数

通过DoString或是lua.DoFile , lua.Require加载Lua文件之后,创建一个LuaFunction类型的对象(函数在Lua中是作为对象存在),通过调用 lua.GetFunction("方法名"), 就可以获取lua文件中对应的函数对象。Example中给出了四种调用函数的方法
luaFunc.Invoke<int, int>(123456)
lua.Invoke<int, int>("test.luaFunc", 123456, true)
luaFunc.ToDelegate<Func<int, int>>();
CallFunc();--优点是不会有垃圾内存,所以不用手动释放GC。后面的样例中作者都是使用的这种。int CallFunc()
    {      
      luaFunc.BeginPCall();      --开始
      luaFunc.Push(123456);--传参
      luaFunc.PCall();      --运行
      int num = (int)luaFunc.CheckNumber();--获得返回值
      luaFunc.EndPCall();--结束
      return num;               
    }


因为在Lua中函数是个对象,所以我们要记得调用Dispose()释放对象,在释放LuaState之前。

C#中获取,声明,使用lua中的变量。

通过LuaState访问 ,lua["变量名"] 创建了一个名为   变量名   的 lua全变量。获取函数对象 LuaFunction func = lua["TestFunc"] as LuaFunction; Lua中的函数对象在这里需要进行强转才可以使用通过调用虚拟机的方法lua.GetTable 来获取lua中的table,table.AddTable("newmap")来添加table,table.GetMetaTable()获取元表,table.ToArray转化数组LuaTable型的变量,在使用完之后也需要手动释放内存,否则会因为内存未自动释放造成内存泄露。在Lua中C#中的那种方法直接用a.b就可以调用C#类型中的静态方法,如果调用的是非静态方法,则是用a:b 。

ToLua协程

协程的准备工作
lua.Start();--虚拟机初始化
LuaBinder.Bind(lua);
looper = gameObject.AddComponent<LuaLooper>();
looper.luaState = lua;

我们在这里是通过ToLua的looper组件去实现协程的功能,通过 coroutine.start进行启动。它会在c#每一帧驱动lua的协同完成所有的协同功能,这里的协同已经不单单是lua自身功能,而是tolua#模拟unity的所有的功能。协程函数的开启 :coroutine.start(协程函数)
协程函数的挂起:   coroutine.step()
协程函数的延时:   coroutine.wait(延时时间)    注意:时间的单位是秒
协程函数的结束:   coroutine.stop(协程对象)   注意:协程函数关闭的协程对象是对应的协程开启函数的返回值
协程下载:            coroutine.www(网址)
其中,除了   coroutine.start(协程函数) 和coroutine.stop(协程对象)之外,其他的协程方法只允许在协程函数的内部使用
ToLua中还提供了另一种使用协程的方法,但是大量使用效率过低,具体案例在example06文件夹中。

线程

在Lua中不存在那种传统意义上的多线程,所谓的多线程都是基于协程而实现的,所以Lua中的线程也都只是那种协作式的多线程,而无法实现那种抢占式的多线程的效果。通过func.CheckLuaThread()来建立线程。之后就可以通过thread.Resume()来唤醒线程。值得注意的是在线程样例中的update中有。
state.CheckTop();
state.Collect();--用于垃圾回收

数组

可以在Lua中直接通过array下标访问数组元素,也可以通过array:IndexOf(4),二分查找array:BinarySearch(3)。可以使用迭代器访问,array:GetEnumerator()获取迭代器, iter.Current 访问元素,iter:MoveNext()将迭代器指向下一个元素。将数组转换为Lua中的table array:ToTable() 。最好不要通过lua来直接访问一些容器,不然可能遇到一些坑。调用通用函数需要转换一下类型,避免可变参数拆成多个参数传递 func.LazyCall((object)array)。

Dictionary

也可以使用迭代器访问,操作同数组GetEnumerator()获取迭代器, iter.Current 访问元素,iter:MoveNext()。详细的Dictionary方法,参数和c#中是一致的。
Dictionary:get_Item()--获取与指定的键相关联的值
Dictionary:set_Item()--设置与指定的键相关联的值
Dictionary:Add()      --将指定的键和值添加到字典中
Dictionary:Clear()    --从 Dictionary中移除所有的键和值
Dictionary:ContainsKey()   --确定 Dictionary是否包含指定的键
Dictionary:ContainsValue()   --确定 Dictionary是否包含特定值
Dictionary:GetObjectData()
Dictionary:OnDeserialization()
Dictionary:Remove() --从 Dictionary中移除所指定的键的值
Dictionary:TryGetValue() --获取与指定的键相关联的值
Dictionary:GetEnumerator() --返回循环访问 Dictionary的枚举数
Dictionary:New() --这个是创建一个字典的方法
Dictionary.this --对象的this引用
Dictionary.__tostring --返回表示当前 Object的 String,这里其实并非C#对象的那个Tostring().而是作者自己重新等装的,但是效果类似,不用纠结,
                     --只不过不是方法而是对象了
Dictionary.Count   --获取包含在 Dictionary中的键/值对的数
Dictionary.Comparer--获取用于确定字典中的键是否相
Dictionary.Keys   --获取包含 Dictionary中的键的集合
Dictionary.Values   --获取包含 Dictionary中的值的集合

枚举类型

主要的方法
tostring(枚举变量)   可以输出该枚举变量的变量名
枚举变量:ToInt()    可以将枚举变量转化为对应的整数
枚举类型.IntToEnum()   可以将整数转化为对应的枚举变量
枚举变量:Equals(整数)   可以实现整数与枚举变量的比较


委托

在c#中增加或删除绑定的lua函数,通过DelegateTraits<委托类型>.Create(func)增加和DelegateFactory.RemoveDelegate,记得不要用的要给他dispose。因为上面这两个方法返回类型都是Delegate,所以我们不要忘记进行一个强制转换。Lua中不能使用+=,因此使用Delegate = Delegate + 方法这种格式。
--添加
LuaFunction func = state.GetFunction("DoClick1");
TestEventListener.OnClick onClick = (TestEventListener.OnClick)DelegateTraits<TestEventListener.OnClick>.Create(func);
listener.onClick += onClick;
--删除
LuaFunction func = state.GetFunction("DoClick1");
listener.onClick = (TestEventListener.OnClick)DelegateFactory.RemoveDelegate(listener.onClick, func);
func.Dispose();
func = null;

该案例中也测试了重载函数与Lua的交互,可以正常使用。

GameObject

Lua中引用Unity的内置对象只要注意一下语法就可以了,用a.b就可以调用C#类型中的静态方法,如果调用的是非静态方法,则是用a:b 。
页: [1]
查看完整版本: 【学习笔记】从零开始学ToLua(二)ToLua导入与样例