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 updatepublic 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 updatevoid Start(){//Lua解析器 能够让我们在Unity中执行LuaLuaEnv env = new LuaEnv();//执行Lua语言env.DoString("print('こにちは')");}// Update is called once per framevoid 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 updatevoid Start(){LuaMgr.GetInstance().Init();//LuaMgr是一个单例类,所以可以直接访问LuaMgr.GetInstance().DoString("require('Main')");}// Update is called once per framevoid 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;elsereturn 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 updatevoid Start(){LuaMgr.GetInstance().Init();LuaMgr.GetInstance().DoLuaFile("Main");//无参无返回的获取//委托CustomCall call = LuaMgr.GetInstance().Global.Get<CustomCall>("testFun");call();}// Update is called once per framevoid 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 updatevoid 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 framevoid Update(){}
}

List和Dictionary映射table

我们先在test中声明

testList={1,2,3,4,5,6}testList2={"123","123",true,1,1.2}--DictionarytestDic1={["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")endtestClass2={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.GameObjectlocal 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.GameObjectlocal 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 doprint(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)doprint(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) doprint(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.Lesson5local 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")
endobj.del=funobj.del()--执行委托

然后是事件

local obj=CS.Lesson7()local fun2=function()print("事件加的函数")
endobj: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 doprint(obj.GetValue(i,j))
end
end

在这里插入图片描述

Lua中null与nil的比较

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

GameObject=CS.UnityEngine.GameObject
Rigidbody=CS.UnityEngine.Rigidbodylocal obj=GameObject("测试加脚本")local rig=obj:GetComponent(typeof(Rigidbody))
print(rig)if rig==nil thenrig=obj:AddComponent(typeof(Rigidbody))
end
print(rig)

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

function IsNull( obj )if obj==nil or obj:Equals(nil) thenreturn trueelse 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.GameObjectWaitForSeconds=CS.UnityEngine.WaitForSecondslocal obj=GameObject("Coroutine")local mono=obj:AddComponent(typeof(CS.LuaCallCSharp))--希望用来被开启的协程
fun=function ()local a=1while true docoroutine.yield()print(a)a=a+1end
endmono: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.rhkb.cn/news/317162.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu如何更换 PyTorch 版本

环境&#xff1a; Ubuntu22.04 WLS2 问题描述&#xff1a; Ubuntu如何更换 PyTorch 版本考虑安装一个为 CUDA 11.5 编译的 PyTorch 版本。如何安装旧版本 解决方案&#xff1a; 决定不升级CUDA版本&#xff0c;而是使用一个与CUDA 11.5兼容的PyTorch版本&#xff0c;您可…

22 重构系统升级-实现不停服的数据迁移和用户切量

专栏的前 21 讲&#xff0c;从读、写以及扣减的角度介绍了三种特点各异的微服务的构建技巧&#xff0c;最后从微服务的共性问题出发&#xff0c;介绍了这些共性问题的应对技巧。 在实际工作中&#xff0c;你就可以参考本专栏介绍的技巧构建新的微服务&#xff0c;架构一个具备…

图像处理的基本操作

一、PyCharm中安装OpenCV模块 二、读取图像 1、基本语法 OpenCV提供了用于读取图像的imread()方法&#xff0c;其语法如下&#xff1a; image cv2.imread&#xff08;filename&#xff0c;flags&#xff09; &#xff08;1&#xff09;image&#xff1a;是imread方法的返回…

前后缀分离,CF1209 C. Maximal Intersection

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 1029C - Codeforces 二、解题报告 1、思路分析 线段相交具有可…

预编码算法学习笔记

文章目录 1. 基本原理2. 常见应用2.1 自编码器2.2 变分自编码器2.3 稀疏自编码器 3. 学习笔记 在机器学习领域&#xff0c;预编码算法是一种强大的工具&#xff0c;用于将高维数据映射到低维表示&#xff0c;从而提取数据中的重要特征。本文将介绍预编码算法的基本原理、常见应…

分布式与一致性协议之拜占庭将军问题(三)

拜占庭将军问题 叛将先发送消息 如果是叛将楚先发送作战消息&#xff0c;干扰作战计划&#xff0c;结果会有所不同吗&#xff1f; 在第一轮作战信息协商中&#xff0c;楚向苏秦发送作战指令"进攻",向齐、燕发送作战指令"撤退"&#xff0c;如图所示(当然还…

SpringCloud 学习笔记 —— 六、Ribbon:负载均衡(基于客户端)

SpringCloud 学习笔记 —— 一、背景-CSDN博客 SpringCloud 学习笔记 —— 二、微服务与微服务架构-CSDN博客 SpringCloud 学习笔记 —— 三、SpringCloud 入门概述-CSDN博客 SpringCloud 学习笔记 —— 四、SpringCloud Rest 学习环境搭建&#xff1a;服务提供者-CSDN博客 …

Linux 的静态库和动态库

本文目录 一、静态库1. 创建静态库2. 静态库的使用 二、动态库1. 为什么要引入动态库呢&#xff1f;2. 创建动态库3. 动态库的使用4. 查看可执行文件依赖的动态库 一、静态库 在编译程序的链接阶段&#xff0c;会将源码汇编生成的目标文件.o与引用到的库&#xff08;包括静态库…

对话访谈——五问RAG与搜索引擎:探索知识检索的未来

记一次关于RAG和搜索引擎在知识检索方面的对话访谈&#xff0c;针对 RAG 与传统搜索引擎的异同,以及它们在知识检索领域的优劣势进行了深入的探讨。 Q&#xff1a;传统搜索引擎吗&#xff0c;通过召回-排序的两阶段模式&#xff0c;实现搜索逻辑的实现&#xff0c;当前RAG技术也…

数字旅游:通过科技赋能,创新旅游服务模式,提供智能化、个性化的旅游服务,满足游客多元化、个性化的旅游需求

目录 一、数字旅游的概念与内涵 二、科技赋能数字旅游的创新实践 1、大数据技术的应用 2、人工智能技术的应用 3、物联网技术的应用 4、云计算技术的应用 三、智能化、个性化旅游服务的实现路径 1、提升旅游服务的智能化水平 2、实现旅游服务的个性化定制 四、数字旅…

想要接触网络安全,应该怎么入门学习?

作为一个网络安全新手&#xff0c;首先你要明确以下几点&#xff1a; 我刚入门网络安全&#xff0c;该怎么学&#xff1f;要学哪些东西&#xff1f;有哪些方向&#xff1f;怎么选&#xff1f;这一行职业前景如何&#xff1f; 其次&#xff0c;如果你现在不清楚学什么的话&…

vim的IDE进阶之路

一 ctags 1 安装 安装ctags比较简单&#xff0c;我用的是vim-plug&#xff0c;网络上随便一搜应该就有很多教程&#xff0c;而且没有什么坑 2 使用 vim之函数跳转功能_nvim函数跳转-CSDN博客https://blog.csdn.net/ballack_linux/article/details/71036072不过针对cuda程序…

Java基础_集合类_List

List Collection、List接口1、继承结构2、方法 Collection实现类1、继承结构2、相关类&#xff08;1&#xff09;AbstractCollection&#xff08;2&#xff09;AbstractListAbstractSequentialList&#xff08;子类&#xff09; 其它接口RandomAccess【java.util】Cloneable【j…

【LAMMPS学习】八、基础知识(5.3)Body particles体粒子

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

Go 学习笔记

Go 学习相关笔记 Go 官方的教学文档顺序不怎么友好&#xff0c;这里根据我自己的学习曲线来记录文档的查看顺序 基础知识 文档预备 新手先要看 Go 的模块管理介绍&#xff0c;这样才知道基础 Go 怎么导入外部包和进行本地的包管理 https://go.dev/doc/modules/managing-dep…

【快速入门】数据库的增删改查与结构讲解

文章的操作都是基于小皮php study的MySQL5.7.26进行演示 what 数据库是能长期存储在计算机内&#xff0c;有组织的&#xff0c;可共享的大量数据的集合。数据库中的数据按照一定的数据模型存储&#xff0c;具有较小的冗余性&#xff0c;较高的独立性和易扩展性&#xff0c;并为…

本地CPU搭建知识库大模型来体验学习Prompt Engineering/RAG/Agent/Text2sql

目录 1.环境 2.效果 3.概念解析 4.架构图 5. AI畅想 6.涉及到的技术方案 7. db-gpt的提示词 1.环境 基于一台16c 32G的纯CPU的机器来搭建 纯docker 打造 2.效果 3.概念解析 Prompt Engineering &#xff1a; 提示词工程 RAG&#xff1a; 检索增强生成&#xff1b; …

CTFHub-Web-SQL注入

CTFHub-SQL注入-WP 1.整数型注入 1.题目说输入1&#xff0c;先将1输入查看结果 2.接着输入4-1&#xff0c;发现输出的结果为4-1&#xff0c;判定存在整数型注入 3.查询字段数&#xff0c;出现了回显&#xff0c;判断这里的字段数为2 1 order by 24.判断注入点在2的位置&…

复杂度(3)

目录 1.二分查找的时间复杂度 2.斐波那契数列及其优化 3.空间复杂度 1.二分查找的时间复杂度 我们熟知的二分查找绝对是一种很厉害的算法&#xff0c;因为这个算法每进行一次都会砍掉一半的数据&#xff0c;相当于是指数级增长&#xff0c;假设我们刚开始的时候数据的个数是…

MS8241/MS8242高速、高输出电流、电压反馈放大器

产品简述 MS8241/MS8242 是一颗高速的电压反馈放大器&#xff0c;具有电流 反馈放大器的高速转换特性&#xff0c;可以应用在所有传统的电压反馈运 放应用方案中。 MS8241/MS8242 能够稳定工作在低增益环路下 &#xff08;增益为 2 和 -1 &#xff09;&#xff0c;仅消耗…