Unity5 AssetBundle 加载
上一篇说了 AssetBundle 打包
本篇接着做加载
加载需要考虑的两个问题:
一、如何将资源加载
二、资源依赖的其他资源如何处理
理论:一个预设 A 依赖 一个材质球B、一个材质球 C、 材质球 B 和材质球 C 又分别依赖一张贴图 B1、C1。
那么想要加载预设 A 首先要先加载 A 直接依赖的 B和C,还要将 B和C 依赖的资源 B1、C1加载,然后再加载 预设 A。这样才能保证 A 正常使用。
如何获取 A 的直接、间接依赖的所有资源名。
上篇分析了 总依类型文件 AssetBundleManifest
在 AssetBundle.manifest
中可以看到所有资源以及资源直接依赖的其他资源。
注意:AssetBundle.manifest
中也是只能看到直接依赖的资源
找到和 *.manifest
同名的文件 AssetBundle
看不到后缀名
AssetBundle 中可以获取到一个资源的所有依赖(包括直接依赖、间接依赖)
加载资源前确保 AssetBundleManifest
已经加载,通过 AssetBundleManifest
对象方法 GetAllDependencies(string assetBundleName);
从中获取到依赖文件
// 通过 assetBundleName 从 AssetBundleManifest 文件中 获取所有依赖资源数据
string[] depends = assetBundleManifest.GetAllDependencies(assetBundleName);
for (int i = 0; i < depends.Length; ++i)
{
// 加载依赖资源
}
// 依赖资源加载完毕,再加载自己
添加 LoadAssetBundle
加载类
using UnityEngine;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System;
using UnityEngine.SceneManagement;
// 回调方法
public delegate void LoadABCallBack(AssetBundle assetBundle);
// AB 文件只能加载一次,重复加载出错,两种方案,
// 一、加载后卸载,下次需要使用时重新加载,
// 二、加载后将AB保存起来,下载加载相同AS时直接取出来使用
public class LoadAssetBundle
{
//单例
public static readonly LoadAssetBundle Instance = new LoadAssetBundle();
//所有资源总依赖文件
public AssetBundleManifest assetBundleManifest = null;
public bool isLoadingAssetBundleMainfest = false;
/// <summary>
/// 加载 AB 资源
/// </summary>
/// <param name="url"></param>
/// <param name="abCallBack"></param>
/// <returns></returns>
private IEnumerator LoadAB(string url, LoadABCallBack loadABCallBack)
{
if (!url.StartsWith("file://"))
{
url = "file://" + url;
}
WWW www = new WWW(url);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.LogError(www.error);
yield break;
}
AssetBundle assetBundle = www.assetBundle;
if (loadABCallBack != null)
{
loadABCallBack(assetBundle);
}
}
/// <summary>
/// 加载资源
/// </summary>
/// <param name="assetPath"></param>
/// <param name="callBack"></param>
/// <param name="assetBundleName"></param>
/// <returns></returns>
private IEnumerator LoadABAsset(string assetPath, LoadABCallBack loadABCallBack, string assetBundleName)
{
// 注意:
// 此处需要添加已加载 AB 资源的管理
// AssetBundl 资源只能被加载一次,如果已经加载了并且没有释放掉,
// 再次加载同一个 AssetBundle 时将报异常 can't be loaded because another AssetBundle with the same files is already loaded.
// 当切换场景时或者确定不使用某个AssetBundle时需要将其释放掉,当再次需要加载该资源时才能重新加载
// 在这个 Demo 中不添加 AB 资源管理了
if (assetBundleManifest == null)
{
// 加载总依赖文件
yield return Game.Instance.StartCoroutine(LoadManifest());
}
// 总依赖文件没加载成功就不再继续执行了
if (assetBundleManifest == null) {
yield break;
}
// 通过 assetBundleName 获取所有依赖资源数据
string[] depends = assetBundleManifest.GetAllDependencies(assetBundleName);
// 加载资源前,要先将其所有依赖的资源全部加载
for (int i = 0; i < depends.Length; ++i)
{
string url = ResourcePathManager.Instance.CheckFilePath(ResourcePathManager.Instance.GetPersistentDataPath, "AssetBundle/" + depends[i]);
yield return Game.Instance.StartCoroutine(LoadAB(url, null));
}
// 依赖资源加载完成,才能开始加载自己
yield return Game.Instance.StartCoroutine(LoadAB(assetPath, loadABCallBack));
}
// 加载资源
public void LoadAsset(string assetPath, LoadABCallBack loadABCallBack, string assetBundleName)
{
Game.Instance.StartCoroutine(LoadABAsset(assetPath, loadABCallBack, assetBundleName));
}
//AB.Unload 卸载 AssetBundl:
//参数 false 只卸载 AssetBundl 资源,已经从 AssetBundle 资源中 Load 并且实例化出来的对象不会被释放
//参数 true 卸载 AssetBundl 的同时,也会把从 AssetBundl 资源中Load 并且实例化出来的对象一起销毁(很危险,慎用)
// 加载场景
public void LoadScene(string sceneAssetPath, LoadABCallBack loadABCallBack, string assetBundleName)
{
Game.Instance.StartCoroutine(LoadABAsset(sceneAssetPath, loadABCallBack, assetBundleName));
}
//加载总依赖资源文件 AssetBundleManifest
public IEnumerator LoadManifest()
{
// AssetBundleManifest 只加载一次,整个游戏过程中都不释放
if (!isLoadingAssetBundleMainfest)
{
isLoadingAssetBundleMainfest = true;
LoadABCallBack loadABCallBack = delegate (AssetBundle assetBundle)
{
if (assetBundle != null)
{
assetBundleManifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
};
string url = ResourcePathManager.Instance.CheckFilePath(ResourcePathManager.Instance.GetPersistentDataPath, "AssetBundle/AssetBundle");
yield return Game.Instance.StartCoroutine(LoadAB(url, loadABCallBack));
}
// 直到加载成功,否则一直在此处等待
while (assetBundleManifest == null) {
yield return new WaitForEndOfFrame ();
}
yield break;
}
}
上面用到的 ResourcePathManager
为资源路径的管理,根据不同平台获取不同路径,
using UnityEngine;
using System.IO;
/// <summary>
/// 管理加载资源路径
/// </summary>
public class ResourcePathManager
{
//单例
public static readonly ResourcePathManager Instance = new ResourcePathManager();
// 根据不同平台获取资源路径,移动平台为沙盒路径, 放在 StreamingAssets 下的文件不在此处
public string GetPersistentDataPath
{
get
{
if (Application.isEditor)
{
//return "file://" + Application.persistentDataPath + "/";
return Application.persistentDataPath + "/";
}
//不同平台下 Persistent 的路径是不同的,这里需要注意一下。
#if UNITY_ANDROID
return "file://" + Application.persistentDataPath + "/";
#elif UNITY_IPHONE
return "file://" + Application.persistentDataPath + "/";
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR
return "file://" + Application.persistentDataPath + "/";
#else
return string.Empty;
#endif
}
}
//优先获取沙盒内的资源,如果沙盒不存在该资源,从本地 StreamingAssets文件夹下获取
public string CheckFilePath(string persistentDataPath, string filePath)
{
string url = System.IO.Path.Combine(persistentDataPath, filePath); // String.Format("{0}/{1}", persistentDataPath, filePath); // System.IO.Path.Combine(persistentDataPath, filePath);
// 沙盒内存在加载文件则返回沙盒内路径
if (File.Exists(url))
{
return url;
}
return GetStreamingAssetsPath(filePath);
}
// 返回本地 StreamAssetsPath 路径
public string GetStreamingAssetsPath(string filePath)
{
if (Application.isEditor)
{
return "file://" + Application.streamingAssetsPath + "/" + filePath;
}
#if UNITY_ANDROID
//return "jar:file:///" + Application.dataPath + "!/assets/";
return Application.streamingAssetsPath + "/" + filePath;
#elif UNITY_IPHONE
//return Application.dataPath + "/Raw/";
return "file://" + Application.streamingAssetsPath + "/" + filePath;
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR
//return Application.dataPath + "/StreamingAssets/";
return "file://" + Application.streamingAssetsPath + "/" + filePath;
#else
return String.Format("{0}/{1}", Application.streamingAssetsPath, filePath);
#endif
}
//AB包资源后缀名
public static readonly string abExtension = "unity3d";
public string GetLoadPath( string path, bool isassetBundle = true)
{
string persistentPath = GetPersistentDataPath;
string combinPath = path;
if (isassetBundle)
{
combinPath = Path.Combine("AssetBundle", path);
}
string url = CheckFilePath(persistentPath, combinPath);
return url;
}
}
上面两个就可以完成初步的加载了
上测试代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.SceneManagement;
public class Game : MonoBehaviour {
public static Game Instance = null;
protected void Awake()
{
Instance = this;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
// 通过 assetBundleName 加载资源
Load("allassets/prefab/building/caishichang.unity3d");
}
if (Input.GetKeyDown(KeyCode.D))
{
string scenePath = Path.Combine(Application.streamingAssetsPath, "AssetBundle/scenes/two.unity3d");
string assetBundleName = "scenes/two.unity3d";
// 加载设置 assetBundleName 的场景(通过 BuildPipeline.BuildAssetBundles 方法打包的场景)
LoadScene(scenePath, assetBundleName);
}
if (Input.GetKeyDown(KeyCode.W))
{
string scenePath = Path.Combine(Application.streamingAssetsPath, "AssetBundle/Scene/two.unity3d");
string assetBundleName = "Scene/two.unity3d";
// 加载通过 BuildPipeline.BuildPlayer 打包的场景
LoadScene(scenePath, assetBundleName);
}
}
private void Load(string name)
{
LoadABCallBack loadCallBack = delegate (AssetBundle assetBundle)
{
if (assetBundle == null)
{
return;
}
string objName = Path.GetFileNameWithoutExtension(name);
GameObject obj = assetBundle.LoadAsset<GameObject>(objName);
GameObject go = Instantiate(obj) as GameObject;
go.name = Path.GetFileNameWithoutExtension(name);
};
// 拼接加载路径
string path = Path.Combine(Application.streamingAssetsPath, "AssetBundle/" + name);
LoadAssetBundle.Instance.LoadAsset(path, loadCallBack, name);
}
private void LoadScene(string scenePath, string assetBundleName)
{
LoadABCallBack loadCallBack = delegate (AssetBundle assetBundle)
{
if (assetBundle == null)
{
return;
}
SceneManager.LoadScene("Two");
};
LoadAssetBundle.Instance.LoadScene(scenePath, loadCallBack, assetBundleName);
}
}
点击 A 加载资源,没有材质、贴图 可以正常加载
点击 D 加载场景 Two,加载完成从 One 场景切换到 Two 场景
注意:加载场景 Two 没问题, Two 打包时就是这个样子,这个是设置 assetBundleName,通过 BuildPipeline.BuildAssetBundles
打包的场景
重启点击 W,加载场景 Two
场景中只能看到 一个立方体 Cube,在 Hierarchy 面板看,并没有丢失 GameObject。找一个查看
原因是 GameObject 引用的资源没有加载上来。
注意: 这次加载的场景是通过 BuildPipeline.BuildPlayer
方法打包的场景,该方法会将场景打包成一个整体,不生成依赖文件,本次丢失的都是场景中预设上依赖的材质、贴图。Cube 立方体并没有丢失,因为Cube立方体是直接创建在场景中的,而不是场景依赖的预设。
说明这种方法打包的场景,没法找到场景的依赖文件,如果场景有依赖其他资源,加载时将会丢失。
解决方案待查