资源热更新

热更新涉及资源热更新和代码热更新(其实lua代码也是资源),那接下来看看如何动态加载一个模型,然后热更成其他素材。这一部分涉及资源打包、动态创建资源等内容。

一、创建物体

为了调试的方便,先将框架配置为本地模式,待测试热更新时再改成更新模式。

  1. public const bool UpdateMode = false; //更新模式-默认关闭
  2. public const bool LuaByteMode = false; //Lua字节码模式-默认关闭
  3. public const bool LuaBundleMode = false; //Lua代码AssetBundle模式

先测试个简单的创建物体,新建一个名为go的物体,然后设置它的坐标为(1,1,1)。这段代码虽然不涉及资源加载,但能展示“把物体添加到场景中”的过程。Main.lua的代码如下:

  1. function Main()
  2. local go = UnityEngine.GameObject ('go')
  3. go.transform.position = Vector3.one
  4. end

要热更新资源,便需要制作资源。这里制作一个名为tankPrefab的坦克模型预设,然后存到Assets/Tank目录下。接下来对它做打包,然后动态加载。

二、资源打包

LuaFramework在打包方面并没有做太多的工作,我们需要手动打包。打开Assets/LuaFramework/Editor/Packager.cs,按照示例的写法,加上下面这一行:将Assets/Tank目录下的所有预设(.prefab)打包成名为tank的包。

  1. /// <summary>
  2. /// 处理框架实例包
  3. /// </summary>
  4. static void HandleExampleBundle() {
  5. string resPath = AppDataPath + "/" + AppConst.AssetDir + "/";
  6. if (!Directory.Exists(resPath)) Directory.CreateDirectory(resPath);
  7. AddBuildMap("prompt" + AppConst.ExtName, "*.prefab", "Assets/LuaFramework/Examples/Builds/Prompt");
  8. AddBuildMap("message" + AppConst.ExtName, "*.prefab", "Assets/LuaFramework/Examples/Builds/Message");
  9. AddBuildMap("prompt_asset" + AppConst.ExtName, "*.png", "Assets/LuaFramework/Examples/Textures/Prompt");
  10. AddBuildMap("shared_asset" + AppConst.ExtName, "*.png", "Assets/LuaFramework/Examples/Textures/Shared");
  11. // 坦克的 ✅
  12. AddBuildMap("tank" + AppConst.ExtName, "*.prefab", "Assets/Tank");
  13. }

点击“Build Windows Resource”,即可在StreamingAssets中看到打包好的文件。

Unity3D资源包里面包含多个资源,就像一个压缩文件一样。在动态加载的时候,便需要有加载包文件、或取包中的资源两步操作(框架已经帮我们做好了这部分工作,直接调用API即可)。

三、动态加载模型

如下图所示,Unity3D资源包里面包含多个资源,就像一个压缩文件一样。在动态加载的时候,便需要有加载包文件、或取包中的资源两步操作(框架已经帮我们做好了这部分工作,直接调用API即可)。

  1. --主入口函数。从这里开始lua逻辑
  2. function Main()
  3. LuaHelper = LuaFramework.LuaHelper;
  4. resMgr = LuaHelper.GetResManager();
  5. resMgr:LoadPrefab('tank', { 'TankPrefab' }, OnLoadFinish);
  6. end
  7. --加载完成后的回调--
  8. function OnLoadFinish(objs)
  9. local go = UnityEngine.GameObject.Instantiate(objs[0]);
  10. LuaFramework.Util.Log("Finish");
  11. end

完成后运行游戏,即可看到动态加载出来的模型。

四、加载资源的过程

只有理解了动态加载,即LoadPrefab的过程,才能算是真正的理解了热更新。LoadPrefab为ResourceManager中定义的方法,在Assets\LuaFramework\Scripts\Manager\ResourceManager.cs中实现。

  1. public void LoadPrefab(string abName, string[] assetNames, LuaFunction func) {
  2. abName = abName.ToLower();
  3. List<UObject> result = new List<UObject>();
  4. for (int i = 0; i < assetNames.Length; i++) {
  5. UObject go = LoadAsset<UObject>(abName, assetNames[i]);
  6. if (go != null) result.Add(go);
  7. }
  8. if (func != null) func.Call((object)result.ToArray());
  9. }
  10. /// <summary>
  11. /// 载入素材
  12. /// </summary>
  13. public T LoadAsset<T>(string abname, string assetname) where T : UnityEngine.Object {
  14. abname = abname.ToLower();
  15. AssetBundle bundle = LoadAssetBundle(abname);
  16. return bundle.LoadAsset<T>(assetname);
  17. }
  18. /// <summary>
  19. /// 载入AssetBundle
  20. /// </summary>
  21. /// <param name="abname"></param>
  22. /// <returns></returns>
  23. public AssetBundle LoadAssetBundle(string abname) {
  24. if (!abname.EndsWith(AppConst.ExtName)) {
  25. abname += AppConst.ExtName;
  26. }
  27. AssetBundle bundle = null;
  28. if (!bundles.ContainsKey(abname)) {
  29. byte[] stream = null;
  30. string uri = Util.DataPath + abname;
  31. Debug.LogWarning("LoadFile::>> " + uri);
  32. LoadDependencies(abname);
  33. stream = File.ReadAllBytes(uri);
  34. bundle = AssetBundle.LoadFromMemory(stream); //关联数据的素材绑定
  35. bundles.Add(abname, bundle);
  36. } else {
  37. bundles.TryGetValue(abname, out bundle);
  38. }
  39. return bundle;
  40. }
  41. /// <summary>
  42. /// 载入依赖
  43. /// </summary>
  44. /// <param name="name"></param>
  45. void LoadDependencies(string name) {
  46. if (manifest == null) {
  47. Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
  48. return;
  49. }
  50. // Get dependecies from the AssetBundleManifest object..
  51. string[] dependencies = manifest.GetAllDependencies(name);
  52. if (dependencies.Length == 0) return;
  53. for (int i = 0; i < dependencies.Length; i++)
  54. dependencies[i] = RemapVariantName(dependencies[i]);
  55. // Record and load all dependencies.
  56. for (int i = 0; i < dependencies.Length; i++) {
  57. LoadAssetBundle(dependencies[i]);
  58. }
  59. }

打包后,Unity3D会产生一个名为AssetBundle.manifest的文件(框架会将该文件放在StreamingAssets中),该文件包含所有包的依赖信息。所以在加载资源前需要先加载这个文件,m_AssetBundleManifest便是指向这个包的变量。相关代码如下:

  1. /// <summary>
  2. /// 初始化
  3. /// </summary>
  4. public void Initialize() {
  5. byte[] stream = null;
  6. string uri = string.Empty;
  7. bundles = new Dictionary<string, AssetBundle>();
  8. uri = Util.DataPath + AppConst.AssetDir;
  9. Debug.Log("uri : " + uri);
  10. if (!File.Exists(uri)) return;
  11. stream = File.ReadAllBytes(uri);
  12. assetbundle = AssetBundle.LoadFromMemory(stream);
  13. manifest = assetbundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
  14. }

加载这个包后,便可以使用下面的语句获取某个包所依赖的所有包名,然后加载它们。

  1. string[] dependencies = manifest.GetAllDependencies(name);

字典类型的bundles保存了所有已经加载资源包。如果某个包已经被加载过,那下次需要用到它时,直接从字典中取出即可,减少重复加载。简化后的代码如下:

  1. /// <summary>
  2. /// 载入AssetBundle
  3. /// </summary>
  4. /// <param name="abname"></param>
  5. /// <returns></returns>
  6. public AssetBundle LoadAssetBundle(string abname) {
  7. if (!abname.EndsWith(AppConst.ExtName)) {
  8. abname += AppConst.ExtName;
  9. }
  10. AssetBundle bundle = null;
  11. if (!bundles.ContainsKey(abname)) {
  12. byte[] stream = null;
  13. string uri = Util.DataPath + abname;
  14. Debug.LogWarning("LoadFile::>> " + uri);
  15. LoadDependencies(abname);
  16. stream = File.ReadAllBytes(uri);
  17. bundle = AssetBundle.LoadFromMemory(stream); //关联数据的素材绑定
  18. bundles.Add(abname, bundle);
  19. } else {
  20. bundles.TryGetValue(abname, out bundle);
  21. }
  22. return bundle;
  23. }

五、资源热更新

“资源热更新”和上一篇的“代码热更新”完全相同,开启更新模式后,将新的资源文件复制到服务器上,框架即可自动下载更新的资源。这里不再复述。

?