Bootstrap

Lua调用C#

目录

创建C#入口

Lua调用类

 Lua调用枚举

 Lua调用数组,列表,字典

 Lua调用C#拓展方法

Lua调用C#Ref与Out知识

Lua调用C#函数重载

Lua调用C#委托与事件

Lua调用C#二维数组

Lua调用C#中nil与null的差距

Lua调用C#中让系类型与lua能够互相访问

Lua调用C#协程

Lua调用C#泛型


创建C#入口

首先我们新建一个C#脚本,取名为Main,因为Lua中没有办法直接访问C#,一定是先从C#中调用lua再将核心逻辑让lua来编写

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

}

Lua调用类

创建调用类的脚本,然后再Main的脚本中调用

require("Lesson1CallClass")

接着来编写调用类的lua脚本

print("----------------------Lua调用C#类相关知识---------------------")

--lua中使用C#的类
--固定格式:CS.命名空间.类名
--Unity的类 比如 GameObject Transform等—— CS.UnityEngine.类名
--CS.UnityEngine.GameObject

--通过C#中的类实例化一个对象
--lua中没有new,直接类名括括号就是实例化对象
--默认调用的,相当于就是无参构造
local obj1=CS.UnityEngine.GameObject()
local obj2=CS.UnityEngine.GameObject("xxx")

--为了方便使用与节约性能,定义全局变量存储C#的类
GameObject=CS.UnityEngine.GameObject
local obj3=GameObject("yyyyy")

--类中的静态对象可以直接使用.来调用
local obj4=GameObject.Find("xxx")

--得到其中的成员变量也是直接.对象
print(obj4.transform.position)
CS.UnityEngine.Debug.Log(obj4.transform.position)

--使用对象中的成员方法,要加:调用
Vector3=CS.UnityEngine.Vector3
obj4.transform:Translate(Vector3.right)
CS.UnityEngine.Debug.Log(obj4.transform.position)


运行出现为:1.场景中出现空物体 2.打印信息 3.位置坐标发生修改

 刚才调用的都是U3d中自带的类,现在我们尝试C#自定义类

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

namespace tx
{
    public class Test2
    {
        public void Speak(string str)
        {
            Debug.Log("Test2"+str);
        }
    }
}
--调用没有在命名空间中的自定义类
local t1=CS.Test()
t1:Speak("11111111")

--调用在命名空间中的自定义类
local t2=CS.tx.Test2()
t2:Speak("2222222")

继承Mono的类:

--继承Mono的类,是不能直接new的
local obj5=GameObject("加脚本Test")
--通过GameObject的AddComponent添加脚本,由于lua中不支持无参泛型函数,我们使用另一个重载
--xlua提供一个方法typeof 得到类的type
obj5:AddComponent(typeof(CS.LuaCallCSharp))

运行可以看到

 Lua调用枚举

新建一个lua脚本在Main中调用,然后写

--枚举调用
--调用Unity中的枚举
--枚举调用规则与调用类相似:CS.命名空间.枚举名.枚举成员

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

local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)

这样在场景中就会出现一个Cube

如果是自定义脚本,在C#中

public enum myem
{
    Idel,
    Move,
    Jump,
    Atk
}

然后在lua中我们调用并进行枚举的一系列转换,利用.__CastFrom(),可以看到

--自定义枚举
myem=CS.myem
local x=myem.Idel
print(x)

--枚举转换相关rgs
--数值转枚举
local a=myem.__CastFrom(1)
print(a)
--字符串转枚举
local b=myem.__CastFrom("Atk")
print(b)

 Lua调用数组,列表,字典

数组,列表,字典在lua中的使用都遵循C#的规则,我们先在C#中创建

    public int[]  arr=new int[5] { 1, 2, 3 ,7,6};

    public List<int> list=new List<int>();

    public Dictionary<int,string> dic = new Dictionary<int,string>();

 对于数组,在lua中长度的获取,元素遍历如下:

local obj =CS.Lesson3()

--长度获取,C#怎么用,lua怎么用
print(obj.arr.Length)
--访问元素,虽然lua中数组从1开始,但是访问的数组是C#的规则,从0开始
print(obj.arr[1])
--遍历
--最大值一定也减一
for i=0,obj.arr.Length-1 do
	print(obj.arr[i])
end

lua中创建一个C#的数组,可以用表表示list与数组

--创建C#中的数组使用Array类中的静态方法
local arr2=CS.System.Array.CreateInstance(typeof(CS.System.Int32),10)
print(arr2.length)
print(arr2[1])

 同理在lua中向表中添加元素,遍历列表

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

在lua中创建一个list对象

--相当于得到了List<string>的一个类别名,需要再实例化
local list_String=CS.System.Collections.Generic.List(CS.System.String)
local list3=list_String()
list3:Add(222222222222222)
print(list3[0])

在lua中向字典中添加元素,遍历字典

--使用与C#一致
obj.dic:Add(1,"xxx")
print(obj.dic[1])
--遍历
for k,v in pairs(obj.dic) 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("124",CS.UnityEngine.Vector3.right)
for k,v in pairs(dic2) do
	print(k,v)
end
--要通过下面方法来获取
print(dic2:get_Item("124"))
--改变
dic2:set_Item("124",676)
print(dic2:get_Item("124"))

 Lua调用C#拓展方法

我们先在C#中新建一个类,并写出对应的拓展方法

如果要在lua中使用拓展方法,要在类前面加上特性[LuaCallCSharp] (建议lua中要使用的类都加上该特性,可以提升性能,因为lua是通过反射的机制调用C的类,效率低)

[LuaCallCSharp]
public static class Tools
{
    //Lesson4的拓展方法
    public static void Move(this Lesson4 obj)
    {
        Debug.Log(obj.name + "移动");
    }
}
public class Lesson4
{
    public string name = "cc";
    public  void Speak(string str)
    {
        Debug.Log(str);
    }

    public static void Eat()
    {
        Debug.Log("eat");
    }
}

之后在lua中调用

Lesson4=CS.Lesson4
--使用静态方法
--CS.命名空间.类名.静态方法()
Lesson4.Eat()

--成员方法 实例化出来用
local  obj=Lesson4()
--成员方法调用用冒号:
obj:Speak("hhhhhhhhhh")

--使用拓展方法
obj:Move()

Lua调用C#Ref与Out知识

在C#中定义三个方法

    public int RefFun(int a,ref int b,ref int c,int d)
    {
        b = a + d;
        c = a - d;
        return 100;
    }
    public int OutFun(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;
    }

在lua中访问他们

Lesson5=CS.Lesson5
local obj = Lesson5()

对于ref, 会以多返回值形式返回给lua,如果函数存在返回值,那么第一个值就是该返回值,之后的返回值就是ref的结果。ref参数需要传一个默认值占位置,如果不传会默认传0

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

对于out, 会以多返回值形式返回给lua,如果函数存在返回值,那么第一个值就是该返回值,之后的返回值就是out的结果,Out参数不需要传一个默认值占位置

local a,b,c=obj:OutFun(15,78)
print(a)
print(b)
print(c)

如果我们混用,则是结合两个的规则 

local a,b,c=obj:RefOutFun(10,5)

Lua调用C#函数重载

先在C#中写出重载函数

    public  int fun1()
    {
        return 111;
    }

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

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

    public float fun1( float b)
    {
        return  b;
    }

虽然lua自己不支持写重载函数,但是支持调用C#中的重载函数

local obj = CS.Lesson6()

print(obj:fun1())
print(obj:fun1(45,45))

由于lua中数值类型只有Number,对C#中多精度的重载函数支持不好,在使用时,可能会出现一些问题,如:

print(obj:fun1(45))
print(obj:fun1(4.45))

 解决方法:通过反射,但是效率低,尽量不用

--得到指定函数的相关信息
local m1=typeof(CS.Lesson6):GetMethod("fun1",{typeof(CS.System.Int32)})
local m2=typeof(CS.Lesson6):GetMethod("fun1",{typeof(CS.System.Single)})
--通过xlua提供的方法,将其转成lua函数使用
--一般转一次,然后重复使用
local f1=xlua.tofunction(m1)
local f2=xlua.tofunction(m2)

--成员方法第一个参数传对象,静态方法不用
print(f1(obj,10))
print(f2(obj,10.2))

Lua调用C#委托与事件

先在C#中定义委托与事件

public class Lesson7
{
    //申明
    public UnityAction del;

    public event UnityAction evAction;

    public void DoEvent()
    {
        if (evAction != null)
        {
            evAction();
        }
    }
}

委托在lua中与在C#中差不多,但是如果第一次往委托中加函数,会是nil,不能直接+,要先=


local obj = CS.Lesson7()

--委托用于装函数,执行C#中的委托就是用来装lua函数的
local fun=function ()
	print("Lua函数Fun")
end
--lua中有复合运算符,不能+=
--如果第一次往委托中加函数,会是nil,不能直接+
--obj.del=obj.del+fun
obj.del=fun
--第二次
obj.del=obj.del+fun

obj.del=obj.del+function ( )
	print("临时申明")--但是不建议
end
--委托执行
obj.del()
print("------------------------")
--obj.del=obj.del-fun
--obj.del=obj.del-fun
obj.del()

--清空所有存储的函数
obj.del=nil

obj.del=fun

obj.del()

事件在C#中与lua中差别很大,使用类似于成员方法

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

--事件+-函数与委托不一样,类似使用成员方法
obj:evAction("+",fun2)
obj:evAction("+",function ( )
	print("临时申明")--但是不建议
end)

obj:DoEvent()

obj:evAction("-",fun2)
obj:DoEvent()
--清事件不能直接nil,只能在C#中加一个方法然后调用

Lua调用C#二维数组

public int[,] arr = new int[2, 3] { { 1, 2, 3 }, { 3, 4, 9 } };

在lua中调用,获取长度,元素以及遍历

local obj=CS.Lesson8()

--获取长度
print("行"..obj.arr:GetLength(0))
print("列"..obj.arr:GetLength(1))

--获取元素
print(obj.arr:GetValue(0,0))
print(obj.arr:GetValue(1,2))

--遍历数组
for i=0,obj.arr:GetLength(0)-1 do
	for j=0,obj.arr:GetLength(1)-1 do
		print(obj.arr:GetValue(i,j))
	end
end

Lua调用C#中nil与null的差距

nil 与null是没办法进行==比较的,假设往场景对象上加一个脚本,如果存在就不加,如果不存在再加,解决方法如下:

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)

此时打印如下:

 方法1:

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

 方法2:

先在Main中写判空全局函数
function IsNull( obj )
	if obj==nil or obj:Equals(nil) then
		return true
	end
	return false
end

在lua中:
if IsNull(rig) then
	rig=obj:AddComponent(typeof(Rigidbody))
end
print(rig)

方法3:在C#中写一个拓展方法在lua中调用

[LuaCallCSharp]
public static class Lesson9
{
    public static bool IsNUll(this Object obj)
    {
        return obj==null;
    }
}
if rig:IsNUll() then
	rig=obj:AddComponent(typeof(Rigidbody))
end
print(rig)

Lua调用C#中让系类型与lua能够互相访问

在前面我们学习了两个特性[CSharpCallLua]和[LuaCallCSharp],其中[CSharpCallLua]主要是用于接口与委托,[LuaCallCSharp]用在拓展方法前面,也可以在每个被Lua调用的类都加,这样可以提升性能。但是对于一些系统类是无法进行修改的。比如我们要调用UI中Slider,我们在场景中创建一个Slider,然后在Lua中写

GameObject=CS.UnityEngine.GameObject

UI=CS.UnityEngine.UI

local slider=GameObject.Find("Slider")
print(slider)

local sliderSp=slider:GetComponent(typeof(UI.Slider))
print(sliderSp)
sliderSp.onValueChanged:AddListener(function ( f )
	print(f)
end)

这样前面两个能够正常打印但是第三个会报错。此时就需要我们在前面加上[CSharpCallLua],我们可以新建一个静态类,使用 XLua进行 C# 和 Lua 的交互配置,可以把所有特性汇总到这里。注意在保存后需要重新生成代码

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

    [LuaCallCSharp]
    public static List<Type> luacallcsharp = new List<Type>() {
        typeof(GameObject),
        typeof(Rigidbody)
        };
}

Lua调用C#协程

在lua中调用协程与C#中一样

--C#中协程启动通过继承mono类,通过里面的启动函数StartCoroutine
GameObject=CS.UnityEngine.GameObject
WaitForSeconds=CS.UnityEngine.WaitForSeconds
--在场景中新建空物体并挂载脚本,脚本继承mono使用开启协程
local obj = GameObject("Coroutine")
local mono=obj:AddComponent(typeof(CS.LuaCallCSharp))

fun=function (  )
	local a=1
	while true do
		--lua中不能直接使用C#中的yield return ,使用lua中的协程返回
		coroutine.yield(WaitForSeconds(1))
		print(a)
		a=a+1
	end
end

mono:StartCoroutine(fun)

此时在u3d中执行会报错 我们不能直接将lua函数传入开启到协程中

 可以采用xlua中的util工具表,这样才能正常启动协程

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

 停止协程可以在判断条件里面加如

fun=function (  )
	local a=1
	while true do
		--lua中不能直接使用C#中的yield return ,使用lua中的协程返回
		coroutine.yield(WaitForSeconds(1))
		print(a)
		a=a+1
		--关闭协程,与C#一样
		if a>10 then
			mono:StopCoroutine(b)
		end
	end
end

Lua调用C#泛型

先在C#中准备好多种泛型函数

public class Lesson12
{
    public interface Itest
    {

    }
    public class Father
    {

    }
    public class Child:Father, Itest
    {

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

    public void testFun2<T>(T a)
    {
        Debug.Log("有参数无约束的泛型方法");
    }
    public void testFun3<T>( ) where T : Father
    {
        Debug.Log("无参数有约束的泛型方法");
    }
    public void testFun4<T>(T a) where T :  Itest
    {
        Debug.Log("有参数有约束的泛型方法 ,约束不是类是接口");
    }
}

然后逐个在lua中调用看看是否可用

local  obj=CS.Lesson12() 

local child=CS.Lesson12.Child()
local father=CS.Lesson12.Father()

--支持有参数有约束的泛型方法
obj:testFun1(child,father)
obj:testFun1(father,child)

--不支持无约束的泛型方法
--obj:testFun2(child)

--不支持有约束无参数的泛型方法
--obj:testFun3()

--不支持非class的泛型方法
--obj:testFun4(child)

如果想要不支持的泛型函数被调用,可以使用下面方法:

--解决方法:得到通用函数,设置泛型类型再使用

--xlua.get_generic_method(类,"函数名")
local test2=xlua.get_generic_method(CS.Lesson12,"testFun2")
local test2_R=test2(CS.System.Int32)
--调用,成员方法第一个参数传调用函数的对象;静态方法不用传
test2_R(obj,1)

但是有一定限制:如果使用mono进行打包则支持使用,如果使用IL2CPP打包,则要求泛型类型为引用类型才能使用、如果为值类型,则需要C#已经调用过同类型的泛型lua中才能使用

;