# AssetBundle 创建与加载
- AssetBundle 只能用代码创建和加载
- 在创建前需要先为资源分配资源包,如图:
![img]()
# AssetBundle 创建
- 区分平台(Windows、OSX、Linux 等)
- 区分创建方式(是否压缩、压缩方式等)
- lua 文件无法被打包,本文使用的方法是:生成 .lua 文件对应的 .txt 文件,将 .txt 文件打包,然后删除生成的 .txt 文件
- 代码需要放到 Editor 文件夹中
using System.Collections.Generic; | |
using System.IO; | |
using System.Text; | |
using UnityEditor; | |
using UnityEngine; | |
namespace Common | |
{ | |
/// <summary> | |
/// CreateAssetBundles | |
/// </summary> | |
public class CreateAssetBundles | |
{ | |
/// <summary> | |
/// BuildAllAssetBundles | |
/// </summary> | |
[MenuItem("Build/Build AssetBundles")] | |
private static void BuildAllAssetBundles() | |
{ | |
// 自定义路径 | |
string assetBundleDirectory = EditorUtility.OpenFolderPanel("选择导出路径", Application.dataPath, ""); | |
if (string.IsNullOrEmpty(assetBundleDirectory)) | |
{ | |
return; | |
} | |
// 固定路径 | |
//string assetBundleDirectory = Application.streamingAssetsPath + "/AssetBundles"; | |
BuildAssetBundleOptions assetBundleOptions; | |
BuildTarget targetPlatform; | |
#if UNITY_STANDALONE_WIN //Windows 独立平台应用程序 | |
assetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle; | |
targetPlatform = BuildTarget.StandaloneWindows64; | |
#elif UNITY_WSA //UWP | |
assetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle; | |
targetPlatform = BuildTarget.WSAPlayer; | |
#elif UNITY_STANDALONE_OSX //Mac OS X(包括 Universal、PPC 和 Intel 架构) | |
assetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle; | |
targetPlatform = BuildTarget.StandaloneOSX; | |
#elif UNITY_STANDALONE_LINUX //Linux | |
assetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle; | |
targetPlatform = BuildTarget.StandaloneLinux; | |
#elif UNITY_ANDROID | |
assetBundleOptions = BuildAssetBundleOptions.ChunkBasedCompression; | |
targetPlatform = BuildTarget.Android; | |
#elif UNITY_IOS | |
assetBundleOptions = BuildAssetBundleOptions.ChunkBasedCompression; | |
targetPlatform = BuildTarget.iOS; | |
#elif UNITY_WEBGL | |
assetBundleOptions = BuildAssetBundleOptions.ChunkBasedCompression; | |
targetPlatform = BuildTarget.WebGL; | |
#endif | |
if (!Directory.Exists(assetBundleDirectory)) | |
{ | |
Directory.CreateDirectory(assetBundleDirectory); | |
} | |
else | |
{ | |
Directory.Delete(assetBundleDirectory, true); | |
Directory.CreateDirectory(assetBundleDirectory); | |
} | |
string[] allTxtPaths = CopyLuaToTxt(); | |
GetFilesInfo(); | |
BuildPipeline.BuildAssetBundles(assetBundleDirectory, assetBundleOptions, targetPlatform); | |
DeleteTxtFiles(allTxtPaths); | |
AssetDatabase.Refresh(); | |
} | |
/// <summary> | |
/// 获取文件信息,用于调试 | |
/// </summary> | |
private static void GetFilesInfo() | |
{ | |
string[] bundleNames = AssetDatabase.GetAllAssetBundleNames(); | |
List<string> allAssetPaths = new List<string>(); | |
foreach (string bundleName in bundleNames) | |
{ | |
string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundle(bundleName); | |
allAssetPaths.AddRange(assetPaths); | |
} | |
StringBuilder stringBuilder = new StringBuilder(); | |
stringBuilder.AppendLine("All AB paths:"); | |
foreach (string path in allAssetPaths) | |
{ | |
stringBuilder.AppendLine(path); | |
} | |
Debug.Log(stringBuilder.ToString()); | |
} | |
/// <summary> | |
/// 将 .lua 文件转换为 .txt 文件 | |
/// </summary> | |
/// <returns>.txt 文件路径 & lt;/returns> | |
private static string[] CopyLuaToTxt() | |
{ | |
string[] bundleNames = AssetDatabase.GetAllAssetBundleNames(); | |
List<string> allAssetPaths = new List<string>(); | |
List<string> allTxtPaths = new List<string>(); | |
foreach (string bundleName in bundleNames) | |
{ | |
string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundle(bundleName); | |
allAssetPaths.AddRange(assetPaths); | |
} | |
foreach (string path in allAssetPaths) | |
{ | |
if (path.EndsWith(".lua")) | |
{ | |
// 读取.lua 文件内容 | |
var utf8 = new System.Text.UTF8Encoding(false); | |
string content = File.ReadAllText(path, utf8); | |
// 构造对应的.txt 文件路径 | |
string txtPath = Path.ChangeExtension(path, "txt"); | |
File.WriteAllText(txtPath, content, utf8); | |
allTxtPaths.Add(txtPath); | |
} | |
} | |
AssetDatabase.Refresh(); | |
return allTxtPaths.ToArray(); | |
} | |
/// <summary> | |
/// 删除 .txt 文件 | |
/// </summary> | |
/// <param name="dir"></param> | |
private static void DeleteTxtFiles(string[] allTxtPaths) | |
{ | |
foreach (string path in allTxtPaths) | |
{ | |
AssetDatabase.DeleteAsset(path); | |
} | |
} | |
} | |
} |
# 缓存记录和加载状态记录
- 加载 AB 时记录引用计数,在计数为 0 时卸载
- 记录加载状态防止协程互相等待卡死或请求同一个资源
using UnityEngine; | |
namespace Common | |
{ | |
/// <summary> | |
/// AssetBundle 缓存记录 | |
/// </summary> | |
public sealed class BundleRecord | |
{ | |
/// <summary> | |
/// AssetBundle 对象 | |
/// </summary> | |
public AssetBundle bundle; | |
/// <summary> | |
/// 当前有多少个加载请求持有这个 Bundle | |
/// </summary> | |
public int refCount; | |
public BundleRecord() { } | |
public BundleRecord(AssetBundle bundle, int refCount) | |
{ | |
this.bundle = bundle; | |
this.refCount = refCount; | |
} | |
} | |
} |
using UnityEngine; | |
namespace Common | |
{ | |
/// <summary> | |
/// Manifest 加载记录 | |
/// </summary> | |
public class LoadingManifest | |
{ | |
/// <summary> | |
/// 加载的 manifest | |
/// </summary> | |
public AssetBundleManifest manifest; | |
/// <summary> | |
/// 是否操作结束 | |
/// </summary> | |
public bool isDone; | |
} | |
} |
using UnityEngine; | |
namespace Common | |
{ | |
/// <summary> | |
/// AssetBundle 加载记录 | |
/// </summary> | |
public class LoadingAssetBundle | |
{ | |
/// <summary> | |
/// 加载的 bundle | |
/// </summary> | |
public AssetBundle bundle; | |
/// <summary> | |
/// 是否操作结束 | |
/// </summary> | |
public bool isDone; | |
/// <summary> | |
/// 加载的引用计数 | |
/// </summary> | |
public int acquireCount; | |
} | |
} |
# 生命周期
- 通过 Handle 管理一次加载的生命周期
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace Common | |
{ | |
/// <summary> | |
/// AssetBundle 加载结果的句柄,包含加载完成后的 AssetBundle 和引用的所有 Bundle Key | |
/// </summary> | |
public sealed class AssetBundleHandle : IDisposable | |
{ | |
/// <summary> | |
/// 加载完成前是否已经请求释放 | |
/// </summary> | |
internal bool releaseRequested; | |
/// <summary> | |
/// 获取引用的所有 Bundle Key,包括依赖 Bundle 和目标 Bundle | |
/// </summary> | |
internal List<string> acquiredBundles; | |
/// <summary> | |
/// 请求路径 | |
/// </summary> | |
public string Path { get; } | |
/// <summary> | |
/// 加载完成后的 AssetBundle | |
/// </summary> | |
public AssetBundle Bundle { get; internal set; } | |
/// <summary> | |
/// 是否完成加载 | |
/// </summary> | |
public bool IsDone { get; internal set; } | |
/// <summary> | |
/// 是否已经释放 | |
/// </summary> | |
public bool IsReleased { get; internal set; } | |
/// <summary> | |
/// 是否持有一个成功加载的结果 | |
/// </summary> | |
public bool IsSuccess => IsDone && !IsReleased && Bundle != null; | |
/// <summary> | |
/// 加载完成事件 | |
/// </summary> | |
public event Action<AssetBundleHandle> Completed; | |
internal AssetBundleHandle(string path) | |
{ | |
this.Path = path; | |
} | |
/// <summary> | |
/// 触发加载完成事件 | |
/// </summary> | |
internal void InvokeCompleted() | |
{ | |
Action<AssetBundleHandle> completed = Completed; | |
Completed = null; | |
completed?.Invoke(this); | |
} | |
/// <summary> | |
/// 释放这一次 Load 获得的所有引用 | |
/// </summary> | |
public void Dispose() | |
{ | |
Release(); | |
} | |
/// <summary> | |
/// 释放这一次 Load 获得的所有引用 | |
/// </summary> | |
private void Release() | |
{ | |
AssetBundleManager.Release(this); | |
} | |
} | |
} |
# 加载器
- 协程需要在 MonoBehaviour 上执行,所以这里需要一个单例类
namespace Common | |
{ | |
/// <summary> | |
/// AB 包加载器 | |
/// </summary> | |
public class AssetBundleLoader : MonoSingleton<AssetBundleLoader> | |
{ | |
protected override void Init() | |
{ | |
base.Init(); | |
DontDestroyOnLoad(this.gameObject); | |
} | |
} | |
} |
# AssetBundle 加载
- 两种加载方式:通过网络请求加载(UnityWebRequest)、通过本地存储加载(AssetBundle.LoadFromFile ())。这里使用网络请求加载方式,目的是为了适配更多的设备
- 同一个 ab 包中如果有多个同名文件,则只会加载第一个匹配的文件
- 加载目标 ab 包前,需要先加载它的依赖 ab 包(如果存在)
- 加载目标 ab 包后,调用委托处理其他逻辑
- 缓存 ab 包,在短时间内大量调用(例如初始化)时只需要加载一次
- 加载的 ab 包在使用完后需要卸载,防止占用内存
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.Networking; | |
namespace Common | |
{ | |
/// <summary> | |
/// AB 包管理器 | |
/// </summary> | |
public static class AssetBundleManager | |
{ | |
/// <summary> | |
///manifest 字典,key 为 AB 包的根目录名称,value 为对应的 manifest 文件 | |
/// </summary> | |
private static Dictionary<string, AssetBundleManifest> manifestDic = new(); | |
/// <summary> | |
/// 正在加载 manifest 的字典,key 为 AB 包的根目录名称,value 为对应的 LoadingManifest 对象 | |
/// </summary> | |
private static Dictionary<string, LoadingManifest> loadingManifest = new(); | |
/// <summary> | |
/// 缓存的 bundle 字典,key 为 AB 包在 StreamingAssets 内的相对路径,value 为对应的 BundleRecord 对象 | |
/// </summary> | |
private static Dictionary<string, BundleRecord> bundles = new(); | |
/// <summary> | |
/// 正在加载的 bundle 字典,key 为 AB 包在 StreamingAssets 内的相对路径,value 为对应的 LoadingRecord 对象 | |
/// </summary> | |
private static Dictionary<string, LoadingAssetBundle> loadingBundles = new(); | |
/// <summary> | |
/// AssetBundleLoader 实例 | |
/// </summary> | |
private static AssetBundleLoader loader = AssetBundleLoader.Instance; | |
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] | |
private static void Reset() | |
{ | |
foreach (BundleRecord record in bundles.Values) | |
{ | |
if (record.bundle != null) | |
{ | |
record.bundle.Unload(false); | |
} | |
} | |
manifestDic.Clear(); | |
loadingManifest.Clear(); | |
bundles.Clear(); | |
loadingBundles.Clear(); | |
} | |
/// <summary> | |
/// 加载资源包 | |
/// </summary> | |
/// <param name="path">AB 包在 StreamingAssets 内的相对路径 & lt;/param> | |
/// <param name="callback"> 委托 & lt;/param> | |
/// <returns></returns> | |
public static AssetBundleHandle LoadAssetBundle(string path, Action<AssetBundleHandle> callback = null) | |
{ | |
AssetBundleHandle handle = new(path); | |
if (callback != null) | |
{ | |
handle.Completed += callback; | |
} | |
loader.StartCoroutine(ToLoadAssetBundle(handle)); | |
return handle; | |
} | |
/// <summary> | |
/// 释放资源包 | |
/// </summary> | |
public static void Release(AssetBundleHandle handle) | |
{ | |
if (handle == null) | |
{ | |
return; | |
} | |
// 已经释放过 | |
if (handle.IsReleased) | |
{ | |
return; | |
} | |
// Bundle 还在加载 | |
if (!handle.IsDone) | |
{ | |
handle.releaseRequested = true; | |
return; | |
} | |
// 加载失败,没有资源需要释放 | |
if (handle.acquiredBundles == null) | |
{ | |
handle.IsReleased = true; | |
handle.Bundle = null; | |
return; | |
} | |
// 释放这张 Handle 持有的全部引用 | |
for (int i = handle.acquiredBundles.Count - 1; i >= 0; --i) | |
{ | |
ReleaseBundle(handle.acquiredBundles[i]); | |
} | |
handle.acquiredBundles = null; | |
handle.Bundle = null; | |
handle.IsReleased = true; | |
} | |
/// <summary> | |
/// 加载资源包 | |
/// </summary> | |
/// <param name="handle"></param> | |
/// <returns></returns> | |
private static IEnumerator ToLoadAssetBundle(AssetBundleHandle handle) | |
{ | |
string path = handle.Path; | |
if (!TryResolveBundlePath(path, out string startName, out string bundleName, out string startPath)) | |
{ | |
Debug.LogError("Invalid path: " + path); | |
LoadFailed(handle); | |
yield break; | |
} | |
// 加载 manifest 文件 | |
AssetBundleManifest targetManifest = null; | |
yield return AcquireManifest(startPath, startName, | |
manifest => | |
{ | |
targetManifest = manifest; | |
}); | |
if (targetManifest == null) | |
{ | |
LoadFailed(handle); | |
yield break; | |
} | |
// 获取依赖文件 | |
string[] dependencies = targetManifest.GetAllDependencies(bundleName); | |
List<string> acquired = new(); | |
foreach (string dependency in dependencies) | |
{ | |
AssetBundle dependencyBundle = null; | |
yield return AcquireBundle(startName, dependency, startPath, | |
bundle => | |
{ | |
dependencyBundle = bundle; | |
}); | |
if (dependencyBundle == null) | |
{ | |
for (int i = acquired.Count - 1; i >= 0; --i) | |
{ | |
ReleaseBundle(acquired[i]); | |
} | |
LoadFailed(handle); | |
yield break; | |
} | |
acquired.Add(CombinePath(startName, dependency)); | |
} | |
// 加载目标 Bundle | |
AssetBundle targetBundle = null; | |
yield return AcquireBundle(startName, bundleName, startPath, | |
bundle => | |
{ | |
targetBundle = bundle; | |
}); | |
if (targetBundle == null) | |
{ | |
for (int i = acquired.Count - 1; i >= 0; --i) | |
{ | |
ReleaseBundle(acquired[i]); | |
} | |
LoadFailed(handle); | |
yield break; | |
} | |
acquired.Add(CombinePath(startName, bundleName)); | |
handle.Bundle = targetBundle; | |
handle.acquiredBundles = acquired; | |
handle.IsDone = true; | |
// 有可能用户在加载过程中已经 Release () | |
if (handle.releaseRequested) | |
{ | |
Release(handle); | |
yield break; | |
} | |
handle.InvokeCompleted(); | |
} | |
/// <summary> | |
/// 加载 manifest | |
/// </summary> | |
/// <param name="startPath">AB 包根目录路径 & lt;/param> | |
/// <param name="startName">AB 包的根目录名称 & lt;/param> | |
/// <param name="callback"> 委托 & lt;/param> | |
/// <returns></returns> | |
private static IEnumerator AcquireManifest(string startPath, string startName, Action<AssetBundleManifest> callback) | |
{ | |
// 已经加载完成 | |
if (manifestDic.TryGetValue(startName, out AssetBundleManifest manifest)) | |
{ | |
callback?.Invoke(manifest); | |
yield break; | |
} | |
// 已经有人正在加载 | |
if (loadingManifest.TryGetValue(startName, out LoadingManifest loading)) | |
{ | |
while (!loading.isDone) | |
{ | |
yield return null; | |
} | |
if (loading.manifest == null) | |
{ | |
callback?.Invoke(null); | |
yield break; | |
} | |
// 加载成功后直接返回结果 | |
if (manifestDic.TryGetValue(startName, out manifest)) | |
{ | |
callback?.Invoke(manifest); | |
} | |
else | |
{ | |
Debug.LogError($"Bundle load state error: {startName}"); | |
callback?.Invoke(null); | |
} | |
yield break; | |
} | |
// 没加载,也没人正在加载 | |
loading = new LoadingManifest(); | |
loadingManifest.Add(startName, loading); | |
using UnityWebRequest request_ab = UnityWebRequestAssetBundle.GetAssetBundle(CombinePath(startPath, startName), 0); | |
yield return request_ab.SendWebRequest(); | |
if (request_ab.result != UnityWebRequest.Result.Success) | |
{ | |
Debug.LogError("Failed to load AssetBundle: " + request_ab.error); | |
// 通知所有等待者 | |
CompleteLoading(startName, loading, null); | |
callback?.Invoke(null); | |
yield break; | |
} | |
AssetBundle bundle_ab = DownloadHandlerAssetBundle.GetContent(request_ab); | |
if (bundle_ab == null) | |
{ | |
Debug.LogError($"Failed to get manifest bundle content: {startName}"); | |
CompleteLoading(startName, loading, null); | |
callback?.Invoke(null); | |
yield break; | |
} | |
manifest = bundle_ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); | |
bundle_ab.Unload(false); | |
if (manifest == null) | |
{ | |
Debug.LogError($"AssetBundleManifest was not found in manifest bundle: {startName}"); | |
CompleteLoading(startName, loading, null); | |
callback?.Invoke(null); | |
yield break; | |
} | |
manifestDic.Add(startName, manifest); | |
// 通知所有等待者 | |
CompleteLoading(startName, loading, manifest); | |
callback?.Invoke(manifest); | |
} | |
/// <summary> | |
/// 加载 bundle | |
/// </summary> | |
/// <param name="startName">AB 包的根目录名称 & lt;/param> | |
/// <param name="bundleName">AB 包名称 & lt;/param> | |
/// <param name="startPath">AB 包根目录路径 & lt;/param> | |
/// <param name="callback"> 委托 & lt;/param> | |
/// <returns></returns> | |
private static IEnumerator AcquireBundle(string startName, string bundleName, string startPath, Action<AssetBundle> callback) | |
{ | |
string key = CombinePath(startName, bundleName); | |
// 已经加载完成 | |
if (bundles.TryGetValue(key, out BundleRecord record)) | |
{ | |
record.refCount++; | |
callback?.Invoke(record.bundle); | |
yield break; | |
} | |
// 已经有人正在加载 | |
if (loadingBundles.TryGetValue(key, out LoadingAssetBundle loadingRecord)) | |
{ | |
loadingRecord.acquireCount++; | |
// 等待那个加载任务完成 | |
while (!loadingRecord.isDone) | |
{ | |
yield return null; | |
} | |
// 加载失败 | |
if (loadingRecord.bundle == null) | |
{ | |
callback?.Invoke(null); | |
yield break; | |
} | |
// 加载成功 | |
if (bundles.TryGetValue(key, out record)) | |
{ | |
callback?.Invoke(record.bundle); | |
} | |
else | |
{ | |
Debug.LogError($"Bundle load state error: {key}"); | |
callback?.Invoke(null); | |
} | |
yield break; | |
} | |
// 没加载,也没人正在加载。第一个请求拥有第一个引用 | |
loadingRecord = new LoadingAssetBundle | |
{ | |
acquireCount = 1 | |
}; | |
loadingBundles.Add(key, loadingRecord); | |
using UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(CombinePath(startPath, bundleName), 0); | |
yield return request.SendWebRequest(); | |
if (request.result != UnityWebRequest.Result.Success) | |
{ | |
Debug.LogError($"Failed to load AssetBundle: {bundleName}\n{request.error}"); | |
// 通知所有等待者 | |
CompleteLoading(key, loadingRecord, null); | |
callback?.Invoke(null); | |
yield break; | |
} | |
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); | |
if (bundle == null) | |
{ | |
Debug.LogError($"Failed to get AssetBundle content: {bundleName}"); | |
CompleteLoading(key, loadingRecord, null); | |
callback?.Invoke(null); | |
yield break; | |
} | |
// 第一个请求完成记录 | |
record = new BundleRecord(bundle, loadingRecord.acquireCount); | |
bundles.Add(key, record); | |
// 通知所有等待者 | |
CompleteLoading(key, loadingRecord, bundle); | |
callback?.Invoke(bundle); | |
} | |
/// <summary> | |
/// 加载完成,通知所有等待者 | |
/// </summary> | |
private static void CompleteLoading(string key, LoadingAssetBundle loading, AssetBundle bundle) | |
{ | |
loading.bundle = bundle; | |
loading.isDone = true; | |
loadingBundles.Remove(key); | |
} | |
/// <summary> | |
/// 加载完成,通知所有等待者 | |
/// </summary> | |
private static void CompleteLoading(string key, LoadingManifest loading, AssetBundleManifest manifest) | |
{ | |
loading.manifest = manifest; | |
loading.isDone = true; | |
loadingManifest.Remove(key); | |
} | |
/// <summary> | |
/// 加载失败,结束 Handle | |
/// </summary> | |
private static void LoadFailed(AssetBundleHandle handle) | |
{ | |
handle.Bundle = null; | |
handle.acquiredBundles = null; | |
handle.IsDone = true; | |
if (handle.releaseRequested) | |
{ | |
handle.IsReleased = true; | |
return; | |
} | |
handle.InvokeCompleted(); | |
} | |
/// <summary> | |
/// 释放 bundle 的引用计数,如果引用计数小于等于 0,则卸载 bundle | |
/// </summary> | |
private static bool ReleaseBundle(string key) | |
{ | |
if (!bundles.TryGetValue(key, out BundleRecord record)) | |
{ | |
return false; | |
} | |
record.refCount--; | |
if (record.refCount <= 0) | |
{ | |
record.bundle.Unload(false); | |
bundles.Remove(key); | |
} | |
return true; | |
} | |
/// <summary> | |
/// 处理路径 | |
/// </summary> | |
/// <param name="path"></param> | |
/// <param name="startName">AB 包的根目录名称 & lt;/param> | |
/// <param name="bundleName">AB 包名称 & lt;/param> | |
/// <param name="startPath">AB 包根目录路径 & lt;/param> | |
/// <returns></returns> | |
private static bool TryResolveBundlePath(string path, out string startName, out string bundleName, out string startPath) | |
{ | |
startName = null; | |
bundleName = null; | |
startPath = null; | |
if (string.IsNullOrEmpty(path)) | |
{ | |
return false; | |
} | |
string normalizedPath = path.Replace('\\', '/').Trim().TrimStart('/'); | |
string[] segments = normalizedPath.Split('/'); | |
if (segments.Length < 2) | |
{ | |
return false; | |
} | |
for (int i = 0; i < segments.Length; ++i) | |
{ | |
segments[i] = segments[i].Trim(); | |
if (string.IsNullOrEmpty(segments[i]) | |
|| segments[i] == "." | |
|| segments[i] == ".." | |
|| segments[i].Contains(':')) | |
{ | |
return false; | |
} | |
} | |
startName = segments[0]; | |
bundleName = string.Join("/", segments, 1, segments.Length - 1); | |
if (string.IsNullOrEmpty(startName) || string.IsNullOrEmpty(bundleName)) | |
{ | |
return false; | |
} | |
startPath = PathHelper.GetPath(CombinePath(null, startName)); | |
return true; | |
} | |
/// <summary> | |
/// 组合路径 | |
/// </summary> | |
private static string CombinePath(string left, string right) | |
{ | |
if (string.IsNullOrEmpty(right)) | |
{ | |
return null; | |
} | |
if (left == null) | |
{ | |
return $"/{right}"; | |
} | |
return $"{left}/{right}"; | |
} | |
} | |
} |
# 使用方法
创建
点击按钮创建 ab 包,如图:
加载
using Common; | |
using UnityEngine; | |
namespace Default | |
{ | |
/// <summary> | |
/// TestAB | |
/// </summary> | |
public class TestAB : MonoBehaviour | |
{ | |
private string path = "/AssetBundles/gas/player"; | |
private bool isDone = false; | |
private AssetBundleHandle handle; | |
private void Start() | |
{ | |
handle = AssetBundleManager.LoadAssetBundle(path, OnTest); | |
} | |
private void Update() | |
{ | |
if (handle != null && !isDone) | |
{ | |
isDone = handle.IsDone; | |
Debug.Log($"handle.IsDone:{handle.IsDone}, handle.IsSuccess:{handle.IsSuccess}"); | |
} | |
} | |
private void OnTest(AssetBundleHandle completedHandle) | |
{ | |
if (!completedHandle.IsSuccess) | |
{ | |
Debug.LogError($"AssetBundle 加载失败:{completedHandle.Path}", this); | |
return; | |
} | |
Debug.Log(completedHandle.Bundle); | |
Debug.Log($"handle == completedHandle: {completedHandle == handle}"); | |
//completedHandle.Dispose(); | |
//handle = null; | |
} | |
private void OnDestroy() | |
{ | |
handle?.Dispose(); | |
handle = null; | |
} | |
} | |
} |
