Bootstrap

【学习笔记】从零开始学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

  1. BaseType: 一些基础类型的绑定代码
  2. Core: 提供的一些核心功能,包括封装的「LuaFunction」「LuaTable」 「LuaThread」「LuaState」「LuaEvent」、调用tolua原生代码等等
  3. 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中给出了四种调用函数的方法
  1. luaFunc.Invoke<int, int>(123456)

  2. lua.Invoke<int, int>("test.luaFunc", 123456, true)

  3. luaFunc.ToDelegate<Func<int, int>>();

  4. 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[i]下标访问数组元素,也可以通过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 。

;