首先,创建一个名为AssetBundleManager
的脚本,用于管理 AssetBundle 的打包、加载和引用计数:
using UnityEngine;public class AssetBundleManager : MonoBehaviour
{private static AssetBundleManager instance;private static AssetBundleManifest manifest;private static string assetBundleDirectory;// 已加载的 AssetBundle 字典private static Dictionary<string, AssetBundle> loadedAssetBundles = new Dictionary<string, AssetBundle>();// AssetBundle 引用计数字典private static Dictionary<string, int> assetBundleRefCount = new Dictionary<string, int>();public static AssetBundleManager Instance{get{if (instance == null){GameObject go = new GameObject("AssetBundleManager");instance = go.AddComponent<AssetBundleManager>();}return instance;}}// 初始化 AssetBundleManagerpublic void Initialize(string manifestPath, string assetBundleFolderPath){manifest = LoadAssetBundleManifest(manifestPath);assetBundleDirectory = assetBundleFolderPath;}// 加载指定 AssetBundle 中的资源public T LoadAsset<T>(string assetBundleName, string assetName) where T : UnityEngine.Object{AssetBundle assetBundle = LoadAssetBundle(assetBundleName);if (assetBundle != null){return assetBundle.LoadAsset<T>(assetName);}return null;}// 卸载 AssetBundlepublic void UnloadAssetBundle(string assetBundleName){if (loadedAssetBundles.ContainsKey(assetBundleName)){AssetBundle assetBundle = loadedAssetBundles[assetBundleName];assetBundle.Unload(false);loadedAssetBundles.Remove(assetBundleName);if (assetBundleRefCount.ContainsKey(assetBundleName)){assetBundleRefCount[assetBundleName]--;if (assetBundleRefCount[assetBundleName] <= 0){assetBundleRefCount.Remove(assetBundleName);}}}}// 加载 AssetBundleprivate AssetBundle LoadAssetBundle(string assetBundleName){if (!loadedAssetBundles.ContainsKey(assetBundleName)){string path = Path.Combine(assetBundleDirectory, assetBundleName);AssetBundle assetBundle = AssetBundle.LoadFromFile(path);if (assetBundle != null){loadedAssetBundles.Add(assetBundleName, assetBundle);}}if (loadedAssetBundles.ContainsKey(assetBundleName)){if (!assetBundleRefCount.ContainsKey(assetBundleName)){assetBundleRefCount.Add(assetBundleName, 1);}else{assetBundleRefCount[assetBundleName]++;}return loadedAssetBundles[assetBundleName];}return null;}// 加载 AssetBundleManifestprivate AssetBundleManifest LoadAssetBundleManifest(string manifestPath){AssetBundle manifestAssetBundle = AssetBundle.LoadFromFile(manifestPath);return manifestAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");}
}
接下来,创建一个名为BuildEditor
的脚本,用于打包指定目录中的资源到 AssetBundle:
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;public class BuildEditor : EditorWindow
{
#if UNITY_ANDROIDstatic string m_CurPlatformName = "Android";
#elif UNITY_IOSstatic string m_CurPlatformName = "IOS";
#elsestatic string m_CurPlatformName = "Windows";
#endif/// <summary>/// 资源打包路径/// </summary>private static string m_OutPath = Application.dataPath + "/GameRes";/// <summary>/// 当前选择的平台/// </summary>private static BuildTarget m_BuildTarget = EditorUserBuildSettings.activeBuildTarget;/// <summary>/// 资源输出路径/// </summary>private static string m_BundlePutPath = Application.streamingAssetsPath + "/" + m_CurPlatformName + "/AssetBundles";/// <summary>/// 打包列表/// </summary>private static List<string> finalFiles = new List<string>();/// <summary>/// 打包完成之后生成的Manifest文件/// </summary>private static AssetBundleManifest m_Manifest;/// <summary>/// 打包/// </summary>[MenuItem("Build/Build ab")]private static void Build(){finalFiles.Clear();//1.清除AssetBundleNameClearAssetBundleName();//2.设置AssetBundleNamePack(m_OutPath);//3.打包if (Directory.Exists(m_BundlePutPath)) Directory.Delete(m_BundlePutPath, true);string tempFilePath = m_BundlePutPath;Directory.CreateDirectory(m_BundlePutPath);m_Manifest = BuildPipeline.BuildAssetBundles(m_BundlePutPath, BuildAssetBundleOptions.DeterministicAssetBundle, m_BuildTarget);if (m_Manifest != null){DeleteManifestFile(m_BundlePutPath);CreateFileList();this.BuildSuccessOrFail("Build AB", "Build Succeed", "OK");}else{this.BuildSuccessOrFail("Build AB", "Build Fail", "OK");}EditorUtility.ClearProgressBar();AssetDatabase.Refresh();Debug.Log("Build Succeed !!!");}/// <summary>/// 清除AssetBundle/// </summary>public static void ClearAssetBundleName(){string[] strs = AssetDatabase.GetAllAssetBundleNames();foreach (var bundleName in strs){AssetDatabase.RemoveAssetBundleName(bundleName, true);}AssetDatabase.Refresh();}/// <summary>/// 检查文件/// </summary>/// <param name="path"></param>private static void Pack(string path){DirectoryInfo infos = new DirectoryInfo(path);FileSystemInfo[] files = infos.GetFileSystemInfos();for (int i = 0; i < files.Length; i++){if (files[i] is DirectoryInfo){Pack(files[i].FullName);}else{if (!files[i].FullName.EndsWith(".meta")){SetAssetBundleName(files[i].FullName);}}}}/// <summary>/// 删除掉当前路径下所有.Manifest文件/// </summary>/// <param name="path"></param>private static void DeleteManifestFile(string path){DirectoryInfo infos = new DirectoryInfo(path);FileSystemInfo[] files = infos.GetFileSystemInfos();for (int i = 0; i < files.Length; i++){if (files[i] is DirectoryInfo){DeleteManifestFile(files[i].FullName);}else{if (files[i].FullName.EndsWith(".manifest")){File.Delete(files[i].FullName);}}}}/// <summary>/// 设置AssetBundleName/// </summary>/// <param name="source"></param>private static void SetAssetBundleName(string source){string _source = source.Replace(@"\\", "/");//截取完整路径到Assets/位置获得Assets路径string _assetPath1 = "Assets" + _source.Substring(Application.dataPath.Length);//获取Assets/之后的路径 以方便做AssetBunndleName使用string _assetPath2 = _source.Substring(Application.dataPath.Length + 1);//获取Assets路径下的文件AssetImporter assetImporter = AssetImporter.GetAtPath(_assetPath1);//获取AssetBundleNamestring bundleName = _assetPath2;//获取文件的扩展名并将其替换成.assetbundlebundleName = bundleName.Replace(Path.GetExtension(bundleName), ".assetbundle");//设置Bundle名assetImporter.assetBundleName = bundleName;//获取每个文件的指定路径并添加到列表里以方便写file文件使用string newFilePath = m_BundlePutPath + PathUtils.GetRelativePath(source, Application.dataPath, "").ToLower();string[] strs = newFilePath.Split('.');finalFiles.Add(strs[0] + ".assetbundle");}/// <summary>/// 生成 file 文件/// </summary>static void CreateFileList(){//files文件 目标路径string targetFilePath = m_BundlePutPath + "/files.txt";//检查是否存在该文件 存在则删除if (File.Exists(targetFilePath))File.Delete(targetFilePath);//统计大小 单位Blong totalFileSize = 0;FileStream fs = new FileStream(targetFilePath, FileMode.CreateNew);StreamWriter sw = new StreamWriter(fs);int count = 0;string file;finalFiles.Add(m_BundlePutPath + "/AssetBundles");File.Delete(m_BundlePutPath + "/AssetBundles.manifest");m_BundlePutPath = m_BundlePutPath.Replace('\\', '/');foreach (var files in finalFiles){file = PathUtils.NormalizePath(files);count++;this.UpdateProgress(count, finalFiles.Count + 1, "Createing files.txt");//文件Hashstring hash = "";//取到文件路径string _path = file.Replace(m_BundlePutPath + "/", string.Empty);FileInfo fi = new FileInfo(file);if (Path.GetExtension(file) == ".assetbundle"){//取到文件在Manifest中引用名字string abname = file.Replace(m_BundlePutPath + "/", "");//通过引用名字去Manifest文件中获取到该文件的Hash值hash = m_Manifest.GetAssetBundleHash(abname).ToString();}else{hash = this.md5file(file);}totalFileSize += fi.Length;//将文件信息按行写入到files文件中 路径|Hash|文件大小(单位B)sw.WriteLine(_path + "|" + hash + "|" + fi.Length);}//最后写入总大小(单位B)sw.WriteLine("" + totalFileSize);sw.Close();fs.Close();}/// <summary>/// 计算文件的MD5值/// </summary>public string md5file(string file){try{FileStream fs = new FileStream(file, FileMode.Open);System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();byte[] retVal = md5.ComputeHash(fs);fs.Close();StringBuilder sb = new StringBuilder();for (int i = 0; i < retVal.Length; i++){sb.Append(retVal[i].ToString("x2"));}return sb.ToString();}catch (Exception ex){throw new Exception("md5file() fail, error:" + ex.Message);}}public void BuildSuccessOrFail(string tittle,string message,string okmsg){EditorUtility.DisplayDialog(tittle, message, okmsg);}/// <summary>/// 进度条更新/// </summary>public void UpdateProgress(int progress, int progressMax, string desc){string title = "Processing...[" + progress + " - " + progressMax + "]";float value = (float)progress / (float)progressMax;EditorUtility.DisplayProgressBar(title, desc, value);}
}
使用示例
using UnityEngine;public class AssetBundleTest : MonoBehaviour
{private void Start(){// 初始化 AssetBundleManager,传入 AssetBundleManifest 路径和 AssetBundle 文件夹路径string manifestPath = "路径/manifest";string assetBundleFolderPath = "路径/AssetBundles";AssetBundleManager.Instance.Initialize(manifestPath, assetBundleFolderPath);// 加载 AssetBundle 中的资源string assetBundleName = "myassets";string assetName = "MyPrefab";GameObject myPrefab = AssetBundleManager.Instance.LoadAsset<GameObject>(assetBundleName, assetName);if (myPrefab != null){// 使用资源Instantiate(myPrefab);}else{Debug.LogError("Failed to load asset from AssetBundle: " + assetBundleName);}// 卸载 AssetBundleAssetBundleManager.Instance.UnloadAssetBundle(assetBundleName);}
}
注解:
- 将
AssetBundleManager
和AssetBundleBuilder
脚本放入 Unity 项目中的任意目录中。 - 选择要打包成 AssetBundle 的资源文件夹。
- 在 Unity 编辑器中,点击菜单栏的
Assets
,然后选择Build AssetBundles
。 - AssetBundle 将会被打包到 StreamingAssets 文件夹中。
- 在需要加载 AssetBundle 的地方,通过
AssetBundleManager.Instance.LoadAsset<T>(assetBundleName, assetName)
来加载指定 AssetBundle 中的资源。assetBundleName
是 AssetBundle 文件的名称(不带后缀)。assetName
是要加载的资源名称。
- 加载完毕后,使用资源并在不再需要时调用
AssetBundleManager.Instance.UnloadAssetBundle(assetBundleName)
来卸载 AssetBundle。
请注意,具体的路径和资源名称应根据你的实际情况进行修改。此外,为了更好地管理资源,你可能还需要额外的代码来处理资源的引用以及资源的释放。