文章

ToLua框架使用

下载和使用

从Github拉取ToLua框架代码。Github地址:https://github.com/topameng/tolua
可以看到下载拉下来的目录结构如下:


创建一个空的Unity工程,然后把Assets目录里的内容复制到Demo工程的Assets目录下,会弹出下面的提示框:

因为是第一次尝试,所以我选择取消,为的是能尽可能熟悉这个菜单下面的过程。


Source/Generate目录下,有两个文件,一个是DelegateFactory,另一个是LuaBinder,暂时不知道有什么用,先保留。


生成Wrap Files

先来看菜单Lua/Gen Lua Wrap Files,这个步骤的代码实现如下:


通过阅读这段代码,和它所调用的函数,可以知道这个步骤从CustomSettings.customTypeList中获得要导出的类型,如果有自定义的类型需要导出,需要在这里手动添加。GenBindTypes函数是对typeList进行过滤,过滤掉以下几种类型:

  • 重复的类型(出现重复会抛异常,中断流程)

  • ToLuaMenu.dropType中添加的类型(不需要的类型)

  • ToLuaMenu.baseType中添加的类型(自定义的list不需要添加,下一行代码会加回来)

整理完之后,会把整理的结果丢到ToLuaExport.allTypes里。然后逐个导出,最后清理。默认的保存路径定义在CustomSettings.saveDir这个变量内,为Assets/Source/Generate
点击生成,可以看到Assets/Source/Generate下生成了很多C#的Wrap代码。


生成Lua Delegates

除了导出的类型,还需要导出Delegate,导出列表定义在CustomSettings.customDelegatesList中,并且从CustomSettings.customTypeList中整理出方法,函数和定义的内部Delegate等等一并导出。
最后归并到一个集合内,批量导出。最后将结果写到Source/Generate/DelegateFactory.cs中:

生成 Lua Binder

生成Lua Binder的过程比较复杂,总的来说也是根据先前导出的类型,构建命名空间树,然后生成Binder,将结果写到Source/Generate/LuaBinder.cs中。
从下图可以看到,LuaBinder就是将类型注册到LuaState中。

至此生成完毕,Generate All的代码,就是把这三个步骤按顺序调用,不过不一定是我写的顺序,可以看看代码:

使用例子说明

在ToLua中,已经给出了一些使用示例,通过阅读这些代码,可以很好熟悉ToLua的使用,下面我就捡一些主要的样例来逐个说明。

从C#中调用Lua

在C#中执行Lua代码,是通过LuaState类来进行的,在使用之前,需要先调用LuaStateStart方法,然后通过DoFile来导入指定的Lua文件中的代码,再使用Call方法来调用指定的函数,C#这一边的示例如下:


它执行的是Assets/Lua/Main.lua文件,改文件的内容如下:


LuaState默认的SearchPath就是Assets/Lua,所以这里不用加路径也可以找到Main

设置和访问Lua中定义的变量

在这里通过DoString把样例代码加载到LuaState中,然后就可以像字典一样设置和访问里面的变量、表,以及访问定义的函数。

using LuaInterface;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
      LuaState state;

      private string script =@"
              print('Objs2Spawn is: '..Objs2Spawn)
              var2read = 42
             varTable = {1,2,3,4,5}
             varTable.default = 1
             varTable.map = {}
             varTable.map.name = 'map'

             meta = {name = 'meta'}
             setmetatable(varTable, meta)

             function TestFunc(strs)
                 print('get func by variable')
             end
       ";

      // Start is called before the first frame update
      void Start()
      {
            state = new LuaState();
            state.Start();
            state["Objs2Spawn"] = 5;
            state.DoString(script);

            Debugger.Log("Read var from lua: " + state["var2read"]);
            Debugger.Log("Read table var from lua: " + state["varTable.default"]);

            LuaFunction func = state.GetFunction("TestFunc");
            func.Call();
            func.Dispose();

            LuaTable table = state["varTable"] as LuaTable;

            LuaTable map = table["map"] as LuaTable;

            Debugger.Log($"Read varTable from lua, default: {table["default"]} map name: {map["name"]}");

            map["name"] = "New";
            Debugger.Log("Modify varTable name: {0}", map["name"]);
            map.Dispose();

            table.AddTable("newmap");

            LuaTable newmap = table["newmap"] as LuaTable;
            newmap["name"] = "newmap";

            Debugger.Log($"varTable.newmap name {newmap["name"]}");
            newmap.Dispose();

            LuaTable metaTable = table.GetMetaTable();

            if (metaTable != null)
            {
                  Debugger.Log("varTable metatable name: {0}", metaTable["name"]);
            }

            object[] list = table.ToArray();

            for (int i = 0; i < list.Length; i++)
            {
                  Debugger.Log("varTable[{0}], is {1}", i, list[i]);
            }

            table.Dispose();
      }

      private void OnDestroy()
      {
            state.CheckTop();
            state.Dispose();
      }
}

函数调用、参数和返回值

调用函数有三种形式,一种是调用LuaFuncCall函数,支持无参函数和多个参数的调用形式,但是没有返回值。第二种是PCall的调用形式,这种调用形式比较麻烦,调用语句和获取返回值要写的代码比较多,另一种是LazyCall的形式,支持可变参数的传递并返回多个返回值,其实是对PCall的封装,由于有GC的影响,已经不推荐使用。

using LuaInterface;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{
      LuaState state;

      private string script = @"
            function TestArray(array)
                  local len = array.Length

                  for i = 0, len - 1 do
                        print('Array: '..tostring(array[i]))
                   end

                  local iter = array:GetEnumerator()

                  while iter:MoveNext() do
                      print('iter: '..iter.Current)
                  end

                  local t = array:ToTable() 

                  for i = 1, #t do
                      print('table: '.. tostring(t[i]))
                  end

                  local pos = array:BinarySearch(3)
                  print('array BinarySearch: pos: '..pos..' value: '..array[pos])

                  pos = array:IndexOf(4)
                  print('array indexof bbb pos is: '..pos)

                  return 1, '123', true
            end 
      ";

      // Start is called before the first frame update
      void Start()
      {
            state = new LuaState();
            state.Start();
            state.DoString(script);

            LuaFunction func = state.GetFunction("TestArray");

            int[] array = { 1, 2, 3, 4, 5 };

            // 开始调用
            func.BeginPCall();

            // 设置参数
            func.Push(array);

            func.PCall();

            // 获取返回值
            double arg1 = func.CheckNumber();
            string arg2 = func.CheckString();
            bool arg3 = func.CheckBoolean();
            Debugger.Log("return is {0} {1} {2}", arg1, arg2, arg3);

            // 结束调用
            func.EndPCall();

            func.Dispose();
      }

      private void OnDestroy()
      {
            state.CheckTop();
            state.Dispose();
      }
}

自定义加载器

在ToLua的例子中,有一个例子是说明如何定义自己的加载器。例子中创建了一个TestCustomLoader类,并集成自LuaClient,重写InitLoader方法,该方法返回自己定义的修改器LuaResLoader

public class TestCustomLoader : LuaClient 
{
      string tips = "Test custom loader";

      protected override LuaFileUtils InitLoader()
      {
            return new LuaResLoader();
      }
}

一开始我还以为LuaResLoaderToLua提供的一些Helper类,阅读其中的代码才知道,它是继承自LuaFileUtils的类,重写ReadFile方法。这可以很方便地去管理Lua文件的加载路径。甚至可以想象这样一种场景:在网络延迟开销影响不大的情况下,通过HTTP URL访问的方式去获取Lua文件的内容并加载到运行环境中。

许可协议:  CC BY 4.0