Unity--AssetBundle AB包管理器

1.简介

AB包(AssetBundle)是Unity中用于资源管理的一种机制,它允许开发者将多个文件(如纹理、模型、音频等)打包成一个单独的文件,以便于在游戏运行时动态加载和卸载。 但是现在出现了最新的Addressable来代替AssetBundle.

Addressables是Unity提供的一个资源管理系统,它是对AssetBundle系统的一种改进和扩展。Addressables允许开发者通过地址(名称或标签)来请求资源,而不是直接操作AssetBundle文件。这使得资源的管理和加载变得更加简单和灵活。现在目前只接收AssetBundle的用法个管理器的代码

2.特点

  1. 资源优化:通过将相关资源打包在一起,可以减少文件数量,简化资源管理。

  2. 减少启动时间:游戏开始时不需要加载所有资源,只需加载必要的AB包,从而减少启动时间。

  3. 动态内容更新:可以单独更新AB包中的内容,而不需要重新下载整个游戏。

3.工作流程

AB包的基本工作流程包括以下几个步骤:

  1. 构建AB包:在Unity编辑器中,将需要打包的资源分配到相应的AB包中,并设置好它们的名称和变体。
  2. 打包:使用Unity的构建系统将资源打包成AB包文件。这些文件通常具有.unity3d扩展名。我们类自己命名都是可以的
  3. 加载AB包:在游戏运行时,通过代码动态加载所需的AB包。可以使用AssetBundle.LoadFromFileAssetBundle.LoadFromMemory等方法。
  4. 使用资源:从AB包中加载具体的资源,如纹理、预制体等。
  5. 卸载AB包:当AB包中的资源不再需要时,应该卸载AB包以释放内存。

在使用AB包时,需要注意资源依赖关系的管理,确保在加载资源时,其依赖的所有资源都已正确加载。此外,AB包的版本控制和更新策略也是开发过程中需要考虑的重要方面。

4.AB包管理器

为了管理ab包, 在需要的时候加载ab包, 因此,我们需要建立一个AB包的管理器, 该管理器提供了加载AB包的方式, 为了方便使用者, 可以通过该管理器直接快速加载某AB包里的某个资源.

4.1 效果图和部分说明

以下是AB包加载效果
Unity-AB包-AB包管理器效果图.jpg

通过AssetBundleBrowerk可以查看到打的ab包
Unity-AB包-AB包打包图.jpg

说明:

  • textures.ab包中包含唯一一张贴图, T_Wood.jpg
  • materials.ab包中含有两个材质, 分别是M_WineWood和M_Wood, 两个材质都引用的同一张贴图W_Wood.jpg.
  • static.ab包中含有两个预制体, wall和floor. wall预制体中的材质是M_Wood, floor中使用的M_WineMood.

依赖关系如下图所示.

其中,在主包中包含3个包, 分别是texture.ab 存放贴图, materials.ab存放材质, static.ab存放预制体. 依赖关系如如下图. 下图中的右图是简化版本. 左图中显示了包与包的依赖关系, 右图展示了贴图-材质-预制体(模型)的关系.

Unity-AB包-依赖关系图.png

根据AssestBundle的加载资源包(ab包)的规则, 如果你想要加载一个资源包,需要先加载该资源包的依赖包,

如果不先加载依赖包,那么运行的效果可能就是丢失了材质/贴图/脚本或者其他资源导致程序出现异常或者崩溃.

因此, 理清楚依赖关系非常重要. 依赖关系可以在主包中的mainfest文件中获得. 依赖关系就是上图中的线条

所以, 加载顺序为: 加载主包->读取依赖关系->加载依赖包->加载目标包.

在一些很大的游戏中,资源包很大, 因此如果加载资源包等待的时间很长这会导致玩家失去耐心, 和何况在这个短平快的时代. 因此可以根据资源包的大小来进行同步加载异步加载. 同理, 加载完AB包后, 里面的资源可能很大,可能很小, 这需要根据实际来同步加载资源异步加载资源.

以下代码是AB包管理器, 用于加载AB包, 其中也包含了加载资源的同步方式和异步方式. 下图是ABManager.cs的代码结构.

Unity-AB包-AB包管理器代码结构.jpg

以下是具体代码:

4.2准备工作

    // AB包管理器的目的: 让外部更方便的进行资源加载

    // 主包:
    private AssetBundle mainAB = null;
    // 依赖包获取用的配置文件
    private AssetBundleManifest manifest = null;
    // 需要将已加载的AB包存起来, 使用字典存起来
    // key->ab包的名称 value->包体
    Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();

4.3 加载AB包的路径以及平台

    // 加载包的路径
    private string PathURL
    {
        get 
        {
            // 可以根据平台不用返回不同的值 streamingAssetsPath 是用于测试
            // 注意: AssetsBundle打包的时候试注意路径名称和文件夹名称, 因为主包名称会被构建为最后一层的路径名称
            return Application.streamingAssetsPath + "/" + MainABName + "/";
        }
    }

    // 主包名: 区分不同的平台,Unity根据环境自动区分, 后期根据目标平台构建AB包
    private string MainABName 
    {
        get 
        {
#if UNITY_IOS
            return "IOS";
#elif UNITY_ANDROID
            return "Android"
#else
            return "PC";
#endif
        }
    }

4.4 同步加载AB包

步骤: 加载主包->加载Manifest文件->加载依赖包->加载目标包

使用AssetBundle.LoadFromFile加载主包/依赖包/目标包,

使用LoadAsset<AssetBundleManifest>("AssetBundleManifest");加载Mainfest文件获依赖关系

// 同步加载AB包
public AssetBundle LoadAssetBundle(string abName)
{
    if (abName == "")
    {
        Debug.LogError("请输入ab包名称");
        return null;
    }

    // 加载主包 单独保存起来
    // 加载主包中的关键配置文件, 获取依赖包 单独保存起来

    // 加载主包和Manifes信息
    if (mainAB == null)
    {
        // 路径 + 主包名
        mainAB = AssetBundle.LoadFromFile(PathURL + MainABName);
        // 加载包与包之间的依赖文件
        manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
    }

    // 获取依赖关系:依赖包
    AssetBundle ab = null;
    if (manifest != null)
    {
        // 遍历mainfest文件, 获取依赖包  获取所有依赖信息
        string[] dependencies = manifest.GetAllDependencies(abName);
        for (int i = 0; i < dependencies.Length; ++i)
        {
            // 如果现有的容器中没有需要加载的包, 则进行加载
            if (!abDic.ContainsKey(dependencies[i]))
            {
                // 根据依赖信息加载依赖包 -- 暂时用不上crc循环冗余校验码
                ab = AssetBundle.LoadFromFile(PathURL + dependencies[i]);
                // 添加到容器中
                abDic.Add(dependencies[i], ab);
            }
        }
    }

    // 加载目标包 加载完依赖包后系统不会自动加载最终我们要的包, 因此需要手动加载
    if (!abDic.ContainsKey(abName))
    {
        ab = AssetBundle.LoadFromFile(PathURL + abName);
        abDic.Add(abName, ab);
    }
    else
    {
        ab = abDic[abName];
    }

    // 返回资源包
    return ab;
}

4.5 异步加载资源包

由于异步加载资源包需要配合协程进行操作, 因此在加载资源包的时候需要开启协程. 同时需要注意协程返回数据的时间, 因为每一个包的大小不一样, 因此同时开启多个协程的时候需要注意一些临界值.

public void LoadAssetBundleAsync(string abName, UnityAction<AssetBundle> callback)
{
    // 开启协程加载AB包
    StartCoroutine(ReallyLoadAssetBundle(abName, callback));
}
// 协程加载AB包
public IEnumerator ReallyLoadAssetBundle(string abName, UnityAction<AssetBundle> callback)
{
    if (abName == null || abName == "")
    {
        Debug.LogError("加载的AB包不能为空");
        yield break;
    }

    // 先判断是否在abDic中
    if (abDic.ContainsKey(abName))
    {
        yield return abDic[abName]; // 这句可以注释掉
        callback(abDic[abName]);
        yield break;
    }
    else
    {
        // 不在abDic中, 加载主包 和Manifest文件

        // 加载主包和Manifest信息
        if (mainAB == null)
        {
            // 路径 + 主包名
            AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(PathURL + MainABName);
            yield return abcr;
            mainAB = abcr.assetBundle;

            // 加载包与包之间的依赖文件
            AssetBundleRequest abr = mainAB.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
            yield return abr;
            manifest = abr.asset as AssetBundleManifest;
        }

        // 遍历依赖包
        string[] dependencies = manifest.GetAllDependencies(abName);
        // 这里使用队列, 是因为多个协程的返回时间不一样, 如果直接使用abDic.Add的方法会导致异常, 即for循环中的i发生改变,, 因此先用队列缓存起来.
        // 另外的做法是, 先将名称放入abDic字典中, 值用Null来代替, 加载完毕后再修改abDic中的值
        Queue<AssetBundle> loadingQueue = new Queue<AssetBundle>(dependencies.Length);
        for (int i = 0; i < dependencies.Length; i++)
        {
            // 开启单个依赖包的加载协程
            StartCoroutine(LoadSingleDependenceAsync(dependencies[i], (temp) =>
            {
                // 加载完毕后入队
                loadingQueue.Enqueue(temp);
            }));
        }

        // 依赖包加载完毕后加载目标包-->开启加载目标包的协程
        StartCoroutine(LoadTargetAssetBundleAsync(abName, (temp) => {
            // 等待所有依赖包加载完成
            // 出队, 添加到abDic中
            while (loadingQueue.Count > 0)
            {
                AssetBundle loadedAB = loadingQueue.Dequeue();
                abDic.Add(loadedAB.name, loadedAB);
            }
            callback(temp);
        }));
    }
}

// 依赖包的加载 : 依赖包名称, 路径, 加载完毕的回调函数
public IEnumerator LoadSingleDependenceAsync(string dependName, UnityAction<AssetBundle> callback)
{
    AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(PathURL + dependName);
    if (abcr.assetBundle != null)
    {
        yield return abcr;
        callback(abcr.assetBundle);
    }
    else
    {
        Debug.LogError("单个依赖包加载出错");
        callback(null);
        yield break;
    }
}

// 加载目标包的协程
public IEnumerator LoadTargetAssetBundleAsync(string abName, UnityAction<AssetBundle> callback)
{
    AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(PathURL + abName);
    if (abcr.assetBundle != null)
    {
        yield return abcr;
        callback(abcr.assetBundle);
    }
    else
    {
        Debug.LogError("目标包加载出错");
        callback(null);
        yield break;
    }
}

4.6 同步加载资源的三种方式

/// <summary>
/// 同步加载
/// </summary>
/// <typeparam name="T">通过泛型加载</typeparam>
/// <param name="abName">资源包的名称</param>
/// <param name="resName">资源名称</param>
/// <returns>加载的资源</returns>
public T LoadResources<T>(string abName, string resName) where T : Object
{
    AssetBundle ab = LoadAssetBundle(abName);
    if (ab == null)
    {
        return null;
    }
    // Debug.Log("同步加载-指定泛型的加载模式");
    return ab.LoadAsset<T>(resName);
}

// 同步加载 不指定类型
public Object LoadResources(string abName, string resName)
{
    // 加载AB包
    AssetBundle ab = LoadAssetBundle(abName);
    Object obj = ab.LoadAsset(resName);
    // Debug.Log("同步加载-不指定类型的加载模式");
    return obj;
}

/// <summary>
/// 使用资源类型的方式进行加载 xLua中不支持泛型所以使用type类型
/// </summary>
/// <param name="abName">资源包的名称</param>
/// <param name="resName">资源名称</param>
/// <param name="type">资源类型</param>
/// <returns>加载的资源</returns>
// 同步加载 根据type指定类型 这里使用System.Type是因为返回的对象Object在Unity和System中均有Object.
public Object LoadResources(string abName, string resName, System.Type type)
{
    // 加载AB包
    AssetBundle ab = LoadAssetBundle(abName);
    Object obj = ab.LoadAsset(resName, type);
    // Debug.Log("同步加载-指定Type的加载模式");
    return obj;
}

4.7 异步加载资源的三种方式

异步加载AB后, 异步加载包中的资源

/// <summary>
/// 异步加载资源-启动协程
/// </summary>
public void LoadResourcesAsync(string abName, string resName, UnityAction<Object> callback)
{
    // 开启协程
    StartCoroutine(ReallyLoadResourcesAsync(abName, resName, callback));
}

// 异步加载资源的协程
public IEnumerator ReallyLoadResourcesAsync(string abName, string resName, UnityAction<Object> callback)
{
    LoadAssetBundleAsync(abName, (tmp) =>
    {
        AssetBundleRequest abr = tmp.LoadAssetAsync(resName);
        if (abr == null || abr.asset == null)
        {
            Debug.LogError("未能加载资源包");
            callback(null);
        }
        else
        {
            callback(abr.asset);
        }
        // 得到资源后abr后返回abr的asset
    });
    yield return resName;
}

/// <summary>
/// 异步加载资源的泛型的方式
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="abName"></param>
/// <param name="resName"></param>
/// <param name="callback"></param>
public void LoadResourcesAsync<T>(string abName, string resName, UnityAction<T> callback) where T : Object
{
    // 开启协程
    StartCoroutine(ReallyLoadResourcesAsync<T>(abName, resName, callback));
}

// 异步加载资源的协程
public IEnumerator ReallyLoadResourcesAsync<T>(string abName, string resName, UnityAction<T> callback) where T : UnityEngine.Object
{
    LoadAssetBundleAsync(abName, (tmp) =>
    {
        AssetBundleRequest abr = tmp.LoadAssetAsync(resName);
        if (abr == null || abr.asset == null)
        {
            Debug.LogError("未能加载资源包");
            callback(null);
        }
        else
        {
            callback(abr.asset as T);
        }
        // 得到资源后abr后返回abr的asset
    });
    yield return null;
}

public void LoadResourcesAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
{
    // 开启协程
    StartCoroutine(ReallyLoadResourcesAsync(abName, resName, type, callback));
}

// 异步加载资源的协程
public IEnumerator ReallyLoadResourcesAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
{
    this.LoadAssetBundleAsync(abName, (tmp) =>
    {
        AssetBundleRequest abr = tmp.LoadAssetAsync(resName, type);
        if (abr == null || abr.asset == null)
        {
            Debug.LogError("未能加载资源包");
            callback(null);
        }
        else
        {
            callback(abr.asset);
        }
        // 得到资源后abr后返回abr的asset
    });
    yield return null;
}

4.8 AB包卸载

// 单个包的卸载
public void UnloadRes(string abName)
{ 
    if (abDic.ContainsKey(abName))
    {
        abDic[abName].Unload(false);
        abDic.Remove(abName);
    }
}

// 所有包的卸载
public void ClearAB()
{
    AssetBundle.UnloadAllAssetBundles(false);
    abDic.Clear();
    mainAB = null;
    manifest = null;
}

4.9 完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class ABManager : SingletonAutoMono<ABManager>
{
    // AB包管理器的目的: 让外部更方便的进行资源加载
    // 主包:
    private AssetBundle mainAB = null;
    // 依赖包获取用的配置文件
    private AssetBundleManifest manifest = null;

    // 加载分为同步加载异步加载

    // AB包加载的特点: 加载后不能再次加载, 因此, 如果作为AB包加载卸载管理器的话
    // 需要将已加载的AB包存起来, 使用字典存起来
    // key-> ab包的名称 value->包体
    Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();

    #region 加载AB包的路径以及平台
    // 加载包的路径
    private string PathURL
    {
        get 
        {
            // 可以根据平台不用返回不同的值 streamingAssetsPath 是用于测试
            // 注意: AssetsBundle打包的时候试注意路径名称和文件夹名称, 应为主包名称会被构建为最后一层的路径名称
            return Application.streamingAssetsPath + "/" + MainABName + "/";
        }
    }

    // 主包名: 区分不同的平台
    private string MainABName 
    {
        get 
        {
#if UNITY_IOS
            return "IOS";
#elif UNITY_ANDROID
            return "Android"
#else
            return "PC";
#endif
        }
    }
#endregion

    #region 同步加载AB包
    // 同步加载以及同步加载的方法
    public AssetBundle LoadAssetBundle(string abName)
    {
        if(abName == "")
        {
            Debug.LogError("请输入ab包名称");
            return null;
        }
        // 加载主包 单独保存起来
        // 加载主包中的关键配置文件, 获取依赖包 单独保存起来

        // 加载主包和Manifes信息
        if (mainAB == null)
        {
            // 路径 + 主包名
            mainAB = AssetBundle.LoadFromFile(PathURL + MainABName);
            // 加载包与包之间的依赖文件
            manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }
        // 获取依赖关系:依赖包
        AssetBundle ab = null;
        if (manifest != null)
        {
            // 遍历mainfest文件, 获取依赖包  获取所有依赖信息

            string[] dependencies = manifest.GetAllDependencies(abName);
            for (int i = 0; i < dependencies.Length; ++i)
            {
                // 如果现有的容器中没有需要加载的包, 则进行加载
                if(!abDic.ContainsKey(dependencies[i]))
                {
                    // 根据依赖信息加载依赖包 -- 暂时用不上crc循环冗余校验码
                    ab = AssetBundle.LoadFromFile(PathURL + dependencies[i]);
                    // 添加到容器中
                    abDic.Add(dependencies[i], ab);
                }

            }
        }
        // 加载资源来源包
        if(!abDic.ContainsKey(abName))
        {
            ab = AssetBundle.LoadFromFile(PathURL + abName);
            abDic.Add(abName, ab);
        }else
        {
            ab = abDic[abName];
        }
        // 返回资源包
        return ab;
    }
    #endregion

    #region 异步加载AB包

    public void LoadAssetBundleAsync(string abName, UnityAction<AssetBundle> callback)
    {
        // 使用协程加载AB包
        StartCoroutine(ReallyLoadAssetBundle(abName, callback));
    }

    public IEnumerator ReallyLoadAssetBundle(string abName, UnityAction<AssetBundle> callback)
    {
        if(abName == null || abName == "")
        {
            Debug.LogError("加载的AB包不能为空");
            yield break;
        }
        // 先判断是否在abDic中
        if(abDic.ContainsKey(abName))
        {
            yield return abDic[abName];
            callback(abDic[abName]);
            yield break;
        }else
        {
            // 不在abDic中, 加载主包 和Manifest文件

            // 加载主包和Manifest信息
            if (mainAB == null)
            {
                // 路径 + 主包名
                
                AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync (PathURL + MainABName);
                yield return abcr;
                mainAB = abcr.assetBundle;
                // 加载包与包之间的依赖文件

                AssetBundleRequest abr = mainAB.LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
                yield return abr;
                manifest = abr.asset as AssetBundleManifest;
            }

            // 遍历依赖包
            // AssetBundle singleAssetBundle;

            string[] dependencies = manifest.GetAllDependencies(abName);
            // 这里使用队列作为缓存是为了防止直接使用abDic.Add添加异常
            // 如果直接使用abDic.Add会导致下标异常
            // 原因是 多个协程加载资源的返回的先后顺序不一致导致for循环导致i值变化, 从而导致abDic的异常
            // 另外一种办法是将资源包的name先添加到abDic的key中, value用null占位, 然后在将加载好的资源包放入到abDic的gvalue中
            Queue<AssetBundle> loadingQueue = new Queue<AssetBundle>(dependencies.Length);
            for (int i = 0; i < dependencies.Length; i++)
            {
                // 开启单个依赖包的加载协程
                StartCoroutine(LoadSingleDependenceAsync(dependencies[i], (temp) =>
                {
                    //singleAssetBundle = temp;
                    //Debug.Log(singleAssetBundle.name);
                    loadingQueue.Enqueue(temp);
                    // 添加到字典中去
                    //abDic.Add(dependencies[i], singleAssetBundle);
                }));               
            }


            // 依赖包加载完毕后加载目标包
            StartCoroutine(LoadTargetAssetBundleAsync(abName, (temp) => {
                // singleAssetBundle = temp;

                // 等待所有依赖包加载完成
                while (loadingQueue.Count > 0)
                {
                    AssetBundle loadedAB = loadingQueue.Dequeue();
                    abDic.Add(loadedAB.name, loadedAB);
                }
                // abDic.Add(abName, singleAssetBundle);
                callback(temp);

            }));


        }
    }

    // 依赖包的加载 : 依赖包名称, 路径, 加载完毕的回调函数
    public IEnumerator LoadSingleDependenceAsync(string dependName, UnityAction<AssetBundle> callback)
    {
        AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(PathURL + dependName);
        if(abcr.assetBundle != null)
        {
            yield return abcr;
            callback(abcr.assetBundle);
        }
        else
        {
            Debug.LogError("单个依赖包加载出错");
            callback(null);
            yield break;
        }  
    }

    // 加载目标包
    public IEnumerator LoadTargetAssetBundleAsync(string abName, UnityAction<AssetBundle> callback)
    {
        AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(PathURL + abName);
        if (abcr.assetBundle != null)
        {
            yield return abcr;
            callback(abcr.assetBundle);
        }
        else
        {
            Debug.LogError("目标包加载出错");
            callback(null);
            yield break;
        }
    }


    #endregion

    #region 使用泛型方式同步加载资源

    /// <summary>
    /// 同步加载
    /// </summary>
    /// <typeparam name="T">通过类型加载</typeparam>
    /// <param name="abName">资源包的名称</param>
    /// <param name="resName">资源类型</param>
    /// <returns></returns>
    public T LoadResources<T>(string abName, string resName) where T : Object
    {
        AssetBundle ab = LoadAssetBundle( abName);
        if(ab == null)
        {
            return null;
        }
        // Debug.Log("同步加载-指定泛型的加载模式");
        return ab.LoadAsset<T>(resName);
    }
    #endregion

    #region 普通方式同步加载资源
    //同步加载 不指定类型
    public Object LoadResources(string abName, string resName)
    {
        //加载AB包
        AssetBundle ab = LoadAssetBundle(abName);
        Object obj = ab.LoadAsset(resName);
        // Debug.Log("同步加载-不指定类型的加载模式");
        return obj;
    }
    #endregion

    #region 使用type方式同步加载资源

    /// <summary>
    /// 使用资源类型的方式进行加载 xLua中不支持泛型所以使用type类型
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    //同步加载 根据type指定类型 这里使用System.Type是因为返回的对象Object在Unity和System中均有Object.
    public Object LoadResources(string abName, string resName, System.Type type)
    {
        //加载AB包
        AssetBundle ab = LoadAssetBundle(abName);
        Object obj = ab.LoadAsset(resName, type);
        //Debug.Log("同步加载-指定Type的加载模式");
        return obj;

    }
    #endregion

    #region 使用异步加载资源

    /// <summary>
    /// 异步加载资源-启动协程
    /// </summary>
    public void LoadResourcesAsync(string abName, string resName, UnityAction<Object> callback)
    {
        // 开启协程
        StartCoroutine(ReallyLoadResourcesAsync(abName, resName, callback));
    }
    // 异步加载资源的协程
    public IEnumerator ReallyLoadResourcesAsync(string abName, string resName, UnityAction<Object> callback)
    {
        LoadAssetBundleAsync(abName, (tmp)=>
        {
            AssetBundleRequest abr = tmp.LoadAssetAsync(resName);
            if (abr == null || abr.asset == null)
            {
                Debug.LogError("未能加载资源包");
                callback(null);
            }
            else
            {
                callback(abr.asset);
            }
            // 得到资源后abr后返回abr的asset
        });
        yield return resName;       
    }
    #endregion

    #region 使用异步泛型加载资源

    /// <summary>
    /// 异步加载资源的泛型的方式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <param name="callback"></param>
    public void LoadResourcesAsync<T>(string abName, string resName, UnityAction<T> callback) where T : Object
    {
        // 开启协程
        StartCoroutine(ReallyLoadResourcesAsync<T>(abName, resName, callback));
    }
    // 异步加载资源的协程
    public IEnumerator ReallyLoadResourcesAsync<T>(string abName, string resName, UnityAction<T> callback) where T : UnityEngine.Object
    {
        LoadAssetBundleAsync(abName, (tmp) =>
        {
            AssetBundleRequest abr = tmp.LoadAssetAsync(resName);
            if (abr == null || abr.asset == null)
            {
                Debug.LogError("未能加载资源包");
                callback(null);
            }
            else
            {
                callback(abr.asset as T);
            }
            // 得到资源后abr后返回abr的asset
        });
        yield return null;
    }

    #endregion

    #region 使用异步Type加载资源
    public void LoadResourcesAsync(string abName, string resName,  System.Type type, UnityAction<Object> callback)
    {
        // 开启协程
        StartCoroutine(ReallyLoadResourcesAsync(abName, resName, callback));
    }
    // 异步加载资源的协程
    public IEnumerator ReallyLoadResourcesAsync(string abName, string resName, System.Type type, UnityAction<Object> callback)
    {
        this.LoadAssetBundleAsync(abName, (tmp) =>
        {
            AssetBundleRequest abr = tmp.LoadAssetAsync(resName, type);
            if (abr == null || abr.asset == null)
            {
                Debug.LogError("未能加载资源包");
                callback(null);
            }
            else
            {
                callback(abr.asset);
            }
            // 得到资源后abr后返回abr的asset
        });
        yield return null;
    }
    #endregion

    #region 卸载AB包
    // 单个包的卸载
    public void UnloadRes(string abName)
    { 
        if(abDic.ContainsKey(abName))
        {
            abDic[abName].Unload(false);
            abDic.Remove(abName);

        }
    }

    // 所有包的卸载

    public void ClearAB()
    {
        AssetBundle.UnloadAllAssetBundles(false);
        abDic.Clear();
        mainAB = null;
        manifest = null;
    }

    #endregion
}

5.测试代码

以下是AB包管理器的测试代码, 分为部分小节, 测试的结果是最开始的图片上的效果.

5.1 相关变量和函数

 // 同步加载测试
    private GameObject cube1;
    private GameObject cube2;
    private GameObject cube3;
    private GameObject floor1;
    // 同步加载测试根节点
    private GameObject root1;

    // 异步加载测试
    private GameObject cube4;
    private GameObject cube5;
    private GameObject cube6;
    private GameObject floor2;
    // 异步加载测试根节点
    private GameObject root2;
// 初始化根节点以便作为区分
private void InitRoot()
{
    root1 = new GameObject();
    root2 = new GameObject();
    root1.name = "同步加载Root";
    root2.name = "异步加载Root";
}

5.2 同步加载测试

public void TestLoadResourceAndPackage()
{
    // 同步加载测试--Type
    cube1 = ABManager.GetInstance().LoadResources("static.ab", "wall", typeof(GameObject)) as GameObject;
    cube1 = Instantiate(cube1);
    cube1.name = "同步--Type";
    cube1.transform.position = Vector3.one * -2;
    cube1.transform.SetParent(root1.transform);

    // 同步加载测试--泛型
    cube2 = ABManager.GetInstance().LoadResources<GameObject>("static.ab", "wall");
    cube2 = Instantiate(cube2);
    cube2.name = "同步--T泛型";
    cube2.transform.position = Vector3.one * -1;
    cube2.transform.SetParent(root1.transform);

    // 同步加载测试
    floor1 = ABManager.GetInstance().LoadResources("static.ab", "floor") as GameObject;
    floor1 = Instantiate(floor1);
    floor1.name = "同步--Normal";
    floor1.transform.position = Vector3.zero;
    floor1.transform.SetParent(root1.transform);
}

5.3异步加载测试

public void TestLoadResourceAndPackAsync()
{
    // 异步加载测试--只测试包的加载
    ABManager.GetInstance().LoadAssetBundleAsync("static.ab", LoadABAsync);

    // 异步加载测试
    ABManager.GetInstance().LoadResourcesAsync("static.ab", "wall", LoadResAndPackAsyncNormal);

    // 异步加载测试--泛型
    ABManager.GetInstance().LoadResourcesAsync<GameObject>("static.ab", "wall", LoadResAndPackAsyncT);

    // 异步加载测试--Type
    ABManager.GetInstance().LoadResourcesAsync("static.ab", "wall", typeof(GameObject), LoadResAndPackAsyncType);
}

// 异步测试AB包
private void LoadABAsync(AssetBundle ab)
{
    Debug.Log("测试异步加载只加载资源包的情况, 加载的包名" + ab.name);
}

// 测试异步加载资源-normal
private void LoadResAndPackAsyncNormal(UnityEngine.Object obj)
{
    cube4 = Instantiate(obj as GameObject);
    cube4.name = "异步--normal";
    cube4.transform.position = Vector3.one;
    cube4.transform.SetParent(root2.transform);
}

// 测试异步加载资源-T泛型
private void LoadResAndPackAsyncT(GameObject obj)
{
    cube5 = Instantiate(obj);
    cube5.name = "异步--T泛型";
    cube5.transform.position = Vector3.one * 2;
    cube5.transform.SetParent(root2.transform);
}

// 测试异步加载资源-Type
private void LoadResAndPackAsyncType(UnityEngine.Object obj)
{
    cube6 = Instantiate(obj as GameObject);
    cube6.name = "异步--Type";
    cube6.transform.position = Vector3.one * 3;
    cube6.transform.SetParent(root2.transform);
}

5.4 完整的测试代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestABManager : MonoBehaviour
{
    // 同步加载测试
    private GameObject cube1;
    private GameObject cube2;
    private GameObject cube3;
    private GameObject floor1;
    // 同步加载测试根节点
    private GameObject root1;

    // 异步加载测试
    private GameObject cube4;
    private GameObject cube5;
    private GameObject cube6;
    private GameObject floor2;
    // 异步加载测试根节点
    private GameObject root2;
    void Start()
    {
        InitRoot();

        TestLoadResourceAndpackage();
        TestLoadResourceAndPackAsync();
    }

    private void InitRoot()
    {
        root1 = new GameObject();
        root2 = new GameObject();
        root1.name = "同步加载Root";
        root2.name = "异步加载Root";
    }


    // 测试同步加载方式
    public void TestLoadResourceAndpackage()
    {

        // 同步加载测试--Type
        cube1 = ABManager.GetInstance().LoadResources("static.ab", "wall", typeof(GameObject)) as GameObject;
        cube1 = Instantiate(cube1);
        cube1.name = "同步--Type";
        cube1.transform.position = Vector3.one * -2;
        cube1.transform.SetParent(root1.transform);

        // 同步加载测试--泛型
        cube2 = ABManager.GetInstance().LoadResources<GameObject>("static.ab", "wall");
        cube2 = Instantiate(cube2);
        cube2.name = "同步--T泛型";
        cube2.transform.position = Vector3.one * -1;
        cube2.transform.SetParent(root1.transform);

        // 同步加载测试
        floor1 = ABManager.GetInstance().LoadResources("static.ab", "floor") as GameObject;
        floor1 = Instantiate(floor1);
        floor1.name = "同步--Normal";
        floor1.transform.position = Vector3.zero;
        floor1.transform.SetParent(root1.transform);
    }

    // 测试异步加载方式
    public void TestLoadResourceAndPackAsync()
    {
        // 异步加载测试--只测试包的加载
        ABManager.GetInstance().LoadAssetBundleAsync("static.ab",LoadABAsync);
        // 异步加载测试
        ABManager.GetInstance().LoadResourcesAsync("static.ab", "wall", LoadResAndPackAsyncNormal);
        // 异步加载测试--泛型
        ABManager.GetInstance().LoadResourcesAsync<GameObject>("static.ab", "wall", LoadResAndPackAsyncT);
        // 异步加载测试--Type
        ABManager.GetInstance().LoadResourcesAsync("static.ab", "wall", typeof(GameObject), LoadResAndPackAsyncType);
    }


    // 异步测试AB包
    private void LoadABAsync(AssetBundle ab)
    {

        Debug.Log("测试异步加载只加载资源包的情况, 加载的包名" + ab.name);
    }

    // 测试异步加载资源-normal
    private void LoadResAndPackAsyncNormal(UnityEngine.Object obj)
    {
        cube4 = Instantiate(obj as GameObject);
        cube4.name = "异步--normal";
        cube4.transform.position = Vector3.one;
        cube4.transform.SetParent(root2.transform);
    }

    // 测试异步加载资源-T泛型
    private void LoadResAndPackAsyncT(GameObject obj)
    {
        cube5 = Instantiate(obj);
        cube5.name = "异步--T泛型";
        cube5.transform.position = Vector3.one * 2;
        cube5.transform.SetParent(root2.transform);

    }
    // 测试异步加载资源-Type
    private void LoadResAndPackAsyncType(UnityEngine.Object obj)
    {
        cube6 = Instantiate(obj as GameObject);
        cube6.name = "异步--Type";
        cube6.transform.position = Vector3.one * 3;
        cube6.transform.SetParent(root2.transform);
    }

}

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unity3D是一款功能强大的游戏开发引擎,支持跨平台开发。在使用Unity3D进行开发时,我们常常需要加载资源(Asset Bundle,简称AB)来实现游戏中的动态资源加载。本地加载AB一般分为以下几个步骤: 首先,我们需要在Unity编辑器中将需要打的资源进行设置,选择对应的资源进行打,生成相应的AB文件。可以选择将资源打成一个或多个AB,根据自己的需求进行设置。 然后,将生成的AB文件复制到游戏的本地目录下。可以将AB文件放在Assets目录下,也可以放在其他指定的目录下,方便我们后续进行加载。 接下来,我们需要通过代码来加载ABUnity3D提供了一些API来实现这个功能。我们可以使用AssetBundle.LoadFromFile或AssetBundle.LoadFromFileAsync来加载AB文件,将AB文件加载到内存中。 加载成功后,我们就可以通过AB中的资源名或者路径来获取对应的资源对象,然后进行相应的使用。可以使用AssetBundle.LoadAsset来加载一个资源对象,或者使用AssetBundle.LoadAssetAsync进行异步加载。 最后,在游戏中不需要使用AB时,我们需要释放加载的AB资源,以释放内存。可以使用AssetBundle.Unload或AssetBundle.UnloadAsync来卸载AB,释放AB占用的资源和内存。 通过以上步骤,我们可以实现在Unity3D中本地加载AB的功能,实现动态资源加载,提高游戏的灵活性和扩展性,为玩家带来更好的游戏体验。 ### 回答2: Unity3D是一款非常强大的游戏开发引擎,支持本地加载Asset BundleAB)。AB是一种含资源文件的打格式,可以含场景、模型、材质、贴图、音频等各种游戏资源。 在Unity3D中,本地加载AB有以下几个步骤: 1. 创建AssetBundle对象:使用AssetBundle类的LoadFromFile方法,指定AB的路径,即可创建一个AssetBundle对象。 2. 加载资源:通过AssetBundle对象的LoadAsset方法,传入资源的类型和名称,即可加载指定的资源。 3. 实例化对象:通过Object类的Instantiate方法,传入加载的资源对象,即可创建其实例,可以将资源实例化为游戏中的对象,比如场景、角色等。 4. 使用资源:加载完成后,可以使用资源对象的各种属性和方法,比如修改材质、播放音频等。 5. 释放资源:加载的资源在不使用时应该进行释放,通过AssetBundle对象的Unload方法,可以释放AB以及其加载的资源,以释放内存。 需要注意的是,本地加载AB需要事先将资源打AB,并将AB放置在合适的路径下。在加载AB时,需要指定准确的路径,并确保路径中的AB存在。 本地加载ABUnity3D非常常见且重要的功能,可以方便地管理和加载游戏资源,提高游戏开发的效率和性能。通过熟练掌握AB的打和加载原理,可以更好地实现游戏的开发需求。 ### 回答3: 在Unity3D中,AB是Asset Bundle的缩写,是一种用于打和加载资源的机制。本地加载AB是指从本地磁盘读取AB文件并加载其中的资源。 首先,我们需要准备好AB文件,可以使用Unity3D提供的BuildPipeline.BuildAssetBundles 方法进行打。打完成后,会生成一个或多个AB文件,通常是以.unity3d或.ab后缀结尾的文件。 然后,我们可以使用AssetBundle.LoadFromFile 方法从指定的本地路径加载AB文件。这个方法接受一个字符串参数,表示AB文件的路径。加载完成后,会返回一个AssetBundle对象。 接下来,我们可以通过AssetBundle对象获取其中的资源。可以使用Load 方法加载一个或多个资源,也可以使用LoadAll 方法加载所有资源。这些方法通常接受一个字符串参数,表示要加载的资源的名称。加载的资源根据其类型而定,可以是GameObject、Texture、AudioClip等等。 加载完资源后,我们可以进行相应的处理,比如显示3D模型、设置游戏场景、播放音频等等。 使用完AssetBundle后,为了释放内存和资源,可以使用AssetBundle.Unload 方法卸载AB文件。这个方法接受一个布尔值参数,表示是否同时卸载AB中所有的资源。通常建议传入true,以确保完全卸载AB。 总结起来,Unity3D中本地加载AB的过程大致如下:准备AB文件 -> 使用AssetBundle.LoadFromFile加载AB文件 -> 使用AssetBundle.Load加载资源 -> 处理加载的资源 -> 使用AssetBundle.Unload卸载AB。 这样,我们就可以通过Unity3D本地加载AB,实现更高效的资源管理和加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值