xLua详解

目录

  • 环境准备
    • xLua导入
  • C#调用Lua
    • Lua解析器
    • Lua文件加载重定向
    • Lua解析管理器
    • 全局变量的获取
    • 全局函数的获取
    • List和Dictionary映射table
    • 类映射table
    • 接口映射table
    • LuaTable映射table
  • Lua调用C#
    • 准备工作
    • Lua使用C#类
    • Lua调用C#枚举
    • Lua使用C# 数组 List 字典
      • 数组
      • List
      • 字典
    • Lua使用C#扩展方法
    • Lua使用C# ref和out函数
      • ref
      • out
    • 函数重载
    • lua调用C# 委托和事件
    • Lua使用C#二维数组
    • Lua中null与nil的比较
    • 系统类型与Lua互相访问
    • lua使用C#协程
    • Lua使用C#泛型

环境准备

xLua导入

我们来到github搜索xLua,直接下载zip压缩包
在这里插入图片描述
我们把这两个文件夹复制到工程中
在这里插入图片描述
编译完之后窗口上就会有这个Xlua的选项(这里有可能会提示某个脚本编译失败,加个using System.Reflection)
我们可以先点击第二个选项,再点击第一个选项生成
在这里插入图片描述
然后这里要导入ABbrowser工具,详情请看AssetBundle详解
这里根据教程还需要导入三个类
我依次写到这上面
BaseManager

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

public class BaseManager<T>where T:new()//where T:new() 指定了泛型类型 T 必须具有无参数的构造函数
{
    private static T instance;

    public static T GetInstance()
    {
        if (instance == null)
            instance = new T();
        return instance;
    }
}

SingletonAutoMono

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

public class SingletonAutoMono<T>:MonoBehaviour where T : Monobehaviour
{
    private static T instance;
    public static T GetInstance()
    {
        if (instance == null)
        {
            GameObject obj = new GameObject();
            obj.name = typeof(T).ToString();
            DontDestoryOnLoad(obj);
            instance = obj.AddComponent<T>();
        }
        return instance;
    }
}

SingletonMono类

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


    // Start is called before the first frame update

    public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
    {
    private static T instance;
    public static T Getinstance()
    {
        retrun instance;
    }
    protected virtual void Awake()
    {
        instance = this;
    }
    }


然后我们要导入AB包管理器,这个也在AssetBundle详解里

C#调用Lua

Lua解析器

这里的核心就是LuaEnv,我们把脚本附着到相机上,然后运行

public class Lesson1_LuaEnv : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        //Lua解析器 能够让我们在Unity中执行Lua
        LuaEnv env = new LuaEnv();

        //执行Lua语言
        env.DoString("print('こにちは')");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

在这里插入图片描述

//帮助我们定时清除Lua中没有手动释放的对象
//在帧更新中定时执行或切场景时执行
env.Tick();
//销毁Lua解析器
env.Dispose();

这里如果有很多个语句需要执行,一句一句调用太慢了怎么办,我们这里可以使用DoString通过字符名来执行Lua脚本名,Lua中使用require来跨脚本运行
我们现在先在Unity中新建一个Resources文件夹,
然后在文件夹中新建一个.txt文件再把后缀更改为.lua
在这里插入图片描述
再用相应的lua软件打开
我们先只写一句话
在这里插入图片描述
但是现在又有个问题,Unity无法直接识别Lua文件,所以再改成Main.lua.txt

  env.DoString("require('Main')");

在这里插入图片描述

Lua文件加载重定向

这部分的内容是自定义lua文件加载路径
当require被调用时,会先去Addloader中的函数路径找文件,然后再去默认路径(Resources)中寻找
我们现在写一个基本逻辑,Addloder会实现重定向功能,它接受一个委托

void Start()
    {
        LuaEnv env = new LuaEnv();
        env.AddLoader(MyCustomLoader);
        env.DoString("require('Main')");

    }
    //自动执行
    private byte[] MyCustomLoader(ref string filePath)
    {
        Debug.Log(filePath);
        //通过函数中的逻辑,去加载lua文件
        return null;
    }

我们新建一个叫做Lua的文件夹
然后在里面新建一个Main文件
我们来详细解释一下这段代码
这里AddLoader的调用参数是一个委托变量,类似于C++中的函数指针,而MyCustomLoader这里是通过env传入的lua文件名。如果我把 env.DoString(“require(‘Main’)”);这行注释掉,那么MyCustomLoader将不会执行
在这里,filePath是Main,Application.dataPath是Unity/xxx/Asset

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System.IO;
using Unity.VisualScripting;

public class Lesson2_Loader : MonoBehaviour
{
  
    void Start()
    {
        LuaEnv env = new LuaEnv();

        env.AddLoader(MyCustomLoader);

        env.DoString("require('Main')");

    }

    //自动执行
    private byte[] MyCustomLoader(ref string filePath)
    {
        //传入的参数是require执行的脚本文件名
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";
        Debug.Log(path);

        if(File.Exists(path))
        {
            return(File.ReadAllBytes(path));
        }
        else
        {
            Debug.Log("重定向失败,文件名为" + filePath);
        }


       //拼接一个lua所在路径
        //通过函数中的逻辑,去加载lua文件
        return null;
    }
}

Lua解析管理器

我们用一个管理器来管理LuaEnv
其他逻辑都是前面提到过的

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

//Lua管理器
//提供lua解析器
//保证解析器的唯一性
public class LuaMgr:BaseManager<LuaMgr>
{
    //执行Lua语言的函数
    //释放垃圾
    //销毁
    //重定向

    private LuaEnv luaEnv;

    public void Init()
    {
        //如果已经初始化了,直接返回
        if (luaEnv != null)
            return;
        luaEnv = new LuaEnv();

        //加载lua脚本重定向
        luaEnv.AddLoader(MyCustomLoader);

    }

    private byte[] MyCustomLoader(ref string filePath)
    {
        
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";
        Debug.Log(path);

        if (File.Exists(path))
        {
            return (File.ReadAllBytes(path));
        }
        else
        {
            Debug.Log("重定向失败,文件名为" + filePath);
        }

        return null;
    }

    public void DoString(string str)
    {
        luaEnv.DoString(str);
    }

    public void Tick()
    {
        luaEnv.Tick();
    }

    public void Dispose()
    {
        luaEnv.Dispose();
        luaEnv = null;
    }
}

然后我们写一个测试类来测试
我们的代码逻辑在这个脚本中第一次使用LuaMgr,所以需要Init,如果初始化之后在其他的脚本就可以直接使用LuaMgr了

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

public class Lesson3_LuaMgr : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        LuaMgr.GetInstance().Init();//LuaMgr是一个单例类,所以可以直接访问

        LuaMgr.GetInstance().DoString("require('Main')");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

接下来我们讲AB包和Mgr相关逻辑
由于AB包不识别lua,所以还是先修改成txt
我们选中Main文件,新建一个lua的AB包
在这里插入图片描述
我们先清除一下代码
在这里插入图片描述
然后构建
在这里插入图片描述
现在我们要做的其实就是在AB包中执行lua
我们再来解释函数逻辑
这里我们又加了一个重定向逻辑MyCustomABLoader,用来从AB包中加载数据,在MyCustomABLoader中,我们之前使用的逻辑是手动声明AB包的对象然后进行读取,但是我们在AB包的教程中做了一个AB包管理器,所以这里使用AB包管理器来做

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

//Lua管理器
//提供lua解析器
//保证解析器的唯一性
public class LuaMgr:BaseManager<LuaMgr>
{
    //执行Lua语言的函数
    //释放垃圾
    //销毁
    //重定向

    private LuaEnv luaEnv;

    public void Init()
    {
        //如果已经初始化了,直接返回
        if (luaEnv != null)
            return;
        luaEnv = new LuaEnv();

        //加载lua脚本重定向
        luaEnv.AddLoader(MyCustomLoader);
        luaEnv.AddLoader(MyCustomABLoader);


    } 
    
   public void DoLuaFIle(string fileName)
    {
        string str = string.Format("require('{0}')",fileName);
    }
    
    private byte[] MyCustomLoader(ref string filePath)
    {
        
        string path = Application.dataPath + "/Lua/" + filePath + ".lua";
        Debug.Log(path);

        if (File.Exists(path))
        {
            return (File.ReadAllBytes(path));
        }
        else
        {
            Debug.Log("重定向失败,文件名为" + filePath);
        }

        return null;
    }

    //重定向加载AB包中的lua脚本
    private byte[] MyCustomABLoader(ref string filePath)
    {
        //从AB包中加载lua文件
        //加载AB包
       // string path = Application.streamingAssetsPath + "/lua";

       // AssetBundle ab = AssetBundle.LoadFromFile(path);

       // TextAsset tx = ab.LoadAsset<TextAsset>(filePath+".lua");

       // //加载除了lua文件 byte数组

        return tx.bytes;

        TextAsset lua=ABMgr.GetInstance().LoadRes<TextAsset>("lua",filePath+".lua");
        if (lua != null)
            return lua.bytes;
        else
            return null;

    }
    public void DoString(string str)
    {
        luaEnv.DoString(str);
    }

    public void Tick()
    {
        luaEnv.Tick();
    }

    public void Dispose()
    {
        luaEnv.Dispose();
        luaEnv = null;
    }
}

全局变量的获取

我们在Main.lua的文件下新建一个Test.lua
在这里插入图片描述
在test中定义如下变量
在这里插入图片描述
这里我们访问全局变量的方式是通过Main.lua进行的
这里很有意思的是,require调用Test的逻辑本质上并不是Main自己调用的,而是xlua调用的,通过堆栈可以看到
所以如果我require(‘一个不存在的文件’),会在xlua层报错也是有迹可循的了。

在这里插入图片描述
现在我们要使用mgr来调用Test中的变量
这个Globe有点类似大G表,可以通过这个操纵Test中的数据

 int i=LuaMgr.GetInstance().Global.Get<int>("testNumber");
 Debug.Log(i);

可以发现成功打印
在这里插入图片描述
但是这里取到的值也只是个拷贝,并不会修改原来的数
如果想修改可以使用set
这里也不能取到test中声明的本地变量,换言之只能操作大G表的内容

全局函数的获取

函数我们依旧在test中写
我们先调用无参无返回值方法,使用委托

public class Lesson5_CallFunction : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        LuaMgr.GetInstance().Init();

        LuaMgr.GetInstance().DoLuaFile("Main");

        //无参无返回的获取
        //委托

        CustomCall call = LuaMgr.GetInstance().Global.Get<CustomCall>("testFun");
        call();

    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

在这里插入图片描述
有参有返回:

public delegate void CustomCall2(int a);

//调用
[CSharpCallLua]//自定义委托一定要有
  CustomCall2 call2 = LuaMgr.GetInstance().Global.Get<CustomCall2>("testFun2");
        call2(10);

这里可能会报错,去xlua窗口重新生成一下就好了
在这里插入图片描述
在这里插入图片描述
这里委托都可以使用Unity/C#/提供的,不需要自己写
接下来是多返回值,在C#中使用out 和 ref来接收
首先是声明委托

//多返回值委托
[CSharpCallLua]
public delegate int CustomCall3(int a, out int b, out bool c, out string d, out int e);

然后是接收

  //多返回值获取
        CustomCall3 call3 = LuaMgr.GetInstance().Global.Get<CustomCall3>("testFun3");
        int b;bool c; string d; int e;
        Debug.Log("多返回值:" + call3(100, out b, out c, out d, out e));
        Debug.Log(b + "_" + c + "_" + d + "_" + e);

在这里插入图片描述
然后是变长参数
首先说一下ref和out的区别
ref 关键字用于传递一个已初始化的变量作为参数,并且要求方法在使用这个参数之前初始化它。
out 关键字用于传递一个未初始化的变量作为参数,方法在使用这个参数之前必须将其初始化。
我们依旧可以用这些参数

[CSharpCallLua]
public delegate int CustomCall4(int a, ref int b, ref bool c, ref string d, ref int e);
        //多返回值获取
        CustomCall4 call4 = LuaMgr.GetInstance().Global.Get<CustomCall4>("testFun3");
        int b1=0; bool c1=false; string d1=""; int e1=0;//要初始化
        Debug.Log("变长参数:" + call4(100, ref b, ref c, ref d, ref e));
        Debug.Log(b + "_" + c + "_" + d + "_" + e);

在这里插入图片描述
以下是完整代码

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

//无参无返回值的委托
public delegate void CustomCall();

//有参有返回的委托
[CSharpCallLua]
public delegate void CustomCall2(int a);

//多返回值委托
[CSharpCallLua]
public delegate int CustomCall3(int a, out int b, out bool c, out string d, out int e);

//变长参数
[CSharpCallLua]
public delegate int CustomCall4(int a, ref int b, ref bool c, ref string d, ref int e);

public class Lesson5_CallFunction : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        LuaMgr.GetInstance().Init();

        LuaMgr.GetInstance().DoLuaFile("Main");

        //无参无返回的获取
        //委托

        CustomCall call = LuaMgr.GetInstance().Global.Get<CustomCall>("testFun");
        call();

        //有参有返回的获取
        CustomCall2 call2 = LuaMgr.GetInstance().Global.Get<CustomCall2>("testFun2");
        call2(10);

        //多返回值获取
        CustomCall3 call3 = LuaMgr.GetInstance().Global.Get<CustomCall3>("testFun3");
        int b;bool c; string d; int e;
        Debug.Log("多返回值:" + call3(100, out b, out c, out d, out e));
        Debug.Log(b + "_" + c + "_" + d + "_" + e);

        //多返回值获取
        CustomCall4 call4 = LuaMgr.GetInstance().Global.Get<CustomCall4>("testFun3");
        int b1=0; bool c1=false; string d1=""; int e1=0;
        Debug.Log("变长参数:" + call4(100, ref b, ref c, ref d, ref e));
        Debug.Log(b + "_" + c + "_" + d + "_" + e);


    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

List和Dictionary映射table

我们先在test中声明

testList={1,2,3,4,5,6}

testList2={"123","123",true,1,1.2}

--Dictionary

testDic1={
	["1"]=1,
	["2"]=1,
	["3"]=1,
	["4"]=1
}

testDic2={
	["1"]=1,
	[true]=1,
	[false]=true
	["123"]=false
}

老方法调用

  List<int> list = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
        for(int i = 0; i < list.Count; i++)
        {
            Debug.Log(list[i]);
        }

不过get还是不能改数据本身
在这里插入图片描述
我们再看一下刚才的testDic,testList2存的不只有int型
所以我们在取到值的时候选择object

 List<object> list2 = LuaMgr.GetInstance().Global.Get<List<object>>("testList2");
        for (int i = 0; i < list2.Count; i++)
        {
            Debug.Log(list2[i]);
        }

在这里插入图片描述

   Dictionary<string, int> dic = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic1");
        foreach(string i in dic.Keys)
        {
            Debug.Log(i + "_" + dic[i]);
        }

在这里插入图片描述
至于testDic2,我们可以都用dictory<object,object>和上述一样

类映射table

在这里我们要实现类逻辑
首先在test中写一个类逻辑

testClass={
	testInt=2,
	testBool=true,
	testString="123",
	testFun=function()
		print("123123123")
	end
}

然后在脚本中声明一个名字一样的类

public class CallLuaClass
{
    //在这个类中声明成员变量
    //名字一定要和Lua那边的一样
    public int testInt;
    public bool testBool;
    public float testFloat;
    public string testString;
    public UnityAction testFun;

    //这个自定义中的变量可以更多也可以更少
    //如果比lua中的多少都会忽略

}

然后在脚本中把这个类”实例化“出来

    LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");
        CallLuaClass obj = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
        Debug.Log(obj.testBool);
        Debug.Log(obj.testInt);
        Debug.Log(obj.testFun);

在这里插入图片描述
和之前一样,这个是值拷贝而不是引用拷贝
然后我们再来看一下嵌套映射,我们在这个类里再加一个

testClass={
	testInt=2,
	testBool=true,
	testString="123",
	testFun=function()
		print("123123123")
	end
	testClass2={
		testInt2=3;
	}
}

然后在声明处

public class CallLuaClass2
{
    public int testInt2;
}
 void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");
        CallLuaClass obj = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
        // Debug.Log(obj.testClass2.testInt2);
        CallLuaClass2 obj2 = obj.testClass2;
        Debug.Log(obj2.testInt2);
    }

接口映射table

先定义一个接口

[CSharpCallLua]//这里也要加
public interface ICSharpCallInterface
{
    //接口中是不允许有成员变量的
    //用属性来接收

    public int testInt
    {
        get;
        set;
    }
    public bool testBool
    {
        get;
        set;
    }

    UnityAction testFun
    {
        get;
        set;
    }

}
  ICSharpCallInterface obj = LuaMgr.GetInstance().Global.Get<ICSharpCallInterface>("testClass");
  obj.testFun();

在这里插入图片描述
但是这里唯一不同的是,接口拷贝是引用拷贝,lua表中的值也变了

LuaTable映射table

我们经常使用的Globe本质上就是一个luaTable
所以想访问一个属性很简单

   LuaTable table= LuaMgr.GetInstance().Global.Get<LuaTable>("testClass");
   ebug.Log(table.Get<int>("testInt"));

   table.Get<LuaFunction>("testFun").Call();//调用函数

不建议使用LuaTable和luaFunction 效率比较低

用完之后一定要记住table.Dispose销毁

Lua调用C#

准备工作

我们先来新建一个文件夹,专门存Lua调用C#的代码
然后新建一个Main脚本
Lua没有办法直接访问C#,一定是先从C#调用Lua脚本后才把核心逻辑交给Lua来编写
先做准备工作

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

public class Main : MonoBehaviour
{
    void Start()
    {
        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoLuaFile("Main");
    }

}

把Main脚本放到摄像机上

我们新建一个lua
在这里插入图片描述
我们在Main中调用新建的lua
在这里插入图片描述

Lua使用C#类

Lua使用C#的类非常简单

CS.命名空间.类名
先看Main脚本

--Main脚本
print("主Lua脚本已启动")
require("Lesson1_CallClass")

然后是Lesson1_CallClass脚本

Lua中没有new 所以我们类名括号就是实例化对象

--Lesson1_CallClass
print("lua调用C#类相关知识点")
local obj1=CS.UnityEngine.GameObject()

我们来梳理一下函数逻辑

  1. 首先我们在C#中调用初始化函数:先创建唯一实例LuaEnv,然后使用重定向(找到lua文件的位置)
  2. 通过LuaEnv的DoString方法执行lua逻辑
  3. 现在我们到了lua脚本中,由于之前我们已经声明将调用lua中的Main文件,所以来到Main文件,这里执行打印语句,然后执行require语句,转到Lesson1_CallClass中
  4. 来到Lesson1_CallClass中,先打印,然后在场景中创建一个空GameObject

结果:
在这里插入图片描述

在这里插入图片描述
现在我使用CS.UnityEngine.GameObject()相当于调用一个无参函数,我也可以在括号里面传入名字作为一个有参函数

如果是类对象中的成员方法,要加冒号

这里我们使用带参构造函数,并且使用Gameobject作为别名,我们通过obj3.transform再加冒号调用translate

print("lua调用C#类相关知识点")

local obj2=CS.UnityEngine.GameObject("Theshy")

GameObject=CS.UnityEngine.GameObject

local obj3=GameObject.Find("Theshy")

obj3.transform:Translate(CS.UnityEngine.Vector3.right)

可以发现确实移动了
在这里插入图片描述
我们之前看的都是Unity自己的类,现在我们写一个我们自己的类

public class Test
{
    public void Speak(string str)
    {
        Debug.Log(str);
    }
}

namespace Theshy
{
    public class Test2
    {
        public void Speak(string str)
        {
            Debug.Log("Test2: " + str);
        }
    }
}

然后在lua里

local t=CS.Test()
t:Speak("我々はここで死に")
local t2=CS.Theshy.Test2()
t2:Speak("次の生者に意味を託す")

在这里插入图片描述
接下来我们要为实例化对象添加脚本,这里别忘了还是使用冒号

GameObject=CS.UnityEngine.GameObject
local obj5=GameObject("加脚本测试")
obj5:AddComponent(typeof(CS.MyClass))

在这里插入图片描述

Lua调用C#枚举

我们先声明一个枚举
然后利用这个枚举创建一个立方体

PrimitiveType=CS.UnityEngine.PrimitiveType
GameObject=CS.UnityEngine.GameObject

local obj=GameObject.CreatePrimitive(PrimitiveType.Cube)--创建立方体

在这里插入图片描述
我们这里自己新建一个枚举,和刚才我们自己写类的方法一样

CS.(命名空间.)枚举名

Lua使用C# 数组 List 字典

数组

我们先在C#脚本中定义一个数组,list,字典

public class VectorSet
{
    public int[] array = new int[5] { 1, 2, 3, 4, 5 };
    public List<int> list = new List<int>();
    public Dictionary<int, string> dic = new Dictionary<int, string>();
}

然后我们在lua中,先写数组逻辑

print("lua调用容器相关知识点")

local obj = CS.VectorSet()

--Lua使用C#数组
--这里不能使用#
print(obj.array.Length)
--访问元素
print(obj.array[0])

然后是数组的遍历,虽然lua中的索引从1开始
但是数组是C#的数组,所以还要按C#的来
并且在lua的for循环默认最后条件是<=的,所以要减一

for i=0,obj.array.Length-1 do
	print(obj.array[i])
end

在这里插入图片描述
如果我想在Lua中创建C#的数组呢?
由于lua中没有new,所以不能像C#一样声明数组,但是在Unity中数组是一个数组类,里面提供了一个静态方法,直接创建一个数组,所以我们这里直接调用这个静态方法就可以了

local array2=CS.System.Array.CreateInstance(typeof(CS.System.Int32),10)

print(array2.Length)
print(array2[0])
print(array2[1])

在这里插入图片描述

List

对于List也一样,遍历和数组一模一样

obj.list:Add(1)
obj.list:Add(2)
obj.list:Add(3)

print(obj.list.Count)

在这里插入图片描述
接下来要写用list创建对象
分两个版本
老版本(2.1.12之前)

local list2=CS.System.Collections.Generic["List`1[System.String]"]()

新版本

local List_String=CS.System.Collections.Generic.List(CS.System.String)--相当于得到一个类
local list3=List_String()

字典

添加元素用add,和上面两个一样
遍历需要用pairs

for k,v in pairs(obj.doc)do
	print(k,v)
end

然后是在Lua中创建一个字典对象

local Dic_String_Vector3=CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic2=Dic_String_Vector3()
dic2:Add("123",CS.UnityEngine.Vector3.right)
for i,v in pairs(dic2) do
	print(i,v)
end

在这里插入图片描述

如果我现在想通过我刚才新建的dic2来访问键值呢?
可以发现打印的是nil

print(dic2["123"])

在这里插入图片描述
这里要用

print(dic2:get_Item("123"))

在这里插入图片描述
如果要修改键值也要用set_Item

Lua使用C#扩展方法

先声明一个类和一个拓展方法

public static class Tools
{
    //拓展方法
    public static void Move(this testClass obj)
    {
        Debug.Log(obj.name + "移动");
    }
}

public class testClass
{
    public string name = "Theshy";
    public void Speak(string str)
    {
        Debug.Log(str);
    }

    public static void Eat()
    {
        Debug.Log("吃东西");
    }
}

然后在lua中调用

print("扩展方法")

testclass=CS.testClass
--使用静态方法

testclass.Eat()

--使用成员函数

local obj=testclass()

obj:Speak("wryyyyyyy")

--使用拓展方法,和使用成员方法方法一致

CS.Tools.Move(obj)

但是会报错
在这里插入图片描述

所以想要在xLua中使用扩展方法,一定要在工具类前面加上特性,然后不要忘了重新生成代码

如果考虑性能的话,可以把所有lua中需要的东西都写上LuaCallCSharp,因为lua底层是通过反射来与C#联系的,会有一定性能损失

  [XLua.LuaCallCSharp]
    //拓展方法
    public static void Move(this testClass obj)
    {
        Debug.Log(obj.name + "移动");
    }

在这里插入图片描述

Lua使用C# ref和out函数

ref

我们先在Unity中写个类

public class Lesson5
{
    public int RefFun(int a,ref int b ,ref int c,int d)
    {
        b = a + d;
        c = a - d;
        return 100;
    }

    public int OufFun(int a, out int b, out int c, int d)
    {
        b = a;
        c = d;
        return 200;
    }

    public int RefOutFun(int a, out int b, ref int c)
    {
        b = a * 10;
        c = a * 20;
        return 300;
    }
}

我们先来看ref的,ref是多返回值,并且需要显式初始化
首先a是第一个返回值,接收的就是函数本身的返回值100,然后b和c分别接ref b ref c

Lesson5=CS.Lesson5

local obj=Lesson5()

local a,b,c=obj.RefFun(1,0,0,1)

print(a)

print(b)

print(c)

在这里插入图片描述

out

out不需要传占位置的值

local obj=Lesson5()

local a,b,c=obj:OutFun(20,30)

print(a)

print(b)

print(c)

在这里插入图片描述

函数重载

我们先写几个简单的重载函数

public class Lesson6
{
   public int Calc()
    {
        return 100;
    }

    public int Calc(int a,int b)
    {
        return a+b;
    }

    public int Calc(int a)
    {
        return a;
    }

    public float Calc(float a)
    {
        return a;
    }
}

然后是lua层

local obj=CS.Lesson6()
print(obj:Calc())
print(obj:Calc(15,1))

在这里插入图片描述
可以看出lua支持调用C#中的重载函数

然后我们再调用后面的

print(obj:Calc(10))
print(obj:Calc(10.2))

在这里插入图片描述
lua中只有number类型,而C#中有多个属性,所以对多精度数据支持的并不好
用反射来解决多精度重载(效率低,尽量别用)

local m1=typeof(CS.Lesson6):GetMethod("Calc",{typeof(CS.System.Int32)})

local m2=typeof(CS.Lesson6):GetMethod("Calc",{typeof(CS.System.Single)})

local f1=xlua.tofunction(m1)
local f2=xlua.tofunction(m2)

f1(obj,10)
f1(obj,10.2)

lua调用C# 委托和事件

我们再写个类,一个委托,一个事件,和一个调用事件的函数

public class Lesson7
{
    public UnityAction del;
    public event UnityAction eventAction;

    public void DoEvent()
    {
        eventAction();
    }
}

这里不能直接+=,并且第一次往委托中加函数是nil不能直接加

local obj=CS.Lesson7()

local fun = function ( )
	print("Lua函数Fun")
end

obj.del=fun

obj.del()--执行委托

然后是事件

local obj=CS.Lesson7()

local fun2=function()
	print("事件加的函数")
end

obj:eventAction("+",fun2)
obj:eventAction("+",fun2)

obj:DoEvent()

在这里插入图片描述
相应的,想减少调用就用减号
如果我想清除事件,不能直接像委托del=nil
而是需要在C#层加一个函数

 public void ClearEvent()
    {
        eventAction = null;
    }

然后

obj:ClearEvent()

Lua使用C#二维数组

我们先在C#中声明一个二维数组

public class Lesson8
{
    public int[,] array = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
}

我们在lua中先取到一个简单的二维数组

print("二维数组")

local obj=CS.Lesson8()

--获取长度
print(obj.array:GetLength(0))
print(obj.array:GetLength(1))

在这里插入图片描述
在Lua中访问C#的二维数组不能通过[0,0]或[0][0]来访问
和之前array一样,二维数组提供了一个方法来返回值

print(obj.array:GetValue(0,0))
print(obj.array:GetValue(0,1))

在这里插入图片描述
然后是二维数组的遍历

for i=0,obj:GetLength(0)-1 do
for j=0,obj:GetLength(1)-1 do
	print(obj.GetValue(i,j))
end
end

在这里插入图片描述

Lua中null与nil的比较

我们现在实现一个需求,先实例化一个物体吗,然后判断其有没有刚体组件,如果没有再加

GameObject=CS.UnityEngine.GameObject
Rigidbody=CS.UnityEngine.Rigidbody

local obj=GameObject("测试加脚本")

local rig=obj:GetComponent(typeof(Rigidbody))
print(rig)

if rig==nil then
	rig=obj:AddComponent(typeof(Rigidbody))
end
print(rig)

这里发现即使没有脚本,也不会新加,可以通过测试发现if rig==nil 这行逻辑没进
在这里插入图片描述
原因是因为null 和nil不是一个东西
所以这里要用
在这里插入图片描述
我们可以自己在lua中的Main文件中写一个逻辑

function IsNull( obj )
	if obj==nil or obj:Equals(nil) then
		return true
	else return false
end

我们也可以使用一个扩展方法,这样就把核心逻辑写在C#中而不是lua中了

系统类型与Lua互相访问

如果我需要在lua中使用一个只读的代码(系统库或三方库),不能加LuaCallCSharp代码,应该怎么做:
假如说我现在需要为UnityAction< float >加特性
我们写这么一段代码

public static class Lesson10
{
    [CSharpCallLua]
    public static List<Type> csharpCallLuaList = new List<Type>()
    {
        typeof(UnityAction<float>)
    };
}

然后点生成代码

lua使用C#协程

这里我们为新创建的Gameobject添加了一个脚本,然后通过这个脚本调用StartCoroutine

GameObject =CS.UnityEngine.GameObject

WaitForSeconds=CS.UnityEngine.WaitForSeconds

local obj=GameObject("Coroutine")

local mono=obj:AddComponent(typeof(CS.LuaCallCSharp))

--希望用来被开启的协程
fun=function ()
	local a=1
	while true do
		coroutine.yield()
		print(a)
		a=a+1
	end
end

mono:StartCoroutine(fun)

但是这样会报错
在这里插入图片描述
所以不能通过StartCoroutine来直接调用fun

所以我们使用一个xlua官方自带的一个工具表

util=require("xlua.util")
mono:StartCoroutine(util.cs_generator(fun))

在这里插入图片描述

Lua使用C#泛型

我们先在C#里写几个泛型函数

public class Lesson12
{
    public interface ITest
    {

    }
    public class TestFather
    {

    }
    public class TestChild:TestFather,ITest
    {

    }

    public void TestFun1<T>(T a, T b) where T : TestFather
    {
        Debug.Log("有参数有约束的泛型方法");
    }

    public void TestFun2<T>(T a)
    {
        Debug.Log("有参数,无约束");
    }

    public void TestFun3<T>() where T : TestFather
    {
        Debug.Log("无参数,有约束");
    }

    public void TestFun4<T>(T a) where T : ITest
    {
        Debug.Log("有参数,有约束,约束不是类是接口");
    }
}

然后来到lua
我们先来测试第一个

local obj=CS.Lesson12()

local child = CS.Lesson12.TestChild()
local father = CS.Lesson12.TestFather()

obj:TestFun1(child,father)
obj:TestFun1(father,child)

在这里插入图片描述
然后我们来调用第二组

报错了,可以发现lua中不支持不约束的泛型
在这里插入图片描述
然后第三组,可以发现lua也不支持无参带约束的泛型
在这里插入图片描述
然后看最后一组
因为在C#中child类继承自接口,所以用child传进去

obj:TestFun4(child)

可以发现4也不行,lua中不支持非class的约束
在这里插入图片描述
接下来讲让上面不支持的泛型函数变得能用

local testFun2=xlua.get_generic_method(CS.Lesson12,"TestFun2")
local testFun2_R=testFun2(CS.System.Int32)
--第一个参数传调用函数的对象
testFun2_R(obj,1)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/579720.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

读天才与算法:人脑与AI的数学思维笔记12_数学的艺术

1. 数学 1.1. 灵光乍现&#xff0c;从来都是厚积薄发 1.1.1. 亨利庞加莱&#xff08;Henri Poincar&#xff09; 1.2. 数学的起源可以追溯到人类试图理解自己所生活的环境&#xff0c;预测接下来会发生什么&#xff0c;从而使我们更加适应环境&#xff0c;并选择对我们有利的…

Python-VBA函数之旅-map函数

目录 一、map函数的常见应用场景 二、map函数使用注意事项 三、如何用好map函数&#xff1f; 1、map函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;神奇夜光杯-CSDN博客 一、map函数的常见应用场景 ma…

Jammy@Jetson Orin - Tensorflow Keras Get Started: Concept

JammyJetson Orin - Tensorflow & Keras Get Started: Concept 1. 源由2. 模型2.1 推理流程2.1.1 获取图像2.1.2 算法识别2.1.3 判断决策 2.2 理想情况2.2.1 多因素输入2.2.2 理想识别概率 2.3 学习过程2.3.1 标记训练集2.3.2 损失函数2.3.3 训练网络2.3.4 渐进方法 3. 总…

使用Docker部署Jupyter Notebook并结合花生壳的内网穿透实现远程访问(详文)

一、前言 本文主要介绍如何利用宝塔面板中的Docker 3.9.3管理器,使用Docker本地部署Jupyter Notebook,并结合花生壳内网穿透工具实现任意浏览器公网远程访问Jupyter登录界面。 安装完成后在宝塔面板中图例 Jupyter Notebook是一个交互式笔记本,支持运行40多种编程语言。…

Vue 组件单元测试深度探索:组件交互与状态变更 专业解析和实践

在Vue组件单元测试中&#xff0c;验证组件之间的交互&#xff08;如父组件与子组件、兄弟组件之间的通信&#xff09;以及状态变更的正确性对于保证整个应用的协调运作至关重要。本文详细介绍了父组件向子组件传递props、子组件向父组件发送事件、兄弟组件通过共享状态&#xf…

xfce4 panel 不能显示QQ,钉钉的状态图标

有一段时间不能显示了&#xff0c;之前刚装完系统的时候很长时间内都是好的&#xff0c;所以刚开始肯定是支持显示这些状态图标的。就是因为不能显示的原因&#xff0c;所以还装了lxQt桌面&#xff0c;这个桌面确实不错。不过还是有时会怀念xfce4&#xff0c;想看看能不能解决这…

Odoo:全球排名第一的免费开源PLM管理系统介绍

概述 利用开源智造OdooPLM产品生命周期管理应用&#xff0c;重塑创新 实现产品生命周期管理数字化&#xff0c;高效定义、开发、交付和管理创新的可持续产品&#xff0c;拥抱数字化供应链。 通过开源智造基于Odoo开源技术平台打造数字化的产品生命周期管理&#xff08;PLM&am…

Java UUID 类中的 getMostSignificantBits 和 leastSignificantBits 方法

getMostSignificantBits() 方法的一些介绍&#xff1a; getMostSignificantBits 这个方法主要用于在 UUID 中获取高64 位的有效位后返回 Long 数据类型。 在获取最高有效位时不会引发异常。 对应的还有一个 getLeastSignificantBits() 方法。 这个方式是从 UUID 中获取低 6…

阳光能源,创造永远:光模块的未来”:随着大数据、区块链、云计算和5G的发展,光模块成为满足不断增长的数据流量需求的关键技术

光模块的类型介绍&#xff1a; 为了适应不同的应用需求&#xff0c;不同参数和功能的光模块应运而生。光模块的分类方式及类型详见如下&#xff1a; &#x1f50e;封装形式&#x1f50d;&#xff1a; &#x1f4e3;&#x1f4e2;光模块按照封装形式来分有以下几种常见类型&a…

如何将本地Android studio项目上传到GitHub

操作步骤&#xff1a; 1、在GitHub上创建账户 2、在androd studio中添加上述创建的GitHub账号 3、在android studio上找到"share project on GitHub"&#xff0c;点击此选项上传当前项目到GitHub 上传成功后&#xff0c;会在GitHub上创建默认仓库repository 注&a…

LeetCode - LCR 008.长度最小的子数组

一. 题目链接 LeetCode - 209. 长度最小的子数组 二. 思路分析 由于此问题分析的对象是「⼀段连续的区间」&#xff0c;因此可以考虑「滑动窗口」的思想来解决这道题。 让滑动窗口满足&#xff1a;从 i 位置开始&#xff0c;窗口内所有元素的和小于target &#xff08;那么当…

图像处理到神经网络:线性代数的跨领域应用探索

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 欢迎加入社区&#xff1a;码上找工作 作者专栏每日更新&#xff1a; LeetCode解锁1000题: 打怪升级之旅 python数据分析…

Python爬虫--Scrapy框架安装

Scrapy框架安装 &#xff0c; Scrapy 是 Python 领域专业的爬虫开发框架&#xff0c;已经完成爬虫程序的大部分通用工具 它使用了 Twisted 异步网络库来处理网络通讯。整体架构大致如下 第一步&#xff1a;挂小灰机或者将要安装的文件下载到本地 Scrapy 框架安装踩坑中 为什…

ubuntu的镜像源+bionic版本

首先第一步 查找和你自己ubuntu版本匹配的版本号 匹配代号如下 在终端输入lsb_release -a查看自己系统上的版本号 可以看到我这个版本号的代号是bionic。 每个版本的镜像文件都是有规律的。 bionic版本的源如下 # 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic ma…

前端用a标签实现静态资源文件(excel/word/pdf)下载

接上文实现的 前端实现将二进制文件流&#xff0c;并下载为excel文件后&#xff0c; 实际项目中一般都会有一个模版下载的功能&#xff0c;一般都由服务端提供一个下载接口&#xff0c;返回文件流或url地址&#xff0c;然后前端再处理成对应需要的类型的文件。 但是&#xff…

HTML5(1)

目录 一.HTML5(超文本&#xff08;链接&#xff09;标记&#xff08;标签<>&#xff09;语言) 1.开发环境&#xff08;写代码&#xff0c;看效果&#xff09; 2.vscode 使用 3.谷歌浏览器使用 4.标签语法 5.HTML基本骨架&#xff08;网页模板&#xff09; 6.标签的…

排序 “肆” 之归并排序

1. 归并排序 1.1 原理介绍 归并排序的基本原理是将一个未排序的数组分解为较小的子数组&#xff0c;然后递归地对这些子数组进行排序&#xff0c;最后再将排好序的子数组合并成一个有序数组。其核心操作是将一维数组中前后相邻的两个有序序列归并为一个有序序列。 其主要步骤包…

【区块链】椭圆曲线数字签名算法(ECDSA)

本文主要参考&#xff1a; 一文读懂ECDSA算法如何保护数据 椭圆曲线数字签名算法 1. ECDSA算法简介 ECDSA 是 Elliptic Curve Digital Signature Algorithm 的简称&#xff0c;主要用于对数据&#xff08;比如一个文件&#xff09;创建数字签名&#xff0c;以便于你在不破坏它…

【Flutter】GetX

前言 状态管理 / 路由管理 / 依赖管理 这三部分之间存在联系 参考文章 建议看官网文章&#xff0c;很详细 &#xff0c;pub.dev搜索get pub.dev的文档 状态管理文章相关链接 状态管理 案例 实现一个计算器&#xff0c;运用GetX去管理它 构建界面 构建一个计算器界面 …

基于SpringBoot的“房产销售平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“房产销售平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体模块图 登录窗口界面 房源信息管理窗口界…