【Unity技术栈】Unity热更新之xLua
聪头 游戏开发萌新

唐老狮–Unity热更新之xLua

2021年3月19日16:19:47

教程URL:https://www.taikr.com/my/course/1083

image

image

环境准备

1.xLua导入

  1. 下载xLua:https://github.com/Tencent/xLua

  2. 解压后,将Assets文件夹下的Plugins和XLua文件夹拖入Unity工程的Assets下

  3. Clear Generated Code + Generate Code

    image

2.安装AssetBundleBrowser

  1. Window->Package Manager->Install Asset Bundle Browser

3.脚本导入

image

C#调用Lua

  • 文件加载重定向:自定义路径寻找lua脚本,找到就返回
image
  • AB包打包报xlua错误,选择xLua->Clear Generated Code->Generate Code

  • 自定义委托和接口,需要使用**[XLua.CSharpCallLua]**特性,并重新生成代码

  • 接口和LuaTable获取Lua中表的内容,是引用拷贝

1.Lua解析器管理器

Singleton参考:唐老狮–Unity 程序基础框架篇

ABManager参考:唐老狮–Unity 热更新之AssetBundle篇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/****************************************************
文件:LuaManager.cs
日期:2021/3/19 19:14:29
功能:Lua解析器管理器
*****************************************************/
using CT;
using CT.Hotfix;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;

public class LuaManager : Singleton<LuaManager>
{
private LuaEnv luaEnv;
//得到Lua中的_G
public LuaTable Global
{
get => luaEnv.Global;
}
//初始化解析器
public void Init()
{
if (luaEnv != null) return;
//初始化
luaEnv = new LuaEnv();
//加载Lua脚本,重定向
luaEnv.AddLoader(MyCustomLoader);
luaEnv.AddLoader(MyCustomABLoader);
}

///平时测试用的Loader xxx.lua
//参数①:require执行的lua脚本文件名
private byte[] MyCustomLoader(ref string filePath)
{
string path = Application.dataPath + "/Lua/" + filePath + ".lua";

if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
else
{
Debug.Log("重定向失败,文件名为: " + filePath);
}
return null;
}

///最终打AB包用的Loader xxx.lua.txt
//最终我们会通过加载AB包再加载其中的Lua脚本资源 来执行它
//AB包中如果要加载文本,后缀需要改为.txt,.lua不被识别
private byte[] MyCustomABLoader(ref string filePath)
{
////从AB包中加载文件
////加载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脚本
TextAsset lua = ABManager.Instance.LoadRes<TextAsset>("lua", filePath + ".lua");
if(lua != null)
{
return lua.bytes;
}
else
{
Debug.Log("重定向失败,文件名为: " + filePath);
return null;
}
}

//执行Lua语言
public void DoString(string str)
{
if(luaEnv == null)
{
Debug.Log("解析器未初始化!");
return;
}
luaEnv.DoString(str);
}
//传入文件名,执行lua脚本
public void DoLuaFile(string fileName)
{
string str = string.Format($"require('{fileName}')");
DoString(str);
}
//释放垃圾
public void Tick()
{
if (luaEnv == null)
{
Debug.Log("解析器未初始化!");
return;
}
luaEnv.Tick();
}
//销毁
public void Dispose()
{
if (luaEnv == null)
{
Debug.Log("解析器未初始化!");
return;
}
luaEnv.Dispose();
luaEnv = null;
}
}

2.C#获取全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/****************************************************
文件:Lesson4_CallVariable.cs
日期:2021/3/19 20:09:49
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson4_CallVariable : MonoBehaviour
{
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");
//使用Lua解析器LuaEnv的Global属性获得变量
int i = LuaManager.Instance.Global.Get<int>("testNumber");//值拷贝
Debug.Log("int: "+i);
bool b = LuaManager.Instance.Global.Get<bool>("testBool");
Debug.Log("bool: "+b);
float f = LuaManager.Instance.Global.Get<float>("testFloat");
Debug.Log("float: " + f);
double d = LuaManager.Instance.Global.Get<double>("testFloat");
Debug.Log("double: " + d);
string s = LuaManager.Instance.Global.Get<string>("testString");
Debug.Log("string: " + s);
//修改值
LuaManager.Instance.Global.Set("testNumber", 55);
int i2 = LuaManager.Instance.Global.Get<int>("testNumber");
Debug.Log("i2: " + i2);
//获得local变量 -- 报错: InvalidCastException: can not assign nil to int
//int i3 = LuaManager.Instance.Global.Get<int>("Test.localNumber");
//Debug.Log(i3);
}
}
1
2
3
4
5
6
--变量
testNumber = 1
testBool = true
testFloat = 1.2
testString = "123"
local localNumber = 100

3.C#获取全局函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/****************************************************
文件:Lesson5_CallFunction.cs
日期:2021/3/19 20:28:27
功能:
*****************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XLua;
///无参无返回委托
public delegate void CustomCall();
///有参又返回委托(需要加特性) a + 1
//该特性是在Xlua命名空间中的
//加了后需要重新生成代码
[CSharpCallLua]
public delegate int CustomCall2(int a);
///多返回值 1.2,false,"123",a (out或ref从第二个返回值开始接收)
[CSharpCallLua]
public delegate int CustomCall3(int a, out float b, out bool d, out string e, out int f); //out(ref略)
///变长参数
//变长参数的类型可以根据实际情况来定,求稳不求快可选object
[CSharpCallLua]
public delegate void CustomCall4(string a, params int[] args);
public class Lesson5_CallFunction : MonoBehaviour
{
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");
///无参无返回值 4种
CustomCall call = LuaManager.Instance.Global.Get<CustomCall>("testFun");
call();
UnityAction ua = LuaManager.Instance.Global.Get<UnityAction>("testFun");
ua();
//系统自带--推荐
Action action = LuaManager.Instance.Global.Get<Action>("testFun");
action();
//Xlua提供的一种函数方式 易产生垃圾,少用
LuaFunction lf = LuaManager.Instance.Global.Get<LuaFunction>("testFun");
lf.Call();
///有参有返回 3种
CustomCall2 call2 = LuaManager.Instance.Global.Get<CustomCall2>("testFun2");
Debug.Log(call2(10));
//系统自带--推荐
Func<int, int> sFunc = LuaManager.Instance.Global.Get<Func<int, int>>("testFun2");
Debug.Log(sFunc(100));
//Xlua提供的一种函数方式 易产生垃圾,少用
LuaFunction lf2 = LuaManager.Instance.Global.Get<LuaFunction>("testFun2");
Debug.Log(lf2.Call(1000)[0]);
///多返回值
//使用out 和 ref 来接收
CustomCall3 call3 = LuaManager.Instance.Global.Get<CustomCall3>("testFun3");
float b;
bool c;
string d;
int e;
Debug.Log(call3(10, out b, out c, out d, out e) + "-" + b + "-" + c + "-" + d + "-" + e);
//Xlua提供的一种函数方式 易产生垃圾,少用
LuaFunction lf3 = LuaManager.Instance.Global.Get<LuaFunction>("testFun3");
object[] objs = lf3.Call(1000);
foreach(var item in objs)
{
Debug.Log(item);
}
///变长参数
CustomCall4 call4 = LuaManager.Instance.Global.Get<CustomCall4>("testFun4");
call4("array: ", 1, 2, 3, 4, 5);//传入时lua会当成一个数组,即...的内容为{1,2,3,4,5},而不是1,2,3,4,5,使用时需注意该点
LuaFunction lf4 = LuaManager.Instance.Global.Get<LuaFunction>("testFun4");
lf4.Call("array2: ", 6, 7, 8, 9, 10);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--函数
testFun = function ( )
print('无参无返回值')
end
testFun2 = function ( a )
print('有参有返回')
return a + 1
end
testFun3 = function ( a )
print('多返回值')
return 1,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

4.C#List和Dictionary映射table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/****************************************************
文件:Lesson6_CallListDict.cs
日期:2021/3/19 21:15:50
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson6_CallListDict : MonoBehaviour
{
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");

//指定类型List
List<int> list = LuaManager.Instance.Global.Get<List<int>>("testList"); //值拷贝
Debug.Log("***********************指定类型List***************************");
foreach (var item in list)
{
Debug.Log(item);
}
//不指定类型
Debug.Log("***********************不指定类型List***************************");
List<object> list2 = LuaManager.Instance.Global.Get<List<object>>("testList2"); //值拷贝
foreach (var item in list2)
{
Debug.Log(item);
}
//指定类型字典
Debug.Log("***********************指定类型Dictionary***************************");
Dictionary<string,int> dic = LuaManager.Instance.Global.Get<Dictionary<string,int>>("testDic"); //值拷贝
foreach(var item in dic)
{
Debug.Log(item.Key + ":" + item.Value);
}
//不指定类型
Debug.Log("***********************不指定类型Dictionary***************************");
Dictionary<object, object> dic2 = LuaManager.Instance.Global.Get<Dictionary<object, object>>("testDic2"); //值拷贝
foreach (var item in dic2)
{
Debug.Log(item.Key + ":" + item.Value);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--List
testList = {1,2,3,4,5,6}
testList2 = {"123", "123", true, 1, 1.2}
--Dictionary
testDic = {
["1"] = 1,
["2"] = 2,
["3"] = 3,
["4"] = 4,
}
testDic2 = {
["1"] = 1,
[true] = 2,
[false] = false,
["123"] = true,
}

5.C#类映射table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/****************************************************
文件:Lesson7_CallClass.cs
日期:2021/3/19 21:28:35
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class CallLuaClass
{
//类的成员变量名一定要和lua一样,且需public
//自定义变量中可以更多或更少,有则赋值,无则忽略
public int testInt;
public bool testBool;
public float testFloat;
public string testString;
public UnityAction testFun;

public CallLuaInClass testInClass;
}
public class CallLuaInClass
{
public int testInInt;
}
public class Lesson7_CallClass : MonoBehaviour
{
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");
CallLuaClass obj = LuaManager.Instance.Global.Get<CallLuaClass>("testClass"); //值拷贝
Debug.Log(obj.testInt + "-" + obj.testBool + "-" + obj.testFloat + "-" + obj.testString);
obj.testFun();
Debug.Log("嵌套: " + obj.testInClass.testInInt);
}
}

Lua table脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
--table
testClass = {
testInt = 2,
testBool = true,
testFloat = 1.2,
testString = "123",
testFun = function ( )
print("test")
end,
testInClass = {
testInInt = 5
}
}

6.C#接口映射table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/****************************************************
文件:Lesson8_CallInterface.cs
日期:2021/3/19 21:40:39
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XLua;

//接口中不允许有成员变量,我们用属性接收
[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 Lesson8_CallInterface : MonoBehaviour
{
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");

ICSharpCallInterface obj = LuaManager.Instance.Global.Get<ICSharpCallInterface>("testClass"); //引用拷贝
Debug.Log(obj.testInt + "-" + obj.testBool + "-" + obj.testFloat + "-" + obj.testString);
obj.testFun();
obj.testInt = 99;
ICSharpCallInterface obj2 = LuaManager.Instance.Global.Get<ICSharpCallInterface>("testClass"); //引用拷贝
Debug.Log(obj2.testInt);
}
}

[lua脚本参考](#Lua table脚本)

7.C#LuaTable映射table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/****************************************************
文件:Lesson9_CallLuaTable.cs
日期:2021/3/19 21:52:24
功能:
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;

public class Lesson9_CallLuaTable : MonoBehaviour
{
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");
LuaTable table = LuaManager.Instance.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", 55);
Debug.Log(table.Get<int>("testInt"));
table.Dispose(); //用完要Dispose
}
}

[lua脚本参考](#Lua table脚本)

Lua调用C#

  • 想在Lua中使用拓展方法,一定要加[LuaCallCSharp]特性

  • 因为Lua是通过反射调用C#,效率较低;加了特性后,就会生成对应的C#代码。建议Lua中要使用的类 都加上该特性 可以提升性能

  • Lua调用协程时,需要给IEnumerator加[CSharpCallLua]

  • 批量给类加特性

1.Lua调用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/****************************************************
文件:LuaCallCSharp.cs
日期:2021/3/20 10:42:13
功能:Lua调用C#
*****************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XLua;

#region Lesson1_CallClass
/// <summary>
/// 自定义类
/// </summary>
public class Test1
{
public void Speak(string str)
{
Debug.Log("Test1: " + str);
}
}

namespace StuCT
{
public class Test2
{
public void Speak(string str)
{
Debug.Log("Test2: " + str);
}
}
}
#endregion

public class LuaCallCSharp : MonoBehaviour
{

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
--Lua调用C#类
--2021年3月20日10:24:15

--CS.命名空间.类名
--例如CS.UnityEngine.GameObject

--定义别名,存储C#的类,可以节省开销
local GameObject = CS.UnityEngine.GameObject

--调用构造函数,生成一个GameObject对象
--Lua中没有new
local obj1 = GameObject()
local obj2 = GameObject('聪头')

--类中的静态对象,使用类名.来调用
local obj3 = GameObject.Find('聪头')

--对象中的成员变量,使用对象.调用
local Debug = CS.UnityEngine.Debug
Debug.Log(obj3.transform.position)

--成员方法,使用对象:方法名调用!!!
local Vector3 = CS.UnityEngine.Vector3
obj3.transform:Translate(Vector3.right)
Debug.Log(obj3.transform.position)

--自定义类 使用方法相同 只是命名空间不同而已
local t = CS.Test1()
t:Speak("你好");
local t2 = CS.StuCT.Test2()
t2:Speak("拜拜")

--继承Mono的类 不能直接new
local obj4 = GameObject('脚本测试')
--xlua提供了一个重要方法 typeof 可以得到类的Type
obj4:AddComponent(typeof(CS.LuaCallCSharp))

2.Lua调用枚举

1
2
3
4
5
6
7
8
9
10
11
#region Lesson2_CallEnum
/// <summary>
/// 自定义测试枚举
/// </summary>
public enum E_MyEnum
{
Idle,
Move,
Atk
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--Lua调用C#枚举
--2021年3月20日10:54:55

--枚举调用规则和类一致
--CS.命名空间.枚举名.枚举成员
local GameObject = CS.UnityEngine.GameObject
local PrimitiveType = CS.UnityEngine.PrimitiveType

local obj = GameObject.CreatePrimitive(PrimitiveType.Cube)

E_MyEnum = CS.E_MyEnum

local c = E_MyEnum.Idle
print(c)

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

3.Lua调用数组、List和Dictionary

1
2
3
4
5
6
7
8
#region Lesson3_ListDict
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>();
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
--Lua调用C# 数组、List、Dictionary

local obj = CS.Lesson3()

--类型是userdata,应使用Length获取长度,不能使用#获取
print(obj.array.Length)

--访问元素
print(obj.array[0])

--遍历要注意 索引从0开始,Lua中for取<=
for i=0, obj.array.Length-1 do
print(obj.array[i])
end

print(string.format('C#类型:%s', obj.array)) --LUA: C#类型:System.Int32[]: 1240040448
print('Lua类型:',type(obj.array)) --LUA: userdata

--Lua中创建一个C#的数组
local array2 = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 5) --创建一个Int[5]
print(array2.Length)
print(array2[0])

print('************************C#调用 List*************************')
obj.list:Add(1)
obj.list:Add(2)
obj.list:Add(3)
for i=0,obj.list.Count-1 do
print(obj.list[i])
end
print(string.format('C#类型:%s', obj.list)) --LUA: C#类型:System.Collections.Generic.List`1[System.Int32]: 236238208
--Lua中创建一个List
--老版本 v2.1.12之前
--猜测:在实现Generic是通过字典实现,Key代表类型名,Value代表构造用的方法
local list2 = CS.System.Collections.Generic["List`1[System.String]"]();
print(list2)
list2:Add("123")
print(list2[0])
--新版本 v2.1.12之后
local List_string = CS.System.Collections.Generic.List(CS.System.String) --得到泛型类型
local list3 = List_string() --创建一个对象
list3:Add("haha")
print(list3[0])

print('************************C#调用 Dictionary*************************')
print(obj.dic) --LUA: System.Collections.Generic.Dictionary`2[System.Int32,System.String]: 506853120
obj.dic:Add(1,"123")
print(obj.dic[1])
--遍历
for k,v in pairs(obj.dic) do
print(k,v)
end
--创建 老版本略
local Vector3 = CS.UnityEngine.Vector3
local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String, Vector3)
local dic2 = Dic_String_Vector3()
dic2:Add("123", Vector3(0,1,0))
for k,v in pairs(dic2) do
print(k,v)
end
--Lua中通过[Key]得不到Value
print('dic2["123"]', dic2["123"])
print(dic2:TryGetValue("123")) --true (0,1,0)

--通过Key获得Value要使用固定方法get_Item
print('dic2:get_Item', dic2:get_Item("123"))
dic2:set_Item("123",nil)
print(dic2:get_Item("123"))

4.Lua调用拓展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#region Lesson4_拓展方法
public class Lesson4
{
public string name = "聪头";
public void Speak(string str)
{
Debug.Log(name + ": " + str);
}
public static void Eat()
{
Debug.Log("吃东西");
}
}
//想在Lua中使用拓展方法,一定要加[LuaCallCSharp]
//建议Lua中要使用的类 都加上该特性 可以提升性能
//因为Lua是通过反射调用C#,效率较低;加了特性后,就会生成对应的C#代码
[LuaCallCSharp]
public static class Tools
{
public static void Move(this Lesson4 obj)
{
Debug.Log(obj.name + "移动");
}
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
--Lua调用C# 拓展方法
--2021年3月20日12:12:31

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

--成员方法
local obj = Lesson4()
obj:Speak("hahaha")

--拓展方法 记得在C#的拓展方法的静态类前加[LuaCallCSharp]特性
obj:Move()

5.Lua调用带Ref和Out的方法

1
2
3
4
5
6
7
8
9
10
11
#region Lesson5_ref和out
public class Lesson5
{
public int RefOutFun(int a, ref int b, out int c, ref int d)
{
c = a + b + d;
d = a + b;
return d;
}
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--lua调用C# ref 和 out
--2021年3月20日12:26:27

local Lesson5 = CS.Lesson5
local obj = Lesson5()

--ref参数 会以多返回值的形式返回给lua
--第一个值是该函数的返回值,之后返回ref或out结果
--ref 占位置 out不占位置
--传入参数 a=1 b=2 d=3
--返回值 d(默认) b c d
local a,b,c,d = obj:RefOutFun(1,2,3)
--[[
public int RefOutFun(int a, ref int b, out int c, ref int d)
{
c = a + b + d;
d = a + b;
return d;
}
--]]
print(a,b,c,d)

6.Lua调用重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#region Lesson6_函数重载
public class Lesson6
{
public int Calc()
{
return 100;
}
public int Calc(int a, int b)
{
return a + b;
}
public int Calc(int a)
{
return a;
}
public float Calc(float a)
{
return a;
}
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--Lua调用C# 重载函数
--2021年3月20日13:33:24

local obj = CS.Lesson6()
--Lua支持调用C#中的重载函数
print(obj:Calc())
print(obj:Calc(1,2))

--Lua类型只有Number,所以对C#多精度的重载函数支持不好
print(obj:Calc(10))
print(obj:Calc(10.2))
--解决办法:利用反射
--缺点:效率低,少用这种重载方式
local m1 = typeof(CS.Lesson6):GetMethod("Calc", {typeof(CS.System.Int32)}) --得到参数为int的重载
local m2 = typeof(CS.Lesson6):GetMethod("Calc", {typeof(CS.System.Single)}) --得到参数为float的重载
--通过xlua提供的一个方法,把它转成lua函数来使用
--一般转一次,然后重复使用
local f1 = xlua.tofunction(m1)
local f2 = xlua.tofunction(m2)
--成员方法 ①:调用者(静态方法不用) ②~n:参数
print(f1(obj,10))
print(f2(obj,10.2))

7.Lua调用委托和事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#region Lesson7_委托和事件
public class Lesson7
{
//申明委托和事件
public UnityAction del;

public event UnityAction eventAction;

public void DoEvent()
{
eventAction?.Invoke();
}
public void ClearEvent()
{
eventAction = null;
}
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
--Lua调用C# 委托和事件
--2021年3月20日13:47:12

local obj = CS.Lesson7()

local fun = function ( )
print("Lua函数Fun")
end
print('***********委托***********')
--第一次不能使用+
obj.del = fun
obj.del = obj.del + fun
--不建议这样写,不好卸载
obj.del = obj.del + function()
print('临时匿名函数(不建议)')
end
obj.del()

obj.del = obj.del - fun
obj.del = obj.del - fun
obj.del()
print('委托清空')
obj.del = nil --清除委托
obj.del = fun
obj.del()

print('***********事件***********')
--Lua中事件 加函数 类似成员方法
obj:eventAction("+", fun)
obj:eventAction("+", fun)
obj:eventAction("-", fun)
obj:eventAction("+", function ( )
print('事件的临时匿名函数(不建议)')
end)
obj:DoEvent()
--清空事件 不能直接设nil
--obj.eventAction = nil
--通过C#的成员方法置空
obj:ClearEvent()
print('事件清空')
obj:eventAction("+", fun)
obj:DoEvent()

8.Lua调用二维数组

1
2
3
4
5
6
#region Lesson8_二维数组遍历
public class Lesson8
{
public int[,] array = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print('**************Lua调用C# 二维数组**************')
--2021年3月20日15:22:53
local obj = CS.Lesson8()
--获取长度 参数:维度
print('行:'..obj.array:GetLength(0))
print('列:'..obj.array:GetLength(1))

--获取元素 不能通过[][]访问
--print(obj.array[0][0])
--使用成员方法
print(obj.array:GetValue(0,0))
print(obj.array:GetValue(1,0))
print('双循环遍历')
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和null比较

1
2
3
4
5
6
7
8
9
10
11
#region Lesson9_判空
[LuaCallCSharp]
public static class Lesson9
{
//拓展一个Object判空的方法给lua用
public static bool IsNull(this object obj)
{
return obj == null;
}
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
print('**************nil和null比较**************')
local GameObject = CS.UnityEngine.GameObject
local Rigidbody = CS.UnityEngine.Rigidbody

local obj = GameObject('测试加脚本')
--得到身上的刚体组件 如果没有就加 有就使用它
local rig = obj:GetComponent(typeof(Rigidbody))
print(rig)
--判断空 nil和null不相等
--Lua实现判空 IsNull详见Main.lua
--[[
if IsNull(rig) then
rig = obj:AddComponent(typeof(Rigidbody))
end
--]]
--通过C#拓展Object方法实现判空(有bug)
--[[
print(rig:IsNull())
if rig:IsNull() then
rig = obj:AddComponent(typeof(Rigidbody))
end
--]]
print(rig)

10.Lua和系统类型交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#region Lesson10_系统类型加特性
//在静态类中,把所有特性汇总在此处
public static class Lesson10
{
[CSharpCallLua]
public static List<Type> csharpCallLuaList = new List<Type>()
{
typeof(UnityAction<float>),
typeof(IEnumerator)
};
[LuaCallCSharp]
public static List<Type> luaCallCSharpList = new List<Type>()
{
typeof(GameObject),
typeof(Rigidbody)
};
}
#endregion
1
2
3
4
5
6
7
8
print('**************Lua和系统类型交互**************')
local UI = CS.UnityEngine.UI
local slider = GameObject.Find("Slider")
local sliderScript = slider:GetComponent(typeof(UI.Slider))
print(sliderScript)
sliderScript.onValueChanged:AddListener(function (f)
print(f)
end)

11.Lua调用协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--Lua调用C# 协程
--2021年3月20日16:16:37
--xlua提供的一个工具表
local util = require('xlua.util')
local GameObject = CS.UnityEngine.GameObject
local WaitForSeconds = CS.UnityEngine.WaitForSeconds

local obj = GameObject('Coroutine')
local mono = obj:AddComponent(typeof(CS.LuaCallCSharp))
fun = function ( )
local a = 1
while true do
print(a)
a = a + 1
--lua中不能直接使用C#中的yield return
--使用lua中的协程返回
coroutine.yield(WaitForSeconds(1))
if a>=10 then
mono:StopCoroutine(b)
end
end
end
--我们不能直接将lua函数传入到开启协程中!!!
--mono:StartCoroutine(fun)
--把lua函数当成协程函数传入,需要调用xlua.util的cs_generator方法
--记得给IEnumerator加[CSharpCallLua]
b = mono:StartCoroutine(util.cs_generator(fun))

12.Lua调用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#region Lesson12_调用泛型方法
public class Lesson12
{
public interface ITest { }
public class TestFather
{ }
public class TestChild : TestFather,ITest
{ }

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("有参数,有接口约束");
}
}
#endregion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
--Lua调用C# 泛型
--2021年3月20日16:55:58
local obj = CS.Lesson12()
local child = CS.Lesson12.TestChild()
local father = CS.Lesson12.TestFather()
--xlua默认只支持有约束有参数的泛型
obj:TestFun1(child, father)
obj:TestFun1(father, child)
--xLua中不支持没有约束的泛型函数
--obj:TestFun2(child)
--xLua中不支持有约束,没有参数的泛型函数
--obj:TestFun3()
--xLua中不支持非类的约束
--obj:TestFun4(child)

--使用限制:
--Mono打包,支持
--IL2CPP打包,泛型参数是引用类型才支持;值类型需要C#已经调用同类型泛型参数,Lua才能够使用
--解决办法:
--1.得到通用函数
--2.设置泛型类型再使用
--参数①:类 ②:函数名
local testFun2 = xlua.get_generic_method(CS.Lesson12, "TestFun2") --得到泛型函数类型
local testFun2_R = testFun2(CS.System.Int32) --得到泛型函数
testFun2_R(obj, 1) --调用成员方法,参数①为对象

Hotfix热补丁

教程URL:https://www.taikr.com/course/1083/task/31496/show#

4个重要操作:

  • 1.给想要被打补丁的类加特性 [Hotfix]

  • 2.加宏 File->BuildSettings->OtherSetting->Scripting Define Symbols添加HOTFIX_ENABLE

  • 3.生成代码

  • 4.hotfix注入

    • 报错:please install the Tools
    • 解决办法:将Tools拷贝到和Assets同级目录下

热补丁缺点:

  • 只要我们修改了热补丁类的代码,我们就需要重新执行第4步
  • 只要我们新增了内容(如函数),就需要重新执行第3、4步

1.热补丁-函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void Update() 
{

}
public int Add(int a, int b)
{
return 0;
}
public void Eat(string s)
{
Debug.Log("eat");
}
public static void Speak(string str)
{
Debug.Log("haha");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
print('*************第一个热补丁*************')
--4个重要操作:
-- 1.加特性 [Hotfix]
-- 2.加宏 HOTFIX_ENABLE
-- 3.生成代码
-- 4.hotfix注入 报错:please install the Tools 将Tools拷贝到和Assets平级
--热补丁缺点:
-- 只要我们修改了热补丁类的代码,我们就需要重新执行第4步
--lua中 热补丁代码固定写法
--xlua.hotfix(类, "函数名", lua函数)
xlua.hotfix(CS.HotfixMain, "Add", function (self,a,b) --成员方法第一个参数需要传self
return a + b;
end)
xlua.hotfix(CS.HotfixMain, "Speak", function (str) --静态方法不需要传第一个参数
print(str);
end)
--调用原方法
local util = require('xlua.util')
util.hotfix_ex(CS.HotfixMain, 'Eat', function (self, str)
self:Eat(str)
print(str)
end)

2.热补丁-多函数替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Hotfix]
public class HotfixTest
{
public HotfixTest()
{
Debug.Log("HotfixTest构造函数");
}

public void Speak(string str)
{
Debug.Log(str);
}

~HotfixTest()
{

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
print('*************多函数替换*************')
--lua中 热补丁代码固定写法
--xlua.hotfix(类, "函数名", lua函数)

--xlua.hotfix(类, {函数名=函数,函数名=函数...})
xlua.hotfix(CS.HotfixMain, {
Update = function (self)
print(os.time())
end,
Add = function (self, a, b)
return a + b
end,
Speak = function (str)
print(str)
end
})

xlua.hotfix(CS.HotfixTest, {
--构造函数热补丁固定写法!!!
--构造和析构 不是替换 而是先调用原逻辑 再调用lua逻辑
--C#内一定要声明构造和析构,才不报错
[".ctor"] = function ()
print('Lua热补丁构造函数')
end,
Speak = function (self, str)
print('聪头:'..str);
end,
--析构函数的固定写法
Finalize = function ()
print('析构')
end
})

3.热补丁-协程替换

1
2
3
4
5
6
7
8
IEnumerator TestCoroutine()
{
while(true)
{
yield return new WaitForSeconds(1f);
Debug.Log("C#协程打印一次");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print('*************协程替换*************')
--2021年3月21日17:43:02

--lua中使用C#协程必须这样声明
util = require('xlua.util')
--xlua.hotfix(类, {函数名=函数,函数名=函数...})
xlua.hotfix(CS.HotfixMain, {
TestCoroutine = function (self)
return util.cs_generator(function ()
while true do
coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
print('Lua打补丁后的协程函数')
end
end)
end
})

4.属性索引器替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//属性
public int Age
{
get => 0;
set => Debug.Log(value);
}
//索引器
public int this[int index]
{
get => index >= array.Length? 0 : array[index];
set{
if(index >= array.Length)
return;
array[index] = value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
print('*************属性索引器替换*************')
--2021年3月21日18:03:06
xlua.hotfix(CS.HotfixMain, {
--属性固定写法
--set_属性名
--get_属性名
set_Age = function (self, value)
print('Lua重定向的属性'..value)
self._age = value
end,
get_Age = function (self)
return self._age
end,
--索引器的固定写法
--set_Item 设置索引器
--get_Item 索引器获取
set_Item = function (self, index, value)
print('Lua重定向索引器, 索引: '..index..'值'..value)
end,
get_Item = function (self, index)
print('Lua重定向索引器')
return 999
end
})

5.热补丁-事件加减替换

1
2
//事件
event UnityAction myEvent;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
print('*************事件加减替换*************')
xlua.hotfix(CS.HotfixMain, {
--add_事件名 代表事件+
--remove_事件名 代表事件-
add_myEvent = function (self, del)
print(del)
print('Lua添加事件函数')
--一定不要如下使用,把委托存C#,会死循环,把函数存Lua
--self:myEvent("+", del)
end,
remove_myEvent = function (self, del)
print(del)
print('Lua移除事件函数')
end
})

6.热补丁-泛型类的替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Hotfix]
public class HotfixTest2<T>
{
public void Test(T str)
{
Debug.Log(str);
}
}

[Hotfix]
public class HotfixMain : MonoBehaviour
{
Start()
{
HotfixTest2<string> t1 = new HotfixTest2<string>();
t1.Test("123");
HotfixTest2<int> t2 = new HotfixTest2<int>();
t2.Test(1000);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
print('*************泛型类的替换*************')

--一个一个替换
xlua.hotfix(CS.HotfixTest2(CS.System.String), {
Test = function (self,str)
print('lua补丁'..str)
end
})
xlua.hotfix(CS.HotfixTest2(CS.System.Int32), {
Test = function (self,str)
print('lua补丁'..str)
end
})

热补丁-Hotfix.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

/****************************************************
文件:HotfixMain.cs
日期:2021/3/21 16:51:42
功能:Hotfix启动脚本
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using UnityEngine.Events;

[Hotfix]
public class HotfixTest
{
public HotfixTest()
{
Debug.Log("HotfixTest构造函数");
}

public void Speak(string str)
{
Debug.Log(str);
}

~HotfixTest()
{

}
}
[Hotfix]
public class HotfixTest2<T>
{
public void Test(T str)
{
Debug.Log(str);
}
}
[Hotfix]
public class HotfixMain : MonoBehaviour
{
HotfixTest hotTest;
public int[] array = new int[]{1,2,3};
private int _age;
//属性
public int Age
{
get => 0;
set => Debug.Log(value);
}
//索引器
public int this[int index]
{
get => index >= array.Length? 0 : array[index];
set{
if(index >= array.Length)
return;
array[index] = value;
}
}
//事件
event UnityAction myEvent;
private void Start()
{
LuaManager.Instance.Init();
LuaManager.Instance.DoLuaFile("Main");

Debug.Log(Add(10,20));
Speak("聪头");
Eat("苹果");

// hotTest = new HotfixTest();
// hotTest.Speak("哈哈");

//StartCoroutine(TestCoroutine());

// Debug.Log(this.Age);
// this.Age = 100;
// Debug.Log(this.Age);

// this[99] = 100;
// Debug.Log(this[9999]);

// myEvent += TestTest;
// myEvent -= TestTest;

// HotfixTest2<string> t1 = new HotfixTest2<string>();
// t1.Test("123");
// HotfixTest2<int> t2 = new HotfixTest2<int>();
// t2.Test(1000);
}

private void TestTest()
{

}

private void Update()
{

}

IEnumerator TestCoroutine()
{
while(true)
{
yield return new WaitForSeconds(1f);
Debug.Log("C#协程打印一次");
}
}

public int Add(int a, int b)
{
return 0;
}
public void Eat(string s)
{
Debug.Log("eat");
}
public static void Speak(string str)
{
Debug.Log("haha");
}
}

补充.调用原方法

出自:https://blog.csdn.net/wangjiangrong/article/details/79818225

  • 有些小伙伴可能有这样的需求,就是我想热更一个方法的时候,还需要调用原方法(类似继承调用父方法)。按照上面的xlua.hotfix我们需要将原方法全部用lua重写一遍,这样很麻烦。xlua为我们提供了一个api来解决:util.hotfix_ex()。使用方法和hotfix一样,记得要require “xlua.util”。用self:methodName(…)来调用原方法
1
2
3
4
5
local util = require "xlua.util";
util.hotfix_ex(CS.MyExamples.Hotfix, "Show", function(self)
    self:Show();--调用原方法
print("lua---Show");
end)
 评论