Unity开发游戏使用XLua的基础
Unity使用Xlua的常用编码方式,做一下记录
1、C#调用lua
1、Lua解析器
private LuaEnv env = new LuaEnv();//保持它的唯一性
void Start()
{
env.DoString("print('你好lua')");
//env.DoString("require('Main')"); 默认在resources文件夹下面
//帮助我们清除lua中我们没有手动释放的对象 垃圾回收
env.Tick();
//销毁lua解释器,用的较少
env.Dispose();
}
2、自定义loader
private void Start()
{
//xlua路径重定向方法
_env.AddLoader(MyLoader);
_env.DoString("require('Main')");
}
private byte[] MyLoader(ref string filepath)
{
//传入的filepath是 require执行的lua脚本文件名,在这里是main
string path = Application.dataPath + "/Lua/" + filepath + ".lua";
//判断文件是否存在
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("重定向失败,文件名为"+filepath);
}
return null;
}
3、Lua管理器
/// <summary>
/// lua管理器
/// 提供lua解析器
/// 保证解析器的唯一性
/// </summary>
public class LuaMgr
{
private static LuaMgr _instance;
public static LuaMgr Instance
{
get
{
if (_instance==null)
{
_instance = new LuaMgr();
}
return _instance;
}
}
private LuaEnv _luaEnv;
/// <summary>
/// 得到lua中的_G
/// </summary>
public LuaTable Gloabl => _luaEnv.Global;
/// <summary>
/// 初始化
/// </summary>
public void Init()
{
if (_luaEnv!=null) return;
_luaEnv = new LuaEnv();
_luaEnv.AddLoader(MyLoader);
_luaEnv.AddLoader(MyABLoader);
}
#region 标准内容
/// <summary>
/// 传入文件名,执行脚本
/// </summary>
/// <param name="fileName"></param>
public void DoLuaFile(string fileName)
{
string str = $"require('{fileName}')";
DoString(str);
}
public void DoString(string str)
{
if(_luaEnv==null)
{
Debug.Log("解析器未初始化");
return;
}
_luaEnv.DoString(str);
}
/// <summary>
/// 释放lua垃圾
/// </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;
}
//自动执行
private byte[] MyLoader(ref string filepath)
{
//传入的filepath是 require执行的lua脚本文件名,在这里是main
string path = Application.dataPath + "/Lua/" + filepath + ".lua";
//判断文件是否存在
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("重定向失败,文件名为"+filepath);
}
return null;
}
//重定向加载AB包中的lua脚本
private byte[] MyABLoader(ref string filepath)
{
//加载路径
string path = Application.streamingAssetsPath + "/lua";
//加载AB包
AssetBundle ab=AssetBundle.LoadFromFile(path);
TextAsset tx = ab.LoadAsset<TextAsset>(filepath+".lua");//加载Lua文件返回
//加载lua文件,byte数组
return tx.bytes;
}
//Lua脚本最终会放在ab包中,
#endregion
}
4、全局变量获取
private void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoLuaFile("Main");
int i= LuaMgr.Instance.Gloabl.Get<int>("testNumber");
var i2= LuaMgr.Instance.Gloabl.Get<bool>("testBool");
var i3= LuaMgr.Instance.Gloabl.Get<float>("testFloat");
var i4= LuaMgr.Instance.Gloabl.Get<string>("testString");
//值拷贝不会修改lua中的值,可以用set
LuaMgr.Instance.Gloabl.Set("testNumber",1000);
i= LuaMgr.Instance.Gloabl.Get<int>("testNumber");
Debug.Log("testNumber"+i);
}
虽然lua中只有number一种数值类型,但是我们可以根据它具体的值,用对应的C#变量类型来存储
5、全局函数的获取
public delegate void CustomCall();
public delegate void CustomCall2(int a);
//加一个特性,只有添加特性才能被调用,,加完特性后还要一部分工作,就是点击Xlua->Generatedate
[CSharpCallLua]
public delegate int CustomCall3(int a);
[CSharpCallLua] //使用out来接
public delegate int CustomCall4(int a,out int b,out bool c,out string d,out int e);
[CSharpCallLua] //使用ref来接
public delegate int CustomCall5(int a,ref int b,ref bool c,ref string d,ref int e);
//变长函数,如果边长函数中类型多种多样,那就使用object
[CSharpCallLua]
public delegate void CustomCall6(string a,params int[] args);
public class Lesson5_CallFunction : MonoBehaviour
{
void Start()
{
LuaMgr.Instance.Init();
// LuaMgr.Instance.DoString("require('Main')");
LuaMgr.Instance.DoLuaFile("Main");
//无参无返回值
var call1= LuaMgr.Instance.Gloabl.Get<UnityAction>("testFun1");
call1();
//有参有返回值
var call2 = LuaMgr.Instance.Gloabl.Get<CustomCall3>("testFun2");
// Debug.Log(call2(2));
var call3 = LuaMgr.Instance.Gloabl.Get<Func<int,int>>("testFun2");
// Debug.Log("Func: "+call3(2));
//有参多返回值
var call4 = LuaMgr.Instance.Gloabl.Get<CustomCall4>("testFun3");
int b;
bool c;
string d;
int e;
// Debug.Log(call4(51,out b,out c,out d,out e));
var call5 = LuaMgr.Instance.Gloabl.Get<CustomCall5>("testFun3");
int b1 = 0;
bool c1 = true;
string d1="";
int e1=0;
// Debug.Log(call5(51,ref b1,ref c1,ref d1,ref e1));
var call6 = LuaMgr.Instance.Gloabl.Get<CustomCall6>("testFun4");
call6("你好",1,5,6,5,4);
// LuaFunction lf = LuaMgr.Instance.Gloabl.Get<LuaFunction>("testFun4");
// lf.Call("你好", 1, 5, 6, 5, 4);
}
print("Variable启动")
-- 无参无返回
testFun1 = function()
print("无参无返回")
end
-- 有参有返回
testFun2 = function(a)
print("有参有返回")
return a + 1
end
-- 多返回
testFun3 = function(a)
print("多返回")
return 1, 2, false, "123", a
end
-- 变长参数
testFun4 = function(a, ...)
print("变长参数")
print(a)
arg = { ... }
for k, v in pairs(arg) do
print(k, v)
end
end
6、List和Dictionary
private void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoLuaFile("Main");
#region List
List<int> list = LuaMgr.Instance.Gloabl.Get<List<int>>("testList");
for (int i = 0; i < list.Count; i++)
{
Debug.Log("----"+list[i]);
}
//不确定类型用object
List<object> list2 = LuaMgr.Instance.Gloabl.Get<List<object>>("testList2");
for (int i = 0; i < list2.Count; i++)
{
Debug.Log("----"+list2[i]);
}
#endregion
#region 字典
Dictionary<string, int> dic = LuaMgr.Instance.Gloabl.Get<Dictionary<string, int>>("testDic");
foreach (var item in dic)
{
Debug.Log(item.Key+"____"+item.Value);
}
Dictionary<object, object> dic2 = LuaMgr.Instance.Gloabl.Get<Dictionary<object, object>>("testDic2");
foreach (var item in dic2)
{
Debug.Log(item.Key+"____"+item.Value);
}
#endregion
}
7、类映射table
//Lua
testClas={
testInt=2,
testBool=true,
testFloat=1.2,
testString="123",
testFun=function()
print("NIHAO")
end
}
//C#
public class CallLuaClass
{
//在类中去生命成员变量,名字要和lua那边一样
//注意,。一定是公有的,不然不能赋值。
//这个自定义中的变量可以多也可以少
public int testInt;
public bool testBool;
public float testFloat;
public string testString;
public UnityAction testFun;//方法
}
public class Lesson7_Table : MonoBehaviour
{
private void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoLuaFile("Main");
CallLuaClass callLuaClass = LuaMgr.Instance.Gloabl.Get<CallLuaClass>("testClas");
Debug.Log(callLuaClass.testString);
callLuaClass.testFun();
}
}
8、接口映射table
注意接口是引用拷贝。改了值后,lua值也会改变
//接口中不允许有成员变量
//我们用属性
[CSharpCallLua]
public interface ICSharpCallInterface
{
int testInt { get; set; }
bool testBool { get; set; }
float testFloat { get; set; }
string testString { get; set; }
UnityAction testFun { get; set; }
}
public class Lesson_Interface : MonoBehaviour
{
void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoLuaFile("Main");
ICSharpCallInterface callInterface = LuaMgr.Instance.Gloabl.Get<ICSharpCallInterface>("testClas");
Debug.Log(callInterface.testFloat);
}
}
9、luatable映射到table
//不过官方不建议使用luaTable和luaFunction
void Start()
{
LuaMgr.Instance.Init();
LuaMgr.Instance.DoLuaFile("Main");
LuaTable table = LuaMgr.Instance.Gloabl.Get<LuaTable>("testClas");
Debug.Log(table.Get<int>("testInt"));
table.Get<LuaFunction>("testFun").Call();
table.Dispose();//使用完了dispose
}
2、lua调用C#(重点)
用的较多。
1、Lua使用C#类
print("************Lua调用C#相关知识点*************")
-- lua中使用C#的类非常简单
-- 固定套路
-- Cs.命名空间.类名
-- Unity的类,比如GameObject Transform等等 ---CS.UnityEngine.类名
-- 通过C#中的类,实例化一个对象,注意lua中没有new
-- 默认调用的相当于无参构造
local obj1 = CS.UnityEngine.GameObject()
local obj2 = CS.UnityEngine.GameObject("郑毅")
-- 为了方便使用,并且节约性能,定义全局变量储存C#中的类
-- 相当于取了一个别名,这也是一种优化方式
GameObject = CS.UnityEngine.GameObject
local obj3 = GameObject("第三个")
-- 类中的静态对象可以直接使用 .来调用
local obj4 = GameObject.Find("郑毅")
--得到对象中的成员变量,直接对象.就可以了
print(obj4.transform.position)
Vector3 = CS.UnityEngine.Vector3;
--使用对象中的成员方法,一定要加:
obj4.transform:Translate(Vector3.right)
print(obj4.transform.position)
-- 调用自定义的类
local t = CS.Test()
t:Speak("你好")
--有命名空间的
local t = CS.MyClass.Test2()
t:Speak("你好")
--继承mono的类
--继承mono的类是不能直接new的
--lua不支持使用无惨的泛型的
local obj5 = GameObject("加脚本测试")
-- Xlua提供了一个重要防范typeof 可以得到类的类型
obj5:AddComponent(typeof(CS.LuaCallC))
---- c#
public class Test
{
public void Speak(string str)
{
Debug.Log("test1"+str);
}
}
namespace MyClass
{
public class Test2
{
public void Speak(string str)
{
Debug.Log("test2"+str);
}
}
}
public class LuaCallC : MonoBehaviour
{
}
2、枚举
PrimitiveType = CS.UnityEngine.PrimitiveType
GameObject = CS.UnityEngine.GameObject
local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)
---获取自定义美剧
myEnum=CS.MyEnum
local c=myEnum.Idle
print(c)
--枚举转换
--数值转枚举
local a=myEnum.__CastFrom(1)
print(a)
--字符串转枚举
local b=myEnum.__CastFrom("Atk ")
print(b)
3、调用C#数组和list和字典
public class Lesson
{
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>();
}
local obj = CS.Lesson()
-- Lua使用数组相关知识
-- 长度
print(obj.array.Length)
print(obj.array[2])
--遍历,虽然lua索引是从1开始
--但是要按照C#来,从0开始
for i = 0, obj.array.Length - 1 do
print(obj.array[i])
end
--创建数组 数组的底层是array类,使用静态方法CreateInstance
local array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 10)
print(array2.Length)
---list
obj.list:Add(10)
obj.list:Add(20)
obj.list:Add(30)
obj.list:Add(40)
for i = 0, obj.list.Count - 1 do
print(obj.list[i])
end
--创建list 相当于得到一个List<int>的一个类的别名,需要再实例化
local list_string = CS.System.Collections.Generic.List(CS.System.Int32)
local list3 = list_string()
list3:Add(50)
print(list3.Count)
print(list3[0])
--- 字典
obj.dic:Add(1, "你好")
print(obj.dic[1])
for k, v in pairs(obj.dic) do
print(k, v)
end
--创建字典
local dicString=CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dic2=dicString()
dic2:Add("001",CS.UnityEngine.Vector3.right)
print(dic2["001"])---这里有个坑,自己创建的字典,这样是访问不到的
print(dic2:get_Item("001")) --只有通过这种方法才可以
dic2:set_Item("001",CS.UnityEngine.Vector3.up)
print(dic2:get_Item("001")) --只有通过这种方法才可以
print(dic2:TryGetValue("001"))--多返回值
4、拓展方法
//拓展方法
//拓展方法的写法
//想要在lua中使用拓展方法,一定要在工具类前面加上特性
//如果不加该特性,除了拓展方法以外,都不会报错
//但是lua是通过反射机制去调用C#类的,效率较低
//加上可提高性能
[LuaCallCSharp]
public static class Tools
{
public static void Move(this Lesson4 obj)
{
Debug.Log(obj.name+"移动");
}
}
public class Lesson4
{
public string name = "名称";
public void Speak(string str)
{
Debug.Log(str);
}
public static void Eat()
{
Debug.Log("吃东西");
}
}
public class LuaCallC : MonoBehaviour
{
private void Start()
{
Lesson4 lesson4 = new Lesson4();
lesson4.Move();//拓展方法,对象可以直接调用
}
}
Lesson4 = CS.Lesson4
Lesson4.Eat()
--成员方法
local a = Lesson4()
a:Speak("说话了啊")
--使用拓展方法
a:Move()
5、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;
}
}
Lesson5 = CS.Lesson5
local obj = Lesson5()
--ref 参数会以多返回值的形式返回给lua
local a, b, c = obj:RefFun(1, 0, 0, 1)
print(a)
print(b)
print(c)
--out 参数会以多返回值的形式返回给lua
--out参数不需要传占位置的值
local a, b, c = obj:OutFun(1, 30)
print(a)
print(b)
print(c)
--综合起来
-- out不用占位值 ref需要占位
local a, b, c = obj:OutFun(1, 30)
print(a)
print(b)
print(c)
6、C#函数重载
public class Lesson6
{
public int Cal()
{
return 100;
}
public int Cal(int a,int b)
{
return a + b;
}
public int Cal(int a)
{
return a;
}
public float Cal(float a)
{
return a;
}
}
Lesson6 = CS.Lesson6
local obj = Lesson6()
print(obj:Cal())
print(obj:Cal(1))
print(obj:Cal(15,20))
--对于C#中多精度的重载函数支持不好
print(obj:Cal(1.25))
--如果要解决重载函数模糊不清的问题
--xlua提供了解决方案,反射机制,但这种方法只做了解
--另外,能不用就不用
local m2 = typeof(CS.Lesson6):GetMethod("Cal", { typeof(CS.System.Single) })
--通过xlua提供的一个方法,把它转换成lua函数来使用。
--一般我们转一次,然后重复使用
local f2 = xlua.tofunction(m2)
print(f2(obj,10.2))
7、委托 事件
public class Lesson8
{
public UnityAction del;
public event UnityAction eventAction;
public void DoEvent()
{
if (eventAction != null) eventAction();
}
}
Lesson8 = CS.Lesson8
local obj = Lesson8()
-- 委托
local func=function()
print("这是lua函数")
end
-- 注意lua没有复合运算符,没有+=
-- 如果第一次往委托中加函数,因为是niu 不能直接+
-- 所以第一次 要先等=
obj.del=func
--第二次可以
obj.del=obj.del+func
obj.del()
--委托清除
obj.del=nil
---事件,事件和委托特别不一样
---有点类似于成员方法
obj:eventAction("+",func)
obj:DoEvent()
--事件不能直接设置为空
8、二维数组
使用GetValue获取
[LuaCallCSharp]
public class Book : MonoBehaviour
{
public int[,] INTArray;
void Awake()
{
INTArray = new int[3, 3]
{
{1,2,3},{2,4,5},{6,7,8}
};
}
public int GetArrayValue(int x,int y)
{
return INTArray[x, y];
}
public void SetArrayValue(int x,int y,int value)
{
INTArray[x, y] = value;
}
}
local book = CS.Book
local go = GameObject("二维数组")
local obj = go:AddComponent(typeof(book))
-- 调用 GetArrayValue 方法
local c = obj:GetArrayValue(1, 1)
print(c)
9、nil
PrimitiveType=CS.UnityEngine.PrimitiveType
Rigidbody=CS.UnityEngine.Rigidbody
local GameObject = CS.UnityEngine.GameObject;
local obj = GameObject("Cube")
local obj2 = GameObject.CreatePrimitive(PrimitiveType.Cube)
local rig = obj2:GetComponent(typeof(Rigidbody))
-- nil和Null没法进行 == 比较
if rig == nil then
print("空啊")
end
print("不为空吗")
--可用这样
if rig:Equals(nil) then
obj2:AddComponent(typeof(Rigidbody))
end
-- 全局函数
print(IsNull(rig))
-- 在C#中
print(rig:Equ())
-- 判断全局函数
function IsNull(obj)
if obj==nil or obj:Equals(nil) then
return true
end
return false
end
10、C# 扩展方法
[LuaCallCSharp]
public static class ObjTest
{
public static bool Equ(this Object obj)
{
return obj==null;
}
}
11、特殊问题
CSharpCallLua :委托、接口
LuaCallCSharp:拓展方法时、建议每个类都加
无法为系统类或者第三方库代码加上这两个特性
Obj = CS.UnityEngine.GameObject
Ui = CS.UnityEngine.UI
local sid=Obj.Find("Slider")
local sliders=sid:GetComponent(typeof(Ui.Slider))
sliders.onValueChanged:AddListener(function(f)
print("你好")
end)
---------------------------------------------------------------
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)
};
}
----------------------------------------------------------------
local ani=Obj.Find("Btn")
local btn=ani:GetComponent(typeof(Ui.Button))
btn.onClick:AddListener(function()
print("鼠标点击了")
end)
---还可以这样
local c=CS.UnityEngine.Events.UnityAction(function()
print("鼠标点击了")
end)
12、协程
--xlua提供的一个工具表,一定要通过require调用后才能用
local util=require('xlua.util')
GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds
--在场景中创建一个空物体,然后添加一个脚本
local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallC))
--希望用来被开启的协程函数
fun = function()
local a = 1
while true do
coroutine.yield(WaitForSeconds(1))
print(a)
a = a + 1
if a>10 then
--关闭协程
mono:StopCoroutine(b)
end
end
end
--我们不能直接将lua函数传入到开启协程中
--如果要把lua函数当做协程函数传入
--必须先调用xlua.util 中的cs_generator(fun)
b=mono:StartCoroutine(util.cs_generator(fun))
13、泛型
支持有约束有参数的泛型函数
不支持没有约束的泛型番薯
不支持有约束,但是没有参数的泛型函数
不支持非Class的约束
public class Lesson12
{
public class TestFather
{
}
public class TestChild:TestFather
{
}
public void TestFun1<T>(T a,T b) where T:TestFather
{
Debug.Log("有参数有约束的泛型方法");
}
public void TestFun2<T>(T a,T b)
{
Debug.Log("有参数无约束的泛型方法");
}
public void TestFun3<T>(T a)
{
Debug.Log("有参数");
}
public void TestFun3<T>() where T:TestFather
{
Debug.Log("有参数");
}
}
local obj=CS.Lesson12()
local child=CS.Lesson12.TestChild()
local father=CS.Lesson12.TestFather()
------------默认情况下支持----------
--支持有约束有参数的泛型函数
obj:TestFun1(child,father)
----------------------------------
-注意 如果是mono 这种方式支持使用
--但是如果是iLcpp打包 泛型参数是引用类型才可以
--要使得可用用
--设置泛型类型再使用
local testFun2=xlua.get_generic_method(CS.Lesson12,"TestFun3")
local testFun2_R=testFun2(CS.System.Int32)
--调用
testFun2_R(obj,10)
--设置泛型类型再使用
local testFun3=xlua.get_generic_method(CS.Lesson12,"TestFun2")
local testFun3_R=testFun3(CS.System.Int32)
--调用
testFun3_R(obj,10,20)