Unity 5.3 Assetbundle热更资源
发表于2018-08-08
Unity5以下的版本,要导出AssetBundle需要自己写一大坨导出的代码(BuildPipeline),想正确处理好资源的依赖关系从而保证资源完整而又不会产生重复资源是一件非常困难的事。Unity5新的AssetBundle系统大大简化了这一操作。Unity打包的时候会自动处理依赖关系,并生成一个.manifest文件,这个文件描述了assetbundle包大小、crc验证、包之间的依赖关系等等,是一个文本文件(但是这个也有坑,有时候prefab更新,重新打包却没有生成新的AssetBundle)。
打包Assetbundle的代码如下
public static void BuildAssetResource(BuildTarget target) { string dataPath = Util.DataPath; if (Directory.Exists(dataPath)) { Directory.Delete(dataPath, true); } string resPath = Util.AppDataPath + "/" + AppConst.AssetDirname + "/"; if (!Directory.Exists(resPath)) { Directory.CreateDirectory(resPath); } //生成assetbundle BuildPipeline.BuildAssetBundles(resPath, BuildAssetBundleOptions.None, target); paths.Clear(); files.Clear(); EditorUtility.ClearProgressBar(); ///----------------------创建文件列表----------------------- string newFilePath = resPath + "/files.txt"; if (File.Exists(newFilePath)) File.Delete(newFilePath); paths.Clear(); files.Clear(); Recursive(resPath); FileStream fs = new FileStream(newFilePath, FileMode.CreateNew); StreamWriter sw = new StreamWriter(fs); for (int i = 0; i < files.Count; i++) { string file = files[i]; if (file.EndsWith(".meta") || file.Contains(".DS_Store")) { continue; } string md5 = Util.md5file(file); string value = file.Replace(resPath, string.Empty); sw.WriteLine(value + "|" + md5); } sw.Close(); fs.Close(); AssetDatabase.Refresh(); }
在资源属性窗口底部有一个选项,设置AssetBundle的名字,设置好AssetBundle的名字调用上面的函数就可以把全部的Assetbundle生成出来。AssetBundle的名字固定为小写。另外,每个AssetBundle都可以设置一个Variant,其实就是一个后缀,实际AssetBundle的名字会添加这个后缀。如果有不同分辨率的同名资源,可以使用这个来做区分。
加载Assetbundle的代码
using UnityEngine; using System.Collections; namespace AssetBundles { public abstract class AssetBundleLoadOperation : IEnumerator { public object Current { get { return null; } } public bool MoveNext() { return !IsDone(); } public void Reset() { } abstract public bool Update(); abstract public bool IsDone(); } public class AssetBundleLoadLevelOperation : AssetBundleLoadOperation { protected string m_AssetBundleName; protected string m_LevelName; protected bool m_IsAdditive; protected string m_DownloadingError; protected AsyncOperation m_Request; public AssetBundleLoadLevelOperation(string assetbundleName, string levelName, bool isAdditive) { m_AssetBundleName = assetbundleName; m_LevelName = levelName; m_IsAdditive = isAdditive; } public override bool Update() { if (m_Request != null) return false; LoadedAssetBundle bundle = AssetBundleManager.GetLoadedAssetBundle(m_AssetBundleName, out m_DownloadingError); if (bundle != null) { if (m_IsAdditive) m_Request = Application.LoadLevelAdditiveAsync(m_LevelName); else m_Request = Application.LoadLevelAsync(m_LevelName); return false; } else return true; } public override bool IsDone() { if (m_Request == null && m_DownloadingError != null) { Debug.LogError(m_DownloadingError); return true; } return m_Request != null && m_Request.isDone; } } public abstract class AssetBundleLoadAssetOperation : AssetBundleLoadOperation { public abstract T GetAsset<T>() where T : UnityEngine.Object; } public class AssetBundleLoadAssetOperationSimulation : AssetBundleLoadAssetOperation { Object m_SimulatedObject; public AssetBundleLoadAssetOperationSimulation(Object simulatedObject) { m_SimulatedObject = simulatedObject; } public override T GetAsset<T>() { return m_SimulatedObject as T; } public override bool Update() { return false; } public override bool IsDone() { return true; } } public class AssetBundleLoadAssetOperationFull : AssetBundleLoadAssetOperation { protected string m_AssetBundleName; protected string m_AssetName; protected string m_DownloadingError; protected System.Type m_Type; protected AssetBundleRequest m_Request = null; public AssetBundleLoadAssetOperationFull(string bundleName, string assetName, System.Type type) { m_AssetBundleName = bundleName; m_AssetName = assetName; m_Type = type; } public override T GetAsset<T>() { if (m_Request != null && m_Request.isDone) { return m_Request.asset as T; } else { return null; } } public override bool Update() { if (m_Request != null) return false; LoadedAssetBundle bundle = AssetBundleManager.GetLoadedAssetBundle(m_AssetBundleName, out m_DownloadingError); if (bundle != null) { m_Request = bundle.m_AssetBundle.LoadAssetAsync(m_AssetName, m_Type); return false; } else { return true; } } public override bool IsDone() { if (m_Request == null && m_DownloadingError != null) { Debug.LogError(m_DownloadingError); return true; } return m_Request != null && m_Request.isDone; } } public class AssetBundleLoadManifestOperation : AssetBundleLoadAssetOperationFull { public AssetBundleLoadManifestOperation(string bundleName, string assetName, System.Type type) : base(bundleName, assetName, type) { } public override bool Update() { base.Update(); if (m_Request != null && m_Request.isDone) { AssetBundleManager.AssetBundleManifestObject = GetAsset<AssetBundleManifest>(); return false; } else { return true; } } } }
using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif using System.Collections; using System.Collections.Generic; namespace AssetBundles { public class LoadedAssetBundle { public AssetBundle m_AssetBundle; public int m_ReferencedCount; public LoadedAssetBundle(AssetBundle assetBundle) { m_AssetBundle = assetBundle; m_ReferencedCount = 1; } } public class AssetBundleManager : MonoBehaviour { static string m_BaseDownloadingURL = ""; static string[] m_ActiveVariants = { }; static AssetBundleManifest m_AssetBundleManifest = null; static Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle>(); static Dictionary<string, WWW> m_DownloadingWWWs = new Dictionary<string, WWW>(); static Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string>(); static List<AssetBundleLoadOperation> m_InProgressOperations = new List<AssetBundleLoadOperation>(); static Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>(); // The base downloading url which is used to generate the full downloading url with the assetBundle names. public static string BaseDownloadingURL { get { return m_BaseDownloadingURL; } set { m_BaseDownloadingURL = value; } } // Variants which is used to define the active variants. public static string[] ActiveVariants { get { return m_ActiveVariants; } set { m_ActiveVariants = value; } } // AssetBundleManifest object which can be used to load the dependecies and check suitable assetBundle variants. public static AssetBundleManifest AssetBundleManifestObject { set { m_AssetBundleManifest = value; } } public static void SetSourceAssetBundleDirectory(string relativePath) { BaseDownloadingURL = Util.GetStreamingAssetsPath() + relativePath; } public static void SetSourceAssetBundleURL(string absolutePath) { BaseDownloadingURL = absolutePath + Utility.GetPlatformName() + "/"; } public static void SetDevelopmentAssetBundleServer() { TextAsset urlFile = Resources.Load("AssetBundleServerURL") as TextAsset; string url = (urlFile != null) ? urlFile.text.Trim() : null; if (url == null || url.Length == 0) { Debug.LogError("Development Server URL could not be found."); } else { AssetBundleManager.SetSourceAssetBundleURL(url); } } // Get loaded AssetBundle, only return vaild object when all the dependencies are downloaded successfully. static public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName, out string error) { if (m_DownloadingErrors.TryGetValue(assetBundleName, out error)) { return null; } LoadedAssetBundle bundle = null; m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle); if (bundle == null) { return null; } // No dependencies are recorded, only the bundle itself is required. string[] dependencies = null; if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies)) { return bundle; } // Make sure all dependencies are loaded foreach (var dependency in dependencies) { if (m_DownloadingErrors.TryGetValue(assetBundleName, out error)) { return bundle; } // Wait all the dependent assetBundles being loaded. LoadedAssetBundle dependentBundle; m_LoadedAssetBundles.TryGetValue(dependency, out dependentBundle); if (dependentBundle == null) { return null; } } return bundle; } public static AssetBundleLoadManifestOperation Initialize() { BaseDownloadingURL = Util.GetRelativePath(); // return Initialize(Utility.GetPlatformName()); return Initialize(AppConst.AssetDirname); } // Load AssetBundleManifest. public static AssetBundleLoadManifestOperation Initialize(string manifestAssetBundleName) { var go = new GameObject("AssetBundleManager", typeof(AssetBundleManager)); DontDestroyOnLoad(go); LoadAssetBundle(manifestAssetBundleName, true); var operation = new AssetBundleLoadManifestOperation(manifestAssetBundleName, "AssetBundleManifest", typeof(AssetBundleManifest)); m_InProgressOperations.Add(operation); return operation; } // Load AssetBundle and its dependencies. protected static void LoadAssetBundle(string assetBundleName, bool isLoadingAssetBundleManifest = false) { if (!isLoadingAssetBundleManifest) { if (m_AssetBundleManifest == null) { Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()"); return; } } // Check if the assetBundle has already been processed. bool isAlreadyProcessed = LoadAssetBundleInternal(assetBundleName, isLoadingAssetBundleManifest); // Load dependencies. if (!isAlreadyProcessed && !isLoadingAssetBundleManifest) { LoadDependencies(assetBundleName); } } // Remaps the asset bundle name to the best fitting asset bundle variant. protected static string RemapVariantName(string assetBundleName) { string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant(); string[] split = assetBundleName.Split('.'); int bestFit = int.MaxValue; int bestFitIndex = -1; // Loop all the assetBundles with variant to find the best fit variant assetBundle. for (int i = 0; i < bundlesWithVariant.Length; i++) { string[] curSplit = bundlesWithVariant[i].Split('.'); if (curSplit[0] != split[0]) continue; int found = System.Array.IndexOf(m_ActiveVariants, curSplit[1]); // If there is no active variant found. We still want to use the first if (found == -1) found = int.MaxValue - 1; if (found < bestFit) { bestFit = found; bestFitIndex = i; } } if (bestFit == int.MaxValue - 1) { Debug.LogWarning("Ambigious asset bundle variant chosen because there was no matching active variant: " + bundlesWithVariant[bestFitIndex]); } if (bestFitIndex != -1) { return bundlesWithVariant[bestFitIndex]; } else { return assetBundleName; } } // Where we actuall call WWW to download the assetBundle. protected static bool LoadAssetBundleInternal(string assetBundleName, bool isLoadingAssetBundleManifest) { // Already loaded. LoadedAssetBundle bundle = null; m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle); if (bundle != null) { bundle.m_ReferencedCount++; return true; } if (m_DownloadingWWWs.ContainsKey(assetBundleName)) { return true; } WWW download = null; string url = m_BaseDownloadingURL + assetBundleName; // For manifest assetbundle, always download it as we don't have hash for it. if (isLoadingAssetBundleManifest) { download = new WWW(url); } else { download = WWW.LoadFromCacheOrDownload(url, m_AssetBundleManifest.GetAssetBundleHash(assetBundleName), 0); } m_DownloadingWWWs.Add(assetBundleName, download); return false; } // Where we get all the dependencies and load them all. static protected void LoadDependencies(string assetBundleName) { if (m_AssetBundleManifest == null) { Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()"); return; } // Get dependecies from the AssetBundleManifest object.. string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName); if (dependencies.Length == 0) return; for (int i = 0; i < dependencies.Length; i++) { dependencies[i] = RemapVariantName(dependencies[i]); } // Record and load all dependencies. m_Dependencies.Add(assetBundleName, dependencies); for (int i = 0; i < dependencies.Length; i++) { LoadAssetBundleInternal(dependencies[i], false); } } // Unload assetbundle and its dependencies. static public void UnloadAssetBundle(string assetBundleName) { UnloadAssetBundleInternal(assetBundleName); UnloadDependencies(assetBundleName); } static protected void UnloadDependencies(string assetBundleName) { string[] dependencies = null; if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies)) { return; } // Loop dependencies. foreach (var dependency in dependencies) { UnloadAssetBundleInternal(dependency); } m_Dependencies.Remove(assetBundleName); } static protected void UnloadAssetBundleInternal(string assetBundleName) { string error; LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error); if (bundle == null) { return; } if (--bundle.m_ReferencedCount == 0) { bundle.m_AssetBundle.Unload(false); m_LoadedAssetBundles.Remove(assetBundleName); } } void Update() { // Collect all the finished WWWs. var keysToRemove = new List<string>(); foreach (var keyValue in m_DownloadingWWWs) { WWW download = keyValue.Value; // If downloading fails. if (download.error != null) { m_DownloadingErrors.Add(keyValue.Key, string.Format("Failed downloading bundle {0} from {1}: {2}", keyValue.Key, download.url, download.error)); keysToRemove.Add(keyValue.Key); continue; } // If downloading succeeds. if (download.isDone) { AssetBundle bundle = download.assetBundle; if (bundle == null) { m_DownloadingErrors.Add(keyValue.Key, string.Format("{0} is not a valid asset bundle.", keyValue.Key)); keysToRemove.Add(keyValue.Key); continue; } m_LoadedAssetBundles.Add(keyValue.Key, new LoadedAssetBundle(download.assetBundle)); keysToRemove.Add(keyValue.Key); } } // Remove the finished WWWs. foreach (var key in keysToRemove) { WWW download = m_DownloadingWWWs[key]; m_DownloadingWWWs.Remove(key); download.Dispose(); } // Update all in progress operations for (int i = 0; i < m_InProgressOperations.Count; ) { if (!m_InProgressOperations[i].Update()) { m_InProgressOperations.RemoveAt(i); } else { i++; } } } // Load asset from the given assetBundle. public static AssetBundleLoadAssetOperation LoadAssetAsync(string assetBundleName, string assetName, System.Type type) { AssetBundleLoadAssetOperation operation = null; assetBundleName = RemapVariantName(assetBundleName); LoadAssetBundle(assetBundleName); operation = new AssetBundleLoadAssetOperationFull(assetBundleName, assetName, type); m_InProgressOperations.Add(operation); return operation; } // Load level from the given assetBundle. public static AssetBundleLoadOperation LoadLevelAsync(string assetBundleName, string levelName, bool isAdditive) { AssetBundleLoadOperation operation = null; assetBundleName = RemapVariantName(assetBundleName); LoadAssetBundle(assetBundleName); operation = new AssetBundleLoadLevelOperation(assetBundleName, levelName, isAdditive); m_InProgressOperations.Add(operation); return operation; } } }
客户端更新Assetbundle的时候并不是把全部生成的Assetbundle都下载更新,前面打包Assetbundle的时候生成一个一个files.txt的文件,这个文件里记录了每个Assetbundle的md5值,只有md5值变了的才会下载
IEnumerator OnUpdateResource() { string dataPath = Util.DataPath; //数据目录 string url = AppConst.WebUrl + AppConst.AppName + "/"; string listUrl = url + "files.txt"; WWW www = new WWW(listUrl); yield return www; if (www.error != null) { yield break; } if (!Directory.Exists(dataPath)) { Directory.CreateDirectory(dataPath); } File.WriteAllBytes(dataPath + "files.txt", www.bytes); string filesText = www.text; string[] files = filesText.Split('\n'); for (int i = 0; i < files.Length; i++) { if (string.IsNullOrEmpty(files[i])) { continue; } string[] keyValue = files[i].Split('|'); string f = keyValue[0]; string localfile = (dataPath + f).Trim(); string path = Path.GetDirectoryName(localfile); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } string fileUrl = url + f; bool canUpdate = !File.Exists(localfile); if (!canUpdate) { string remoteMd5 = keyValue[1].Trim(); string localMd5 = Util.md5file(localfile); canUpdate = !remoteMd5.Equals(localMd5); if (canUpdate) { File.Delete(localfile); } } if (canUpdate) { //这里都是资源文件,用线程下载 BeginDownload(fileUrl, localfile); } } while (!updateWorker.IsDownFinish()) { yield return new WaitForEndOfFrame(); } yield return new WaitForEndOfFrame(); StartGame(); }
这些都准备好之后,我们需要一个web服务器,到http://nginx.org/下载一个nginx,并把生成的Assetbundle拷到其目录下的
启动nginx,导出一个apk包,运行就可以看到效果了。
题外话,android下热更dll不能用这种方式,5x的打包方去加载dll的Assetbundle会是空的,要用4.x打包方式,如果有人知道5.x怎么弄,求告知
BuildPipeline.BuildAssetBundle(mainAsset, null, Application.dataPath + "mydll.assetbundle", BuildAssetBundleOptions.None,BuildTarget.Android); WWW www = new WWW(url); yield return www; if(www.error != null) { Debug.Log("加载 出错"); } if(www.isDone) { Debug.Log("加载完毕"); AssetBundle ab = www.assetBundle; try { Assembly aly = System.Reflection.Assembly.Load(((TextAsset)www.assetBundle.mainAsset).bytes); foreach (var i in aly.GetTypes()) { //调试代码 Debug.Log(i.Name); Component c = this.gameObject.AddComponent(i); } } catch (Exception e) { Debug.Log("加载DLL出错"); Debug.Log(e.Message); } }
最后附上工程项目https://github.com/caolaoyao/AutoUpdateAssetBundle
主要参考:http://unity3d.com/cn/node/17559
来自:https://blog.csdn.net/l_jinxiong/article/details/50877926