找回密码
 立即注册
查看: 352|回复: 0

XLua——热更新

[复制链接]
发表于 2022-7-16 11:44 | 显示全部楼层 |阅读模式
什么是热更新?

        游戏上线后,出现游戏Bug或者需要更新小版本内容,游戏如果没有做热更新支持的话,就需要重重新打包游戏,然后上传平台进行审核,审核通过后玩家才可以下载新版本。审核期间需要时间,而且频繁的重新去下载游戏新包,玩家可能会反感,造成玩家流失。
游戏有热更新技术的支持下,不用重新打包上传平台审核,然后发布游戏,让玩家去下载新版本。将资源放在服务器上,通过下载更新游戏内容或者修复游戏Bug。
https://github.com/Tencent/xLua
接入XLua框架我们可以很容易的实现代码的热补丁(热更新)
热补丁
    侵入性小,老项目原有代码不做任何调整就可使用。 运行时影响小,不打补丁基本和原来程序一样。 出问题了可以用Lua来打补丁,这是才走lua代码逻辑

XLua热补丁使用方式

1.打开该特性

添加HOTFIX_ENABLE宏,(在Unity3D的File->Build Setting->Scripting Define Symbols下添加)。编辑器、各手机平台这个宏要分别设置!如果是自动化打包,要注意在代码里头用API设置的宏是不生效的,需要在编辑器设置。
建议平时开发业务代码不打开HOTFIX_ENABLE,只在build手机版本或者要在编译器下开发补丁时打开HOTFIX_ENABLE)


2、执行XLua/Generate Code菜单。

3、注入。

构建手机包这个步骤会在构建时自动进行,编辑器下开发补丁需要手动执行"XLua/Hotfix Inject In Editor"菜单。打印“hotfix inject finish!”或者“had injected!”才算成功,否则会打印错误信息。
如果已经打印了“hotfix inject finish!”或者“had injected!”,执行xlua.hotfix仍然报类似“xlua.access, no field __Hitfix0_Update”的错误,要么是该类没配置到Hotfix列表,要么是注入成功后,又触发了编译,覆盖了注入结果。
约束

    不支持静态构造函数。 目前只支持Assets下代码的热补丁,不支持引擎,c#系统库的热补丁。

API

xlua.hotfix(class, [method_name], fix)

    描述:注入lua补丁 class : C#类,两种表示方法,CS.Namespace.TypeName或者字符串方式"Namespace.TypeName",字符串格式和C#的Type.GetType要求一致,如果是内嵌类型(Nested Type)是非Public类型的话,只能用字符串方式表示"Namespace.TypeName+NestedTypeName"; method_name : 方法名,可选; fix:如果传了method_name,fix将会是一个function,否则通过table提供一组函数。table的组织按key是method_name,value是function的方式。
util.hotfix_ex(class, method_name, fix)

    描述 : xlua.hotfix的增强版本,可以在fix函数里头执行原来的函数,缺点是fix的执行会略慢。 method_name : 方法名; fix : 用来替换C#方法的lua function。

打补丁

客户端测试过程中,只要修改了热补丁的代码,都需要重新进行hotfix注入
xlua可以用lua函数来替换C#的构造函数,函数,属性。事件的替换。lua实现都是函数,比如属性对于一个getter函数和一个setter函数,事件对应一个add函数和一个remove函数。
函数
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using XLua;
  5. [Hotfix]
  6. public class HotfixCalc : MonoBehaviour
  7. {
  8.     LuaEnv luaenv = new LuaEnv();
  9.     void Start()
  10.     {
  11.         luaenv.DoString(@"
  12. xlua.hotfix(CS.HotfixCalc, 'Add', function(self, a, b)
  13.     return a + b
  14. end)
  15. xlua.hotfix(CS.HotfixCalc, 'staticAdd', function( a, b)
  16.     return a + b
  17. end)
  18.             ");
  19.         Debug.Log( "Add "+ Add(1, 1));
  20.         Debug.Log("Add " + Add( Vector3.one, Vector3.one));
  21.         Debug.Log("staticAdd " + HotfixCalc.staticAdd(1,1));
  22.     }
  23.     public int Add(int a, int b)
  24.     {
  25.         return a - b;
  26.     }
  27.     public Vector3 Add(Vector3 a, Vector3 b)
  28.     {
  29.         return a - b;
  30.     }
  31.     public static int staticAdd(int a, int b)
  32.     {
  33.         return a - b;
  34.     }
  35. }
复制代码
输出


    静态函数和成员函数的区别是,成员函数会加一个self,参数,这个self在Stateless方式下是C#对象本身(对应C#的this) 普通参数对于lua的参数,ref参数对应lua的一个参数和一个返回值,out参数对于lua的一个返回值。
构造函数

    构造函数对应的method_name是‘.ctor’。 和普通函数不一样是,构造函数的热补丁并不是替换,而是执行原有逻辑后调用lua
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using XLua;
  5. [Hotfix]
  6. public class Hotfix构造函数 : MonoBehaviour
  7. {
  8.     LuaEnv luaenv = new LuaEnv();
  9.     void Start()
  10.     {
  11.         HotfixTestClass1 oldTest = new HotfixTestClass1();
  12.         luaenv.DoString(@"
  13. xlua.hotfix(CS.HotfixTestClass1, '.ctor', function()
  14.     print('新构造函数')
  15. end)
  16.             ");
  17.         Debug.Log("------------------");
  18.         HotfixTestClass1 newTest = new HotfixTestClass1();
  19.     }
  20. }
  21. [Hotfix]
  22. public class HotfixTestClass1
  23. {
  24.     public HotfixTestClass1()
  25.     {
  26.         Debug.Log("构造函数");
  27.     }
  28. }
复制代码


析构函数

method_name是"Finalize",传一个self参数。
和普通函数不一样的是,析构函数的热补丁并不是替换,而是开头调用lua函数后继续原有逻辑。
属性
  1. using UnityEngine;
  2. using XLua;
  3. [Hotfix]
  4. public class HotfixGetSet : MonoBehaviour
  5. {
  6.     public int age;
  7.     public int Age
  8.     {
  9.         get { return age; }
  10.         set { age = value; }
  11.     }
  12.     LuaEnv luaenv = new LuaEnv();
  13.     void Start()
  14.     {
  15.         //get_属性名
  16.         // set_属性名
  17.         luaenv.DoString(@"
  18.                 xlua.hotfix(CS.HotfixGetSet, 'get_Age', function()
  19.                     return 10
  20.                 end)
  21.                                                                                  
  22.                 xlua.hotfix(CS.HotfixGetSet, 'set_Age', function(self, value) -- value: 传入的值
  23.                     print('set 属性')
  24.                 end)
  25.             ");
  26.         Debug.Log(Age);
  27.         Age = 10;
  28.     }
  29. }
复制代码


[]操作符

赋值对应set_Item,取值对应get_Item。第一个参数是self,赋值后面跟key,value,取值只有key参数,返回值是取出的值。
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using XLua;
  5. [Hotfix]
  6. public class HotfixIndex : MonoBehaviour
  7. {
  8.     public int[] test = { 77, 88, 99, 100 };
  9.     public int this[int index]
  10.     {
  11.         get { return test[index]; }
  12.         set { test[index] = value; }
  13.     }
  14.     void Start()
  15.     {
  16.         LuaEnv luaenv = new LuaEnv();
  17.         luaenv.DoString(@"
  18.             -- 索引器
  19.             --set_Item  get_Item
  20.                 xlua.hotfix(CS.HotfixIndex, 'set_Item', function(self, index, v)
  21.                         print( tostring(index) .. '  ' .. tostring(v))
  22.                         self.test[index] = v
  23.                 end)
  24.                 xlua.hotfix(CS.HotfixIndex, 'get_Item', function(self, index)
  25.                         return self.test[index]
  26.                 end)
  27.             ");
  28.         this[1] = 20000;
  29.         Debug.Log(this[1]);
  30.     }
  31. }
复制代码


其它操作符

C#的操作符都有一套内部表示,比如+号的操作符函数名是op_Addition(其它操作符的内部表示可以去请参照相关资料),覆盖这函数就覆盖了C#的+号操作符。
事件

比如对于事件“AEvent”,+=操作符是add_AEvent,-=对应的是remove_AEvent。这两个函数均是第一个参数是self,第二个参数是操作符后面跟的delegate。
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Events;
  5. using XLua;
  6. [Hotfix]
  7. [LuaCallCSharp]
  8. public class HotfixAction : MonoBehaviour
  9. {
  10.     event UnityAction myEvent;
  11.     void Start()
  12.     {
  13.         LuaEnv luaenv = new LuaEnv();
  14.         luaenv.DoString(@"
  15.                 xlua.hotfix(CS.HotfixAction, 'add_myEvent', function(self, del)
  16. --会去去尝试使用lua使用C#事件的方法去添加
  17. --在事件加减的重定向lua函数中
  18. --千万不要把传入的委托往事件里存
  19. --否则会死循环
  20. -- self:myEvent('+', del)  !!!!!!
  21. --如果想重定向,肯定是希望把逻辑让lua处理
  22. --所以一定是把传入的函数,存在lua的容器中
  23.                     print('add_myEvent')               
  24.                 end)
  25.                 xlua.hotfix(CS.HotfixAction, 'remove_myEvent', function(self, del)
  26.                     print('remove_myEvent')
  27.                 end)
  28.             ");
  29.         myEvent += Test;
  30.     }
  31.     private void Test()
  32.     {
  33.         Debug.Log("xxxxxxxxxxxxxxx");
  34.     }
  35. }
复制代码


泛化类型

其它规则一致,需要说明的是,每个泛化类型实例化后都是一个独立的类型,只能针对实例化后的类型分别打补丁。比如
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using XLua;
  5. [Hotfix]
  6. public class Hotfix泛型 : MonoBehaviour
  7. {
  8.     // Start is called before the first frame update
  9.     void Start()
  10.     {
  11.         LuaEnv luaenv = new LuaEnv();
  12.         luaenv.DoString(@"
  13.         xlua.hotfix(CS.HotfixTest(CS.System.Int32), 'Test', function(self, str)
  14.             print(' lua 中打的补丁 ' .. str )
  15.         end)
  16.             xlua.hotfix(CS.HotfixTest(CS.System.String), 'Test', function(self, str)
  17.             print(' lua 中打的补丁 ' .. str )
  18.         end)
  19. ");
  20.         HotfixTest<int> hotfixTest = new HotfixTest<int>();
  21.         hotfixTest.Test(1);
  22.         HotfixTest<string> hotfixTest1 = new HotfixTest<string>();
  23.         hotfixTest1.Test("string");
  24.         HotfixTest<float> hotfixTest2 = new HotfixTest<float>();
  25.         hotfixTest2.Test(1.5f);
  26.     }
  27. }
  28. [Hotfix]
  29. public class HotfixTest<T>
  30. {
  31.     public void Test(T str)
  32.     {
  33.         Debug.Log("该泛型未修改" + str);
  34.     }
  35. }
复制代码


Unity协程

通过util.cs_generator 可以用一个function模拟一个Ienumerator,在里头用coroutine.yield,就类似C#里头yield return 。比如下面的C#代码和对应的hotfix代码是等同效果
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using XLua;
  5. [Hotfix]
  6. public class HotFixSubClass : MonoBehaviour
  7. {
  8.     void Awake()
  9.     {
  10.         LuaEnv luaenv = new LuaEnv();
  11.         luaenv.DoString(@"
  12.             -- xlua.hotfix(类, { 函数名 = 函数, 函数名 = 函数.....})
  13.             local util = require 'xlua.util'   -- 要在lua中配合C#协程函数 必须使用它
  14.             xlua.hotfix(CS.HotFixSubClass ,{
  15.                     Start = function(self)
  16.                     return util.cs_generator(function()
  17.                         while true do
  18.                             coroutine.yield(CS.UnityEngine.WaitForSeconds(3))
  19.                             print('Wait for 3 seconds')
  20.                         end
  21.                     end)
  22.                 end;
  23.                 })
  24.         ");
  25.     }
  26.     IEnumerator Start()
  27.     {
  28.         while (true)
  29.         {
  30.             yield return new WaitForSeconds(3);
  31.             Debug.Log("Wait for 3 seconds");
  32.         }
  33.     }
  34. }
复制代码


整个类

如果要替换整个类,或者一个类中的多个方法,不需要一次次的调用xlua.hotfix去替换,可以整个一次完成。只要给一个table,按method_name = function组织即可
-- xlua.hotfix(类, { 函数名 = 函数, 函数名 = 函数.....})
  1. xlua.hotfix(CS.StatefullTest, {
  2.     ['.ctor'] = function(csobj)
  3.         return util.state(csobj, {evt = {}, start = 0, prop = 0})
  4.     end;
  5.     set_AProp = function(self, v)
  6.         print('set_AProp', v)
  7.         self.prop = v
  8.     end;
  9.     get_AProp = function(self)
  10.         return self.prop
  11.     end;
  12.     get_Item = function(self, k)
  13.         print('get_Item', k)
  14.         return 1024
  15.     end;
  16.     set_Item = function(self, k, v)
  17.         print('set_Item', k, v)
  18.     end;
  19.     add_AEvent = function(self, cb)
  20.         print('add_AEvent', cb)
  21.         table.insert(self.evt, cb)
  22.     end;
  23.     remove_AEvent = function(self, cb)
  24.        print('remove_AEvent', cb)
  25.        for i, v in ipairs(self.evt) do
  26.            if v == cb then
  27.                table.remove(self.evt, i)
  28.                break
  29.            end
  30.        end
  31.     end;
  32.     Start = function(self)
  33.         print('Start')
  34.         for _, cb in ipairs(self.evt) do
  35.             cb(self.start, 2)
  36.         end
  37.         self.start = self.start + 1
  38.     end;
  39.     StaticFunc = function(a, b, c)
  40.        print(a, b, c)
  41.     end;
  42.     GenericTest = function(self, a)
  43.        print(self, a)
  44.     end;
  45.     Finalize = function(self)
  46.        print('Finalize', self)
  47.     end
  48. })
复制代码
手游为什么要热更新,C#为什么不能热更新,LUA为什么可以 - 时空观察者9号 - 博客园
xLua/hotfix.md at master · Tencent/xLua · GitHub

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-1 09:00 , Processed in 0.137588 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表