【Lua】xLua逻辑热更新
1 前言Lua基础语法 中系统介绍了 Lua 的语法体系,ToLua逻辑热更新 中介绍了 ToLua 的应用,本文将进一步介绍 Unity3D 中基于 xLua 实现逻辑热更新。
逻辑热更新是指:在保持程序正常运行的情况下,在后台修改代码逻辑,修改完成并推送到运行主机上,主机无缝接入更新后的代码逻辑。Unity3D 中,基于 Lua 的逻辑热更新方案主要有 ToLua、xLua、uLua、sLua,本文将介绍 xLua 逻辑热更新方案。
1)热更新的好处
[*]不用浪费流量重新下载;
[*]不用通过商店审核,版本迭代更加快捷;
[*]不用重新安装,用户可以更快体验更新的内容
2)xLua 插件下载
xLua 是腾讯研发的 Unity3D 逻辑热更新方案,目前已开源,资源见:
[*]github:https://github.com/Tencent/xLua
[*]gitcode:https://gitcode.net/mirrors/Tencent/xlua
3)xLua 插件导入
将插件的 Assets 目录下的所有文件拷贝到项目的 Assets 目录下,如下:
4)生成 Wrap 文件
导入插件后,菜单栏会多一个 XLua 窗口,点击 Generate Code 会生成一些 Wrap 文件,生成路径见【Assets\XLua\Gen】,这些 Wrap 文件是 C# 与 Lua 沟通的桥梁。每次生成文件时,建议先点击下 Clear Generate Code,再点击 Generate Code。
5)官方教程文档
在【Assets\XLua\Doc\XLua教程.doc】中可以查阅官方教程文档,在线教程文档见:
[*]github:https://github.com/Tencent/xLua/tree/master/Assets/XLua/Doc/XLua教程.md
[*]gitcode:https://gitcode.net/mirrors/Tencent/xLua/tree/master/Assets/XLua/Doc/XLua教程.md
6)官方Demo
2 xLua 应用
2.1 C# 中执行 Lua 代码串
HelloWorld.cs
using UnityEngine;
using XLua;
public class HelloWorld : MonoBehaviour {
private void Start() {
LuaEnv luaEnv = new LuaEnv();
string luaStr = @"print('Hello World')
CS.UnityEngine.Debug.Log('Hello World')";
luaEnv.DoString(luaStr);
luaEnv.Dispose();
}
}
行如下:
说明:第一个日志是 lua 打印的,所以有 "LUA: " 标识,第二个日志是 Lua 调用 C# 的 Debug 方法,所以没有 "LUA: " 标识。
2.2 C# 中调用 Lua 文件
1)通过 Resources.Load 加载 lua 文件
ScriptFromFile.cs
using UnityEngine;
using XLua;
public class ScriptFromFile : MonoBehaviour {
private void Start() {
LuaEnv luaEnv = new LuaEnv();
TextAsset textAsset = Resources.Load<TextAsset>(&#34;02/LuaScript.lua&#34;);
luaEnv.DoString(textAsset.text);
luaEnv.Dispose();
}
}
LuaScript.lua.txt
print(&#34;Load lua script&#34;) 说明:LuaScript.lua.txt 文件放在 【Assets\Resources\02】目录下。因为 Resource 只支持有限的后缀,放 Resources 下的 lua 文件得加上 txt 后缀。
2)通过内置 loader 加载 lua 文件
ScriptFromFile.cs
using UnityEngine;
using XLua;
public class ScriptFromFile : MonoBehaviour {
private void Start() {
LuaEnv luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;02/LuaScript&#39;&#34;);
luaEnv.Dispose();
}
}
说明:require 实际上是调一个个的 loader 去加载,有一个成功就不再往下尝试,全失败则报文件找不到。 目前 xLua 除了原生的 loader 外,还添加了从 Resource 加载的 loader。因为 Resource 只支持有限的后缀,放 Resources 下的 lua 文件得加上 txt 后缀。
3)通过自定义 loader 加载 lua 文件
ScriptFromFile.cs
using UnityEngine;
using XLua;
using System.IO;
using System.Text;
public class ScriptFromFile : MonoBehaviour {
private void Start() {
LuaEnv luaEnv = new LuaEnv();
luaEnv.AddLoader(MyLoader);
luaEnv.DoString(&#34;require &#39;02/LuaScript&#39;&#34;);
luaEnv.Dispose();
}
private byte[] MyLoader(ref string filePath) {
string path = Application.dataPath + &#34;/Resources/&#34; + filePath + &#34;.lua.txt&#34;;
string txt = File.ReadAllText(path);
return Encoding.UTF8.GetBytes(txt);
}
}
2.3 C# 中调用 Lua 变量
AccessVar.cs
using UnityEngine;
using XLua;
public class AccessVar : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#3903/LuaScript&#39;&#34;);
TestAccessVar();
}
private void TestAccessVar() {
bool a = luaEnv.Global.Get<bool>(&#34;a&#34;);
int b = luaEnv.Global.Get<int>(&#34;b&#34;);
float c = luaEnv.Global.Get<float>(&#34;c&#34;);
string d = luaEnv.Global.Get<string>(&#34;d&#34;);
Debug.Log(&#34;a=&#34; + a + &#34;, b=&#34; + b + &#34;, c=&#34; + c + &#34;, d=&#34; + d); // a=True, b=10, c=7.8, d=xxx
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
LuaScript.lua.txt
a = true
b = 10
c = 7.8
d = &#34;xxx&#34;2.4 C# 中调用 Lua table
1)通过自定义类映射 table
AccessTable.cs
using UnityEngine;
using XLua;
public class AccessTable : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;04/LuaScript&#39;&#34;);
TestAccessTable();
}
private void TestAccessTable() {
Student stu = luaEnv.Global.Get<Student>(&#34;stu&#34;);
Debug.Log(&#34;name=&#34; + stu.name + &#34;, age=&#34; + stu.age); // name=zhangsan, age=23
stu.name = &#34;lisi&#34;;
luaEnv.DoString(&#34;print(stu.name)&#34;); // LUA: zhangsan
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
class Student {
public string name;
public int age;
}
LuaScript.lua.txt
stu = {name = &#34;zhangsan&#34;, age = 23, sex = 0, 1, 2, 3} 说明:允许 table 中元素个数与自定义类中属性个数不一致,允许自定义类中属性顺序与 table 中元素顺序不一致;类中需要映射的属性名必须与 table 中相应元素名保持一致(大小写也必须一致);修改映射类的属性值,不影响 table 中相应元素的值。
2)通过自定义接口映射 table
AccessTable.cs
using UnityEngine;
uing XLua;
public class AccessTable : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;04/LuaScript&#39;&#34;);
TestAccessTable();
}
private void TestAccessTable() {
IStudent stu = luaEnv.Global.Get<IStudent>(&#34;stu&#34;);
Debug.Log(&#34;name=&#34; + stu.name + &#34;, age=&#34; + stu.age); // name=zhangsan, age=23
stu.name = &#34;lisi&#34;;
luaEnv.DoString(&#34;print(stu.name)&#34;); // LUA: lisi
stu.study(&#34;program&#34;); // LUA: subject=program
stu.raiseHand(&#34;right&#34;); // LUA: hand=right
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
public interface IStudent {
public string name {get; set;}
public int age {get; set;}
public void study(string subject);
public void raiseHand(string hand);
}
说明:在运行脚本之前,需要先点击下 Clear Generate Code,再点击 Generate Code;允许 table 中元素个数与自定义接口中属性个数不一致,允许自定义接口中属性顺序与 table 中元素顺序不一致;接口中需要映射的属性名和方法名必须与 table 中相应元素名和函数名保持一致(大小写也必须一致);修改映射接口的属性值,会影响 table 中相应元素的值。
LuaScript.lua.txt
stu = {
name = &#34;zhangsan&#34;,
age = 23,
study = function(self, subject)
print(&#34;subject=&#34;..subject)
end
}
--function stu.raiseHand(self, hand)
function stu:raiseHand(hand)
print(&#34;hand=&#34;..hand)
end3)通过 Dictionary 映射 table
AccessTable.cs
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class AccessTable : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;04/LuaScript&#39;&#34;);
TestAccessTable();
}
private void TestAccessTable() {
Dictionary<string, object> stu = luaEnv.Global.Get<Dictionary<string, object>>(&#34;stu&#34;);
Debug.Log(&#34;name=&#34; + stu[&#34;name&#34;] + &#34;, age=&#34; + stu[&#34;age&#34;]); // name=zhangsan, age=23
stu[&#34;name&#34;] = &#34;lisi&#34;;
luaEnv.DoString(&#34;print(stu.name)&#34;); // LUA: zhangsan
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
说明:修改映射 Dictionary 的元素值,不影响 table 中相应元素的值。
LuaScript.lua.txt
stu = {name = &#34;zhangsan&#34;, age = 23, &#34;math&#34;, 2, true} 4)通过 List 映射 table
AccessTable.cs
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class AccessTable : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;04/LuaScript&#39;&#34;);
TestAccessTable();
}
private void TestAccessTable() {
List<object> list = luaEnv.Global.Get<List<object>>(&#34;stu&#34;);
string str = &#34;&#34;;
foreach(var item in list) {
str += item + &#34;, &#34;;
}
Debug.Log(str); // math, 2, True,
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
LuaScript.lua.txt
stu = {name = &#34;zhangsan&#34;, age = 23, &#34;math&#34;, 2, true} 5)通过 LuaTable 映射 table
AccessTable.cs
using UnityEngine;
using XLua;
public class AccessTable : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;04/LuaScript&#39;&#34;);
TestAccessTable();
}
private void TestAccessTable() {
LuaTable table = luaEnv.Global.Get<LuaTable>(&#34;stu&#34;);
Debug.Log(&#34;name=&#34; + table.Get<string>(&#34;name&#34;) + &#34;, age=&#34; + table.Get<int>(&#34;age&#34;)); // name=zhangsan, age=23
table.Set<string, string>(&#34;name&#34;, &#34;lisi&#34;);
luaEnv.DoString(&#34;print(stu.name)&#34;); // LUA: lisi
table.Dispose();
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
说明:修改映射 LuaTable 的属性值,会影响 table 中相应元素的值
LuaScript.lua.txt
stu = {name = &#34;zhangsan&#34;, age = 23, &#34;math&#34;, 2, true}2.5 C# 中调用 Lua 全局函数
1)通过 delegate 映射 function
AccessFunc.cs
using System;
using UnityEngine;
using XLua;
public class AccessFunc : MonoBehaviour {
private LuaEnv luaEnv;
// 需要设置 public, 并且点击 Generate Code
public delegate int MyFunc1(int arg1, int arg2);
// 需要设置 public, 并且点击 Generate Code
public delegate int MyFunc2(int arg1, int arg2, out int resOut);
// 需要设置 public, 并且点击 Generate Code
public delegate int MyFunc3(int arg1, int arg2, ref int resRef);
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;05/LuaScript&#39;&#34;);
TestAccessFunc1();
TestAccessFunc2();
TestAccessFunc3();
TestAccessFunc4();
}
private void TestAccessFunc1() { // 测试无参函数
Action func1 = luaEnv.Global.Get<Action>(&#34;func1&#34;);
func1(); // LUA: func1
}
private void TestAccessFunc2() { // 测试有参函数
Action<string> func2 = luaEnv.Global.Get<Action<string>>(&#34;func2&#34;);
func2(&#34;xxx&#34;); // LUA: func2, arg=xxx
}
private void TestAccessFunc3() { // 测试有返回值函数
MyFunc1 func3 = luaEnv.Global.Get<MyFunc1/span>>(&#34;func3&#34;);
Debug.Log(func3(2, 3)); // 6
}
private void TestAccessFunc4() { // 测试有多返回值函数
MyFunc1 func41 = luaEnv.Global.Get<MyFunc1>(&#34;func4&#34;);
Debug.Log(func41(2, 3)); // 5
int res, resOut;
MyFunc2 func42 = luaEnv.Global.Get<MyFunc2>(&#34;func4&#34;);
res = func42(2, 3, out resOut);
Debug.Log(&#34;res=&#34; + res + &#34;, resOut=&#34; + resOut); // res=5, resOut=-1
int ans, resRef = 0;
MyFunc3 func43 = luaEnv.Global.Get<MyFunc3>(&#34;func4&#34;);
ans = func43(2, 3, ref resRef);
Debug.Log(&#34;ans=&#34; + ans + &#34;, resRef=&#34; + resRef); // res=5, resRef=-1
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
说明:Lua 函数支持多返回值,但 C# 函数不支持多返回值,要想让 C# 接收 Lua 函数的多个返回值,需要通过 out 或 ref 参数接收第 2 个及之后的返回值。
LuaScript.lua.txt
--无参函数
function func1()
print(&#34;func1&#34;)
end
--有参函数
function func2(arg)
print(&#34;func2, arg=&#34;..arg)
end
--有返回值函数
function func3(a, b)
return a * b
end
--有多返回值函数
function func4(a, b)
return a + b, a - b
end2)通过 LuaFunction 映射 function
AccessFunc.cs
using UnityEngine;
using XLua;
public class AccessFunc : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;05/LuaScript&#39;&#34;);
TestAccessFunc1();
TestAccessFunc2();
TestAccessFunc3();
TestAccessFunc4();
}
private void TestAccessFunc1() { // 测试无参函数
LuaFunction func1 = luaEnv.Global.Get<LuaFunction>(&#34;func1&#34;);
func1.Call(); // LUA: func1
}
private void TestAccessFunc2() { // 测试有参函数
LuaFunction func2 = luaEnv.Global.Get<LuaFunction>(&#34;func2&#34;);
func2.Call(&#34;xxx&#34;); // LUA: func2, arg=xxx
}
private void TestAccessFunc3() { // 测试有返回值函数
LuaFunction func3 = luaEnv.Global.Get<LuaFunction>(&#34;func3&#34;);
object[] res = func3.Call(2, 3);
Debug.Log(res); // 6
}
private void TestAccessFunc4() { // 测试有多返回值函数
LuaFunction func4 = luaEnv.Global.Get<LuaFunction>(&#34;func4&#34;);
object[] res = func4.Call(2, 3);
Debug.Log(&#34;res1=&#34; + res + &#34;, res2=&#34; + res); // res1=5, res2=-1
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
说明:LuaScript.lua.txt 同第 1)节;LuaFunction 映射方式相较 delegate 方式,性能消耗较大。
2.6 Lua 中创建 GameObject 并获取和添加组件
TestGameObject.cs
using UnityEngine;
using XLua;
public class TestGameObject : MonoBehaviour {
private LuaEnv luaEnv;
private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;06/LuaScript&#39;&#34;);
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
LuaScript.lua.txt
local GameObject = CS.UnityEngine.GameObject
local PrimitiveType = CS.UnityEngine.PrimitiveType
local Color = CS.UnityEngine.Color
local Rigidbody = CS.UnityEngine.Rigidbody
GameObject(&#34;xxx&#34;) --创建空对象
go = GameObject.CreatePrimitive(PrimitiveType.Cube)
go:GetComponent(&#34;MeshRenderer&#34;).sharedMaterial.color = Color.red
rigidbody = go:AddComponent(typeof(Rigidbody))
rigidbody.mass = 10002.7 La 中访问 C# 自定义类
TestSelfClass.cs
using UnityEngine;
using XLua;
public class TestSelfClass : MonoBehaviour {
private LuaEnv luaEnv;
private void Awake() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;07/LuaScript&#39;&#34;);
}
private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}
// 需要点击 Generate Code
class Person {
public string name;
public int age;
public Person(string name, int age) {
this.name = name;
this.age = age;
}
public void Run() {
Debug.Log(&#34;run&#34;);
}
public void Eat(string fruit) {
Debug.Log(&#34;eat &#34; + fruit);
}
public override string ToString() {
return &#34;name=&#34; + name + &#34;, age=&#34; + age;
}
}
LuaScript.lua.txt
local Person = CS.Person
person = Person(&#34;zhangsan&#34;, 23)
print(&#34;name=&#34;..person.name..&#34;, age=&#34;..person.age) -- LUA: name=zhangsan, age=23
print(person:ToString()) -- LUA: name=zhangsan, age=23
person:Run() -- run
person:Eat(&#34;aple&#34;) -- eat aple3 Lua Hook MonoBehaviour 生命周期方法
MonoBehaviour 生命周期方法见→MonoBehaviour的生命周期。
TestLife.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class TestLife : MonoBehaviour {
private LuaEnv luaEnv;
private Dictionary<string, Action> func;
private void Awake() {
luaEnv = new LuaEnv();
luaEnv.DoString(&#34;require &#39;08/LuaScript&#39;&#34;);
GetFunc();
CallFunc(&#34;awake&#34;);
}
private void OnEnable() {
CallFunc(&#34;onEnable&#34;);
}
private void Start() {
CallFunc(&#34;start&#34;);
}
privatevoid Update() {
CallFunc(&#34;update&#34;);
}
private void OnDisable() {
CallFunc(&#34;onDisable&#34;);
}
private void OnDestroy() {
CallFunc(&#34;onDestroy&#34;);
}
private void GetFunc() {
func = new Dictionary<string, Action>();
AddFunc(&#34;awake&#34;);
AddFunc(&#34;onEnable&#34;);
AddFunc(&#34;start&#34;);
AddFunc(&#34;update&#34;);
AddFunc(&#34;onDisable&#34;);
AddFunc(&#34;onDestroy&#34;);
}
private void AddFunc(string funcName) {
Action fun = luaEnv.Global.Get<Action>(funcName);
if (fun != null) {
func.Add(funcName, fun);
}
}
private void CallFunc(string funcName) {
if (func.ContainsKey(funcName)) {
Action fun = func;
fun();
}
}
private void OnApplicationQuit() {
func.Clear();
func = null;
luaEnv.Dispose();
luaEnv = null;
}
}
LuaScript.lua.txt
function awake()
print(&#34;awake&#34;)
end
function onEnable()
print(&#34;onEnable&#34;)
end
function start()
print(&#34;start&#34;)
end
function update()
print(&#34;update&#34;)
end
function onDisable()
print(&#34;onDisable&#34;)
end
function onDestroy()
print(&#34;onDestroy&#34;)
end 声明:本文转自【Lua】xLua逻辑热更新
页:
[1]