XGundam05 发表于 2023-4-11 07:38

Unity 通用框架搭建(十)——使用Xlua热更新

前言

热更新,是游戏开发中十分重要的一个环节。热更新,指用户不重启应用的前提下能实现代码逻辑的更新。因此,本文主要介绍腾讯开源的热更新方案Xlua的使用,以及在项目框架中对Xlua进行支持。
Xlua 教程

Lua代码的本质实际上是一个字符串,Xlua插件让系统能够去执行这段代码并且和C#之间能够相互通信。
1、加载Lua文件

对Unity游戏而言,可以通过资源加载的方式加载TextAsset资源后执行lua代码:
publicvoidDoScript(string luaName){TextAsset luaScript = AssetsManager.Instance.LoadAsset<TextAsset>(luaName);var scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
      meta.Set("__index", luaEnv.Global);
      scriptEnv.SetMetaTable(meta);
      scriptEnv.Set("LuaTable", scriptEnv);
      scriptEnv.Set("LuaName", luaName);
      meta.Dispose();
      luaEnv.DoString(luaScript.text, luaScript.name, scriptEnv);}同样也可以通过require 方法加载lua文件,require实际上是调用一个个的loader去加载,有一个加载成功就不继续往下尝试,全失败则找不到文件。因此,我们可以自定义加载的loader:
/// <summary>/// 自定义加载/// </summary>/// <param name="file">lua代码</param>/// <returns></returns>publicbyte[]AddLoader(refstring file){TextAsset luaScript = AssetsManager.Instance.LoadAsset<TextAsset>(file);return System.Text.Encoding.UTF8.GetBytes(luaScript.text);}2、C# 访问Lua

在C# 中可以通过泛型方法 luaenv.Global.Get(str)获取Lua中全局变量的值,
Lua调用侧的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。
LuaTable表的映射:
映射到普通的class或struct。这种映射方式属于值拷贝,因此Lua或C#中对值进行改变相互不会有影响映射到接口interface。这种方式需要生产依赖代码,属于引用拷贝,Lua或C#中对值的修改会相互影响对于轻量级的table表可以直接映射为Dictionary<>或者List<>直接映射到LuaTable类。这种方式不需要生成代码,但是比接口映射的方式慢一个数量级,没有类型检查
Lua中function的映射
映射到delegate,官方建议采用这种方式,性能好,而且类型安全,但是要生成代码。对于多返回值的,用out 或者 ref参数接收映射到LuaFunction,不用生成代码,但是性能不好,类型不安全。
3、Lua访问C#

Lua中没有new关键字,所有C#相关的都放到CS下,包括构造函数,静态成员属性、方法如在Lua中新建一个对象:
local obj=CS.UnityEngine.GameObject("")上述lua代码就等同于在C#中的
GameObject obj=newGameObject("")对于一些静态类,可以使用全局或者全局变量先引用后访问,减少代码量,还能提高性能:
local GameObject = CS.UnityEngine.GameObject
GameObject.Find('')输入输出参数规则:
Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref 修饰的算一个输入参数,out 参数不
算,然后从左往右对应lua调用侧的实参列表。
4、XLua的配置

打标签。
Xlua用白名单来指明生成哪些代码,通过白名单attribute来配置,如从lua想调用C#中的某个类,希望生成适配代码,就打上LuaCallCSharp标签
publicclassLuaManager{}2、对于一些系统类和第三方组件我们可以通过静态列表来打标签。在项目中配置Xlua导出文件配置。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Reflection;usingUnityEngine;usingUnityEngine.Events;usingUnityEngine.Playables;usingUnityEngine.UI;usingXLua;/// <summary>/// xlua自定义导出/// </summary>publicstaticclassXLuaCustomExport{/// <summary>/// dotween的扩展方法在lua中调用/// </summary>publicstaticList<Type> dotween_lua_call_cs_list =newList<Type>(){typeof(DG.Tweening.AutoPlay),typeof(DG.Tweening.AxisConstraint),typeof(DG.Tweening.Ease),typeof(DG.Tweening.LogBehaviour),typeof(DG.Tweening.LoopType),typeof(DG.Tweening.PathMode),typeof(DG.Tweening.PathType),typeof(DG.Tweening.RotateMode),typeof(DG.Tweening.ScrambleMode),typeof(DG.Tweening.TweenType),typeof(DG.Tweening.UpdateType),typeof(DG.Tweening.DOTween),typeof(DG.Tweening.DOVirtual),typeof(DG.Tweening.EaseFactory),typeof(DG.Tweening.Tweener),typeof(DG.Tweening.Tween),typeof(DG.Tweening.Sequence),typeof(DG.Tweening.TweenParams),typeof(DG.Tweening.Core.ABSSequentiable),typeof(DG.Tweening.Core.TweenerCore<Vector3, Vector3, DG.Tweening.Plugins.Options.VectorOptions>),typeof(DG.Tweening.TweenCallback),typeof(DG.Tweening.TweenExtensions),typeof(DG.Tweening.TweenSettingsExtensions),typeof(DG.Tweening.ShortcutExtensions),typeof(PlayableDirector),//typeof(DG.Tweening.ShortcutExtensions43),//typeof(DG.Tweening.ShortcutExtensions46),//typeof(DG.Tweening.ShortcutExtensions50),//dotween pro 的功能//typeof(DG.Tweening.DOTweenPath),//typeof(DG.Tweening.DOTweenVisualManager),};}XLua.ReflectionUse
一个C#类型类型加了这个配置,xLua会生成link.xml阻止il2cpp的代码剪裁。
对于扩展方法,必须加上LuaCallCSharp或者ReflectionUse才可以被访问到。
建议所有要在Lua访问的类型,要么加LuaCallCSharp,要么加上ReflectionUse,这才能够保证在各平台都能正常运行。
Unity项目中使用Xlua

在游戏开发中有部分经常更改的逻辑会采用Lua来进行开发,因此我们可以LuaBehaviour继承Monobehaviour,讲关键的生命周期函数映射到Lua,那样就可以使用Lua来进行一些纯Lua的开发工作。
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingXLua;usingSystem;publicclassInjection{publicstring name;publicGameObjectvalue;}publicclassInjectionLuaScript{publicstring name;publicLuaBehaviourvalue;}publicdelegatevoidCSCallLuaAction(paramsobject[] args);publicdelegateobject[]CallLuaFunction(paramsobject[] args);/// <summary>/// 纯Lua开发使用/// </summary>publicclassLuaBehaviour:MonoBehaviour{privateTextAsset luaScript;privateInjection[] injections;privateInjectionLuaScript[] otherScript;privateAction luaAwake;privateAction luaStart;privateAction luaEnable;privateAction luaUpdate;privateAction luaDisable;privateAction luaDestory;publicLuaTable scriptEnv;privatevoidAwake(){var luaEnv = LuaManager.Instance.luaEnv;
      scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
      meta.Set("__index", luaEnv.Global);
      scriptEnv.SetMetaTable(meta);
      meta.Dispose();

      scriptEnv.Set("self",this);foreach(var injection in injections){
            scriptEnv.Set(injection.name, injection.value);}foreach(var injection in otherScript){
            scriptEnv.Set(injection.name, injection.value);}
      luaEnv.DoString(luaScript.text, luaScript.name, scriptEnv);

      luaAwake = scriptEnv.Get<Action>("Awake");
      scriptEnv.Get("Start",out luaStart);
      scriptEnv.Get("OnEnable",out luaEnable);
      scriptEnv.Get("Update",out luaUpdate);
      scriptEnv.Get("OnDisable",out luaDisable);
      scriptEnv.Get("OnDestroy",out luaDestory);if(luaAwake !=null){luaAwake();}}// Start is called before the first frame updatevoidStart(){if(luaStart !=null){luaStart();}}privatevoidOnEnable(){if(luaEnable !=null){luaEnable();}}// Update is called once per framevoidUpdate(){if(luaUpdate !=null){luaUpdate();}}privatevoidOnDisable(){if(luaDisable !=null){luaDisable();}}publicvoidCallLuaFunction(string name,paramsobject[] args){var call = scriptEnv.Get<CSCallLuaAction>(name);if(call !=null){call(args);}}publicvoidCallLuaFunction(string name){var call = scriptEnv.Get<CSCallLuaAction>(name);if(call !=null){call(null);}}privatevoidOnDestroy(){if(luaDestory !=null){luaDestory();}
      luaAwake =null;
      luaStart =null;
      luaEnable =null;
      luaUpdate =null;
      luaDisable =null;
      luaDestory =null;
      scriptEnv.Dispose();
      injections =null;}}同样对于UI窗口组件我们可以在基类UIBase中添加对Lua的支持。
usingUnityEngine;usingXLua;usingSystem;publicclassLuaBase:UIBase{publicTextAsset luaScript;publicdelegatevoidCSCallLuaDelegate(paramsobject[] args);privateInjection[] injections;privateInjectionLuaScript[] otherScript;privateAction luaInit;privateCSCallLuaDelegate luaShow;privateCSCallLuaDelegate luaHide;privateAction luaStart;privateAction luaEnable;privateAction luaUpdate;privateAction luaDisable;privateAction luaDestory;publicLuaTable scriptEnv;privatebool isInitLua =false;publicoverridevoidInit(){base.Init();initLua();}privatevoidinitLua(){if(!isInitLua){var luaEnv = LuaManager.Instance.luaEnv;
            scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
            meta.Set("__index", luaEnv.Global);
            scriptEnv.SetMetaTable(meta);
            meta.Dispose();

            scriptEnv.Set("self",this);foreach(var injection in injections){
                scriptEnv.Set(injection.name, injection.value);}foreach(var injection in otherScript){
                scriptEnv.Set(injection.name, injection.value);}
            luaEnv.DoString(luaScript.text, luaScript.name, scriptEnv);
            luaInit = scriptEnv.Get<Action>("Init");
            luaShow = scriptEnv.Get<CSCallLuaDelegate>("Show");
            luaHide = scriptEnv.Get<CSCallLuaDelegate>("Hide");
            luaStart= scriptEnv.Get<Action>("Start");
            luaEnable = scriptEnv.Get<Action>("OnEnable");
            luaUpdate = scriptEnv.Get<Action>("Update");
            luaDisable = scriptEnv.Get<Action>("OnDisable");
            luaDestory = scriptEnv.Get<Action>("OnDestroy");}if(luaInit !=null){luaInit();}}voidStart(){if(luaStart !=null){luaStart();}}privatevoidOnEnable(){if(luaEnable !=null){luaEnable();}if(luaUpdate!=null){
         Scheduler.Instance.UpdateEvent += luaUpdate;}}publicoverridevoidShow(paramsobject[] args){if(luaShow !=null){luaShow(args);}base.Show(args);}publicoverridevoidHide(paramsobject[] args){if(luaHide !=null){luaHide(args);}base.Hide(args);}privatevoidOnDisable(){if(luaDisable !=null){luaDisable();}if(luaUpdate !=null){
            Scheduler.Instance.UpdateEvent -= luaUpdate;}}publicvoidCallLuaFunction(string name,paramsobject[] args){var call = scriptEnv.Get<CSCallLuaDelegate>(name);if(call !=null){call(args);}}publicvoidCallLuaFunction(string name){var call = scriptEnv.Get<CSCallLuaDelegate>(name);if(call !=null){call();}}privatevoidOnDestroy(){if(luaDestory !=null){luaDestory();}
      luaInit =null;
      luaStart =null;
      luaEnable =null;
      luaUpdate =null;
      luaDisable =null;
      luaDestory =null;
      luaShow =null;
      luaHide =null;
      scriptEnv.Dispose();
      injections =null;}}这样有一些常用切频繁修改的窗口我们甚至可以使用纯Lua的开发方式。
页: [1]
查看完整版本: Unity 通用框架搭建(十)——使用Xlua热更新