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

unity assetbundle + xlua 热更框架

[复制链接]
发表于 2023-4-8 20:58 | 显示全部楼层 |阅读模式
assetbundle打包

打包使用assetbundle browser 插件即可,具体打包流程可参考Unity3d的AssetBundle打包,这个工具的好处在于方便打包,能更好的解决资源依赖复用的问题。

打包lua文件

unity是默认不支持.lua文件类型的,所以也不能对后缀为.lua的文件打包,但是unity官方是有相关接口的。具体实现代码如下ST_LuaImporter.cs
  1. [ScriptedImporter(1,".lua")]publicclassST_LuaImporter:ScriptedImporter{publicoverridevoidOnImportAsset(AssetImportContext ctx){var luaTxt = File.ReadAllText(ctx.assetPath);var assetText =newTextAsset(luaTxt);
  2.         ctx.AddObjectToAsset("main obj", assetText);//将对象assetText作为导入操作的主要对象。
  3.         ctx.SetMainObject(assetText);}}
复制代码
将此代码放到Editor目录下即可正常打包lua文件。

开始打包

创建一个Cube预制体并给他绑定一个Lua脚本,设置他的bundle标签model


至于Cube对象是如何绑定TestMono脚本的,可以参见文章Unity + XLua 简单框架搭建的详细过程,这里不做过多描述
TestMono.lua文件内容如下
  1. local TestMono =class()-- 自定义数据-- 构造函数function TestMono:Ctor(params)end-- 开始函数function TestMono:Start()endfunction TestMono:Update()--self.transform:Rotate(CS.UnityEngine.Vector3(0,1,0));
  2.     self.transform:Rotate(CS.UnityEngine.Vector3(1,0,0));endreturn TestMono
复制代码
同样给TestMono脚本打上lua标签


开始打包


打包输出路径是自定的,可上传至服务器方便热更新,这个后面介绍。压缩方式随意,勾选拷贝到StreamAssets方便本地测试(如果用服务器数据,那么不勾选)。打包成功如下


可以看到除了lua包和model包还有一个StandloneWindows包,这个包里面包含了bundle各个模块的信息以及依赖。Md5FileList.txt为了方便后面做热更对比文件对比而生成的,打包时生成这个文件需要修改assetbundle browser源码,具体修改如下


assetbundle browser找到AssetBundleBuildTab文件,添加以下代码
  1. /// <summary>/// Create FileList/// </summary>voidCreateFileList(){string filePath = m_UserData.m_OutputPath +"\\Md5FileList.txt";if(File.Exists(filePath)){
  2.          File.Delete(filePath);}StreamWriter streamWriter =newStreamWriter(filePath);string[] files = Directory.GetFiles(m_UserData.m_OutputPath);for(int i =0; i < files.Length; i++){string tmpfilePath = files[i];if(tmpfilePath.Equals(filePath)|| tmpfilePath.EndsWith(".manifest"))continue;
  3.          Debug.Log(tmpfilePath);
  4.          tmpfilePath.Replace("\","/");
  5.          streamWriter.WriteLine(tmpfilePath.Substring(m_UserData.m_OutputPath.Length +1)+"|"+GetFileMD5(tmpfilePath));}
  6.      streamWriter.Close();
  7.      streamWriter.Dispose();}/// <summary>/// 获取文件的MD5/// </summary>stringGetFileMD5(string filePath){System.Security.Cryptography.MD5 MD5 =newSystem.Security.Cryptography.MD5CryptoServiceProvider();System.Text.StringBuilder stringBuilder =newSystem.Text.StringBuilder();FileStream fileStream =newFileStream(filePath, FileMode.Open);byte[] bytes = MD5.ComputeHash(fileStream);
  8.      fileStream.Close();for(int i =0; i < bytes.Length; i++){
  9.          stringBuilder.Append(bytes[i].ToString("x2"));}return stringBuilder.ToString();}
复制代码

在Build 事件里加上函数即可。这样就可以在打包的同时生成包体MD5,Md5FileList.txt内容如下,格式是包名加生成的16进制哈希码。



加载本地Bundle资源测试
  1. /// <summary>/// 加载资源/// </summary>/// <typeparam name="T">资源类型</typeparam>/// <param name="assetBundleName">资源名</param>/// <param name="assetBundleGroupName">组名</param>/// <returns></returns>publicTLoadResource<T>(string assetBundleName,string assetBundleGroupName)whereT:UnityEngine.Object{if(string.IsNullOrEmpty(assetBundleGroupName)){returndefault(T);}if(!DicAssetBundle.TryGetValue(assetBundleGroupName,out assetbundle)){
  2.         assetbundle = AssetBundle.LoadFromFile(GetStreamingAssetsPath()+ assetBundleGroupName);
  3.         DicAssetBundle.Add(assetBundleGroupName, assetbundle);}object obj = assetbundle.LoadAsset(assetBundleName,typeof(T));var one = obj asT;return one;}/// <summary>/// 卸载资源组/// </summary>/// <param name="assetBundleGroupName">组名</param>publicvoidUnLoadResource(string assetBundleGroupName){if(DicAssetBundle.TryGetValue(assetBundleGroupName,out assetbundle)){
  4.         Debug.Log(assetBundleGroupName);
  5.         assetbundle.Unload(false);if(assetbundle !=null){
  6.             assetbundle =null;}
  7.         DicAssetBundle.Remove(assetBundleGroupName);
  8.         Resources.UnloadUnusedAssets();}}publicstringGetStreamingAssetsPath(){string StreamingAssetsPath =#if UNITY_EDITOR || UNITY_STANDALONE_WIN
  9.     Application.streamingAssetsPath +"/";#elif UNITY_ANDROID"jar:file://"+ Application.dataPath +"!/assets/";#elif UNITY_IPHONE
  10.     Application.dataPath +"/Raw/";#elsestring.Empty;#endifreturn StreamingAssetsPath;}
复制代码
修改Lua自定义Loader,require时使用Bundle资源
ST_LuaManger.cs
  1. publicstaticbyte[]LuaLoader(refstring filename){TextAsset ta = ST_BundleManager.Instance.LoadResource<TextAsset>(filename,"lua");return System.Text.Encoding.UTF8.GetBytes(ta.text);}//移除requier(lua环境只会Require 文件一次,移除后会从新require)//require 会调用自定义LoaderpublicvoidRemoveRequire(string name){
  2.     MyLuaEnv.DoString($@"
  3.     for key, _ in pairs(package.preload) do
  4.         if string.find(tostring(key),'{name}') == 1 then
  5.             package.preload[key] = nil
  6.         end
  7.     end
  8.     for key, _ in pairs(package.loaded) do
  9.         if string.find(tostring(key), '{name}') == 1 then
  10.             package.loaded[key] = nil
  11.         end
  12.     end");}
复制代码
测试脚本
  1. if(GUILayout.Button("Cube")){GameObject cube = ST_BundleManager.Instance.LoadResource<GameObject>("Cube","model");Instantiate(cube,newVector3(0,y++,0),Quaternion.identity);}
复制代码
测试结果


可以看到资源被正确加载了。

远程资源下载与热更

第一次使用服务器数据的时候要清空本地StreamAsset文件夹。热更新的思路是对比本地的Md5FileList.txt,和服务器上的,如果有md5不同的包,或者新的包那么就请求下载,并保存到本地(也可以用先对比版本号的方法。可以将打包的资源放到了[GitCode]方便下载(https://about.gitcode.net/)


右键点击这个按钮即可复制原始下载链接,亲测可用
ST_AssetBundleManager.cs 完整代码
  1. usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Threading.Tasks;usingUnityEditor;usingUnityEngine;usingUnityEngine.Networking;publicstaticclassBundleConfig{publicstaticreadonlystring AssetBundlePath ="https://。。。。";publicstaticreadonlystring Md5FileName ="Md5FileList.txt";publicstaticreadonlystring OnFireUpdate ="OnFireUpdate";publicstaticreadonlystring OnStarDownLoad ="OnStarDownLoad";publicstaticreadonlystring OnStarSave ="OnStarSave";publicstaticreadonlystring OnUpdateEnd ="OnUpdateEnd";}publicclassST_BundleManager:MonoBehaviour{staticbool _isDesdroy =false;privatestaticreadonlyobject padlock =newobject();privatestaticST_BundleManager instance;staticAssetBundle assetbundle =null;staticDictionary<string, AssetBundle> DicAssetBundle;publicstaticST_BundleManager Instance
  2.     {get{if(_isDesdroy){returnnull;}if(instance ==null){lock(padlock){if(instance ==null){GameObject go =newGameObject("AssetBundleManager");DontDestroyOnLoad(go);
  3.                         instance = go.AddComponent<ST_BundleManager>();
  4.                         DicAssetBundle =newDictionary<string, AssetBundle>();}}}return instance;}}/// <summary>/// bundel资源加载/// </summary>/// <typeparam name="T">资源类型</typeparam>/// <param name="assetBundleName">资源名</param>/// <param name="assetBundleGroupName">资源组</param>/// <returns></returns>publicTLoadResource<T>(string assetBundleName,string assetBundleGroupName)whereT:UnityEngine.Object{if(string.IsNullOrEmpty(assetBundleGroupName)){returndefault(T);}if(!DicAssetBundle.TryGetValue(assetBundleGroupName,out assetbundle)){
  5.             assetbundle = AssetBundle.LoadFromFile(GetStreamingAssetsPath()+ assetBundleGroupName);
  6.             DicAssetBundle.Add(assetBundleGroupName, assetbundle);}object obj = assetbundle.LoadAsset(assetBundleName,typeof(T));var one = obj asT;return one;}/// <summary>/// 资源卸载/// </summary>/// <param name="assetBundleGroupName">资源名</param>publicvoidUnLoadResource(string assetBundleGroupName){if(DicAssetBundle.TryGetValue(assetBundleGroupName,out assetbundle)){
  7.             assetbundle.Unload(false);if(assetbundle !=null){
  8.                 assetbundle =null;}
  9.             DicAssetBundle.Remove(assetBundleGroupName);
  10.             Resources.UnloadUnusedAssets();}}/// <summary>/// 获取本地资源路径/// </summary>/// <returns></returns>publicstringGetStreamingAssetsPath(){string StreamingAssetsPath =#if UNITY_EDITOR || UNITY_STANDALONE_WIN
  11.         Application.streamingAssetsPath +"/";#elif UNITY_ANDROID"jar:file://"+ Application.dataPath +"!/assets/";#elif UNITY_IPHONE
  12.         Application.dataPath +"/Raw/";#elsestring.Empty;#endifreturn StreamingAssetsPath;}//下载跟新资源publicIEnumeratorDownloadAssetBundles(UnityWebRequest www){List<string> downLoads =GetDownloadMoudle(www.downloadHandler.text);SaveAssetBundle(BundleConfig.Md5FileName,www.downloadHandler.data);int i =0;if(downLoads.Count >0){//触发热更事件
  13.             ST_EventManager.PostEvent(BundleConfig.OnFireUpdate, downLoads.Count);}while(i < downLoads.Count){
  14.             www = UnityWebRequest.Get(BundleConfig.AssetBundlePath + downLoads[i]);//触发下载事件
  15.             ST_EventManager.PostEvent(BundleConfig.OnStarDownLoad, downLoads[i]);yieldreturn www.SendWebRequest();if(www.result != UnityWebRequest.Result.ConnectionError){//触发保存事件
  16.                 ST_EventManager.PostEvent(BundleConfig.OnStarSave, downLoads[i]);SaveAssetBundle(downLoads[i],www.downloadHandler.data);}else{thrownewException(www.error);}
  17.             i++;}//触发跟新完事件
  18.         ST_EventManager.PostEvent(BundleConfig.OnUpdateEnd);}/// <summary>/// 检查跟新/// </summary>/// <param name="callback">回调函数</param>/// <returns></returns>publicIEnumeratorCheckUpdate(Action<bool,UnityWebRequest> callback){UnityWebRequest www = UnityWebRequest.Get(BundleConfig.AssetBundlePath + BundleConfig.Md5FileName);yieldreturn www.SendWebRequest();if(www.result != UnityWebRequest.Result.ConnectionError){List<string> downLoads =GetDownloadMoudle(www.downloadHandler.text);callback(downLoads.Count >0, www);}else{thrownewException(www.error);}}/// <summary>/// 根据Md5FileList获取需要跟新的模块/// </summary>/// <param name="webFileList"></param>/// <returns></returns>List<string>GetDownloadMoudle(string webFileList){List<string> ret =newList<string>();string[] sArray = webFileList.Split(newchar[2]{'\r','\n'});//AssetBundleManifest assetBundleManifest;if(!File.Exists(GetStreamingAssetsPath()+ BundleConfig.Md5FileName)){foreach(string str in sArray){if(str !=""){
  19.                     ret.Add(str.Split('|')[0]);}}}else{string pastMd5 = File.ReadAllText(GetStreamingAssetsPath()+ BundleConfig.Md5FileName);Dictionary<string,string> dict =newDictionary<string,string>();string[] psArray = pastMd5.Split(newchar[2]{'\r','\n'});foreach(string str in psArray){if(str !=""){var nv = str.Split('|');
  20.                     dict.Add(nv[0], nv[1]);}}foreach(string str in sArray){if(str !=""){var nv = str.Split('|');if(!dict.ContainsKey(nv[0])|| dict[nv[0]]!= nv[1]){
  21.                         ret.Add(nv[0]);}}}}return ret;}/// <summary>/// 保存资源到本地/// </summary>/// <param name="fileName">资源名</param>/// <param name="bytes">数据</param>/// <param name="saveLocalComplate">完成回调</param>voidSaveAssetBundle(string fileName,byte[] bytes,Action saveLocalComplate =null){string path =GetStreamingAssetsPath()+ fileName;FileInfo fileInfo =newFileInfo(path);FileStream fs = fileInfo.Create();
  22.         fs.Write(bytes,0, bytes.Length);
  23.         fs.Flush();
  24.         fs.Close();
  25.         fs.Dispose();if(saveLocalComplate !=null){saveLocalComplate();}}privatevoidOnDestroy(){
  26.         _isDesdroy =true;}}
复制代码
测试热更
  1. int y =0;voidStart(){
  2.     ST_EventManager.RegisterEvent<int>(BundleConfig.OnFireUpdate,(e)=>{
  3.         Debug.Log("开始热跟新,资源数:"+ e);});
  4.     ST_EventManager.RegisterEvent<string>(BundleConfig.OnStarDownLoad,(e)=>{
  5.         Debug.Log("开始下载资源:"+ e);});
  6.     ST_EventManager.RegisterEvent<string>(BundleConfig.OnStarSave,(e)=>{
  7.         Debug.Log("开始保存资源:"+ e);});
  8.     ST_EventManager.RegisterEvent(BundleConfig.OnUpdateEnd,()=>{
  9.         Debug.Log("更新完成");//取消TestMono的Requrie,卸载lua,保证使用新的代码
  10.         ST_LuaMannager.Instance.RemoveRequire("TestMono");
  11.         ST_BundleManager.Instance.UnLoadResource("lua");});}privatevoidOnGUI(){if(GUILayout.Button("开始下载")){StartCoroutine(ST_BundleManager.Instance.CheckUpdate((needUp, www)=>{if(needUp){
  12.                 Debug.Log("检测到资源更新");StartCoroutine(ST_BundleManager.Instance.DownloadAssetBundles(www));}else{
  13.                 Debug.Log("无更新");}}));}if(GUILayout.Button("Cube")){GameObject cube = ST_BundleManager.Instance.LoadResource<GameObject>("Cube","model");Instantiate(cube,newVector3(0,y++,0),Quaternion.identity);
  14.         ST_LuaMannager.Instance.RemoveRequire("TestMono");}}
复制代码

点击开始下载,输出如下:


点击cube正常加载:


修改TestMono的Update函数,原来Cube是沿着X轴旋转,现在让他沿着Y周旋转,从新打包并上传到服务器
  1. function TestMono:Update()
  2.     self.transform:Rotate(CS.UnityEngine.Vector3(0,1,0));--self.transform:Rotate(CS.UnityEngine.Vector3(1,0,0));end
复制代码

可以看到只跟新了lua和standlone,并且跟新后创建的Cube是按Y轴旋转的,热更大功告成,,写完博客11点半了,睡觉睡觉。。。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-23 12:33 , Processed in 0.097039 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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