首页 > 其他分享 >【Lua】xLua逻辑热更新

【Lua】xLua逻辑热更新

时间:2023-03-19 17:36:01浏览次数:50  
标签:xLua 逻辑 name void private Lua luaEnv using public

1 前言

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

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

1)热更新的好处

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

2)xLua 插件下载

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

3)xLua 插件导入

​ 将插件的 Assets 目录下的所有文件拷贝到项目的 Assets 目录下,如下:

img

4)生成 Wrap 文件

​ 导入插件后,菜单栏会多一个 XLua 窗口,点击 Generate Code 会生成一些 Wrap 文件,生成路径见【Assets\XLua\Gen】,这些 Wrap 文件是 C# 与 Lua 沟通的桥梁。每次生成文件时,建议先点击下 Clear Generate Code,再点击 Generate Code。

img

5)官方教程文档

​ 在【Assets\XLua\Doc\XLua教程.doc】中可以查阅官方教程文档,在线教程文档见:

6)官方Demo

img

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();
    }
}

​ 运行如下:

img

​ 说明:第一个日志是 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逻辑热更新

标签:xLua,逻辑,name,void,private,Lua,luaEnv,using,public
From: https://www.cnblogs.com/zhyan8/p/17233701.html

相关文章

  • Beyond Independent Relevance: Methods and Evaluation Metrics for Subtopic Retrie
    目录概符号说明S-recallandS-precisionZhaiC.,CohenW.W.andLaffertyJ.Beyondindependentrelevance:methodsandevaluationmetricsforsubtopicretrieva......
  • 白盒测试之逻辑覆盖
    PS:课程笔记(里面有余九九课本里的举例)首先,逻辑覆盖是一种基于程序内部逻辑结构的动态白盒测试方法;其次,根据逻辑覆盖的强度由低到高可以分为语句覆盖、判定覆盖、条件......
  • 逻辑运算符短路特性
    &&短路特性遇到假即为假,不会判断下一组表达式||短路特性遇到真即为真,不会判断下一组表达式......
  • 数字电路1. 逻辑门电路及常见门电路实现
    一、逻辑门电路的概念逻辑门电路是一种电路设计,用于处理数字信号(仅包括0和1)。它们使用逻辑门来执行不同的逻辑操作,如与门、或门、非门、异或门等,这些门基于布尔代数。逻辑门......
  • 逻辑备份与online ddl流程
    逻辑备份:6步   onlineddl:5步 ......
  • sql多列in的用法及逻辑
    假设表结构及数据如下:sql多列in查询涉及到的查询逻辑如下:1、一般场景我们希望查询结果是 cc=1 dd=2  或者cc=2dd=4的这两行,sql语句如下:SELECT*fromtes......
  • 测试的底层逻辑
    作者:京东科技孙亮写这篇文章,是希望把我的一些我认为是非常有价值的经验总结出来,能够帮助刚做测试不久的新同事,或者是测试经验丰富的老同事以共享。希望我们可爱的新同事,......
  • 逻辑回归
    什么是逻辑回归?逻辑回归(LogisticRegression)是一种二分类模型,用于将数据分为两个不同的类别。它是一种广泛应用的模型,被用于各种领域,包括医学、金融、营销和社会科学。在......
  • 五、自定义登录逻辑
    ​ 当进行自定义登录逻辑时需要用到之前讲解的UserDetailsService和PasswordEncoder。但是SpringSecurity要求:当进行自定义登录逻辑时容器内必须有PasswordEncoder实例......
  • 五、自定义登录逻辑
    ​ 当进行自定义登录逻辑时需要用到之前讲解的UserDetailsService和PasswordEncoder。但是SpringSecurity要求:当进行自定义登录逻辑时容器内必须有PasswordEncoder实例......