1 前言

Lua基础语法 中系统介绍了 Lua 的语法体系,ToLua逻辑热更新 中介绍了 ToLua 的应用,本文将进一步介绍 Unity3D 中基于 xLua 实现逻辑热更新。

​ 逻辑热更新是指:在保持程序正常运行的情况下,在后台修改代码逻辑,修改完成并推送到运行主机上,主机无缝接入更新后的代码逻辑。Unity3D 中,基于 Lua 的逻辑热更新方案主要有 ToLua、xLua、uLua、sLua,本文将介绍 xLua 逻辑热更新方案。

1)热更新的好处

  • 不用浪费流量重新下载;
  • 不用通过商店审核,版本迭代更加快捷;
  • 不用重新安装,用户可以更快体验更新的内容

2)xLua 插件下载

​ xLua 是腾讯研发的 Unity3D 逻辑热更新方案,目前已开源,资源见:

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】中可以查阅官方教程文档,在线教程文档见:

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>("02/LuaScript.lua");
luaEnv.DoString(textAsset.text);
luaEnv.Dispose();
}
}

​ LuaScript.lua.txt

print("Load lua script")

​ 说明: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("require '02/LuaScript'");
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("require '02/LuaScript'");
luaEnv.Dispose();
} private byte[] MyLoader(ref string filePath) {
string path = Application.dataPath + "/Resources/" + filePath + ".lua.txt";
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("require '03/LuaScript'");
TestAccessVar();
} private void TestAccessVar() {
bool a = luaEnv.Global.Get<bool>("a");
int b = luaEnv.Global.Get<int>("b");
float c = luaEnv.Global.Get<float>("c");
string d = luaEnv.Global.Get<string>("d");
Debug.Log("a=" + a + ", b=" + b + ", c=" + c + ", d=" + 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 = "xxx"

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("require '04/LuaScript'");
TestAccessTable();
} private void TestAccessTable() {
Student stu = luaEnv.Global.Get<Student>("stu");
Debug.Log("name=" + stu.name + ", age=" + stu.age); // name=zhangsan, age=23
stu.name = "lisi";
luaEnv.DoString("print(stu.name)"); // LUA: zhangsan
} private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
} class Student {
public string name;
public int age;
}

​ LuaScript.lua.txt

stu = {name = "zhangsan", age = 23, sex = 0, 1, 2, 3}

​ 说明:允许 table 中元素个数与自定义类中属性个数不一致,允许自定义类中属性顺序与 table 中元素顺序不一致;类中需要映射的属性名必须与 table 中相应元素名保持一致(大小写也必须一致);修改映射类的属性值,不影响 table 中相应元素的值。

2)通过自定义接口映射 table

​ AccessTable.cs

using UnityEngine;
using XLua; public class AccessTable : MonoBehaviour {
private LuaEnv luaEnv; private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString("require '04/LuaScript'");
TestAccessTable();
} private void TestAccessTable() {
IStudent stu = luaEnv.Global.Get<IStudent>("stu");
Debug.Log("name=" + stu.name + ", age=" + stu.age); // name=zhangsan, age=23
stu.name = "lisi";
luaEnv.DoString("print(stu.name)"); // LUA: lisi
stu.study("program"); // LUA: subject=program
stu.raiseHand("right"); // LUA: hand=right
} private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
} [CSharpCallLua]
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 = "zhangsan",
age = 23,
study = function(self, subject)
print("subject="..subject)
end
} --function stu.raiseHand(self, hand)
function stu:raiseHand(hand)
print("hand="..hand)
end

3)通过 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("require '04/LuaScript'");
TestAccessTable();
} private void TestAccessTable() {
Dictionary<string, object> stu = luaEnv.Global.Get<Dictionary<string, object>>("stu");
Debug.Log("name=" + stu["name"] + ", age=" + stu["age"]); // name=zhangsan, age=23
stu["name"] = "lisi";
luaEnv.DoString("print(stu.name)"); // LUA: zhangsan
} private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}

​ 说明:修改映射 Dictionary 的元素值,不影响 table 中相应元素的值。

​ LuaScript.lua.txt

stu = {name = "zhangsan", age = 23, "math", 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("require '04/LuaScript'");
TestAccessTable();
} private void TestAccessTable() {
List<object> list = luaEnv.Global.Get<List<object>>("stu");
string str = "";
foreach(var item in list) {
str += item + ", ";
}
Debug.Log(str); // math, 2, True,
} private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}

​ LuaScript.lua.txt

stu = {name = "zhangsan", age = 23, "math", 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("require '04/LuaScript'");
TestAccessTable();
} private void TestAccessTable() {
LuaTable table = luaEnv.Global.Get<LuaTable>("stu");
Debug.Log("name=" + table.Get<string>("name") + ", age=" + table.Get<int>("age")); // name=zhangsan, age=23
table.Set<string, string>("name", "lisi");
luaEnv.DoString("print(stu.name)"); // LUA: lisi
table.Dispose();
} private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
}

​ 说明:修改映射 LuaTable 的属性值,会影响 table 中相应元素的值

​ LuaScript.lua.txt

stu = {name = "zhangsan", age = 23, "math", 2, true}

2.5 C# 中调用 Lua 全局函数

1)通过 delegate 映射 function

​ AccessFunc.cs

using System;
using UnityEngine;
using XLua; public class AccessFunc : MonoBehaviour {
private LuaEnv luaEnv; [CSharpCallLua] // 需要设置 public, 并且点击 Generate Code
public delegate int MyFunc1(int arg1, int arg2);
[CSharpCallLua] // 需要设置 public, 并且点击 Generate Code
public delegate int MyFunc2(int arg1, int arg2, out int resOut);
[CSharpCallLua] // 需要设置 public, 并且点击 Generate Code
public delegate int MyFunc3(int arg1, int arg2, ref int resRef); private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString("require '05/LuaScript'");
TestAccessFunc1();
TestAccessFunc2();
TestAccessFunc3();
TestAccessFunc4();
} private void TestAccessFunc1() { // 测试无参函数
Action func1 = luaEnv.Global.Get<Action>("func1");
func1(); // LUA: func1
} private void TestAccessFunc2() { // 测试有参函数
Action<string> func2 = luaEnv.Global.Get<Action<string>>("func2");
func2("xxx"); // LUA: func2, arg=xxx
} private void TestAccessFunc3() { // 测试有返回值函数
MyFunc1 func3 = luaEnv.Global.Get<MyFunc1>("func3");
Debug.Log(func3(2, 3)); // 6
} private void TestAccessFunc4() { // 测试有多返回值函数
MyFunc1 func41 = luaEnv.Global.Get<MyFunc1>("func4");
Debug.Log(func41(2, 3)); // 5 int res, resOut;
MyFunc2 func42 = luaEnv.Global.Get<MyFunc2>("func4");
res = func42(2, 3, out resOut);
Debug.Log("res=" + res + ", resOut=" + resOut); // res=5, resOut=-1 int ans, resRef = 0;
MyFunc3 func43 = luaEnv.Global.Get<MyFunc3>("func4");
ans = func43(2, 3, ref resRef);
Debug.Log("ans=" + ans + ", resRef=" + 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("func1")
end --有参函数
function func2(arg)
print("func2, arg="..arg)
end --有返回值函数
function func3(a, b)
return a * b
end --有多返回值函数
function func4(a, b)
return a + b, a - b
end

2)通过 LuaFunction 映射 function

​ AccessFunc.cs

using UnityEngine;
using XLua; public class AccessFunc : MonoBehaviour {
private LuaEnv luaEnv; private void Start() {
luaEnv = new LuaEnv();
luaEnv.DoString("require '05/LuaScript'");
TestAccessFunc1();
TestAccessFunc2();
TestAccessFunc3();
TestAccessFunc4();
} private void TestAccessFunc1() { // 测试无参函数
LuaFunction func1 = luaEnv.Global.Get<LuaFunction>("func1");
func1.Call(); // LUA: func1
} private void TestAccessFunc2() { // 测试有参函数
LuaFunction func2 = luaEnv.Global.Get<LuaFunction>("func2");
func2.Call("xxx"); // LUA: func2, arg=xxx
} private void TestAccessFunc3() { // 测试有返回值函数
LuaFunction func3 = luaEnv.Global.Get<LuaFunction>("func3");
object[] res = func3.Call(2, 3);
Debug.Log(res[0]); // 6
} private void TestAccessFunc4() { // 测试有多返回值函数
LuaFunction func4 = luaEnv.Global.Get<LuaFunction>("func4");
object[] res = func4.Call(2, 3);
Debug.Log("res1=" + res[0] + ", res2=" + res[1]); // 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("require '06/LuaScript'");
} 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("xxx") --创建空对象
go = GameObject.CreatePrimitive(PrimitiveType.Cube)
go:GetComponent("MeshRenderer").sharedMaterial.color = Color.red
rigidbody = go:AddComponent(typeof(Rigidbody))
rigidbody.mass = 1000

2.7 Lua 中访问 C# 自定义类

​ TestSelfClass.cs

using UnityEngine;
using XLua; public class TestSelfClass : MonoBehaviour {
private LuaEnv luaEnv; private void Awake() {
luaEnv = new LuaEnv();
luaEnv.DoString("require '07/LuaScript'");
} private void OnApplicationQuit() {
luaEnv.Dispose();
luaEnv = null;
}
} [LuaCallCSharp] // 需要点击 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("run");
} public void Eat(string fruit) {
Debug.Log("eat " + fruit);
} public override string ToString() {
return "name=" + name + ", age=" + age;
}
}

​ LuaScript.lua.txt

local Person = CS.Person

person = Person("zhangsan", 23)
print("name="..person.name..", age="..person.age) -- LUA: name=zhangsan, age=23
print(person:ToString()) -- LUA: name=zhangsan, age=23
person:Run() -- run
person:Eat("aple") -- eat aple

3 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("require '08/LuaScript'");
GetFunc();
CallFunc("awake");
} private void OnEnable() {
CallFunc("onEnable");
} private void Start() {
CallFunc("start");
} private void Update() {
CallFunc("update");
} private void OnDisable() {
CallFunc("onDisable");
} private void OnDestroy() {
CallFunc("onDestroy");
} private void GetFunc() {
func = new Dictionary<string, Action>();
AddFunc("awake");
AddFunc("onEnable");
AddFunc("start");
AddFunc("update");
AddFunc("onDisable");
AddFunc("onDestroy");
} 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[funcName];
fun();
}
} private void OnApplicationQuit() {
func.Clear();
func = null;
luaEnv.Dispose();
luaEnv = null;
}
}

​ LuaScript.lua.txt

function awake()
print("awake")
end function onEnable()
print("onEnable")
end function start()
print("start")
end function update()
print("update")
end function onDisable()
print("onDisable")
end function onDestroy()
print("onDestroy")
end

​ 声明:本文转自【Lua】xLua逻辑热更新

【Lua】xLua逻辑热更新的更多相关文章

  1. Unity3D逻辑热更新,第二代舒爽解决方案,L#使用简介

    热更新 天下武功,无坚不破,唯快不破 热更新就是为了更快的把内容推到用户手中. 之前,我设计了C#Light,经过半年多的持续修补,勉强可用,磕磕绊绊.感谢那些,试过,骂过,用过的朋友,在你们的陪伴下 ...

  2. C#Light Unity逻辑热更新解决方案0.20 发布

    之前一直是Beta,这次已经实际运用到项目中间了,去掉beta状态 在项目中使用面对一些新的问题,还有以前没注意的bug. 更新列表 一.增加类中类的支持 二.增加对foreach的支持,同C#语法 ...

  3. Unity逻辑热更新

    http://www.xuanyusong.com/archives/3075 http://www.unitymanual.com/thread-36503-1-1.html http://www. ...

  4. 腾讯开源手游热更新方案,Unity3D下的Lua编程

    原文:http://www.sohu.com/a/123334175_355140 作者|车雄生 编辑|木环 腾讯最近在开源方面的动作不断:先是微信跨平台基础组件Mars宣布开源,腾讯手游又于近期开源 ...

  5. Unity 热更新XLua

    什么是冷更新 开发者将测试好的代码,发布到应用商店的审核平台,平台方会进行稳定性及性能 测试.测试成功后,用户即可在AppStore看到应用的更新信息,用户点击应用更 新后,需要先关闭应用,再进行更新 ...

  6. XLua热更新用法全流程总结(所有容易出问题的点)

    Xlua热更新流程总结 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享.心创 ...

  7. (原创)cocos lua 热更新从零开始(一)最简单demo

    开发环境:WIN7 + cocos2dx 3.10 lua版本 0.学习这篇内容的基础是你要会创建并运行一个cocos lua项目 1.热更新的思想所谓的热更新,就是在线更新代码和资源.热更新的过程首 ...

  8. Unity3D热更新之LuaFramework篇[10]--总结篇

    背景 19年年初的时候,进到一家新单位,公司正准备将现有的游戏做成支持热更的版本.于是寻找热更方案的任务就落在了我头上. 经过搜索了解,能做Unity热更的方案是有好几种,但是要么不够成熟,要么不支持 ...

  9. Unity3D热更新方案网摘总结

    参考:http://blog.csdn.net/guofeng526/article/details/52662994 http://blog.csdn.net/u010019717/article/ ...

  10. 热更新 && 增量更新

    Unity中SLua.Tolua.XLua和ILRuntime效率评测 http://blog.csdn.net/u011467512/article/details/72716376 如何阅读lua ...

随机推荐

  1. JMS微服务开发示例(八)双机热备

    双机热备,指两个一模一样的微服务,两个同时在运行,但是只有一个在工作,当工作中的微服务垮掉后,另一个会自行补上. 要实现这个,只需要设置 SingletonService = true. var mi ...

  2. [转帖]ORACLE等待事件:enq: TX - row lock contention

    https://www.cnblogs.com/kerrycode/p/5887150.html enq: TX - row lock contention等待事件,这个是数据库里面一个比较常见的等待 ...

  3. [转帖]tidb-lightning 逻辑模式导入

    https://docs.pingcap.com/zh/tidb/stable/tidb-lightning-configuration 本文档介绍如何编写逻辑导入模式的配置文件,如何进行性能调优等内 ...

  4. [转帖]clickHouse单机模式安装部署(RPM安装)

    关于版本和系统的选择 操作系统:Centos-7 ClickHouse: rpm 在安装,20.x 安装前的准备 CentOS7 打开文件数限 在 /etc/security/limits.conf ...

  5. [转帖]【软件测试】Jmeter性能测试(性能测试,Jmeter使用与结果分析)

    文章目录 前言 一.性能测试 1. 什么是性能测试? 2. 性能测试的重要性 3. 性能指标--QPS和TPS ①QPS ②TPS 二.压测工具Jmeter 1. 什么是Jmeter? 2. Jmet ...

  6. [转帖]2022年 SRE、DevOps技能图谱

    https://zhuanlan.zhihu.com/p/568752990 在过去一段时间,我面试过一些 DevOps 相关从业者,并且曾经收到过一些知乎小伙伴的提问,针对于 DevOps 以及相关 ...

  7. TypeScript枚举类型

    枚举 简单理解就是将所有的情况列举出来. 枚举不是用来定义类型的哈.就是说枚举不是一种数据类型. enum xxx={ key1=value1, key2=value2, } 通过 xxx.key1的 ...

  8. Leetcode 42题 接雨水(Trapping Rain Water) Java语言求解

    题目链接 https://leetcode-cn.com/problems/trapping-rain-water/ 题目内容 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱 ...

  9. 在bat中切换盘符

    在bat代码中如何在不同的盘符中切换?直接输入盘符的名字,前面不要加cd,示例 cd %~dp0 d: cd D:\Python37 e: cd E:\Code\KSFramework c: cd C ...

  10. 4.9 x64dbg 内存处理与差异对比

    LyScript 插件中针对内存读写函数的封装功能并不多,只提供了最基本的内存读取和内存写入系列函数的封装,本章将继续对API接口进行封装,实现一些在软件逆向分析中非常实用的功能,例如ShellCod ...