xLua的学习笔记

Tencent/xLua下载地址:https://github.com/Tencent/xLua

Lua文件的加载


通过LuaEnv类来执行字符串lua代码

  1. luaEnv.DoString(“lua代码”);
  2. luaEnv.DisPose(); //销毁lua代码
public class HelloWorld01 : MonoBehaviour {

    private LuaEnv luaEnv;

    void Start () {
        luaEnv = new LuaEnv();
        //luaEnv.DoString("print(\"Hello World!\")");
        luaEnv.DoString(" CS.UnityEngine.Debug.Log('Hello World!') ");
    }

    void Update () {

    }
    private void OnDestroy()
    {
        luaEnv.Dispose();      //销毁
    }
}

加载Lua文件的两种方式

1.通过TextAsset获取lua代码的文本资源:
TextAsset luaText = Resources.Load(“HelloWorld.lua”);
2.用lua的require函数即可
比如:DoString(“require ‘HelloWorld’”)

using UnityEngine;
using XLua;

public class HelloWorld02 : MonoBehaviour {
    private LuaEnv luaEnv;

    void Start () {
        luaEnv = new LuaEnv();
        //TextAsset luaText = Resources.Load<TextAsset>("HelloWorld.lua");  //HelloWorld.lua.txt,不区分大小写
        //luaEnv.DoString(luaText.ToString());
        luaEnv.DoString("require 'HelloWorld'"); //HelloWorld.lua.txt
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }

}

自定义Loader

1.在xLua加自定义loader是很简单的,只涉及到一个接口:
public delegate byte[] CustomLoader(ref string filepath);
通过LuaEnv.AddLoader可以注册个回调,该回调参数是字符串(filePath为lua文件的路径)。lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,则执行系统默认的loader,如果不为空则把返回的byte数组当做要执行的lua代码。

using UnityEngine;
using XLua;

public class HelloWorld03 : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.AddLoader(myLoader);
        luaEnv.DoString(" require 'HelloWorld' ");
    }

    private byte[] myLoader(ref string filePath)
    {
        Debug.Log(filePath);        //输出lua文件名,不是真实路径
        string s = "print('123')";
        return System.Text.Encoding.UTF8.GetBytes(s);   //输出"123" 不会执行HelloWorld.lua中的内容
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

2.将filepath修改为真实路径,并执行真实路径下的lua文件
注意:要将lua文件放置在Application.streamingAssetsPath支持的默认路径Assets/streamingAssets路径下.

using UnityEngine;
using System.IO;
using XLua;

public class HelloWorld04 : MonoBehaviour {

    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.AddLoader(myLoader);
        luaEnv.DoString(" require 'HelloWorld' ");
    }

    private byte[] myLoader(ref string filePath)
    {
        //Debug.Log(Application.streamingAssetsPath);
        string path = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";//真实路径
        Debug.Log(path);

        //return File.ReadAllBytes(path);
        return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(path));
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

C#访问Lua



从Lua中获取一个全局基本数据类型

使用LuaEnv类中Gloabl.Get<数据类型>(“Lua中定义的数据名”)方法获取
如int a = luaEnv.Gloabl.Get< int >(“a”);

CSharpCallLua.lua.txt文件内容:
a = 100
str = “oop”
isRun = false

using UnityEngine;
using XLua;

public class CShapeCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");

        int a = luaEnv.Global.Get<int>("a");
        string str = luaEnv.Global.Get<string>("str");
        bool isRun = luaEnv.Global.Get<bool>("isRun");
        Debug.Log(a);
        Debug.Log(str);
        Debug.Log(isRun);
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}   

从Lua中访问一个全局的table

1.映射普通的class或者struct
要注意的是,映射的过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。

CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,
}

using UnityEngine;
using XLua;

public class CSharpCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");
        Person person = luaEnv.Global.Get<Person>("person");
        Debug.Log(person.name + person.age);
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

class Person {
    public string name;
    public int age;

}

2、映射到一个interface(推荐使用
》在接口上使用[CSharpCallLua]特性,自动生成代码来对应Lua中的table字段
》table中函数的第一个字段默认为传过来来的对象自身,必须添加对应形参self,如:function(self,a,b)

CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,
eat = function(self,a,b)
print(a+b)
CS.UnityEngine.Debug.Log(“我正在吃饭”)
return a+b
end
}

using UnityEngine;
using XLua;

public class CSharpCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");
        IPerson person = luaEnv.Global.Get<IPerson>("person");
        Debug.Log(person.name + person.age);
        Debug.Log(person.eat(10, 20));
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}
[CSharpCallLua]  //生成代码对应table的字段
interface IPerson
{
    string name { set; get; }
    int age { get; set; }
    int eat(int a, int b);
}

3、更轻量级的by value方式:映射到Dictionary< string,T >,List< T >
Dictionary< string,T >只能映射键值对,List< T >智能映射单个数据类型,T是什么类型就会映射什么类型

CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,1,true,100.01,
eat = function(self,a,b)
print(a+b)
CS.UnityEngine.Debug.Log(“我正在吃饭”)
return a+b
end
}

using System.Collections.Generic;
using UnityEngine;
using XLua;

public class CSharpCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");
        //映射到一个Dictionary<string,T>,T是什么类型就接受什么类型,且该映射只接受键值对
        Dictionary<string, object> dict = luaEnv.Global.Get<Dictionary<string, object>>("person");
        foreach (string key in dict.Keys)
        {
            Debug.Log(key + ":" + dict[key]);
        }

        //映射到一个List<T>,T是什么类型,就只会接受该类型数据,且该映射只接受单个数据类型,不接受键值对
        List<object> list = luaEnv.Global.Get<List<object>>("person");
        foreach (object obj in list)
        {
            Debug.Log(obj);
        }
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

4.另外一种by ref方式:映射到LuaTable类
》这种方式好处是不需要生成代码,但也有一些问题,比如慢,比方式2要慢一个数量级,比如没有类型检查
》luaTable.length //表示数组长度,不包括键值对
》luaTable.GetKeys(); //取得所有的key值,包括数组的索引(非键值对数据)
》luaTable.Get< keyT,valueT >(key); //通过key值取得value值

CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,
phone = “17879508158”,
1,
“lua”,
true,
100.01,
eat = function(self,a,b)
print(a+b)
CS.UnityEngine.Debug.Log(“我正在吃饭”)
return a+b
end
}

using UnityEngine;
using XLua;

public class CSharpCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");
        LuaTable luaTable = luaEnv.Global.Get<LuaTable>("person");

        Debug.Log(luaTable.Length);  //luaTable.length表示数组长度,不包括键值对

        foreach (object key in luaTable.GetKeys())
        {
            Debug.Log(key+":"+luaTable.Get<object,object>(key));
        }
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

访问一个全局的function

1.映射到delegate(推荐使用
[CSharpCallLua]
delegate int Add(int a,int b,out int resa,out int outb);
》当lua函数有多个返回值,使用out参数或者ref参数
》方法定义钱必须加上[CSharpCallLua]特性

CSharpCallLua.lua.txt文件内容:
add = function(a,b)
print(“add”)
return a+b,a,b
end

using UnityEngine;
using XLua;

public class CSharpCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");
        //使用委托delegate映射lua中的函数
        int resa, resb;
        Add add = luaEnv.Global.Get<Add>("add");
        int res = add(10, 20, out resa, out resb);
        Debug.Log("res=" + res + ",resa=" + resa + ",resb=" + resb);
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

[CSharpCallLua]
delegate int Add(int a,int b,out int resa,out int outb);

2.映射到LuaFunction
缺点:性能不佳,性能不如delegate的映射
》使用XLua中提供的LuaFunction类映射
》LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。

CSharpCallLua.lua.txt文件内容:
add = function(a,b)
print(“add”)
return a+b,a,b
end

using UnityEngine;
using XLua;

public class CSharpCallLua : MonoBehaviour
{
    private LuaEnv luaEnv;

    void Start()
    {
        luaEnv = new LuaEnv();

        luaEnv.DoString(" require 'CSharpCallLua' ");
        //使用XLua中提供的LuaFunction类来映射lua中的函数
        LuaFunction luaFunc = luaEnv.Global.Get<LuaFunction>("add");
        object[] objs = luaFunc.Call(10, 30);

        foreach (object obj in objs)
        {
            Debug.Log(obj);
        }
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

[CSharpCallLua]
delegate int Add(int a,int b,out int resa,out int outb);

Lua调用C


以下是C#代码,不会变,各个小节贴上Lua代码

using UnityEngine;
using XLua;

public class LuaCallCSharp : MonoBehaviour {
    private LuaEnv luaEnv;
    void Start () {
        luaEnv = new LuaEnv();
        luaEnv.DoString(" require 'LuaCallCSharp' ");
    }
    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

Lua中写C#代码时,所有的类前要加上完整的命名空间CS
例如:CS.UnityEngine.Debug.Log(“这是在Lua里面写C#代码”);


new C#对象

C#中创建对象:GameObject newGameObj = new UnityEngine.GameObject();
LuaCallCSharp.lua.txt代码:

local newGameObject1 =  CS.UnityEngine.GameObject()
--重载方法
local newGameObject2 =  CS.UnityEngine.GameObject("LuaCreateNewGameObject")

访问C#静态属性,方法

1.读静态属性
CS.UnityEngine.Time.deltaTime
2.写静态属性
CS.UnityEngine.Time.timeScale = 0.5
3.调用静态方法
CS.UnityEngine.GameObject.Find(‘helloworld’)
4.如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能:
例如:local mainCamera = CS.UnityEngine.GameObject.Find(“Main Camera”)
mainCamera.name = “LuaCamera”
LuaCallCSharp.lua.txt代码:

--获取静态属性
local deltaTime = CS.UnityEngine.Time.deltaTime;
print(deltaTime)
--设置静态属性
CS.UnityEngine.Time.timeScale = 0.5

--调用静态方法
local mainCamera =  CS.UnityEngine.GameObject.Find("Main Camera")
mainCamera.name = "LuaCamera"

访问C#成员属性,方法

local cube = CS.UnityEngine.GameObject.Find(“Cube”);
1.读成员属性
cube.name
2.写成员属性
cube.name = “luaCube”
3.调用成员方法
cube:GetComponent(“BoxCollider”);
4.重点:调用成员方法时,统一用冒号“:”,不用点“.”,用点必须把自身对象作为参数传递进去
–错误用法:cube.GetComponent(“BoxCollider”);
–正确用法1:cube.GetComponent(cube,”BoxCollider”);
–正确用法2:cube:GetComponent(“BoxCollider”);

--调用成员方法
--统一用冒号“:”,不用点“.”,用点必须把自身对象作为参数传递进去
local cube = CS.UnityEngine.GameObject.Find("Cube");
--错误:local cubeBoxCollider = cube.GetComponent("BoxCollider");
--正确:local cubeBoxCollider = cube.GetComponent(cube,"BoxCollider");
cube.name = "luaCube"
local cubeBoxCollider = cube:GetComponent("BoxCollider");

CS.UnityEngine.GameObject.Destroy(cubeBoxCollider)

父类属性,方法

xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类实例)访问基类的成员属性,成员方法


参数的输入输出属性(out,ref)

Lua调用测的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用测的实参列表;
Lua调用测的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。


重载方法

直接通过不同的参数类型进行重载函数的访问,例如:
testobj:TestFunc(100)
testobj:TestFunc(‘hello’)
将分别访问整数参数的TestFunc和字符串参数的TestFunc。
注意:xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于lua的number,上面的例子中TestFunc如果有这些重载参数,第一行将无法区分开来,只能调用到其中一个(生成代码中排前面的那个)


操作符

支持的操作符有:+,-,*,/,==,一元-,<,<=, %,[]


参数带默认值的方法

和C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。


可变参数方法

对于C#的如下方法:
void VariableParamsFunc(int a, params string[] strs)
可以在lua里头这样调用:
testobj:VariableParamsFunc(5, ‘hello’, ‘john’)


使用Extension methods

在C#里定义了,lua里就能直接使用。


泛化(模版)方法

不直接支持,可以通过Extension methods功能进行封装后调用。


枚举类型

枚举值就像枚举类型下的静态属性一样。
testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)
上面的EnumTestFunc函数参数是Tutorial.TestEnum类型的
另外,如果枚举类加入到生成代码的话,枚举类将支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换,例如:
CS.Tutorial.TestEnum.__CastFrom(1)
CS.Tutorial.TestEnum.__CastFrom(‘E1’)


delegate使用(调用,+,-)

C#的delegate调用:和调用普通lua函数一样
+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者是lua函数。
-操作符:和+相反,把一个delegate从调用链中移除。
Ps:delegate属性可以用一个luafunction来赋值。


event

比如testobj里头有个事件定义是这样:public event Action TestEvent;
增加事件回调
testobj:TestEvent(‘+’, lua_event_callback)
移除事件回调
testobj:TestEvent(‘-‘, lua_event_callback)


64位整数支持

Lua53版本64位整数(long,ulong)映射到原生的64未整数,而luaji版本t,相当于lua5.1的标准,本身不支持64位,xlua做了个64位支持的扩展库,C#的long和ulong都将映射到userdata:
1、支持在lua里头进行64位的运算,比较,打印
2、支持和lua number的运算,比较
3、要注意的是,在64扩展库中,实际上只有int64,ulong也会先强转成long再传递到lua,而对ulong的一些运算,比较,我们采取和java一样的支持方式,提供一组API,详情请看API文档。


C#复杂类型和table的自动转换

对于一个有无参构造函数的C#复杂类型,在lua侧可以直接用一个table来代替,该table对应复杂类型的public字段有相应字段即可,支持函数参数传递,属性赋值等,例如:
C#下B结构体(class也支持)定义如下:
public struct A
{
public int a;
}

public struct B
{
public A b;
public double c;
}
某个类有成员函数如下:
void Foo(B b)
在lua可以这么调用
obj:Foo({b = {a = 100}, c = 200})


获取类型(相当于C#的typeof)

比如要获取UnityEngine.ParticleSystem类的Type信息,可以这样
typeof(CS.UnityEngine.ParticleSystem)


“强”转

lua没类型,所以不会有强类型语言的“强转”,但有个有点像的东西:告诉xlua要用指定的生成代码去调用一个对象,这在什么情况下能用到呢?有的时候第三方库对外暴露的是一个interface或者抽象类,实现类是隐藏的,这样我们无法对实现类进行代码生成。该实现类将会被xlua识别为未生成代码而用反射来访问,如果这个调用是很频繁的话还是很影响性能的,这时我们就可以把这个interface或者抽象类加到生成代码,然后指定用该生成代码来访问:
cast(calc, typeof(CS.Tutorial.Calc))
上面就是指定用CS.Tutorial.Calc的生成代码来访问calc对象。

后续边学习边更新…
(敬请期待)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cchoop

有用的话请杯肥宅水

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值