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

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

[复制链接]
发表于 2023-4-11 07:38 | 显示全部楼层 |阅读模式
前言

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

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

对Unity游戏而言,可以通过资源加载的方式加载TextAsset资源后执行lua代码:
  1. publicvoidDoScript(string luaName){TextAsset luaScript = AssetsManager.Instance.LoadAsset<TextAsset>(luaName);var scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
  2.         meta.Set("__index", luaEnv.Global);
  3.         scriptEnv.SetMetaTable(meta);
  4.         scriptEnv.Set("LuaTable", scriptEnv);
  5.         scriptEnv.Set("LuaName", luaName);
  6.         meta.Dispose();
  7.         luaEnv.DoString(luaScript.text, luaScript.name, scriptEnv);}
复制代码
同样也可以通过require 方法加载lua文件,require实际上是调用一个个的loader去加载,有一个加载成功就不继续往下尝试,全失败则找不到文件。因此,我们可以自定义加载的loader
  1. /// <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中新建一个对象:
  1. local obj=CS.UnityEngine.GameObject("[Asset Pool]")
复制代码
上述lua代码就等同于在C#中的
  1. GameObject obj=newGameObject("[Asset Pool]")
复制代码
对于一些静态类,可以使用全局或者全局变量先引用后访问,减少代码量,还能提高性能:
  1. local GameObject = CS.UnityEngine.GameObject
  2. GameObject.Find('[Asset Pool]')
复制代码
输入输出参数规则:
Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref 修饰的算一个输入参数,out 参数不
算,然后从左往右对应lua调用侧的实参列表。
4、XLua的配置

    打标签。
    Xlua用白名单来指明生成哪些代码,通过白名单attribute来配置,如从lua想调用C#中的某个类,希望生成适配代码,就打上LuaCallCSharp标签
  1. [LuaCallCSharp]publicclassLuaManager{}
复制代码
2、对于一些系统类和第三方组件我们可以通过静态列表来打标签。在项目中配置Xlua导出文件配置。
  1. usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Reflection;usingUnityEngine;usingUnityEngine.Events;usingUnityEngine.Playables;usingUnityEngine.UI;usingXLua;/// <summary>/// xlua自定义导出/// </summary>publicstaticclassXLuaCustomExport{/// <summary>/// dotween的扩展方法在lua中调用/// </summary>[LuaCallCSharp][ReflectionUse]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的开发工作。
  1. usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingXLua;usingSystem;[System.Serializable]publicclassInjection{publicstring name;publicGameObjectvalue;}[System.Serializable]publicclassInjectionLuaScript{publicstring name;publicLuaBehaviourvalue;}[CSharpCallLua]publicdelegatevoidCSCallLuaAction(paramsobject[] args);[CSharpCallLua]publicdelegateobject[]CallLuaFunction(paramsobject[] args);/// <summary>/// 纯Lua开发使用/// </summary>[LuaCallCSharp]publicclassLuaBehaviour:MonoBehaviour{[SerializeField]privateTextAsset luaScript;[SerializeField]privateInjection[] injections;[SerializeField]privateInjectionLuaScript[] otherScript;privateAction luaAwake;privateAction luaStart;privateAction luaEnable;privateAction luaUpdate;privateAction luaDisable;privateAction luaDestory;publicLuaTable scriptEnv;privatevoidAwake(){var luaEnv = LuaManager.Instance.luaEnv;
  2.         scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
  3.         meta.Set("__index", luaEnv.Global);
  4.         scriptEnv.SetMetaTable(meta);
  5.         meta.Dispose();
  6.         scriptEnv.Set("self",this);foreach(var injection in injections){
  7.             scriptEnv.Set(injection.name, injection.value);}foreach(var injection in otherScript){
  8.             scriptEnv.Set(injection.name, injection.value);}
  9.         luaEnv.DoString(luaScript.text, luaScript.name, scriptEnv);
  10.         luaAwake = scriptEnv.Get<Action>("Awake");
  11.         scriptEnv.Get("Start",out luaStart);
  12.         scriptEnv.Get("OnEnable",out luaEnable);
  13.         scriptEnv.Get("Update",out luaUpdate);
  14.         scriptEnv.Get("OnDisable",out luaDisable);
  15.         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();}
  16.         luaAwake =null;
  17.         luaStart =null;
  18.         luaEnable =null;
  19.         luaUpdate =null;
  20.         luaDisable =null;
  21.         luaDestory =null;
  22.         scriptEnv.Dispose();
  23.         injections =null;}}
复制代码
同样对于UI窗口组件我们可以在基类UIBase中添加对Lua的支持。
  1. usingUnityEngine;usingXLua;usingSystem;[LuaCallCSharp]publicclassLuaBase:UIBase{publicTextAsset luaScript;[LuaCallCSharp]publicdelegatevoidCSCallLuaDelegate(paramsobject[] args);[SerializeField]privateInjection[] injections;[SerializeField]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;
  2.             scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
  3.             meta.Set("__index", luaEnv.Global);
  4.             scriptEnv.SetMetaTable(meta);
  5.             meta.Dispose();
  6.             scriptEnv.Set("self",this);foreach(var injection in injections){
  7.                 scriptEnv.Set(injection.name, injection.value);}foreach(var injection in otherScript){
  8.                 scriptEnv.Set(injection.name, injection.value);}
  9.             luaEnv.DoString(luaScript.text, luaScript.name, scriptEnv);
  10.             luaInit = scriptEnv.Get<Action>("Init");
  11.             luaShow = scriptEnv.Get<CSCallLuaDelegate>("Show");
  12.             luaHide = scriptEnv.Get<CSCallLuaDelegate>("Hide");
  13.             luaStart= scriptEnv.Get<Action>("Start");
  14.             luaEnable = scriptEnv.Get<Action>("OnEnable");
  15.             luaUpdate = scriptEnv.Get<Action>("Update");
  16.             luaDisable = scriptEnv.Get<Action>("OnDisable");
  17.             luaDestory = scriptEnv.Get<Action>("OnDestroy");}if(luaInit !=null){luaInit();}}voidStart(){if(luaStart !=null){luaStart();}}privatevoidOnEnable(){if(luaEnable !=null){luaEnable();}if(luaUpdate!=null){
  18.            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){
  19.             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();}
  20.         luaInit =null;
  21.         luaStart =null;
  22.         luaEnable =null;
  23.         luaUpdate =null;
  24.         luaDisable =null;
  25.         luaDestory =null;
  26.         luaShow =null;
  27.         luaHide =null;
  28.         scriptEnv.Dispose();
  29.         injections =null;}}
复制代码
这样有一些常用切频繁修改的窗口我们甚至可以使用纯Lua的开发方式。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-5-5 21:04 , Processed in 0.096221 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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