Xlua与C#相互调用,先简单分为C#调用lua代码和lua调用C#代码,由于之前项目用的华佗热更,Lua热更没有项目经验,这里只做一些浅显的个人理解,有错误还望大牛指正!!!
Xlua与C#相互调用
C#调用lua代码
1.在C#中编写lua语句
使用lua解析器编写
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class Lesson1_luaEnv : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//lua解析器对象
LuaEnv env = new LuaEnv();
//参数为字符串,执行lua语句
env.DoString("print('nihao')");
//手动清除没有释放的对象,相当于GC
//env.Tick();
//销毁lua解析器
//env.Dispose();
//执行lua脚本
env.DoString("require('Main')");
}
}
2.重定向文件
LuaEnv env = new LuaEnv();
env.AddLoader(MyCustomLoaderPath);
private byte[] MyCustomLoaderPath(ref string filePath){//todo}
addloader是一个委托,添加一个路径函数到委托里面即可
3.编写lua管理器
思路:保证lua解析器的唯一性,并且提供一些api重定向文件,使解析器按照自定义路径访问lua脚本
/// <summary>
/// lua管理器
/// 提供 lua解析器
/// </summary>
public class LuaMgr :BaseManager<LuaMgr>
{
//执行Lua语言的函数
//释放垃圾
//销毁
//重定向
private LuaEnv luaEnv;
/// <summary>
/// 得到Lua中的_G
/// </summary>
public LuaTable Global
{
get
{
return luaEnv.Global;
}
}
public void Init()
{
//已经初始化了 别初始化 直接返回
if (luaEnv != null)
return;
//初始化
luaEnv = new LuaEnv();
//加载lua脚本 重定向,先访问指定文件夹中是否有lua文件,再尝试指定AB包文件中是否有.txt文件,都没有再访问默认路径
luaEnv.AddLoader(MyCustomLoader);
luaEnv.AddLoader(MyCustomABLoader);
}
private byte[] MyCustomLoader(ref string filePath)
{
//拼接lua文件所在的路径
string path = Application.dataPath + "/LuaScripts/" + filePath + ".lua";
//如果对应的文件存在
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
}
private byte[] MyCustomABLoader(ref string filePath)
{
Debug.Log("进入AB包加载 重定向函数");
//从AB包中加载lua文件
//加载AB包
string path = Application.streamingAssetsPath + "/lua";
AssetBundle ab = AssetBundle.LoadFromFile(path);
//加载Lua文件 返回
TextAsset tx = ab.LoadAsset<TextAsset>(filePath + ".lua");
//加载Lua文件 byte数组
return tx.bytes;
}
....///其他函数,包括执行某个lua脚本函数,释放垃圾,销毁解析器等,可参考在C#中编写lua语句自行编写
4.执行lua中的存储在_G中的变量和函数
由于存储在_G表中属于全局的,直接使用lua管理器中的Global属性获取对应的全局变量或者函数
LuaMgr.GetInstance().Init();
//执行某个lua脚本
LuaMgr.GetInstance().DoLuaFile("Test");
//获取lua脚本中的变量值
var testnumber = LuaMgr.GetInstance().Global.Get<int>("testnumber");
print("testnumber:"+testnumber);
var testbool = LuaMgr.GetInstance().Global.Get<bool>("testbool");
print("testbool:" + testbool);
//这里使用set并不会更改对应lua文件中对应变量的值,可以简单理解为更改缓存的值
LuaMgr.GetInstance().Global.Set("testnumber", 22);
对应的lua文件
//Test.lua
testnumber=1
testbool=true
testFloat=1.2
testString="123"
print(testnumber,testbool,testFloat,testString)
对于执行lua文件中对应的function,可以参考路径重定向,其实就是声明一个委托变量,然后通过Global.Get添加存储在_G表中对应lua function,只要参数类型返回值类型能够对应上就ok了,只要根据这个原理,其他类型的函数都可以完成调用
[CSharpCallLua]
public delegate void CustomCall();
[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);
[CSharpCallLua]
public delegate void CustomCall5(string a, params int[] args);//变长参数的类型 是根据实际情况来定的
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main"); //Main执行require("Test")
//也可以使用UnityAction Action等委托
//甚至也可以使用Xlua提供的LuaFunction
var call=LuaMgr.GetInstance().Global.Get<CustomCall>("testfun");
call();
//多返回值
//使用 out 和 ref 来接收
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 = true;
string d1 = "";
int e1 = 0;
Debug.Log("第一个返回值:" + call4(200, ref b1, ref c1, ref d1, ref e1));
Debug.Log(b1 + "_" + c1 + "_" + d1 + "_" + e1);
//变长参数
CustomCall5 call5 = LuaMgr.GetInstance().Global.Get<CustomCall5>("testfun4");
call5("123", 1, 2, 3, 4, 5, 566, 7, 7, 8, 9, 99);
}
对应的lua文件
//Test.lua
--无参无返回
testfun=function()
print("无参无返回")
end
--多返回
testfun3=function(a)
print("多返回参数")
return a,a+1,true
end
--边长参数
testfun4=function(a,...)
print("变长参数")
print(a)
arg={...}
for K,v in pairs(arg) do
print(v)
end
end
5.C#调用lua中的"list",“Dictionary”
lua上的列表和字典本质上都是table,由于是动态语言,弱语言类型,所以使用限制很薄弱,对于我们清楚类型的lua table,我们在C#可以指定类型,对于无法确定指定类型的lua table,我们可以使用C#中的Object
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main"); //main.lua执行require("Test")
//同一类型List
List<int> list = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
Debug.Log("*******************List************************");
for (int i = 0; i < list.Count; ++i)
{
Debug.Log(list[i]);
}
//不指定类型 object
List<object> list3 = LuaMgr.GetInstance().Global.Get<List<object>>("testList2");
Debug.Log("*******************List object************************");
for (int i = 0; i < list3.Count; ++i)
{
Debug.Log(list3[i]);
}
Debug.Log("*******************Dictionary************************");
Dictionary<string, int> dic = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
foreach (string item in dic.Keys)
{
Debug.Log(item + "_" + dic[item]);
}
Debug.Log("*******************Dictionary object************************");
Dictionary<object, object> dic3 = LuaMgr.GetInstance().Global.Get<Dictionary<object, object>>("testDic2");
foreach (object item in dic3.Keys)
{
Debug.Log(item + "_" + dic3[item]);
}
}
对应的lua文件
//Test.lua
testList={1,2,3,4,5,6,7,8}
testList2={1,"1213",3,true,5,nil,7.2,8}
testDic={
["1"]=1,
["2"]=2,
["3"]=3
}
testDic2={
["1"]=1,
[true]=2,
[false]=true,
["123"]=false
}
5.类、接口、Xlua中的luatable映射lua中的table
1.类映射
声明一个类,成员对应lua中指定的table 需要注意的是,C#类成员变量名需和映射的lua文件对应table中成员变量名一致
C#类
//成员变量以及函数和lua脚本中对应数量可以不一致
[CSharpCallLua]
public class CsharpCallLua {
public int testInt;
public bool testBool;
public float testFloat;
public string testString;
public UnityAction testFun;
public CallluaInClass testInClass;
}
//Test.lua
testClass={
testInt=2,
testBool=true,
testFloat=1.2,
testString="123",
testFun=function()
print("121231")
end,
testInClass={
testInInt=99,
}
}
然后老套路,使用Global.Get实现映射
CsharpCallLua obj = new CsharpCallLua();
obj = LuaMgr.GetInstance().Global.Get<CsharpCallLua>("testClass");
Debug.Log(obj.testInt);
Debug.Log(obj.testBool);
Debug.Log(obj.testFloat);
Debug.Log(obj.testString);
Debug.Log(obj.testInClass.testInInt);
obj.testFun();
接口映射和类映射相似,无非把成员变量改为成员属性即可
LuaTable直接使用Global属性get对应lua table直接映射
LuaTable table = LuaMgr.GetInstance().Global.Get<LuaTable>("testClass");
Debug.Log(table.Get<int>("testInt"));
Debug.Log(table.Get<LuaFunction>("testFun").Call());
table.Dispose();
lua调用C#代码
为了简介文章,以下所使用的C#代码都在同一个mono文件LuaCallCsharp中
1.lua使用C#类
想要在lua脚本中直接使用unity引擎相关的,使用规则为CS.命名空间.类名
例如CS.UnityEngine.GameObject为UnityEngine.GameObject;
CS.UnityEngine.Debug为UnityEngine.Debug;
//C#测试脚本
public class test1 {
public void Speak(string str)
{
Debug.Log("test1:" + str);
}
}
namespace M
{
public class test2
{
public void Speak(string str)
{
Debug.Log("test2:" + str);
}
}
}
public class LuaCallCsharp : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
}
如果想在lua调用该Mono脚本中的test1或者test2类,使用他们的成员函数,我们首先需要动态生成一个预制体,然后挂载该脚本,最后引用命名空间类名使用对应类
--lua脚本
--类
--cs.命名空间.类名
GameObject=CS.UnityEngine.GameObject;
Debug=CS.UnityEngine.Debug;
Vector3=CS.UnityEngine.Vector3;
local obj1=GameObject();
local obj2=GameObject("测试物体");
--成员变量用. 成员方法用:
local obj4=GameObject.Find("测试物体")
Debug.Log(obj4.name);
Debug.Log(obj4.transform.position);
obj4.transform:Translate(Vector3.forward);
Debug.Log(obj4.transform.position)
local t=CS.test1()
t:Speak("说话")
local t2=CS.M.test2()
t2:Speak("吃饭")
local obj5=GameObject("LuaCallCsharp加脚本测试")
obj5:AddComponent(typeof(CS.LuaCallCsharp));
2.lua使用C# 枚举
在C#中定义枚举
public enum E_MyEnum
{
Idle,
Move,
Atk
}
lua代码如下
--枚举
PrimitiveType=CS.UnityEngine.PrimitiveType
GameObject=CS.UnityEngine.GameObject
E_MyEnum=CS.E_MyEnum
--这里是使用GameObject中的静态方法生成一个立方体,所以用.而不是:调用
local obj1=GameObject.CreatePrimitive(PrimitiveType.Cube);
local c=E_MyEnum.Idle;
print(c)
--枚举转换,可以通过索引和枚举变量中的状态名进行转换
local a=E_MyEnum.__CastFrom(1)
print(a)
local b=E_MyEnum.__CastFrom("Atk")
print(b)
3.lua使用C# 数组 list 字典
在C#定义一些数组 list 字典等类型
public class Lesson3 {
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文件中的知识点与前两课相似,无非就是新增一些lua api 不做解释
--数组 list dic
print("**************array*****************")
local obj =CS.Lesson3()
print(obj.array.Length)
--访问指定元素
print(obj.array[1])
--遍历
for i=0,obj.array.Length-1 do
print(obj.array[i])
end
--在lua中创建一个数组
local array2=CS.System.Array.CreateInstance(typeof(CS.System.Int32),11)
print(array2.Length)
print(array2[0])
print("**************LIST*****************")
obj.list:Add(2)
obj.list:Add(4)
obj.list:Add(8)
--长度
print(obj.list.Count)
print(obj.list)
for i=0,obj.list.Count-1 do
print(obj.list[i])
end
--在lua中创建list
--旧版
local list2=CS.System.Collections.Generic["List`1[System.String]"]()
print(list2)
list2:Add(2)
print(list2.Count)
--新版本 Xlua>V2.1.12
local List_string= CS.System.Collections.Generic.List(CS.System.String)
local list3=List_string()
list3:Add("5555555555")
print(list3[0])
print("**************Dictionary*****************")
obj.dic:Add(1,"你好")
print(obj.dic[1])
for k,V in pairs(obj.dic) do
print(k,v)
end
local dic_Vector3=CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic2=dic_Vector3()
dic2:Add("121",CS.UnityEngine.Vector3.right)
for k,V in pairs(dic2) do
print(k,v)
end
--特殊
print(dic2:get_Item("121"));
dic2:set_Item("123",CS.UnityEngine.Vector3.zero)
print(dic2:get_Item("121"));
4.lua使用C# ref和out的方法
首先回顾之前所学知识,成员方法在lua中使用:调用,静态方法使用.调用,拓展方法也使用冒号调用,但是拓展方法所在的类需要加上特性[XLua.LuaCallCSharp],
在C#中 ref主要是引用 out则用于多放回参数
先声明对应的函数
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 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;
}
}
从上面可以简单看出,RefFun,OutFun,RefOutFun都可以最大接收3个返回参数,因为ref需要提前初始化变量,out则返回参数
对应lua文件和打印结果去下
--ref 需要对应的占位符
--out 不需要传参 也就是不需要传占位
local L5=CS.lesson5();
local a,b,c,d=L5:RefFun(1,2,3,4);
print(a,b,c,d);
--LUA: 100 5 -3 nil
local a,b,c,d=L5:OutFun(1,4);
print(a,b,c,d);
--LUA: 200 1 4 nil
local a,b,c,d=L5:RefOutFun(1,4);
print(a,b,c,d);
-- 300,10,20,nil
--LUA: 300 10 20 nil
5.lua使用C# 中的函数重载
我们知道,C#是强类型语言,lua是弱类型语言,那么两种语言在执行函数重载时有所不同,lua本身不支持函数重载,C#支持重载,但我们是使用lua文件去调用C# 所以应该符合的规则是C# 按照道理说应该支持重载 实际上有所不同,我们仔细思考一下,使用lua文件调用C#中已经写好的函数,那么lua需要传入实参,但由于lua是弱语言,他并不能区分int float double的区别,因此在C#中 如果我们声明两个重载是int 和float类型,那么在lua中传实参就会出现问题 如果是int和string类型,则不会,如果我们非要用int和float的重载,Xlua也提供了解决方法
--Xlua提供了反射机制去解决这种问题
local m1=typeof(Lesson6):GetMethod("Calc",{typeof(CS.System.Int32)})
local m2=typeof(Lesson6):GetMethod("Calc",{typeof(CS.System.Single)})
local f1=xlua.tofunction(m1)
local f2=xlua.tofunction(m2)
--第一个参数为对象,如果是静态方法,则省略
print(f2(obj,10.2))
6.lua使用C# 中的委托和事件
其实学到这里我们基本可以知道,lua调用C#的各种知识点无非就是两种语言使用相同类型的不同约束,例如使用:调用成员方法,而C#是直接.就可以使用,重载中强弱类型的使用限制等,在C#委托中,我们给委托添加函数的时候,可以使用+= 添加函数,对于只添加一个函数的委托,我们还可以使用=,而在lua中,并不支持复合运算符,也就是+=不可以被使用,所以在lua中向委托添加函数,则只能用=,如果后续想继续添加函数,则使用A=A+B的形式添加。
对于事件,事件在类外只能使用+=去添加函数不能使用=,这直接封死了所有可以添加函数的情况,Xlua给我们提供了解决方法,将在lua中事件的使用(本来是.调用)改成了类似于成员函数的调用,并且通过传参解决添加和删除函数的问题
对于委托和时间的清空,委托直接赋值nil解决,而事件则在C#类声明事件清空函数,在lua中通过调用成员方法清空事件
public class Lesson7
{
public UnityAction del;
public event UnityAction eventAction;
public void DoEvent()
{
if (eventAction != null) eventAction();
}
public void ClearEvent()
{
if (eventAction != null) eventAction = null;
}
}
--lua代码
local obj=CS.Lesson7()
--C#类中的委托主要是添加lua中的函数的
local fun=function()
print("Lua函数fun")
end
--Lua没有复合运算符 不能+=
--第一个函数应该用C#委托中的=,往后的函数可以用A=A+B
obj.del=fun
obj.del=obj.del+fun
--也可以用类似lamda的写法
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
print("*********清空所有注册函数**********")
--添加测试
obj.del=fun
obj.del()
-------------事件------------
print("*********事件**********")
local fun2=function()
print("事件加的函数")
end
--需要把事件当成函数使用
--有点类似于成员方法的使用
obj:eventAction("+",fun2)
obj:eventAction("+",fun2)
obj:DoEvent()
--事件取消
print("*********事件取消**********")
obj:eventAction("-",fun2)
obj:DoEvent()
--事件清除
print("*********事件清除**********")
--清除事件不能直接设置为空
--原因在于C#中的事件在事件外不能直接清空
--可以在C#事件对应类里面添加方法清空事件
obj:ClearEvent();
obj:DoEvent();
7.lua使用C# 调用二维数组
使用数组的成员方法调用
public class lesson8 {
public int[,] array = new int[2, 3] { {1,2,3 }, {9,9,9} };
}
print("***************lua调用C# 二维数组*************")
local obj=CS.lesson8()
--获取二维数组长度
print("行:"..obj.array:GetLength(0))
print("列:"..obj.array:GetLength(1))
--获取某一个元素,以下两种都不对,基于C#习惯可能会这样用
--但lua并不支持这两种方式访问数组
--print(obj.array[0,0])
--print(obj.array[0][0])
--使用C#数组提供的访问数组的成员方法访问
print(obj.array:GetValue(0,0))
--遍历二维数组
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
8.lua中的nil 和C#中的Null比较
注释写的比较清楚,也不是很难,不做过多解释
print("***************lua调用C# C#中的Null和Lua中nil比较*************")
--需求:往场景上物体添加一个脚本,如果存在就不在,不存在脚本就加
GameObject=CS.UnityEngine.GameObject;
Rigidbody=CS.UnityEngine.Rigidbody;
lesson9=CS.lesson9;
local obj=GameObject("测试加刚体物体")
local rig=obj:GetComponent(typeof(Rigidbody));
--rig是一个C#的对象 无法与lua中的空 也就是nil比较
--如果要比较 有以下几种方法
--1.使用C#对象中的Equals(nil)比较
--rig:Equals(nil)
--2.声明一个全局方法,使得可以判空
--例如ISNull(obj)
--3.可以在C#声明一个判空的扩展方法 前提对象是继承Object类的
-- if rig:Equals(nil) then
-- print("需要加刚体")
-- obj:AddComponent(typeof(Rigidbody))
-- end
-- if ISNull(rig) then
-- print("需要加刚体")
-- obj:AddComponent(typeof(Rigidbody))
-- end
if rig:isNull() then
print("需要加刚体")
obj:AddComponent(typeof(Rigidbody))
end
9.lua调用C#中的协程
注释写的也比较清楚,不做过多解释,这里只做一小部分解释,util=require(“util”)可以是Xlua中util.lua文件在当前lua脚本的同级目录下,即同个文件夹下,也可以是添加默认路径中的指定路径,即通过修改lua的package.path路径,在重定向文件路径搜索不到文件时,搜索默认的指定路径,这样就可以不用拖动XLua中util.lua的位置,这两种做法均可
print("*************lua 调用C#协程************")
--Xlua提供的工具表
util=require("util")
GameObject =CS.UnityEngine.GameObject
WaitForSeconds=CS.UnityEngine.WaitForSeconds
local obj=GameObject("Test Coroutine")
local mono =obj:AddComponent(typeof(CS.LuaCallCsharp))
--希望被开启的协程函数
fun=function()
local a=1;
while true do
--不能直接使用C#中的yield return
--使用lua中的协程返回
coroutine.yield(WaitForSeconds(1))
print(a)
a=a+1
if a>10 then
mono:StopCoroutine(b)
end
end
end
--不能直接使用C#中的协程开启方法
-- mono:StartCoroutine(fun)
--使用Xlua提供的工具表
b=mono:StartCoroutine(util.cs_generator(fun))
10.lua调用C# 给系统变量加特性
当我们需要在lua中调用C#的某些事件,但该事件无法被添加[XLua.CSharpCallLua]特性,可以使用如下方法去添加特性
[XLua.CSharpCallLua]
public static List<Type> lu = new List<Type>()
{
typeof(UnityAction<float>)
};
11.lua调用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("有参有约束的函数,但参数不是类");
}
}
print("*********泛型函数*********")
local lesson12=CS.lesson12;
local obj=lesson12();
local father=lesson12.father();
local child=lesson12.child();
--lua仅支持有参数有约束的泛型函数
obj:TestFun1(father,child);
obj:TestFun1(child,father);
-- --lua不支持没有约束的泛型函数
-- obj:TestFun2(child)
-- obj:TestFun2(father)
-- --lua不支持无参数有约束的函数
-- obj:TestFun3()
-- --lua不支持非类的约束
-- obj:TestFun4(child)
--用XLua提供的方法有一些约束
--如果打包是以Mono打包 则都可以使用
--如果打包是以il2cpp 引用类型都可以使用
--值类型 除非C#已经调用过同类型的泛型参数,否则无法使用
--Xlua提供了一些方法,让不支持的泛型支持
--1.得到通用函数
local testfun2=xlua.get_generic_method(CS.lesson12,"TestFun2")
--2.设置泛型类型再使用
local testfun2_T=testfun2(CS.System.Object)
--调用
testfun2_T(obj,child)
local testfun3=xlua.get_generic_method(CS.lesson12,"TestFun3")
local testfun3_T=testfun3(CS.lesson12.father)
testfun3_T(obj)
local testfun4=xlua.get_generic_method(CS.lesson12,"TestFun4")
local testfun4_T=testfun4(CS.lesson12.ITest)
testfun4_T(obj,child)