Unity5 AssetBundle 加载

Unity5 AssetBundle 加载
上一篇说了 AssetBundle 打包
本篇接着做加载

加载需要考虑的两个问题:
一、如何将资源加载
二、资源依赖的其他资源如何处理

理论:一个预设 A 依赖 一个材质球B、一个材质球 C、 材质球 B 和材质球 C 又分别依赖一张贴图 B1、C1。
那么想要加载预设 A 首先要先加载 A 直接依赖的 B和C,还要将 B和C 依赖的资源 B1、C1加载,然后再加载 预设 A。这样才能保证 A 正常使用。

如何获取 A 的直接、间接依赖的所有资源名。
上篇分析了 总依类型文件 AssetBundleManifestAssetBundle.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立方体是直接创建在场景中的,而不是场景依赖的预设。
说明这种方法打包的场景,没法找到场景的依赖文件,如果场景有依赖其他资源,加载时将会丢失。

解决方案待查

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值