jquave 发表于 2023-1-3 09:08

【Lua】ToLua逻辑热更新

1 前言

Lua基础语法 中系统介绍了 Lua 的语法体系,xLua逻辑热更新 中介绍了 xLua 的应用,本文将进一步介绍 Unity3D 中基于 ToLua 实现逻辑热更新。
      逻辑热更新是指:在保持程序正常运行的情况下,在后台修改代码逻辑,修改完成并推送到运行主机上,主机无缝接入更新后的代码逻辑。Unity3D 中,基于 Lua 的逻辑热更新方案主要有 ToLua、xLua、uLua、sLua,本文将介绍 ToLua 逻辑热更新方案。
1)热更新的好处
不用浪费流量重新下载;不用通过商店审核,版本迭代更加快捷;不用重新安装,用户可以更快体验更新的内容。
2)ToLua 插件下载
github:https://github.com/topameng/toluagitcode:https://gitcode.net/mirrors/topameng/tolua
3)ToLua 插件导入
      将插件的 Assets 目录下的所有文件拷贝到项目的 Assets 目录下,如下:


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


5)配置 wrap 文件
      用户如果想添加新的 wrap 配置,可以在【Assets\Editor\CustomSettings.cs】文件里添加,并且需要在菜单栏里重新点击 Generate All,在【Assets\Source\Generate】中查看是否生成了相应的文件。
6)官方Demo


      本文基于官方 Demo 讲解 ToLua 的使用。
2 ToLua 应用

2.1 C# 中执行 Lua 代码串

HelloWorld.cs
using UnityEngine;
using LuaInterface;

public class HelloWorld : MonoBehaviour {
    void Awake()
    {
      LuaState lua = new LuaState();
      lua.Start();
      string luaStr = @"print('Hello World')";
      lua.DoString(luaStr, "HelloWorld.cs");
      lua.CheckTop();
      lua.Dispose();
      lua = null;
    }
}      运行如下:


2.2 C# 中调用 Lua 文件

ScriptFromFile.cs
using UnityEngine;
using LuaInterface;

public class ScriptFromFile : MonoBehaviour {
    private LuaState lua = null;

    private void Start() {
      lua = new LuaState();
      lua.Start();
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\02");
      lua.DoFile("LuaScript.lua");
      lua.CheckTop();
    }

    private void OnApplicationQuit() {
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
print("Load lua script")2.3 C# 中调用 Lua 函数

CallLuaFunction.cs
using UnityEngine;
using LuaInterface;
using System;

public class CallLuaFunction : MonoBehaviour {
    private LuaState lua = null;
    private LuaFunction luaFunc = null;

    private void Start() {
      lua = new LuaState();
      lua.Start();
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\03");
      lua.DoFile("LuaScript.lua");
      GetFunc();
      print("Invoke1: " + Invoke1());
      print("Invoke2: " + Invoke2());
      print("PCall: " + PCall());
      print("Delegate: " + Delegate());
      lua.CheckTop();
    }

    private void GetFunc() {
      //luaFunc = lua["luaFunc"] as LuaFunction; // 方式一
      luaFunc = lua.GetFunction("luaFunc"); // 方式二
    }

    private string Invoke1() { // 方法一
      string res = luaFunc.Invoke<int, string>(123456);
      return res;
    }

    private string Invoke2() { // 方法二
      string res = lua.Invoke<int, string>("luaFunc", 123456, true);
      return res;
    }

    private string PCall() { // 方法三
      luaFunc.BeginPCall();
      luaFunc.Push(123456);
      luaFunc.PCall();
      string res = luaFunc.CheckString();
      luaFunc.EndPCall();
      return res;
    }

    // 需要在CustomSettings.cs的customDelegateList里面添加"_DT(typeof(System.Func<int, string>))", 并且重新点击菜单窗口的Lua->Generate All
    private string Delegate() { // 方法四
      DelegateFactory.Init();
      Func<int, string> func = luaFunc.ToDelegate<Func<int, string>>();
      string res = func(123456);
      return res;
    }

    private void Call() { // 方法五(lua中无返回值的函数可以调用此方法)
      luaFunc.Call(123456);
    }

    private void OnApplicationQuit() { // 退出应用回调
      if (luaFunc != null) {
            luaFunc.Dispose();
            luaFunc = null;
      }
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
function luaFunc(num)                        
        return "num=" .. num
end      打印如下:


2.4 C# 中调用 Lua 变量

AccessLuaVar.cs
using UnityEngine;
using LuaInterface;

public class AccessLuaVar : MonoBehaviour {
    private LuaState lua = null;

    private void Start() {
      lua = new LuaState();
      lua.Start();
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\04");
      InitValue();
      AccessTab();
      lua.CheckTop();
    }

    private void InitValue() { // 在执行lua脚本前, 给lua脚本中变量赋值
      lua["var"] = 5;
      lua.DoFile("LuaScript.lua");
    }

    private void AccessTab() { // 访问tab
      LuaTable tab = lua.GetTable("tab"); // 获取table
      object[] list = tab.ToArray(); // 遍历table元素
      for (int i = 0; i < list.Length; i++)
      {
            print("tab[" + i + "]=" + list);
      }
      print("a=" + tab["a"]);
      tab["a"] = "yyy";
      print("a=" + tab["a"]);
      LuaTable map = (LuaTable) tab["map"];
      print("name=" + map["name"] + ", age=" + map["age"]);
      map.Dispose();
      tab.Dispose();
    }

    private void OnApplicationQuit() { // 退出应用回调
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
print('var='..var)

tab = {1, 2, 3, a = "xxx"}
tab.map = {name = "zhangsan", age = 23}      打印如下:


2.5 Lua 中使用协程

1)方法一
TestCoroutine.cs
using UnityEngine;
using LuaInterface;

//方法一和方法二展示的两套协同系统勿交叉使用,此为推荐方案
public class TestCoroutine : MonoBehaviour {
    private LuaState lua = null;
    private LuaLooper looper = null;
    private LuaFunction func = null;

        private void Awake() {
      lua= new LuaState();
      lua.Start();
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\05");
      LuaBinder.Bind(lua);      
      looper = gameObject.AddComponent<LuaLooper>();
      looper.luaState = lua;
      lua.DoFile("LuaScript.lua");
      func = lua.GetFunction("TestCortinue");
      func.Call();
    }

    private void OnApplicationQuit() {
      if (func != null) {
            func.Dispose();
            func = null;
      }
      looper.Destroy();
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
function CortinueFunc()
        local www = UnityEngine.WWW("http://www.baidu.com")
        coroutine.www(www)
        local str = tolua.tolstring(www.bytes)
        print(str:sub(1, 128))
        print("current frameCount: "..Time.frameCount)
        coroutine.step() --挂起协程, 下一帧继续执行
        print("yield frameCount: "..Time.frameCount)
end

function TestCortinue()       
    coroutine.start(CortinueFunc)
end      打印如下:


2)方法二
TestCoroutine.cs
using UnityEngine;
using LuaInterface;

//方法一和方法二展示的两套协同系统勿交叉使用,类unity原生,大量使用效率低
public class TestCoroutine : LuaClient {

    protected override void OnLoadFinished() {
      base.OnLoadFinished();
      luaState.AddSearchPath(Application.dataPath + "\\Scenes\\05");
      luaState.DoFile("LuaScript.lua");
      LuaFunction func = luaState.GetFunction("TestCortinue");
      func.Call();
      func.Dispose();
    }

    private new void OnApplicationQuit() {
      base.OnApplicationQuit();
    }
}      说明: LuaClient 继承 MonoBehaviour。
LuaScript.lua
function CortinueFunc()
    WaitForSeconds(1)
    print('WaitForSeconds end time: '.. UnityEngine.Time.time)
    WaitForFixedUpdate()
    print('WaitForFixedUpdate end frameCount: '..UnityEngine.Time.frameCount)
    WaitForEndOfFrame()
    print('WaitForEndOfFrame end frameCount: '..UnityEngine.Time.frameCount)
    Yield(null)
    print('yield null end frameCount: '..UnityEngine.Time.frameCount)
    Yield(0)
    print('yield(0) end frameCime: '..UnityEngine.Time.frameCount)
    local www = UnityEngine.WWW('http://www.baidu.com')
    Yield(www)
    print('yield(www) end time: '.. UnityEngine.Time.time)
    local str = tolua.tolstring(www.bytes)
    print(str:sub(1, 128))
end

function TestCortinue()
    StartCoroutine(CortinueFunc)
end      打印如下:


2.6 C# 给 Lua 传递数组

TestArray.cs
using UnityEngine;
using LuaInterface;

public class TestArray : MonoBehaviour {
    private LuaState lua = null;
    private LuaFunction func = null;

        private void Start() {
      lua= new LuaState();
      lua.Start();
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\06");
      lua.DoFile("LuaScript.lua");
      int[] array = {1, 2, 3};
      func = lua.GetFunction("TestArray");
      Call(array);
      //LazyCall(array);
      lua.CheckTop();
    }

    private void Call(int[] array) { // 方式一
      func.BeginPCall();
      func.Push(array);
      func.PCall();
      double arg1 = func.CheckNumber();
      string arg2 = func.CheckString();
      bool arg3 = func.CheckBoolean();
      func.EndPCall();
      print("arg1: " + arg1 + ", arg2: " + arg2 + ", arg3: " + arg3);
    }

    private void LazyCall(int[] array) { // 方式二
      object[] objs = func.LazyCall((object)array);
      if (objs != null) {
            print("objs: " + objs + ", objs: " + objs + ", objs: " + objs);
      }
    }

    private void OnApplicationQuit() {
      if (func != null) {
            func.Dispose();
            func = null;
      }
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
function TestArray(array)
    local len = array.Length
    --通过下标遍历数组
    for i = 0, len - 1 do
      print('Array: '..tostring(array))
    end
    --通过迭代器遍历数组
    local iter = array:GetEnumerator()
    while iter:MoveNext() do
      print('iter: '..iter.Current)
    end
    --通过表格遍历数组
    local t = array:ToTable()
    for i = 1, #t do
      print('table: '.. tostring(t))
    end
    --二分查找数组元素, 返回元素下标, 若数组中不存在该元素, 返回负数
    local pos = array:BinarySearch(3)
    print('array BinarySearch, pos='..pos..', value='..array)
    --查找数组元素下标
    pos = array:IndexOf(4)
    print('array indexof, pos='..pos)
    --返回多值
    return 1, '123', true
end2.7 C# 给 Lua 传递字典

TestDictionary.cs
using System.Collections.Generic;
using UnityEngine;
using LuaInterface;

public sealed class Account {
    public int id;
    public string name;
    public int sex;

    public Account(int id, string name, int sex) {
      this.id = id;
      this.name = name;
      this.sex = sex;
    }
}

public class TestDictionary : MonoBehaviour {
    private LuaState lua = null;
    private LuaFunction func = null;
    private Dictionary<int, Account> map = new Dictionary<int, Account>();

        private void Awake() {
      InitDirec();
      lua = new LuaState();
      lua.Start();
      LuaBinder.Bind(lua);
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\07");
      lua.DoFile("LuaScript.lua");
      func = lua.GetFunction("TestDict");
      Call();
    }

    private void Call() {
      func.BeginPCall();
      func.Push(map);
      func.PCall();
      func.EndPCall();
    }

    private void InitDirec() {
      map.Add(1, new Account(1, "张三", 0));
      map.Add(2, new Account(2, "李四", 1));
      map.Add(3, new Account(3, "王五", 0));
    }

    private void OnApplicationQuit() {
      if (func != null) {
            func.Dispose();
            func = null;
      }
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
function TestDict(map)
    --遍历map的所有value元素
    local iter = map:GetEnumerator()
    while iter:MoveNext() do
      local v = iter.Current.Value
      print('id: '..v.id ..', name: '..v.name..', sex: '..v.sex)
    end
    --遍历map的key集
    local keys = map.Keys
    iter = keys:GetEnumerator()
    print('------------print dictionary keys---------------')
    while iter:MoveNext() do
      print(iter.Current)
    end
    --遍历map的value集
    local values = map.Values
    iter = values:GetEnumerator()
    print('------------print dictionary values---------------')
    while iter:MoveNext() do
      print(iter.Current.name)
    end
    --根据key查找value元素
    local flag, account = map:TryGetValue(1, nil)
    if flag then
      print('TryGetValue result ok: '..account.name)
    end
    print('kick '..map.name)
    --移除元素, 并打印剩余的value
    map:Remove(2)
    iter = map:GetEnumerator()
    while iter:MoveNext() do
      local v = iter.Current.Value
      print('id: '..v.id ..' name: '..v.name..' sex: '..v.sex)
    end
end      补充:CustomSettings.cs 里需要配置 Account 如下:
    //在这里添加你要导出注册到lua的类型列表
    public static BindType[] customTypeList = {               
      //------------------------为例子导出--------------------------------
      _GT(typeof(Account)),
      _GT(typeof(Dictionary<int, Account>)).SetLibName("AccountMap"),
      _GT(typeof(KeyValuePair<int, Account>)),
      _GT(typeof(Dictionary<int, Account>.KeyCollection)),
      _GT(typeof(Dictionary<int, Account>.ValueCollection)),
      //-------------------------------------------------------------------
      ...
    }      配置后,需要点击 Generate All 生成相关 Wrap 文件,如果出现以下报错:
LuaException: LuaScript.lua:12: field or property MoveNext does not exist      修改 System_Collections_Generic_Dictionary_int_Account_KeyCollectionWrap.cs 和 System_Collections_Generic_Dictionary_int_Account_ValueCollectionWrap.cs 文件,将 ToLua.PushValue(L, o) 替换为 ToLua.Push(L, o),如下:
//ToLua.PushValue(L, o);
ToLua.Push(L, o);2.8 C# 给 Lua 传递列表

TestList.cs
using UnityEngine;
using LuaInterface;
using System.Collections.Generic;

public class TestList : MonoBehaviour {
    private LuaState lua = null;

    private void Awake() {
      lua = new LuaState();
      lua.Start();
      LuaBinder.Bind(lua);
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\08");
      lua.DoFile("LuaScript.lua");
      DelegateFactory.Init();
      List<string> list = GetList();
      Call(list);
    }

    private void Call(List<string> list) {
      LuaFunction func = lua.GetFunction("TestList");
      func.BeginPCall();
      func.Push(list);
      func.PCall();
      func.EndPCall();
      func.Dispose();
      func = null;
    }

    private List<string> GetList() {
      List<string> list = new List<string>();
      list.Add("zhang");
      list.Add("li");
      list.Add("wang");
      return list;
    }

    private void OnApplicationQuit() {
      lua.Dispose();
      lua = null;
    }
}      LuaScript.lua
function printList(list)
    str = ""
    for i = 0, list.Count - 1 do
      str = str..(list)..", "
    end
    print(str)
end

function TestList(list)
    printList(list) --zhang, li, wang,
    list:Add("chen")
    printList(list) --zhang, li, wang, chen,
    list:Sort()
    printList(list) --chen, li, wang, zhang,
    print("index="..list:IndexOf("wang")) --index=2
    list:Remove("li")
    printList(list) --chen, wang, zhang,
end2.9 Lua 中创建 GameObject 并获取和添加组件

TestGameObject.cs
using UnityEngine;
using LuaInterface;

public class TestGameObject : MonoBehaviour {
    private LuaState lua = null;

        private void Awake() {
      lua = new LuaState();
      lua.Start();
      LuaBinder.Bind(lua);
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\09");
      lua.DoFile("LuaScript.lua");
    }

    private void OnApplicationQuit() {
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
local GameObject = UnityEngine.GameObject
local PrimitiveType = UnityEngine.PrimitiveType --需要在CustomSettings.cs里添加"_GT(typeof(PrimitiveType))"
local MeshRenderer = UnityEngine.MeshRenderer
local Color = UnityEngine.Color
local Rigidbody = UnityEngine.Rigidbody

go = GameObject.CreatePrimitive(PrimitiveType.Cube)
go:GetComponent("MeshRenderer").sharedMaterial.color = Color.red
rigidbody = go:AddComponent(typeof(Rigidbody))
rigidbody.mass = 10003 Lua Hook MonoBehaviour 生命周期方法

      MonoBehaviour 生命周期方法见→MonoBehaviour的生命周期。
TestLife.cs
using UnityEngine;
using LuaInterface;
using System.Collections.Generic;

public class TestLife : MonoBehaviour {
    private LuaState lua = null;
    private Dictionary<string, LuaFunction> func;

    private void Awake() {
      lua = new LuaState();
      lua.Start();
      LuaBinder.Bind(lua);
      lua.AddSearchPath(Application.dataPath + "\\Scenes\\10");
      lua.DoFile("LuaScript.lua");
      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, LuaFunction>();
      AddFunc("awake");
      AddFunc("onEnable");
      AddFunc("start");
      AddFunc("update");
      AddFunc("onDisable");
      AddFunc("onDestroy");
    }

    private void AddFunc(string funcName) {
      LuaFunction fun = lua.GetFunction(funcName);
      if (fun != null) {
            func.Add(funcName, fun);
      }
    }

    private void CallFunc(string funcName) {
      if (func.ContainsKey(funcName)) {
            LuaFunction fun = func;
            fun.Call();
      }
    }

    private void OnApplicationQuit() {
      foreach(var fun in func.Values)
      {
            fun.Dispose();
      }
      func.Clear();
      func = null;
      lua.Dispose();
      lua = null;
    }
}LuaScript.lua
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
页: [1]
查看完整版本: 【Lua】ToLua逻辑热更新