Unity 资源管理器(一)

unity 资源管理器 AssetBundle


对于资源管理的理解

什么是资源管理?我的理解是按照一个既定的规则去将游戏中需要用到的资源进行分类、整理,然后提供统一的接口,让玩家在游戏中可以方便的加载调用资源。基于这个准则,就对资源管理器的框架有了一个初步的构想。

资源管理器目标:

  1. 制定一个资源存放的规则,包含资源命名、资源存储路径、路径命名等。

  2. 提供统一的资源动态加载的接口(AssetBundle和Resources文件夹下的资源的动态加载)

这只是第一版,所以比较简单,后面还会继续更新,在具体实现这个资源管理器之前,我们必须对AssetsBundle有一定的了解先。

为什么要使用AssetBundle?

Unity提供了两种动态加载资源的方式,分别是Resource.Load、和AssetBundle。我们先分别介绍下这两种加载方式的优缺点。

Resouce.Load的优缺点:

优点:

  1. 使用方便。只需要把资源放在Resources文件夹下,无需额外操作,就可以使用Resource.Load动态的加载资源。

  2. 好像就只有方便在这一个优点了。

缺点:

  1. Resources文件夹存储过多之后,会增加程序的启动时间。

  2. 增加了多平台资源替换的难度。

Resources具体的内容可以查看这个博客:https://blog.csdn.net/qq_21397217/article/details/80542155

AssetBundle的优缺点:

优点:

  1. 可以放在服务器,供游戏运行时下载使用,降低了初始包的大小。

  2. 热更新

  3. 可以使用LZMA和LZ4压缩算法,减少包的大小

缺点:

  1. 使用起来相对于Resource文件,要繁琐一点,需要先打包,然后再加载使用。

  2. 会有一些坑,但是现在应该很多坑都已经填好了(使用不多,暂时还没遇到过什么坑)

综上所述,除非在Editor模式下,最终包都不推荐使用Resource.Load()去动态的加载资源。下面就来简要的介绍一下AssetsBundle的使用过程(比较浅)。

AssetBundle打包

手动设置AssetBundle名称

在打包之前,我们需要先设置为资源设置AssetBundle,在inspector视图底部,找到AssetBundle选项,默认是None,如图:

设置AssetBundle

第一个参数是AB包的名称,第二个参数是别名(后缀)。AssetBundle名称不区分大小写,打包的时候都会转为小写,相同名称的资源,会被打在一个AssetBundle包里。

使用脚本自动为资源添加AssetBundle名称

我们先将我们需要打包的资源放到指定文件夹。可以将同一个AB包里的资源放在同一个文件夹下,然后获取该文件夹下所有的资源,并为其设置AssetBundle名称,核心代码如下:

/// <summary>
/// 删除所有AssetBundleName
/// </summary>
public static void ClearAssetBundlesName()
{
    int length = AssetDatabase.GetAllAssetBundleNames().Length;
    string[] oldAssetBundleNames = new string[length];

    for (int i = 0; i < length; i++)
    {
        oldAssetBundleNames[i] = AssetDatabase.GetAllAssetBundleNames()[i];
    }

    for (int j = 0; j < length; j ++)
    {
        AssetDatabase.RemoveAssetBundleName(oldAssetBundleNames[j], true);
    }
    Debug.LogError("已经成功删除项目中所有已设置的AssetBundleName");
}

/// <summary>
/// 为资源重新设置AssetBundleName
/// </summary>
public static void ReSetAssetBundlesName(string path, string abName, string abVariant)
{
    AssetImporter asset = AssetImporter.GetAtPath(path); // 把一个目录的对象检索为AssetImporter
    
    if (asset)
    {
        asset.assetBundleName = abName;      //设置Bundle文件的名称
        asset.assetBundleVariant = abVariant;  // 设置Bundle文件的扩展名
        asset.SaveAndReimport();    // 保存设置,并重新导入资源
    }
    else
    {
        Debug.LogError("获取该资源失败" + file.FullName);
    }

    AssetDatabase.Refresh();
}

打包

据说以前打包AssetBundle巨麻烦(我还年轻,没有经历过那个时代),现在精简为两个接口。


// 方法一:BuildPipeline.BuildAssetBundle,将指定资源集合打包
Obsolete public static bool BuildAssetBundle(Object mainAsset, Object[] assets, string pathName, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
Obsolete public static bool BuildAssetBundle(Object mainAsset, Object[] assets, string pathName, out uint crc, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);

// 方法二:BuildPipeline.BuildAssetBundles,打包所有设置了AssetBundle的资源
public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);

我比较懒,所以直接使用了第二个函数去打包。具体打包代码如下:

[MenuItem("Assets/Build AssetBundles")]
void static Build()
{
    BuildTarget buildTarget = BuildTarget.StandaloneWindows;
    string buildPath = Path.Combine(Application.streamingAssetsPath, buildTarget.ToString());

    // 若路径不存在则创建
    if (!Directory.Exists(buildPath))
    {
        Directory.CreateDirectory(buildPath);
    }

    // 打包
    BuildPipeline.BuildAssetBundles(buildPath, BuildAssetBundleOptions.None, buildTarget);
    AssetDatabase.Refresh();
}

备注:MenuItem可以在Assets菜单下面添加一个菜单项——Build AssetBundles

到这里我们就打包完成了,可以在我们设置的路径 [streamingAssetsPath/StandaloneWindows] 下看到我们刚刚打包的资源文件。

AssetBundle 包
AssetBundle 包

AssetsBundle 加载

每次打包AssetBundle包,都会自动创建一个与文件夹名字相同的AssetBundle,这个AssetBundle存储这次打包的所有AssetBundle依赖信息。如下图:

此处输入图片的描述
此处输入图片的描述

我们在加载一个AssetBundle之前,需要先将其相关的依赖给加载进来,比如当我们要加载一个模型,则需要先将其材质和贴图加载进来,否则材质就是丢失,当然如果我们全部打包在一个文件夹中,就不会有这些问题了。

所以在加载资源之前,我们先要加载上图所示的依赖包StandaloneWindows。具体代码如下所示:

/// <summary>
/// 获取 AssetBundleManifest
/// </summary>
/// <returns></returns>
public AssetBundleManifest GetAssetBundleManifest()
{
    if (_abManifest)
        return _abManifest;

    /// AssetBundleManifestPath = StreamingAssets/StandaloneWindows
    AssetBundle manifestBundle = LoadAssetBundleOnly(AssetBundleManifestPath);
    _abManifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

    return _abManifest;
}

加载完依赖包之后,我们就可以开始加载AssetBundle了。代码如下

/// <summary>
/// 加载 AssetBundle,同时加载所有的依赖
/// </summary>
/// <param name="assetbundleName"></param>
/// <returns></returns>
public AssetBundle LoadAssetBundleWithAllDependencies(string assetbundleName)
{
    if (AssetBundleIsLoaded(assetbundleName))
    {
        return _assetBundleCacheMap[assetbundleName];
    }

    AssetBundleManifest manifest = GetAssetBundleManifest();
    string[] dependencies = manifest.GetAllDependencies(assetbundleName);

    // 加载依赖资源
    for (int i = 0; i < dependencies.Length; i++)
    {
        if (_assetBundleCacheMap.ContainsKey(dependencies[i]))
            continue;
        LoadAssetBundleOnly(dependencies[i]);
    }

    return LoadAssetBundleOnly(assetbundleName);
}

/// <summary>
/// 只加载需要加载的 AssetBundle,不加载对应的依赖
/// </summary>
/// <param name="assetbundleName"></param>
/// <returns></returns>
public AssetBundle LoadAssetBundleOnly(string assetbundleName)
{
    if (_assetBundleCacheMap.ContainsKey(assetbundleName))
    {
        return _assetBundleCacheMap[assetbundleName];
    }

    AssetBundle bundle;
    if (assetbundleName.EndsWith(RMConfig.NetAssetsVariant))
    {
        WWW www = WWW.LoadFromCacheOrDownload(assetbundleName, 0);
        bundle = www.assetBundle;
    }
    else
    {
        bundle = AssetBundle.LoadFromFile(Path.Combine(GetAbSavePathWithPlatform(Application.platform), assetbundleName));
    }

    _assetBundleCacheMap[assetbundleName] = bundle;
    return bundle;
}

最后,再从或得到的AssetBundle中加载我们需要的资源:

/// <summary>
/// 同步 从指定AssetsBundle中加载资源
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="assetbundleName"></param>
private T LoadAssetsFromAssetBundle<T>(string assetbundleName, string assetsName) where T : Object
{
    AssetBundle bundle = LoadAssetBundleWithAllDependencies(assetbundleName);

    if (bundle == null)
    {
        Debug.LogError(string.Format("不存在路径为{0}的AssetBundle!!!", assetbundleName));
        return null;
    }

    return bundle.LoadAsset<T>(assetsName);
}

/// <summary>
/// 异步 从指定AssetsBundle中加载资源
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="assetbundleName"></param>
private void LoadAssetsFromAssetBundleAsync<T>(string assetbundleName, string assetsName, System.Action<T> onFinish) where T : Object
{
    CoroutineManager.Instance.AddTask(LoadAssetBundleWithAllDependenciesAsync(assetbundleName, (bundle) =>
    {
        CoroutineManager.Instance.AddTask(LoadAssetsFromBundleAsync(bundle, assetsName, onFinish));
    }));
}

/// <summary>
/// 异步 从指定AssetsBundle中加载资源
/// </summary>
/// <param name="assetbundleName"></param>
/// <returns></returns>
public IEnumerator LoadAssetsFromBundleAsync<T>(AssetBundle bundle, string assetsName, System.Action<T> onFinish) where T : Object
{
    if (bundle == null)
    {
        onFinish(null);

        yield break;
    }

    AssetBundleRequest request = bundle.LoadAssetAsync<T>(assetsName);

    yield return request;
    if (onFinish != null)
        onFinish(request.asset as T);
}

到这里,AssetBundle的初步使用就介绍完了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容