XLua学习

所有的lua代码都是在lua虚拟机上跑的,一个LuaEnv实例对应一个lua虚拟机。

出于开销考虑,官方建议全局唯一:

XLua.LuaEnv luaenv = new XLua.LuaEnv();

一、lua文件的执行和加载

LoadString:加载一个代码块,但不执行,返回类型可以指定为一个delegate或者一个LuaFunction

T LoadString(string chunk, string chunkName = "chunk", LuaTable env = null)

DoString:执行一个代码块

object[] DoString(string chunk, string chunkName = "chuck", LuaTable env = null)

例如:

luaenv.DoString("content")

content可以是lua代码,但更常见的用法是去包含lua文件,使用require函数:

 DoString("require 'byfile'")

require实际上是调用loader来加载文件,xlua原生的loader支持加载Resources文件夹下的.txt文件。我们也可以自定义loader来加载特定路径格式的文件。

 public delegate byte[] CustomLoader(ref string filepath);
 public void LuaEnv.AddLoader(CustomLoader loader)

使用LuaEnv.AddLoader方法添加loader,例:

 luaenv.AddLoader((ref string filename) =>
 {
     if (filename == "InMemory")
     {
         string script = "return {ccc = 9999}";
         return System.Text.Encoding.UTF8.GetBytes(script);
     }
     return null;
 });

这时LuaEnv就会根据我们自定义的loader来加载文件了。

ps:官方推荐加载一个lua文件,然后在这个lua文件中加载其他文件。

二、C#调用Lua

首先官方案例中这样推荐:

为每个MonoBehaviour设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突

LuaEnv luaEnv = new LuaEnv();

LuaTable scriptEnv;
scriptEnv = luaEnv.NewTable();

LuaTable meta = luaEnv.NewTable();
meta.Set("__index", luaEnv.Global);
scriptEnv.SetMetaTable(meta);
meta.Dispose();

由于设置了独立的luaTable,之后的操作都在scriptEnv中进行。

c#访问lua,无论是访问基本类型、function还是table

本质上都是使用xLua提供的一个方法,访问lua中的数据并映射到c#中的指定类型:

 public void Get<TKey, TValue>(TKey key, out TValue value)

访问基本类型:

var basicType=scriptEnv.Get<TBasic>("Name")

访问function:

i.映射到delegate

推荐使用的方式,性能好很多,而且类型安全,缺点是要生成代码。

 Action luaStart;
 scriptEnv.Get("start", out luaStart);

ii.映射到LuaFunction

LuaFunction是xLua提供的一个类,优缺点刚好和第一种相反。 使用也简单,LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值

LuaFunction func = scriptEnv.Get<LuaFunction>("FuncName");
object[] os= func.Call(p1, p2);

访问table:

i.映射到普通class或struct

table的属性可以多于或者少于class的属性。可以嵌套其它复杂类型。 要注意的是,这个过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。

Test test = scriptEnv.Get<Test>("Name");

ii.映射到一个interface

这种方式为引用方式的映射,依赖于生成代码,代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。

ITest test = scriptEnv.Get<ITest>("Name");

iii.映射到Dictionary<>,List<>

不想定义class或者interface的话,可以考虑用这个,前提table下key和value的类型都是一致的。

Dictionary<T1, T2> dict = scriptEnv.Get<Dictionary<T1, T2>>("Name")

iv.映射到LuaTable类

不需要生成代码,但也有一些问题,比如慢,比方式2要慢一个数量级,比如没有类型检查,只能以k-v的形式使用

LuaTable tab = scriptEnv.Get<LuaTable>("Name");
print(tab.Get<T>("Name"));

使用建议:

  1. 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。

  2. 如果lua侧的实现部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。

PS:如果希望把一个lua函数适配到一个C# delegate,或者把一个lua table适配到一个C# interface,该delegate或者interface需要加上CSharpCallLua属性

三、Lua调用C#

调用方法与在c#中类似,但有以下区别:

1.lua中没有new关键字

2.所有c#相关的都要放在CS下,访问任何成员(类、函数、方法等)都要以CS.Namespace.Any的形式

3.调用非静态方法时常常需要传入方法所在类的实例为第一参数

new一个对象:

local newGameObj = CS.UnityEngine.GameObject()

获取类型:

typeof(CS.UnityEngine.GameObject)

访问静态属性、方法:

属性:

CS.UnityEngine.Time.deltaTime

方法:

CS.UnityEngine.GameObject.Find('helloworld')

小技巧:如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能

访问成员属性、方法

属性:

obj.test

方法:

obj.Test(obj,obj.test)
//冒号语法糖
obj:Test(obj.test)

ref、out关键字:

Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用侧的实参列表;(因为out关键字修饰的变量在方法内部要被初始化,所以省略输入)

Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。

//c#侧
public class Test
{
    public int TestFunc(int a,ref int b,out int c)
    {
        ...
    }
}

//lua侧调用
local test=Test()
local a,ref0,out0 = test:TestFunc(0,0)

重载:

xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于lua的number,如果一个函数有多个int、float、double类型的重载,并且数量相等,那么第一行将无法区分开来,只能调用到其中一个(生成代码中排前面的那个)。还有一种情况是当函数重载中存在params关键字时,也有可能会导致上述问题。

扩展方法:

只要在c#中定义了,lua中可以直接使用。

枚举:

和c#中枚举使用方法一样。

同时提供了一个_CastForm方法可以实现从值类型到枚举类型的转换

CS.NameSpace.TestEnum.__CastFrom(1)
CS.NameSpace.TestEnum.__CastFrom('Test1')

泛型方法:

支持部分情况

  • mono下可以
  • il2cpp下,如果泛型参数是引用类型可以
  • il2cpp下,如果泛型参数是值类型,C#那有用同样的泛型参数调用过

通过提供的xlua.get_generic_method方法调用。

//测试类
public class Test
    {
        private int a = 100;
        //泛型方法
        public int GenericMethodTest<T>(T t)
        {
            Debug.Log(t);
            Debug.Log(typeof(T));
            return a;
        }
    }
//实例化Test类
local test=CS.Test.Test()
//取得泛型方法    CS.Test为命名空间
local testGenericFunc=xlua.get_generic_method(CS.Test.Test,"GenericMethodTest")
//为泛型方法指定类型
local realFunc=testGenericFunc(typeof(CS.System.Int32))

//调用 若泛型方法为静态,则无需传第一个实例参数
realFunc(test,5)

协程:

调用unity中的协程

//包含xlua提供的工具
local util = require 'xlua.util'   
//使用util.cs_generator来生成IEnumerator
local t_fun = util.cs_generator(function()    
    for i=1,10 do
        print(i)
        coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
    end
end)
//开启协程 self是一个传入的monobehaviour脚本
self:StartCoroutine(t_fun)

PS:除基本类型外,所有希望lua访问的c#代码都要加上LuaCallCSharp属性,这样xLua才会生成代码。否则会尝试以效率较低的反射方式访问。

xLua相关配置详见:https://github.com/Tencent/xLua/blob/master/Assets/XLua/Doc/configure.md

猜你喜欢

转载自blog.csdn.net/a977621265/article/details/88351709