unity3d热更新解决方案,使用ulua插件开发的框架。
ulua插件下载地址 www.ulua.org,下面要说的是ulua的开发框架。
首先是 LuaLoader 类,它负责把一个 lua 的 table 加载起来,使此 lua 的 table 像一个 unity 的 component 一样挂在游戏对象上,代码如下:
using LuaInterface; using System; using UnityEngine; public class LuaLoader : MonoBehaviour { public string Name; LuaTable m_table; LuaFunction m_updateFunc; LuaFunction m_fixedUpdateFunc; /// <summary> /// 通过 Name 名,加载对应的 lua table,并将之“挂”在游戏对象上。 /// </summary> /// <returns>是否加载成功</returns> public bool Load() { if (string.IsNullOrEmpty(Name)) return false; m_table = LuaHelper.GetLuaTable(Name); if (m_table == null) return false; // Init lua m_table["transform"] = transform; m_table["gameObject"] = gameObject; // m_updateFunc = GetMethod("Update"); m_fixedUpdateFunc = GetMethod("FixedUpdate"); return true; } void Awake() { if (Load()) CallMethod("Awake"); else { if (!string.IsNullOrEmpty(Name)) // 如果 Name 为空,可能是 Add component throw new ArgumentNullException("Load lua table failed, no table in " + Name); } } void Start() { if (m_table == null) // 此处应为 Add component 的情况 { if (string.IsNullOrEmpty(Name)) throw new ArgumentException("string.IsNullOrEmpty(Name)"); if (!Load()) throw new ArgumentNullException("Load lua table failed, no table in " + Name); } CallMethod("Start"); } void Update() { if (m_updateFunc != null) m_updateFunc.Call(Time.deltaTime); } void FixedUpdate() { if (m_fixedUpdateFunc != null) m_fixedUpdateFunc.Call(); } void OnEnable() { CallMethod("OnEnable"); } void OnDisable() { CallMethod("OnDisable"); } void OnDestroy() { CallMethod("OnDestroy"); // 释放内存 m_table["transform"] = null; m_table["gameObject"] = null; m_table.Release(); m_table = null; if (m_updateFunc != null) m_updateFunc.Release(); if (m_fixedUpdateFunc != null) m_fixedUpdateFunc.Release(); LuaScriptMgr.Instance.LuaGC(); } LuaFunction GetMethod(string methodName) { return m_table != null ? m_table[methodName] as LuaFunction : null; } void CallMethod(string name) { var func = GetMethod(name); if (func != null) { func.Call(); func.Release(); // 释放内存 } } public LuaTable Table { get { return m_table; } } }
其次是 lua 与 c# 的交互,提供了两个帮助类,一个是 LuaHelper ,与游戏逻辑无关的方法封装在里面;另外一个是 LuaUtils, lua 中要访问 c# 代码的方法(与游戏逻辑有关的)都封装在里面。
LuaHelper 关键的几个方法代码如下:
using LuaInterface; using Resource; using System; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using UnityEngine; using UObject = UnityEngine.Object; public static class LuaHelper { #region Lua public static LuaTable GetLuaComponent(Transform transform) { if (transform == null) throw new ArgumentNullException("transform"); var loaders = transform.GetComponents<LuaLoader>(); var rightLoader = loaders.FirstOrDefault(lt => lt.Table != null); return rightLoader != null ? rightLoader.Table : null; } public static LuaTable GetLuaComponent(GameObject gameObject) { return GetLuaComponent(gameObject.transform); } public static LuaTable GetLuaComponent(Transform transform, string type) { if (transform == null) throw new ArgumentNullException("transform"); if (string.IsNullOrEmpty(type)) throw new ArgumentException("type"); var loaders = transform.GetComponents<LuaLoader>(); var rightLoader = loaders.FirstOrDefault(lt => lt.Table != null && lt.Table.name == type); return rightLoader != null ? rightLoader.Table : null; } public static LuaTable GetLuaComponent(GameObject gameObject, string type) { return GetLuaComponent(gameObject.transform, type); } public static LuaTable AddLuaComponent(GameObject gameObject, string type) { var loader = gameObject.AddComponent<LuaLoader>(); loader.Name = type; loader.Load(); return loader.Table; } /// <summary> /// 从ab包中加载table,供lua使用 /// </summary> /// <param name="name">table名</param> public static void LoadLuaTable(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name"); LuaTable table = LuaScriptMgr.Instance.GetLuaTable(name); if (table == null) { using (var loadLua = new LoadLuaHandler(name)) LuaScriptMgr.Instance.DoString(loadLua.Text); } } public static LuaTable GetLuaTable(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("name"); LuaTable table = LuaScriptMgr.Instance.GetLuaTable(name); if (table == null) { using (var loadLua = new LoadLuaHandler(name)) LuaScriptMgr.Instance.DoString(loadLua.Text); table = LuaScriptMgr.Instance.GetLuaTable(name); } return table; } public static LuaFunction GetLuaFunction(string className, string funcName) { if (string.IsNullOrEmpty(className)) throw new ArgumentException(className); if (string.IsNullOrEmpty(funcName)) throw new ArgumentException(funcName); LuaTable table = GetLuaTable(className); return table[funcName] as LuaFunction; } public static object[] CallFunction(string className, string funcName, params object[] args) { LuaFunction func = GetLuaFunction(className, funcName); if (func == null) throw new ArgumentNullException(string.Format("Cann't find lua function: {0}.{1}", className, funcName)); var returnArgs = args == null ? func.Call() : func.Call(args); func.Release(); return returnArgs; } #endregion }
另外,关于通过 lua 代码给c#打补丁的功能,是在 UIPanel OnEnable 的第一帧检测补丁和打补丁的,c#代码如下:
using LuaInterface; using System; using System.Collections.Generic; using UnityEngine; public class LuaPatchManager : IDisposable { List<Patch> m_patches; #region Singleton static LuaPatchManager s_instance; public static LuaPatchManager Instance { get { return s_instance; } } #endregion #region Patch class Patch : IDisposable { LuaFunction m_validate; LuaFunction m_correct; public Patch(LuaFunction validate, LuaFunction correct) { if (validate == null) throw new ArgumentNullException("validate"); if (correct == null) throw new ArgumentNullException("correct"); m_validate = validate; m_correct = correct; } public bool Validate(UIPanel uiPanel) { var objs = m_validate.Call(uiPanel); return (bool)objs[0]; } public void Correct(UIPanel uiPanel) { m_correct.Call(uiPanel); } #region IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } void Dispose(bool disposing) { if (disposing) { m_validate.Release(); m_validate = null; m_correct.Release(); m_correct = null; } } ~Patch() { Dispose(false); } #endregion } #endregion private LuaPatchManager(LuaTable listTable) { if (listTable == null || listTable.Values.Count <= 0) throw new ArgumentException("listTable == null || listTable.Values.Count <= 0"); m_patches = new List<Patch>(); foreach (string name in listTable.Values) { var patch = LuaHelper.GetLuaTable(name); if (patch != null) { var validateFunc = patch["Validate"] as LuaFunction; var correctFunc = patch["Correct"] as LuaFunction; if (validateFunc != null && correctFunc != null) m_patches.Add(new Patch(validateFunc, correctFunc)); patch.Release(); } } listTable.Release(); LuaScriptMgr.Instance.LuaGC(); } public static void Load() { LuaTable listTable = null; string targetFileName = "LuaPatchList"; try { listTable = LuaHelper.GetLuaTable(targetFileName); } catch { Debug.LogWarning("No file: " + targetFileName); } if (listTable != null) { if (listTable.Values.Count > 0) s_instance = new LuaPatchManager(listTable); listTable.Release(); } } public void DoPatch(UIPanel uiPanel) { if (uiPanel == null) throw new ArgumentNullException("uiPanel"); for (int i = 0; i < m_patches.Count; i++) { Patch p = m_patches[i]; if (p.Validate(uiPanel)) { p.Correct(uiPanel); break; } } } #region IDisposable public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } void Dispose(bool disposing) { if (disposing) { for (int i = 0; i < m_patches.Count; i++) m_patches[i].Dispose(); m_patches.Clear(); m_patches = null; } } ~LuaPatchManager() { Dispose(false); } #endregion }
lua代码如下:
LuaPatchList= { "MainInfoControllerPatch", };
一个补丁的例子如下:
MainInfoControllerPatch={}; local function ClickTest() TipsShowController.Show("Who are you?"); end -- 验证此uiPanel是否是希望打补丁的uiPanel -- function MainInfoControllerPatch.Validate(uiPanel) return uiPanel.transform.parent~=nil and uiPanel.transform.parent.name=="MainInfoController(Clone)"; end -- 纠正此uiPanel上的展示内容,执行方法等 -- function MainInfoControllerPatch.Correct(uiPanel) local titleLabel=uiPanel.transform:Find("LabelName"):GetComponent("UILabel"); titleLabel.text="TianJie"; local mustBuyButton=uiPanel.transform:Find("ButtonFashion"):GetComponent("UIButton"); mustBuyButton.onClick:Clear(); EventDelegate.Add(mustBuyButton.onClick,DelegateFactory.EventDelegate_Callback(ClickTest)); end