Unity--AssetBundle AB包管理器

news2024/11/15 9:44:40

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);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2050285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python匿名函数之lambda表达式使用详解

概要 在Python编程中,函数是组织代码和实现逻辑的基础单元。除了使用def关键字定义命名函数外,Python还提供了创建匿名函数的方式,即lambda表达式。lambda表达式是一种简洁的函数定义方式,通常用于需要简短函数的场景。本文将详细介绍Python匿名函数的概念、使用场景及其高…

基于51单片机的双机通信控制系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1Y4wOJKOYf2E4JeEktyKdTw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

微分方程(Blanchard Differential Equations 4th)中文版Section3.1

3.1 PROPERTIES OF LINEAR SYSTEMS AND THE LINEARITY PRINCIPLE(线性系统问题与线性算子原理) 在第2章中,我们专注于研究微分方程组的定性和数值方法。之所以这样做,是因为我们很少能找到具有两个或更多个因变量的系统的明确解公式。唯一的例外是线性系统。在本章中,我们…

Linux·权限与工具-yum与vim

1. Linux软件包管理器 yum 1.1 什么是软件包 在Linux下安装软件&#xff0c;一个通常的办法是下载到程序的源代码&#xff0c;并进行编译&#xff0c;得到可执行程序。但这样做太麻烦了&#xff0c;于是有些人把一些常用的软件提前编译好&#xff0c;做成软件包(可以理解成Win…

12.2 使用prometheus-sdk向pushgateway打点

本节重点介绍 : 使用golang sdk打prometheus4种指标&#xff0c;推送到pushgateway gauge、counter、histogram、summary的初始化4种类似的设置值的方法推送到pushgateway的方法 prometheus配置采集pushgateway&#xff0c;grafana上配大盘 golang-sdk 项目地址 https://git…

【图数据库系列】Cypher查询语句:常用语法指南

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

HTML常用标签和CSS的运用,以及使用HTML做一个简历

目录 1.HTML标签 1.1 文档结构标签 1.2 文本格式标签 1.3 列表标签 1.4 链接和媒体标签 1.5 表格标签 1.6 表单标签 1.7 分区和布局标签 1.8 元数据标签 2.css样式 2.1 字体样式 2.2 文本样式 2.3 背景样式 2.4 边框样式 2.5 间距样式 2.6 宽度和高度 2.7 显示…

三种简单排序:插入排序、冒泡排序与选择排序 【算法 05】

三种简单排序&#xff1a;插入排序、冒泡排序与选择排序 在编程中&#xff0c;排序算法是基础且重要的知识点。虽然在实际开发中&#xff0c;我们可能会直接使用标准库中的排序函数&#xff08;如C的std::sort&#xff09;&#xff0c;但了解并实现这些基础排序算法对于理解算法…

JS编程中有哪些常见的编程“套路”或习惯

JS编程中有哪些常见的编程“套路”或习惯 从个人的编程经验来看&#xff0c;不管你是前端JS编程还是后端Java编程&#xff0c;在一些习惯上基本是通用的。就是你编写的JS代码必须要功能完善且易于阅读易于维护。那么这里整理一下JS编程过程中一些比较行之有效的编程习惯。 函数…

3.Default Constructor的构造操作

目录 1. 问题引入 2. 4种implicitly声明的default constructor 1. 问题引入 “default constructors......在需要的时候被编译产生出来”。关键词是“在需要的时候”&#xff0c;被谁需要&#xff0c;做什么事情&#xff1f;看看下面的代码&#xff0c;然后梳理下思路。 cl…

章二十、Servlet ——

一、 web开发概述 所谓web开发,指的是从网页中向后端程序发送请求,与后端程序进行交互&#xff0c;流程如下&#xff1a; ● 什么是服务器&#xff1f; Web服务器是指驻留于因特网上某种类型计算机的程序&#xff0c;它可以向浏览器等Web客户端提供文档&#xff0c;也可以放置…

算法的学习笔记—反转链表(牛客JZ24)

&#x1f600;前言 在算法面试中&#xff0c;链表问题是一个常见的考点&#xff0c;而反转链表更是其中的经典题目之一。本篇文章将通过具体的代码实现和思路解析&#xff0c;带你深入理解反转链表的解法。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1f600;反…

【前端】NodeJS:项目上线

文章目录 1 项目上线1.1 搭建本地仓库1.1.1 初始化仓库1.1.2 全局忽略1.1.3 提交 1.2 连接远程仓库1.2.1 新建仓库1.2.2 复制地址1.2.3 添加远程存储库 1.3 发布项目 2 配置HTTPS证书2.1 HTTPS2.2 操作流程: 1 项目上线 1.1 搭建本地仓库 1.1.1 初始化仓库 1.1.2 全局忽略 1.…

福泰轴承股份有限公司进销存系统pf

TOC springboot413福泰轴承股份有限公司进销存系统pf 绪论 1.1 研究背景 现在大家正处于互联网加的时代&#xff0c;这个时代它就是一个信息内容无比丰富&#xff0c;信息处理与管理变得越加高效的网络化的时代&#xff0c;这个时代让大家的生活不仅变得更加地便利化&#…

Idea里配置Maven版本

一、安装Maven 1. 官网下载maven地址&#xff1a; Maven – Download Apache Maven Binary是可执行版本&#xff0c;已经编译好可以直接使用。 Source是源代码版本&#xff0c;需要自己编译成可执行软件才可使用。tar.gz和zip两种压缩格式,其实这两个压缩文件里面包含的内容是…

AWS域名注册服务:为您的在线业务打下坚实基础

在如今的数字时代&#xff0c;域名是每个在线业务的基础。一个好的域名不仅可以提升品牌形象&#xff0c;还能为用户提供便捷的访问体验。亚马逊网络服务&#xff08;AWS&#xff09;提供了强大的域名注册服务&#xff0c;帮助企业轻松获取和管理域名。我们九河云将深入探讨AWS…

Spring:IOC的详解☞Bean的实例化、Bean的生命周期

1、Bean基础配置 bean的基础配置&#xff1a; <bean id"" class""/> Bean的别名&#xff1a;name属性 Bean的作用范围&#xff1a;scope配置 使用bean的scope属性可以控制bean的创建是否为单例&#xff1a; singleton 默认为单例prototype 为非单…

HiveSQL:提取json串内容——get_json_oject和json_tuple

提取json串中内容&#xff0c;json格式示例如下 方法&#xff08;运行环境Hive&#xff09; get_json_objectjson_tuple get_json_object select json_data,get_json_object(json_data,$.price) as price -- 取第一层,get_json_object(json_data,$.inquiry_params) as in…

Hive3:常用的内置函数

1、查看函数列表 -- 查看所有可用函数 show functions; -- 查看count函数使用方式 describe function extended count;2、数学函数 -- round 取整&#xff0c;设置小数精度 select round(3.1415926); -- 取整(四舍五入) select round(3.1415926, 4); -- 设置小数精度4位(四…

wordpress资源下载博客站微信小程序源码v1.0

小程序说明&#xff1a; 本套源码基于hbuilder制作&#xff0c;支持QQ小程序&#xff0c;微信小程序等各大平台小程序版本&#xff0c;小程序需要配合后端wordpres系统管理发布&#xff0c;带有广告功能&#xff0c;非常适合运营。 功能说明&#xff1a; 1.全新pods rest a…