目录
xLua导入
打包工具导入
单例基类导入与AB包管理器导入
Lua解析器
文件加载与重定向
Lua解析器管理器
全局变量获取
全局函数获取
对于无参数无返回值
对于有参数有返回值
对于多返回值
对于变长参数
完整代码
List与Dictionary映射Table
类映射Table
接口映射Table
LuaTable映射Table
xLua导入
在github上搜索xLua,选择第一个下载,进入master的Assets里面导入
导入后可以在工具栏中看到XLua,这样导入就完成了。
打包工具导入
这里上篇中有讲,可以看一下 https://blog.csdn.net/sara_shengxin/article/details/144596428?spm=1001.2014.3001.5502
单例基类导入与AB包管理器导入
获取链接http://通过网盘分享的文件:Base.rar 链接: https://pan.baidu.com/s/1-WpLVyrRFowVAs1AudrbdQ?pwd=raux 提取码: raux
AB包管理器导入,在上一篇中最终的代码复制过来就可以ABMgr
Lua解析器
构建一个lua解析器,使得能够在unity中执行lua语言,提供的方法主要有:DoString(),Tick(),Dispose().此时调用lua脚本我们要将他放在Resources文件夹里面,且要修改lua脚本的后缀为.txt
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//引入命名空间
using XLua;
public class Lesson1LuaEnv : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//lua解析器 能够在Unity中执行lua语言
LuaEnv env= new LuaEnv();
//执行Lua语言
env.DoString("print('nihao')");
//执行一个lua脚本 ,利用lua中的多脚本执行require
env.DoString("require('Main')");//默认脚本加载路径在resource中并且后缀为.txt
//帮助清除Lua中我们没有手动清除的对象,垃圾回收
//帧更新中定时执行 或者切场景的时候执行
env.Tick();
//销毁Lua解析器
env.Dispose();
}
// Update is called once per frame
void Update()
{
}
}
在lua的Main脚本中,我们这样写
print("主lua脚本启动")
文件加载与重定向
上面所说的加载lua脚本的方法虽然可行,但是比较麻烦。我们还可以利用XLua中提供的一个重定向的方法AddLoader(),允许我们自定义加载lua的方法
using System.IO;
using UnityEngine;
using XLua;
public class Lesson2Loader : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaEnv env = new LuaEnv();
//xlua提供一个路径重定向的方法
//允许我们自定义加载lua文件的规则
//当我们执行lua语言 require时,相当于执行一个lua脚本
//他会执行我们自定义传入的这个函数
env.AddLoader(MyCustomLoader);
env.DoString("require('Main')");
}
// Update is called once per frame
void Update()
{
}
//自动执行
private byte[] MyCustomLoader(ref string filepath)
{
//通过函数中的逻辑加载lua文件
//传入的参数是require执行lua脚本文件名
//拼接一个lua文件所在的路径
string path =Application.dataPath+"/LUA/"+filepath+".lua";
Debug.Log(path);
//有路径就去加载文件
//File知识点 C#提供的文件读写的类
//判断文件是否存在
if(File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("文件不存在"+filepath);
}
return null;
}
}
使用require("lua脚本")的方法会先去自定义的函数里面找文件,如果找到则返回,找不到会在默认路径里面寻找
Lua解析器管理器
有了上面的经验,我们可以创建一个lua管理器,为C#解析lua提供一些通用的方法
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Rendering.VirtualTexturing;
using XLua;
/// <summary>
/// lua管理器
/// 提供lua解析器
/// 保证解析器唯一性
/// 提供通用的方法
/// </summary>
public class LuaMgr:BaseManager<LuaMgr>
{
//执行lua语言的函数
//释放垃圾
//重定向
//销毁
private LuaEnv luaenv;
/// <summary>
/// 得到lua中的_G
/// </summary>
public LuaTable Global
{
get
{
return luaenv.Global;
}
}
/// <summary>
/// 初始化
/// </summary>
public void Init()
{
if (luaenv != null)
return;
luaenv = new LuaEnv();
//重定向
luaenv.AddLoader(MyCustomLoader);
luaenv.AddLoader(MyCustomLoaderAB);
}
//自动执行
private byte[] MyCustomLoader(ref string filepath)
{
//通过函数中的逻辑加载lua文件
//传入的参数是require执行lua脚本文件名
//拼接一个lua文件所在的路径
string path = Application.dataPath + "/LUA/" + filepath + ".lua";
//有路径就去加载文件
//File知识点 C#提供的文件读写的类
//判断文件是否存在
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("文件不存在" + filepath);
}
return null;
}
//Lua脚本会放在AB包中
//最终我们会通过加载AB包再加载其中的Lua脚本资源来执行
//AB包中如果要加载文本,后缀还是会有限制 .lua不能被识别
//打包时还是要把lua文件后缀改为txt
//重定向加载AB中的lua脚本
private byte[] MyCustomLoaderAB(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;
//通过AB包管理器加载的lua脚本
TextAsset lua =ABgr.GetInstance().LoadRes<TextAsset>("LUA", filepath + ".lua");
if (lua != null)
{
return lua.bytes;
}
else
{
Debug.Log("重定向失败"+filepath);
}
return null;
}
/// <summary>
/// 传入lua文件名,执行lua脚本
/// </summary>
/// <param name="filename"></param>
public void DoLuaFile(string filename)
{
string s = string.Format("require('{0}')", filename);
DoString(s);
}
/// <summary>
/// 执行lua语言
/// </summary>
/// <param name="s"></param>
public void DoString(string s)
{
if(luaenv == null)
{
Debug.Log("解析器未初始化");
return;
}
luaenv.DoString(s);
}
/// <summary>
/// 释放垃圾
/// </summary>
public void Tick()
{
if (luaenv == null)
{
Debug.Log("解析器未初始化");
return;
}
luaenv.Tick();
}
/// <summary>
/// 销毁
/// </summary>
public void Dispose()
{
if (luaenv == null)
{
Debug.Log("解析器未初始化");
return;
}
luaenv.Dispose();
luaenv= null;
}
}
全局变量获取
我们修改lua中Main脚本的内容
print("主lua脚本启动")
--Unity中写lua执行
--xlua帮我们处理
--只要是执行lua脚本都会自动进入我们的重定向函数中找文件
require("Test")
然后我们在test脚本中声明一些变量,必须是全局变量,因为通过C#不能获取Lua中的本地变量
testnum=1
testbool=true
testfloat=11.8
teststring="xxxx"
然后我们通过Global属性,利用Get获取,如果我们要修改值的内容,利用Set进行修改。如果直接修改是不能改变lua中的内容的,因为是浅拷贝
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson4CallVarlable : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
//使用lua解析器中的luaenv中的Global属性
//获取
int i=LuaMgr.GetInstance().Global.Get<int>("testnum");
Debug.Log(i);
//该值
LuaMgr.GetInstance().Global.Set("testnum",100);
int i2 = LuaMgr.GetInstance().Global.Get<int>("testnum");
Debug.Log(i2);
//不能获取lua中的本地局部变量
//int test=LuaMgr.GetInstance().Global.Get<int>("test");
//Debug.Log(test);
}
// Update is called once per frame
void Update()
{
}
}
全局函数获取
在获取全局函数时,我们有四种类型的函数:无参数无返回值,有参数有返回值,多返回值,变长参数。
对于无参数无返回值
我们有四种方式获取:
//声明一个无参数无返回值的委托
public delegate void CallNoReturn();
//start中调用
CallNoReturn callNoReturn = LuaMgr.GetInstance().Global.Get<CallNoReturn>("testFun");
callNoReturn();
//Unity自带的委托(无返回值)
UnityAction ua= LuaMgr.GetInstance().Global.Get<UnityAction>("testFun");
ua();
//C#的委托(无返回值)
Action action = LuaMgr.GetInstance().Global.Get<Action>("testFun");
action();
//xLua 提供的一种获取函数的方式(尽量少用,会产生一些垃圾)
LuaFunction lf= LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun");
lf.Call();
--无参无返回
testFun=function ( )
print("无参数无返回")
end
对于有参数有返回值
也是类似的:其中要注意的时,由于必须添加[CSharpCallLua],因此要在Xlua中先清除生成代码,再生成代码,然后再运行
/声明一个有参数有返回值的委托
//该特性是在xlua命名空间中的
[CSharpCallLua]
public delegate int CallHaveReturn(int a);
//start中调用
CallHaveReturn callHaveReturn = LuaMgr.GetInstance().Global.Get<CallHaveReturn>("testFunc");
Debug.Log("有参有返回"+callHaveReturn(8));
Func<int,int> func = LuaMgr.GetInstance().Global.Get<Func<int, int>>("testFunc");
Debug.Log(func(5));
LuaFunction lf1 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFunc");
Debug.Log(lf1.Call(19)[0]);//一个返回值返回数组第0个
--有参有返回
testFunc=function ( a)
print("有参数有返回值")
return a+4
end
对于多返回值
我们使用out或者ref来接收,他们两个类似,区别为使用out不需要初始化,使用ref需要为他初始化
同样保存后也需要在XLua中重新生成代码再运行
我们还可以使用LuaFunction,使用数组来存他的多个返回值然后再遍历输出
//声明一个多返回值的委托 用out
[CSharpCallLua]
public delegate int CallMutReturn(int a, out int b, out bool c, out float d);
//声明一个多返回值的委托 用ref
[CSharpCallLua]
public delegate int CallMutReturn1(int a, ref int b, ref bool c, ref float d);
CallMutReturn callMutReturn = LuaMgr.GetInstance().Global.Get<CallMutReturn>("testFunc3");
int b;
bool c;
float d;
Debug.Log("第一个返回值" + callMutReturn(123,out b,out c,out d));
Debug.Log(b+"_"+c+"_"+d);
CallMutReturn1 callMutReturn1 = LuaMgr.GetInstance().Global.Get<CallMutReturn1>("testFunc3");
int b1=0;
bool c1=false;
float d1=0.0f;
Debug.Log("第一个返回值" + callMutReturn1(100, ref b1, ref c1, ref d1));
Debug.Log(b1 + "_" + c1 + "_" + d1);
//使用luaFunction
LuaFunction lf2 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFunc3");
object[] obj = lf2.Call(10);
for(int i = 0; i < obj.Length; i++)
{
Debug.Log("第"+i+"个返回值是"+obj[i]);
}
对于变长参数
我们使用自定义的一个委托,变长参数我们使用params,如果能确定是同一类型,后面可以使用该类型,如果有各种类型就使用object
//声明一个变长参数的委托
[CSharpCallLua]
public delegate void CallChange(int a,params object[] obj);
CallChange callChange = LuaMgr.GetInstance().Global.Get<CallChange>("testFunc4");
callChange(3,false,"jwhdkh",7.666,658);
LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFunc4");
lf3.Call(2, false, "jwhdkh", 7.666, 658);
--变长参数
testFunc4=function ( a,... )
print("变长参数")
print(a)
arg={...}
for k,v in pairs(arg) do
print(k,v)
end
end
完整代码
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Events;
using XLua;
//声明一个无参数无返回值的委托
public delegate void CallNoReturn();
//声明一个有参数有返回值的委托
//该特性是在xlua命名空间中的
[CSharpCallLua]
public delegate int CallHaveReturn(int a);
//声明一个多返回值的委托 用out
[CSharpCallLua]
public delegate int CallMutReturn(int a, out int b, out bool c, out float d);
//声明一个多返回值的委托 用ref
[CSharpCallLua]
public delegate int CallMutReturn1(int a, ref int b, ref bool c, ref float d);
//声明一个变长参数的委托
[CSharpCallLua]
public delegate void CallChange(int a,params object[] obj);//如果lua中后面的变长数组全是int或者是一种类型,那么可以就写params int[] b或者params string[] c
public class Lesson5CallFunction : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
无参无返回的获取
//委托
CallNoReturn callNoReturn = LuaMgr.GetInstance().Global.Get<CallNoReturn>("testFun");
callNoReturn();
//Unity自带的委托(无返回值)
UnityAction ua= LuaMgr.GetInstance().Global.Get<UnityAction>("testFun");
ua();
//C#的委托(无返回值)
Action action = LuaMgr.GetInstance().Global.Get<Action>("testFun");
action();
//xLua 提供的一种获取函数的方式(尽量少用,会产生一些垃圾)
LuaFunction lf= LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun");
lf.Call();
//有参有返回
CallHaveReturn callHaveReturn = LuaMgr.GetInstance().Global.Get<CallHaveReturn>("testFunc");
Debug.Log("有参有返回"+callHaveReturn(8));
Func<int,int> func = LuaMgr.GetInstance().Global.Get<Func<int, int>>("testFunc");
Debug.Log(func(5));
LuaFunction lf1 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFunc");
Debug.Log(lf1.Call(19)[0]);//一个返回值返回数组第0个
///多返回值
//使用out与ref来接收(out接收外面的值不需要初始化,ref需要初始化)
CallMutReturn callMutReturn = LuaMgr.GetInstance().Global.Get<CallMutReturn>("testFunc3");
int b;
bool c;
float d;
Debug.Log("第一个返回值" + callMutReturn(123,out b,out c,out d));
Debug.Log(b+"_"+c+"_"+d);
CallMutReturn1 callMutReturn1 = LuaMgr.GetInstance().Global.Get<CallMutReturn1>("testFunc3");
int b1=0;
bool c1=false;
float d1=0.0f;
Debug.Log("第一个返回值" + callMutReturn1(100, ref b1, ref c1, ref d1));
Debug.Log(b1 + "_" + c1 + "_" + d1);
//使用luaFunction
LuaFunction lf2 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFunc3");
object[] obj = lf2.Call(10);
for(int i = 0; i < obj.Length; i++)
{
Debug.Log("第"+i+"个返回值是"+obj[i]);
}
变长参数
CallChange callChange = LuaMgr.GetInstance().Global.Get<CallChange>("testFunc4");
callChange(3,false,"jwhdkh",7.666,658);
LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFunc4");
lf3.Call(2, false, "jwhdkh", 7.666, 658);
}
}
List与Dictionary映射Table
我们在lua中分别创建一个同类型与不同类型的列表与字典,列表一般用来没有自定义索引的表,如果确定类型就用指定类型,如果不确定就用object。字典一般是用来有自定义索引的表,如果确定类型就用指定类型,如果不确定就用object。
--List
testlist ={1,2,3,4,5,6}
testlist2={"vv",4,34,true,4.68}
--Dictionary
testDic={
["1"]=1,
["2"]=2,
["3"]=3,
["4"]=4,
["5"]=5
}
testDic2={
["1"]=1,
[true]=2,
[false]=true,
["4"]=4.5,
["5"]=false
}
注意:遍历列表时用for,遍历字典时我们使用的时foreach。并且无论是字典还是列表,都为浅拷贝。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson6CallListDic : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
//同一类型 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]);
}
//为值拷贝 浅拷贝,不会改变lua中的值
//list[0] = 100;
//List<int> list2 = LuaMgr.GetInstance().Global.Get<List<int>>("testlist");
//for (int i = 0; i < list.Count; i++)
//{
// Debug.Log(list2[i]);
//}
//不指定类型 List
List<object> list3= LuaMgr.GetInstance().Global.Get<List<object>>("testlist2");
for (int i = 0; i < list3.Count; i++)
{
Debug.Log(list3[i]);
}
Debug.Log("*********************Dic**********************");
//同一类型 Dic
Dictionary<string,int> dic1= LuaMgr.GetInstance().Global.Get<Dictionary<string,int>>("testDic");
foreach(string item in dic1.Keys)
{
Debug.Log(item + "_" + dic1[item]);
}
//若直接修改值,也为浅拷贝
//不同类型 Dic
Dictionary<object, object> dic2 = LuaMgr.GetInstance().Global.Get<Dictionary<object, object>>("testDic2");
foreach (object item in dic2.Keys)
{
Debug.Log(item + "_" + dic2[item]);
}
}
// Update is called once per frame
void Update()
{
}
}
类映射Table
lua中没有类,用表自定义类
testClass={
testint=5,
testbool=true,
testfloat=4.5,
teststring="xnbsa",
testFun=function ( )
print("243566764")
end
testInClass={
testInInt=66,
testInString=("*****************这是一个嵌套表******************")
}
}
在C#中,我们也定义一个类,其中声明的成员变量名字要和lua中一致,数量可以多也可以少。类中的嵌套在C#上的表现也是再声明一个类,其中的成员变量与函数名也要相同。
其中,在类中的拷贝为深拷贝,在C#中修改值会同步修改lua中的值
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class CallLuaClass
{
//在类中声明成员变量 名字要和lua中的一样
//数量可以比lua中多也可以更少
public int testint;
public float testfloat;
public bool testbool;
public string teststring;
public UnityAction testFun;
public CallLuaInClass testInClass;
}
public class CallLuaInClass
{
public int testInInt;
public string testInString;
}
public class Lesson7CallClass : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
CallLuaClass obj = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
Debug.Log(obj.testint);
Debug.Log(obj.testfloat);
Debug.Log(obj.testbool);
//Debug.Log(obj.teststring);
//obj.testFun();
Debug.Log(obj.testInClass.testInInt);
Debug.Log(obj.testInClass.testInString);
//浅拷贝
//obj.testint = 999;
//CallLuaClass obj1 = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testClass");
//Debug.Log(obj1.testint);
}
// Update is called once per frame
void Update()
{
}
}
接口映射Table
lua中的脚本我们继续使用类的代码
testClass={
testint=5,
testbool=true,
testfloat=4.5,
teststring="xnbsa",
testFun=function ( )
print("243566764")
end
}
但是在C#中,接口是不允许有成员变量的,我们要使用属性来接收lua中的变量。
由于我们要使用[CSharpCallLua],在每一次对接口进行修改后我们都需要在XLua中进行清空然后生成代码再运行。
接口也可以有嵌套,嵌套方式与类中相似
注意的是接口中的拷贝也为深拷贝
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XLua;
[CSharpCallLua]
//接口中是不允许有成员变量,因此我们用属性来接收
//与类相似,内容属性可多可少,但是每一次修改interface结构后都需要先清空xLua再生成运行
//嵌套几乎和类一样,无非就是要遵循接口的规则
public interface ICCallLua
{
int testint
{
get; set;
}
bool testbool
{
get; set;
}
float testfloat
{
get; set;
}
string teststring
{
get; set;
}
UnityAction testFun
{
get; set;
}
}
public class Lesson8CallInterface : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
ICCallLua obj = LuaMgr.GetInstance().Global.Get<ICCallLua>("testClass");
Debug.Log(obj.testint);
Debug.Log(obj.testbool);
Debug.Log(obj.teststring);
Debug.Log(obj.testfloat);
obj.testFun();
//接口为引用拷贝,改了值lua中的值也会变化
obj.testint = 18888;
ICCallLua obj1 = LuaMgr.GetInstance().Global.Get<ICCallLua>("testClass");
Debug.Log(obj1.testint);
}
// Update is called once per frame
void Update()
{
}
}
LuaTable映射Table
同样使用lua之前的代码
testClass={
testint=5,
testbool=true,
testfloat=4.5,
teststring="xnbsa",
testFun=function ( )
print("243566764")
end
}
在之前的使用中,我们多次用到LuaTable,在LuaMgr的定义中我们知道,他将其定义为Global,来得到Get里面的变量与函数
但是我们不建议使用LuaTable与LuaFunction,因为他们效率低且会产生垃圾,在不需要使用后要记得销毁
并且他也为深拷贝
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class Lesson9CallLuaTable : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
//不建议用LuaTable与LauFunction,因为效率低且容易产生垃圾
//是一种引用对象
LuaTable table=LuaMgr.GetInstance().Global.Get<LuaTable>("testClass");
Debug.Log(table.Get<int>("testint"));
Debug.Log(table.Get<bool>("testbool"));
Debug.Log(table.Get<float>("testfloat"));
Debug.Log(table.Get<string>("teststring"));
table.Get<LuaFunction>("testFun").Call();
//是引用拷贝
table.Set("testint", 1949);
Debug.Log(table.Get<int>("testint"));
LuaTable table1 = LuaMgr.GetInstance().Global.Get<LuaTable>("testClass");
Debug.Log(table1.Get<int>("testint"));
//不用了要记得销毁,会有垃圾
table.Dispose();
table1.Dispose();
}
// Update is called once per frame
void Update()
{
}
}