Unity--XLua调用C#

news2024/11/15 6:54:49

Unity–XLua调用C#

由于Unity/C# 和lua是两种语言,两种语言的特性不一样,因此,如果要互相调用的话,需要第三方作桥梁. 因此,为了在Unity中/C#中使用lua的特性,需要在Unity中安装插件,Xlua/toLua作为桥梁. 通过该桥梁可以使得lua中的数据传递Unity中,Unity中的数据传递到lua中,或者进行类/成员函数/成员变量/静态函数的调用. 以下是在关系图,下图是Lua调用Unity/C#的一个大致过程, 本文主要介绍xlua在Unity中的使用以及lua如何调用C#

Lua调用Unity/C#

由于是lua调用Unity/C#,因此,许多逻辑都是在lua中书写的,在C#中准备部分数据,以便lua调用

1. lua调用C#中的类

说明: 由于C#存在类所在命名空间,因此,不同命名空间下的类可能名称是相同的,因此在使用中的是需要注意.此外,为了简写命名空间,我们可以给C#中的类名区别名. 即 CS.Namespace.ClassName 具体可以为:CS.UnityEngine.GameObject

例如:

GameObject = CS.UnityEngine.GameObject
Transform = CS.UnityEngine.GameObject.Transfrom

由于在C#中,要使用类,我们一般的方式是使用new的方式实例化对象,在lua中没有new的方式,因此在lua中使用()就相当于无参构造

例如:

local obj1 = CS.UnityEngine.GameObject()
local obje2 = CS.UnityEngine.GameObject("Go")

如前说的,我们可以取别名 因此,上面的lua代码可以写为

-- 取别名
GameObject = CS.UnityEngine.GameObject
-- 使用别名创建对象
local obj = GameObject()

使用类中的静态变量和静态方法

我们可以通过类名.的方法调用

例如:

-- 取别名
GameObject = CS.UnityEngine.GameObject
-- 实例化对象
local obj = GameObject()
-- 使用成员属性并打印出来
print(obj.transform.position)
-- 使用点的方式调用静态珊瑚
local go = GameObject.Find("Go");
-- 取别名
Debug = CS.UnityEngine.Debug
-- 调用Deug中的Log函数
Debug.Log(go.transform.position)


调用实例化对象中的成员属性和成员方法

调用成员属性的时候直接使用点的方式即可,掉用对象中的成员方法需要使用冒号:的方式进行调用. 同理,我们自己写的类也是如此.

例如:

-- 取别名
GameObject = CS.UnityEngine.GameObject
Vector3 = CS.UnityEngine.Vector3
Debug = CS.UnityEngine.Debug

--实例化对象
local obj = GameObject()

-- 调用成员函数
obj.transform:Translate(Vector3.right)

Unity中的继承了Mono的类

由于Unity的Mono本身不能通过new的方式进行实例化,因此,我们在需要通过名称是实例化GameObject的时候需要填写参数,在C#中使用的泛型的方式,但是在lua中无法使用泛型,因此,需要调用c#中的重载的方法.

例如:

GameObject = CS.UnityEngine.GameObject
local obj = GameObject("更名的GameObject")

2.lua调用C#中的枚举

lua调用C#中的枚举和调用类/类的实例中的属性是一样的. 调用自定义枚举也是如此.

例如:

-- 取别名
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
Debug = CS.UnityEngine.Debug
-- 使用Unity中的创建内置的游戏物体 -- PrimitiveType.Cube在Unity中就是枚举
local obj1 = GameObject.CreatePrimitive(PrimitiveType.Cube)

-- 自定义枚举
AnimationEnum = CS.E_AnimationEnum
local obj2 = AnimationEnum.Idle
print(obj2)

需要注意的是,我们在C#中使用枚举,是直接使用枚举对象或者枚举.枚举值进行调用,在lua中,需要转换才能被使用

转换方法如下

.__CastFrom(数字/字符串)

根据函数名称,我们知道,传入参数是一个数字,或者字符串,得到的是是一个枚举

例如:

C#中的自定义枚举

public Enum E_AnimationEnum
{
    Idle,
    Walk,
    Run,
    Jump
}

lua代码:

AnimationEnum = CS.E_AnimationEnum
local obj = AnimationEnum.Idle
print(obj) 
-- obj中是userdata,保存的是C#/C++中的原始数据

-- 数字枚举转换
local a = AnimationEnum.__CastFrom(1)
print(a)
-- 打印出来是:Idle : 0

-- 字符串转枚举
local b = AnimationEnum.__CastFrom("Jump")
print(b)
-- 打印出来是:Walk : 1

3.lua调用C#的数组/List/Dictionary

1.ua调用数组

在C#中的代码如下:

ArrayTest.cs

public Class AttayTest
{
    public int[] array = new int[9] { 1,2,3,4,5,6,7,8,9 };
}

遍历数组

在C#中使用.Length进行获得数组的长度,在lua中调用的时候也可以,因为数组基础功能了Array这个类,该类中有Length的属性,因此,在 lua中可以直接调用

-- 取别名
Debug = CS.UnityEngine.Debug
-- 实例化对象
local obj = CS.AttayTest()
-- 遍历c#中的数组
for i = 0,obj.array.Length - 1 do
	print("index: " .. obj.array[i])
end

我们已经会了遍历数组中的的元素,如何在lua中创建C#数组呢

根据上面提到的Array类的方法中,我们发现有CreateInstance函数来实现创建各种类型的数组. 该函数原型如下,且有6个重载,特家具

public static Array CreateInstance(Type elementType,int length);
public static Array CreateInstance(Type elementType,int length1,int length2);
public static Array CreateInstance(Type elementType,int length1,int length2,int length3);
public static Array CreateInstance(Type elementType,params int[] lengths);
public static Array CreateInstance(Type elementType,int[] lengths,int[] lowerBounds);
public static Array CreateInstance(Type elementType,params long[] lengths);

这六个函数都是Array类的静态方法,用于创建指定类型和维度的数组实例。它们在System命名空间中定义,可以在C#代码中直接调用。下面是对每个函数的详细解释:

  1. CreateInstance(Type elementType,int length):
    • 这个方法创建一个一维数组,其元素类型由elementType指定,长度由length指定。
  2. CreateInstance(Type elementType,int length1,int length2):
    • 这个方法创建一个二维数组,元素类型由elementType指定,第一维的长度由length1指定,第二维的长度由length2指定。
  3. CreateInstance(Type elementType,int length1,int length2,int length3):
    • 这个方法创建一个三维数组,元素类型由elementType指定,第一、二、三维的长度分别由length1length2length3指定。
  4. CreateInstance(Type elementType,params int[] lengths):
    • 这个方法是最通用的创建数组的方法,可以创建任意维度的数组。elementType指定元素类型,lengths是一个参数数组,每个元素对应数组的一个维度长度。
  5. CreateInstance(Type elementType,int[] lengths,int[] lowerBounds):
    • 这个方法与上一个方法类似,但它允许指定每个维度的下界(即数组的起始索引)。lengths指定每个维度的长度,lowerBounds指定每个维度的起始索引。
  6. CreateInstance(Type elementType,params long[] lengths):
    • 这个方法与CreateInstance(Type elementType,params int[] lengths)类似,但它接受long类型的参数,允许创建长度非常大的数组。

根据CreateInstance的第一个参数,我了解到需要传入一个泛型,因此 我们在lua中创建数组的时候,需要指定类型

由于xlua对泛型的支持不算友好,因此,需要使用typeof()来指定类型

例如:

-- 给系统中Int32的类型取别名
Int = CS.System.Int32
-- 使用重载函数创建一维数组
local obj = CS.System.Array.CreateInstance(typeof(Int)8)
-- 遍历以为数组
for i = 0,obj.Length - 1 do
	obj[i] = i * 2
	print("lua创建c#中的数组,index = " .. i .. "value = ".. obj[i])
end

2.lua调用List

在C#的代码TestList.cs中有一个空的list

public class TestList
{
    public List<int> list = new List<int>();
}

在lua中调用list的成员函数

-- 实例化对象
local obj = CS.TestList()

-- 添加数据
obj.list: Add(1)
obj.list: Add(10)
obj.list: Add(100)

-- 遍历list
for i = 0,obj.list.Count - 1 do
	print("list: " .. obj.list[i])
end

在lua中创建list对象

版本1->旧版本

使用CS.System.Collections.Generic的方式构建lList,但还不是List类的实例

例如: 构建了一个List的类,list中的类型System.String,即系统中的String类型

CS.System.Collections.Generic["List`1[System.String]"]

使用该类构建list对象

-- 使用()构建list对象
CS.System.Collections.Generic["List`1[System.String]"]()
-- 或者分开使用
List_String = CS.System.Collections.Generic["List`1[System.String]"]
-- 实例化对象
local stringList = List_String()


版本2->新版本

省去了"List1[System.String]"这一串,使用CS.System.String`代替

local List_String = CS.System.Collections.Generic.List(CS.System.String)
-- 实例化对象
local list = List_String()
-- 添加数据
list3: Add("Pi")
list3: Add("Banana")
list3: Add("chese")
-- 打印数据
print(list[1])
print(list[2])

3.lua调用Dictionary

在C#的代码TestDictionary.cs中有如下代码. 其中包含了一个key为string类型的键,int类型的Value

public class TestDictionary
{
    // Dictionary
    public Dictionary<string,int> dic = new Dictionary<string,int>();
}

调用c#中的字典

-- 实例化对象
local obj = CS.TestDictionary()
-- 给c#中的字典添加数据
obj.dic: Add("One"1)
obj.dic: Add("two"2)
obj.dic: Add("three"3)
obj.dic: Add("four"4)

Lua中遍历C#中的字典. 在C#中遍历的时候使用的是foreach通过迭代器的方式遍历,在lua中没有foreach,因此只能使用for配送pais来遍历

-- 实例化对象
local obj = CS.TestDictionary()
-- 给c#中的字典添加数据
obj.dic: Add("One"1)
obj.dic: Add("two"2)
obj.dic: Add("three"3)
obj.dic: Add("four"4)
-- 遍历字典

-- 遍历使用for循环,c#中使用的是foreach
for k,v in pairs(obj.dic) do
	print(k,v)
end

在lua中创建Dictionary,使用CS.System.Collections.Generic.Dictionary(key类型,value类型)来构建字典

例如,在lua创建一个string为key,Vector3为value的键Dictionary,lua代码如下

-- 取别名
Vector3 = CS.UnityEngine.Vector3
String = CS.System.String
-- 构建字典类
local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(String,Vector3)
-- 实例化字典
local dic2 = Dic_String_Vector3()
-- 添加数据
dic2: Add("Zero",Vector3.Zero)
dic2: Add("Forward",Vector3.rorward)
dic2: Add("Right",Vector3.right)
-- 遍历字典
for k,v in pairs(dic2) do
	print(k,v)
end

[注意]: 在C#中可以通过[key]的方式来获取值,例如:dic["Zero"]获取值,但是在lua中无法使用该方法,需要使用固定写法get_Item(键值)
例如:

print(dic2:get_Item("Right"))

同时,还有另外一种获取值的方式TryGetValue(键名)

print(dic2: TryGetValue("Forward"))

使用TryGetValue的方式获得值,先返回的是一个bool值,表示是否得到了key对应的值,如果能得到Value,然后返回对应的值

修改自定中的值,通过set_Item的方式修改值

例如:

-- 使用功能set_Item来设置值
dic: set_Item("Right",Vector3.left)
print(dic:get_Item("Right"))

4.lua调用扩展方法

在C#中,在不改变源代码的情况下给我们自己的类添加其他功能,我们是通过c#的特性添加扩展方法,因此,在lua中也是可以可以调用c#中的扩展方法.

在C#代码中有如下代码:

[XLua.LuaCallCSharp]
public static class Tools
{
    // 拓展方法 必须是静态的,且有this指针
    // 这是一个扩展方法,它为 TestExtend 类型添加了一个名为 ToUpperFirstLetter 的新方法
    public static string ToUpperFirstLetter(this TestExtend obj)
    {
        // 如果字符串不为空,则转换首字母为大写
        if (!string.IsNullOrEmpty(obj.s1))
        {
            return char.ToUpper(obj.s1[0]) + obj.s1.Substring(1);
        }
        return obj.s1;
    }
}

public class TestExtend
{
    public string s1 = "i am writing this letter to inform you of my recent situation";

    public string s2 = "we've been experiencing a hurricane here recently,so we've been very busy lately";

    // 静态成员方法
    public static void Greating()
    {
        Debug.Log("Daily Greating");
    }
    
    public void SendLetter(string userName,string emailAddress,string subject)
    {
        Debug.Log("sendign the letter with the subject:" + subject + "to user: " + userName + "(" + emailAddress + ")");
    }

}

在lua中的代码如下:

TestExtend = CS.TestExtend

-- 先调用静态方法
TestExtend.Greating()

-- 实例化对象
local obj = TestExtend()
-- 调用成员方法
obj:SendLetter("Caron4""xxx@gmail.com""Daily Greetings")

-- 调用扩展方法
-- 需要再添加xLua特性
-- [XLua.LuaCallCSharp]
-- 建议添加上,可以提升速度,因为lua调用c#是通过反射的方式调用的 
local str = obj:ToUpperFirstLetter();
print(str)


5.lua调用C#中含有ref out修饰参数的函数

在C#中,refout 是两个用于参数传递的关键字,它们允许方法通过引用传递参数,而不是通过值传递。这种传递方式允许方法修改传入参数的值,并在方法外部保持这些更改. ref和out的区别,在于ref需要初始化,out不需要初始化。

在lua中如调用呢? 以下是C#中的代码。

public class TestREfOut
{
    // 普通函数
    public void NormalFun(int a,int b,int c)
    {
        Debug.Log($"a = {a},b = {b},c = {c}");
    }

    // 被ref修饰的参数-需要初始化
    public int RefFun(int a,ref int b,ref int c,int d)
    {
        b = a + d;
        c = a - d;
        return 200;
    }

    // 被out修饰的参数-不需要初始化
    public int OutFun(int a,out int b,out int c,int d)
    {
        b = a;
        c = d;
        return 300;
    }

    // 被ref 和 out同时修饰的函数
    public int RefOutFun(int a,ref int b,out int c)
    {
        b = a * 10;
        c = a * 20;
        return 400;
    }
}

以下是lua中的调用代码,在调用的术时候,需要注意C#中的函数是否有返回值,如果有返回值,返回的第一个参数是函数的返回值,而不是被ref或者out修饰的值

-- 别名
TestREfOut = CS.TestREfOut

local obj = TestREfOut()

-- 调用普通函数
obj: NormalFun(100200300);

-- 调用ref的函数
local a,b,c = obj:RefFun(100020)
print("ref返回值" .. a) 	-- 打印 200
print("ref返回值" .. b)	-- 打印 30
print("ref返回值" .. c) 	-- 打印 10

-- 调用out的函数
-- C#中的函数签名 4个参数,1个返回值
-- public int RefFun(int a,ref int b,ref int c,int d)
-- public int OutFun(int a,out int b,out int c,int d)
local a,b,c = obj:OutFun(1020)
print("out返回值" .. a) 	-- 打印 300
print("out返回值" .. b)	-- 打印 10
print("out返回值" .. c) 	-- 打印 20

-- 混合调用ref和out的函数

-- C#中的函数签名 3个参数,1个返回值
-- public int RefOutFun(int a,out int b,ref int c)
local a,b,c = obj:RefOutFun(100)
print("refout返回值" .. a) 		-- 打印 400
print("refout返回值" .. b)		-- 打印 100
print("refout返回值" .. c)		-- 打印 200


6.lua调用C#中的重载函数

在C#中我们可以使用名称相同,参数类型不同和返回值不同来进行实现重载函数. 在lua中中调用,需要特备注意.

以下是C#中的代码

public class TestOverride
{
    // 普通函数
    public int Calc()
    {
        return 100;
    }
    // 有返回值,有两个参数的重载
    public int Calc(int num1,int num2)
    {
        return num1 + num2;
    }
    // 有返回值和一个参数的重载
    public int Calc(int num)
    {
        return num;
    }
    // 有返回值和一个类型不同的参数的重载
    public float Calc(float num)
    {
        return num;
    }
}

在lua是支持重载函数的,但是由于lua中的中保存数字的类型只有一种,因此我们在调用饭后float/double等类型的重载函数需要注意. 以下是具体代码.

-- 取别名
TestOverride = CS.TestOverride
-- 实例化对象
local obj = TestOverride();

-- 调用普通的成员函数
local a = obj:Calc()
print("a = ".. a)
local b = obj:Calc(100200)
print("b = " ..b)
-- a和b都被打印出来,说明是支持重载的


调用精度较高的重载函数的时候,,会出现问题

例如:

-- 取别名
TestOverride = CS.TestOverride
-- 实例化对象
local obj = TestOverride();
-- 调用重载函数
local c = obj:Calc(300)
print("c = " .. c)
-- 调用精度较高的重载函数
local d = obj:Calc(300.569)
print("d = ".. d)
-- 的打印的结果为0  是因为lua中的数字存储的类型只有number一种类型

-- 因此不建议使用精度不同的重载函数

为了将解决进度的问题,我们需要使用反射来获取类型

先通过GetMethod获取类中的方法,在GetMethod中实则参数的类型,将函数转换一下,然后在使用,注意转换为float的时候, 类型需要填写为CS.System.Single

例如:

local m1 =typeof(CS.TestOverride):GetMethod("Calc"{typeof(CS.System.Int32)})
-- 取别名
TestOverride = CS.TestOverride
-- 实例化对象
local obj = TestOverride();
-- 转换重载函数
local m1 = typeof(CS.TestOverride):GetMethod("Calc"{typeof(CS.System.Int32)})
local m2 = typeof(CS.TestOverride):GetMethod("Calc"{typeof(CS.System.Single)})

-- 一般只转换一次,然后重复使用
local f1 = xlua.tofunction(m1)
local f2 = xlua.tofunction(m2)

-- 调用的时候,如果是成员方法的话,第一个参数传对象
-- 如果是静态方法的话,就不用传对象
print(f1(obj,10))
print(f2(obj,10.2))

7.Lua调用C#中的委托和事件

Unity中的游戏开发中,大型项目往往是lua项目,而游戏中的UI的交互往往是通过lua调用的. 因此UI的交互这种事情可以交给lua来做,比如,按钮点击,滑块调节.

以下是C#中的代码

public class TestDelegate
{
    // 申明委托
    public UnityAction del;
}

由于委托/事件具有添加,移除,执行和销毁等基本功能,添加和移除使用+=/-=来操作的,由于在lua总没有复合运算符,因此我们子啊进行添加事件或者移除事件的时候需要做一些修改. 调用委托和C#中的一样

以下是有关委托的相关的操作

lua中的代码如下

-- 别名
TestDelegate = CS.TestDelegate

local obj = TestDelegate();
-- lua中的函数,待添加到委托中
local fun = function()
	print("lua函数Fun")
end

-- 第一次使用/添加的时候需要等号=赋值
obj.del = fun
-- 使用 = 也算添加进去了一次函数
print("lua使用+给C#的委托添加函数")
obj.del = obj.del + fun
obj.del = obj.del + fun
-- 调用
obj.del()

-- 添加匿名函数

obj.del = obj.del + function ()
	print("lua给C#委托添加了匿名函数")
end

-- 执行委托
obj.del()
-- 移除委托中的函数
print("lua端移除委托中的函数")
obj.del = obj.del - fun
obj.del = obj.del - fun
obj.del = obj.del - fun

-- 执行委托
obj.del()
-- 发现还是打印的的"lua给C#委托添加了匿名函数"
-- 这说明,委托中无法移除匿名函数,但是可以使用nil来清空

-- 清空委托
print("lua将C#中的委托置为nil等于清空了委托,也包括匿名函数")
obj.del = nil

以下是有关事件的相关的操作

public class TestEvent
{
    // 声明事件
   public event UnityAction eventAction;
    
    public void DoEvent()
    {
        if(eventAction!= null)
        {
            // 执行事件
            eventAction();
        }
    }
	// 清空事件
    public void ClearEvent()
    {
        eventAction = null;
        Debug.Log("事件清空完毕");
    }
}

lua调用C#中的事与调用委托非常不一样

以下是lua中调用C#中的事件的代码

-- 取别名
TestEvent = CS.TestEvent
-- 实例化对象
local obj = TestEvent();
-- 待添加到事件中的函数
local fun2 = function()
	print("lua给C#事件添加函数")
end

-- lua中使用c#事件添加函数,有点类似与调用成员方法 冒号: 事件名("+",函数变量)

obj:eventAction("+",fun2)
-- 调用的时候不能直接调用,需要在c#写函数,lua调用函数的方式调用事件

obj:DoEvent()
-- 添加匿名函数
obj:eventAction("+"function()
	print("Lua给事件添加匿名函数")
end)
obj:DoEvent()

print("移除事件事件中的函数")
obj:eventAction("-",fun2)
obj:eventAction("-",fun2)
-- 移除后再执行事件
obj:DoEvent()

-- lua中不管是委托还是事件,不建议使用添加匿名函数
-- lua中的事件,需要清空的话,需要在C#中写一个函数进行清空
-- 然后在lua中调用,换句话说,lua不支持直接清空c#的事件
print("lua调用C#中清空事件的函数")
-- 清空事件
obj:ClearEvent();

8.lua调用C#中的二维数组

这是为了不充电前面的3的相关内容. 在C#中使用二维数组很简单,但是在lua中调用需要做一些改变

以下是C#中的代码

public class TestTWoDimAttary
{
    // 一个二维数组
    public int[] array = new int[23] { { 123 }{ 456 } };
    // 构造函数
    public TestTWoDimAttary(){  }
}

  • lua中 需要通过GetLength(维度)来获取长度
  • lua中通过GetValue获取二维数组中的值
  • lua中通过SetValue设置/修改二维数组中的值

lua代码如下

local obj = CS.TestTWoDimAttary()

-- 获取二维数组的长度
print("行的长度:" .. obj.array:GetLength(0))
print("列的长度:" .. obj.array:GetLength(1))

-- 获取值
print("通过getValue的方式获取值:" .. obj.array:GetValue(00))
print("通过getValue的方式获取值:" .. obj.array:GetValue(10))

--lua遍历c#中的数组
for i = 0,obj.array: GetLength(0) - 1 do
	for j = 0,obj.array:GetLength(1) -1  do
		print(obj.array: GetValue(i,j))
	end
end

9.lua中的nil和c#中的null比较

在lua和C#的对象映射中,C#中对空的判断是null而自lua中对空的判断是nil

两者都是空,但是并不代表可以使用.

例如: 检测GameObject上是否有个组件, 没有该组件,就添加组件,使用C#中的Equal()方法进行判断

-- 别名
GameObject = CS.UnityEngine.GameObject
Rigidbody = CS.UnityEngine.Rigidbody
BoxCollider = CS.UnityEngine.BoxCollider

-- 
local obj = GameObject("添加刚体的物体")
-- 判断实例化的物体上是否有刚体组件
local rig = obj: GetComponent(typeof(Rigidbody))
-- 注意 需要使用Equals(nil)来判断,
if rig:Equals(nil) then
    rig = obj: AddComponent(typeof(Rigidbody))
end

由于lua中的某对象很有可能就是nil值,因此,我们可以在lua中进行优化,提供判断是否空的兼容性,即在开头添加以下函数进行判断. 当然这种工具列的函数,非常建议卸载一个文件中,然后使用require的方式引用该文件,在需要判断是否为空的地方调用即可

--方法2: 使用写全局函数nil和Equals(nil)进行判断

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

除了上面的方法,我们还可以在C#中进行做处理,代码如下,以下代码是给了Unity中的GameObject添加了拓展方法,并使用了[LuaCallCSharp]进行标记,表示lua调用C#中的代码. 需要再Unity编辑器中重新使用XLua生成代码

// 扩展方法
[LuaCallCSharp]
public static class class9
{
    public static bool IsNull(this UnityEngine.Object obj)
    {
        return obj == null;    
    }
}

10.lua调用C#中的系统函数

除了能够调用Unity中的系统函数,我们也需要调用C#中的系统函数,我们的一般做法是给代码添加特性[LuaCallCSharp]或者[CSharpCallLua],然而,.Net提供的代码我们无法添加特性,结合扩展的思想,我们可以将我们需要的内容放在一个自定义类中,对自定义类进行扩展即可.

以下代码定义了一个名为 TestSystem 的公共静态类,其中包含两个静态列表:CSharpCallLuaListLuaCallCSharpList。这两个列表用于在不同的编程环境(C# 和 Lua)之间进行类型交互。该静态类的设计允许我们在 C# 和 Lua 之间轻松地传递和操作数据,特别是在使用 Lua 脚本进行游戏开发时。通过这种方式,开发者可以在 C# 中编写核心逻辑,同时利用 Lua 的灵活性和易于修改的特性来快速迭代游戏设计。

当然,你可以自定名称,放在指定的文件夹中

public static class TestSystem
{
    [CSharpCallLua]
    public static List<Type> CSharpCallLuaList = new List<Type>()
    {
        // 需要什么特性就添加什么特性
        typeof(UnityAction<float>)typeof(UnityAction<int>)typeof(UnityAction<double>)
    };

    [LuaCallCSharp]
    public static List<Type> LuaCallCSharpList = new List<Type>()
    {
        // 需要什么特性就添加什么特性
        typeof(GameObject)typeof(Transform)typeof(Image)

    };
}

在Unity的场景中创建一个Slider,

我们想通过lua得到当前Slider的值,在C#中是通过AddListener()的方式进行监听,由于是系统AddListener是系统提供的函数,无法添加特性,因此,我们可以使用上面的扩展类进行或者,通过在List中添加即可. 同时,也需要重新生成代码

-- 别名
GameObject = CS.UnityEngine.GameObject
UI = CS.UnityEngine.UI

-- 查找Slider的GameObject
local slider = GameObject.Find("Slider")

print(slider)
-- 获取slider上的脚本slider
local sliderScript = slider:GetComponent(typeof(UI.Slider))
-- 绑定slider上的value的值变化情况
sliderScript.onValueChanged:AddListener(function (sliderValue)
	print("slider's Value: " .. sliderValue)
end)

11.lua调用C#中的协程

lua无法直接开启Unity中的协程,需要使用XLua中提供更多工具类,将lua的协程函数进行转换,然后再调用
工具类如下require("xlua.util")

具体流程如下:

  1. 准备好了协程函数和工具与类
  2. 使用工具转换协程函数,并使用lua的开启协程
  3. 保存协程
  4. 在需要的时候关闭协程

在lua中的代码如下:

-- 引入工具
util = require("xlua.util")

-- 别名
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
		-- 使用lua中的协程返回
		coroutine.yield(WaitForSeconds(1))
        
		print("当前a的值:" .. a)
		a = a + 1
		
		if a > 10 then
            -- 关闭协程,和C#中的一样
			mono:StopCoroutine(cor)
		end
	end
end

-- 准备好了协程函数和工具与类
-- 使用工具转换协程函数,并使用lua的开启协程
-- 保存协程
cor = mono:StartCoroutine(util.cs_generator(fun))


12.lua调用C#中的泛型

由于lua中没有泛型,只能通过typeof获得类型. 因此,在使用泛型的方向上,我们可以使用typeof来解决无法直接使用泛型的问题

代码解释:

除了常见的值类型的泛型,int,short,long,float,double,bool,string,enum等,还有我们之定义的类和接口,这些都可以作为泛型,因此,在C#中是可以制动推到的,且在匿名函数中能够字节表现出来. 但是在lua中缺有以下限制. lua中他默认支持有参数,有约束的泛型. 其他条件的就会被限制,毕竟lua是轻量级的脚本语言

以下是C#中各种泛型的代码.

public class TestT
{
    // 内部类和接口
    public interface ITest { }
    public class TestFather : ITest { }
    public class TestChild : TestFather,ITest { }
    // 普通函数
    public void Test()
    {
        Debug.Log("普通测试函数");
    }
	// 有参数,有约束
    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.TestT()

-- 实例化对象
local father = CS.TestT.TestFather()
local child = CS.TestT.TestChild()

-- 普通函数
print("test")
obj:Test()
-- 有参数,有约束的泛型方法调用
obj: TestFun1(child,father)
obj: TestFun1(father,child)
-- 可以被打印出来

-- 有参数,没有约束的泛型方法
-- lua中不支持有参数没有约束的泛型
-- obj: TestFun2(child)

-- 无参数,有约束的泛型方法
-- obj: TestFun3()
-- lua中不支持无参数有约束的泛型

-- 有参数,有约束,约束是接口
-- lua中不支持约束是接口的泛型
-- obj: TestFun4()

在上面的代码中,有很多代码的调用是被注释,这是由于lua无法直接调用这些代码

因此,为了解决上面的问题,我们需要通过xlua工具进行转换,然后才可以通过调用.

同时,如果使用xlua的工作转换了,打包的时候就只能通过Mono的方式进行打包,如果使用il2cpp的方式,泛型使只能是引用类型,如果值值类型的话,除非C#那边已经调用过了 同类型的泛型参数,lua中才能被使用
否者 lua中无法使用

以下代码是xLua工具转换了的泛型,使用 xlua.get_generic_method(类名,函数名)进行转换

-- 转换填写参数: 类名,带有泛型的方法名
local testFun2 = xlua.get_generic_method(CS.TestT,"TestFun2")
-- 确定泛型的类型
local testFun2_Reall = testFun2(CS.System.Int32)
-- 转换完毕后调用
-- 调用
-- 如果是成员方法,第一个参数是 使用函数的对象
-- 如果是静态方法,不用传使用函数的对象
testFun2_Reall(obj,11)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构—— 再探二叉树

1. TOP-K问题 TOP-K问题&#xff1a;求数据结合中前K个最大或者最小的数据 比如&#xff1a;专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等 思路&#xff1a; 1. 用数据集合中前K个数据来建堆&#xff1a; …

easypoi模板导出word并且合并行

导出流程 引入依赖制作模板合并导出 引入依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.1.2</version> </dependency>制作模板 合并行是备注那一列&#xff0c;这一列…

UCIE-state machion

1.retrain/linkerror/active状态 状态转变都是从下往上。 &#xff08;1&#xff09;Retrain&#xff1a;multistack和raw model相互违背&#xff1b; &#xff08;2&#xff09;Linkerror&#xff1a;如果一个stack需要进入linkerror&#xff0c;表示链路已经存在问题&…

论文解读:从Dijkstra的On-the-Fly到Go的三色标记算法,并行垃圾回收的起源

我们经常听到关于垃圾回收的说法是&#xff0c;某种垃圾回收算法是一种特定语言特有的&#xff0c;容易理解成&#xff0c;垃圾回收的算法跟特定编程语言是绑定的&#xff0c;但是仔细想想&#xff0c;垃圾回收器是一种分配和管理内存的机制或者程序&#xff0c;内存管理跟语言…

微分方程(Blanchard Differential Equations 4th)中文版Section2.3

阻尼谐振子 在本节中,我们将描述一种解析技术,它适用于本书中最重要的模型之一——阻尼谐振子。这一二阶微分方程用于建模各种现象,如质量-弹簧系统、电路理论中的RLC电路,以及人体的血糖调节系统。 例如,考虑汽车的悬挂系统。它可以平滑崎岖道路上的颠簸,并帮助保持轮…

告别U盘:看医院如何挑选高效安全的文件摆渡系统

基于法规要求和自身安全管理需要&#xff0c;医院普遍使用网闸&#xff0c;将网络隔离为院内网、院外网。网络隔离后&#xff0c;医院各科室部门仍存在频繁的网间数据交换需求&#xff0c;需要文件摆渡系统进行内外网数据的安全交换。具体交换场景如下&#xff1a; 1.影像科&am…

iOS Native与JS通信:JSBridge

文章目录 一、简介二、JS 调用 Native1.使用 URL Schemea.UIWebViewb.WKWebView 2.使用 JavaScriptCore (iOS 7)3.使用 WKWebView 和 WKScriptMessageHandler (iOS 8) 三、Native 调用 JS1.使用 UIWebView2.使用 WKWebView3.使用 JavaScriptCore (iOS 7) 一、简介 对于移动应用…

江西学术会议:第五届计算机、大数据与人工智能国际会议

第五届计算机、大数据与人工智能国际会议(ICCBDAI 2024)将于2024年11月1日-3日在江西景德镇召开。本届会议由景德镇陶瓷大学主办&#xff0c;西安交通大学、暨南大学、南京邮电大学、景德镇学院、ELSP&#xff08;爱迩思出版社&#xff09;、ESBK国际学术交流中心、AC学术平台协…

ctfshow之web29~web51

目录 web29 题解&#xff1a; web30 web31 web32&#xff08;32~36&#xff09; web33 web34 web35 web36 web37 web38 web39 web40 web41 web42 &#xff08;42~51&#xff09; web43 web44 web45 web50 web51 web29 前瞻知识&#xff1a; isset() …

【sgCreateReadonlyForm】自定义小工具:敏捷开发→自动化生成只读表单代码片段脚本(无需列表展示数据,多用于查看某一条数据记录)

sgCreateReadonlyForm源码 <template><!-- 前往https://blog.csdn.net/qq_37860634/article/details/141389231 查看使用说明 --><div :class"$options.name"><div class"sg-head">只读表单生成工具<el-dropdown:show-timeo…

8.20 roles的基本用法+使用剧本安装nginx

安装nginx并更改其端口 创建目录 mkdir /etc/ansible/playbook 编辑配置文件 vim /etc/ansible/palybook/nginx.yml --- - hosts: s remote_user: root tasks: - name: 卸载httpd yu…

人工智能 | 结对编程助手GithubCopilot

简介 GitHub Copilot 是一款 AI 结对程序员&#xff0c;可帮助您更快、更少地编写代码。它从注释和代码中提取上下文&#xff0c;以立即建议单独的行和整个函数。GitHub Copilot 由 GitHub、OpenAI 和 Microsoft 开发的生成式 AI 模型提供支持。它可作为 Visual Studio Code、…

智慧水务平台:数智化驱动,‌实现管理全面升级!‌

智慧生产体系聚焦水务行业的生产环节,涵盖水源管理、水厂管理、生产调度、二次供水管理等各个环节。对各生产环节的实时生产数据和设备运行参数进行监测,并提供报警、日常运维、能耗分析、流程优化,为水务生产管理的成本压降、效率提升、安全保障、服务优化提供支撑。 智慧管网…

Echarts添加水印

如果直接说水印,很难在官方找到一些痕迹,但是换个词【纹理】就能找到了。水印就是一种特殊的纹理背景。 Echarts-backgroundColor backgroundColor 支持使用rgb(255,255,255),rgba(255,255,255,1),#fff等方式设置为纯色,也支持设置为渐变色和纹理填充,具体见option.colo…

哪个牌子的开放式耳机性价比高?五款地表最强机型推荐!

在我们的日常生活中&#xff0c;街道、地铁车厢或公交车等地方常常充满了噪音&#xff0c;这些杂音不仅可能扰乱心情&#xff0c;还可能对我们的听力造成潜在的伤害。在这样的环境下&#xff0c;如果想要享受音乐或追剧&#xff0c;同时又能保持对周围环境的警觉&#xff0c;开…

充电宝哪个品牌好?360度全方面测评热门款充电宝

在这个智能手机、平板电脑等移动设备普及的时代&#xff0c;充电宝已成为我们日常生活中不可或缺的伴侣。无论是在通勤途中、旅行出行&#xff0c;还是在户外运动时&#xff0c;充电宝都能为我们的设备提供源源不断的电力支持。然而&#xff0c;市场上充电宝品牌众多&#xff0…

c++开发,下载安装Boost库并检测是否安装成功

c开发&#xff0c;下载安装Boost库并检测是否安装成功 系统说明下载Boost库安装测试验证 系统说明 win10系统 下载Boost库 从官方网站下载&#xff0c;点击版本号 进去后选择windows系统的下载 安装 第1步 将下载后的压缩包解压到你想存储的文件夹中&#xff0c;比如我这里…

自主身份:Web3如何重新定义个人数据所有权

随着数字时代的快速发展&#xff0c;个人数据成为了一种新型的资产&#xff0c;深刻影响着我们的生活。然而&#xff0c;在Web2时代&#xff0c;个人数据往往被科技巨头所掌控&#xff0c;用户在享受互联网服务时&#xff0c;无意中失去了对自己数据的控制权。Web3的到来&#…

Java 调整字符串,验证码生成

package text7;public class ZiFanz {public static void main(String[] args) {//1.定义两个字符串String strA "abcde";String strB "deabc";//2.abcde->bcdea->cdeab->deabc旋转字符串//旋转并比较boolean result cheak(strA, strB);System…

时间序列分析中的特征提取

一、说明 在多变量时间序列分析期间&#xff0c;数据包含随时间推移测量的多个数据。为了管理模型性能&#xff0c;建议进行特征提取&#xff0c;以使模型的数据点更加紧凑。 二、时间序列的挑战 2.1 特征提取 仅选择“信息性”特征&#xff0c;这些特征在多变量时间序列分析…