|
前言
热更新,是游戏开发中十分重要的一个环节。热更新,指用户不重启应用的前提下能实现代码逻辑的更新。因此,本文主要介绍腾讯开源的热更新方案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("[Asset Pool]")
复制代码 上述lua代码就等同于在C#中的- GameObject obj=newGameObject("[Asset Pool]")
复制代码 对于一些静态类,可以使用全局或者全局变量先引用后访问,减少代码量,还能提高性能:- local GameObject = CS.UnityEngine.GameObject
- GameObject.Find('[Asset Pool]')
复制代码 输入输出参数规则:
Lua调用侧的参数处理规则:C#的普通参数算一个输入形参,ref 修饰的算一个输入参数,out 参数不
算,然后从左往右对应lua调用侧的实参列表。
4、XLua的配置
打标签。
Xlua用白名单来指明生成哪些代码,通过白名单attribute来配置,如从lua想调用C#中的某个类,希望生成适配代码,就打上LuaCallCSharp标签
- [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>[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的开发工作。- 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;
- 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;[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;
- 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的开发方式。 |
|