Unity资源打包AssetBundle以及加载小记

概要

对于游戏开发来说,资源管理是必不可少的。Unity中使用AssetBundle是大部分开发者的选择,但是使用官方的AB标签形式去打包的话,需要管理每个资产的标签,这有些分散与不易不易组织。而AddressableAsset目前没看到大规模使用的资料,而Unity的新技术总是没那么让人放心。所以最终我决定仿照大部分的项目自己写一个AB打包以及加载的模块来处理资源问题。

设计与实现

打包

打包大部分为编辑器代码。分为打包器配置、打包配置、打包三个部分。各种配置以及存储使用Json实现,也可以使用ScriptableAsset,但Json对字典等的序列化支持的更好。

扩展编辑器界面

打包器配置

打包器的配置没什么特别的,就是确认各种路径以及忽略项目之类的。

/// <summary>
/// AB配置数据类
/// </summary>
[System.Serializable]
public class GeneratorCfgData
{
    public string ABExtension = ".ab";
    public readonly string ABPackageCfgsJsonPath = Path.Combine(Application.dataPath, "Editor", "ABCfg", "ABCfg.json");
    public readonly string ABStorePath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "AssetsBundle");//打包结果存储位置
    public readonly string ABCopyPath = Path.Combine(Application.streamingAssetsPath, "AssetsBundle");//打包结果拷贝存储位置
    public bool isWarnAutoDepBundleSize = true;//是否警告自动依赖收集包大小
    public float warnAutoDepBundleFileSize_KB = 1024.0f;//警告自动依赖收集包大小(单位KB)

    ///获取文件时 需要忽略的文件后缀名
    public List<string> notCollectFileExtensions = new List<string>();
    ///获取文件时 需要忽略的文件夹
    public List<string> notCollectDirFullName = new List<string>();
    ///收集依赖时 需要忽略的文件后缀名
    public List<string> notCollectFileExtensions_Dependencies = new List<string>();
    ///收集依赖时 需要忽略的被依赖的文件后缀名
    public List<string> notCollectFileExtensions_Dependencies_Dep= new List<string>();

    ///打包选项
    public BuildAssetBundleOptions buildAssetBundleOptions = BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.ForceRebuildAssetBundle;
    ///打包目标平台
    public BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget;
    ///是否清空输出目录
    public bool isCleanOutputDir = true;
    ///拷贝到SteamingAssets
    public bool copy2SteamingAssets = false;
}

打包配置

打包的的配置用来确定在什么范围内生成一个或多个AB。

/// <summary>
/// AB包配置数据类
/// </summary>
[System.Serializable]
public class ABPackageCfgData
{
    public string name;//此规则的名字
    public bool isUsing = true;//是否启用此打包规则
    public ePackWay packWay = ePackWay.none;//打包方式


    public string assetDir;//资源的搜索根目录
    public string searchePattern;//搜索通配符
    public bool isRecursionSearch = true;//是否递归的搜索(total不适用)
    public bool isForceIncludeDepends = false;//是否强制包含依赖(优先用它)
    public bool isIncludeTopDir = false;//是否包含顶层目录(eachFolder适用)
}

/// <summary>
/// 打包方式
/// </summary>
public enum ePackWay
{
    none = 0,//未定义
    eachFile = 1,//每个文件一个ab
    eachFolder = 2,//每个文件夹一个ab
    total = 3,//整个根目录一个ab
}

打包

打包的主要流程如StartGenABs()函数所示,我会先生成一份中间数据来把打包配置对应的一个或多个AB的信息生成出来,中间数据包括AB包名字、所有资源路径、依赖的其它AB名字(稍后生成)等。

生成了中间数据之后是去收集依赖,确认AB之间的依赖关系,如果出现了没有在任何AB中的依赖资源,那就把这些依赖单独生成一个中间数据。

有了中间数据之后,把它转换成Unity内部的AssetBundleBuild对象,用于给打包管线打包使用。

    public static void StartGenABs()
    {
        ////防止没有保存
        //SaveGeneratorCfg();
        //SaveABCfgs();

        try
        {
            //生成中间数据
            List<ABMidData> midDatas = GenABMidData();
            //处理收集依赖
            CollectDependencies(midDatas);

            //生成最终的unity用的打包配置
            List<AssetBundleBuild> ABBList = GenRealABBuilData(midDatas);
            //打包AB包
            BuildAB(ABBList);
        }
        catch (CancleBuildABException e)
        {
            Debug.LogWarning(e);
            throw;
        }

        EditorUtility.ClearProgressBar();//关掉进度条
    }

其中,收集依赖主要靠AssetDatabase.GetDependencies(string pathName)函数实现,它能获得Unity内部的资源索引。

在打包的最后阶段,我会生成一个自定义的清单文件,把资源到AB的关系映射出来,在加载资源时将会用到它来查找资源属于哪一个AB。

Tip:这里存在一个性能方面的疑虑,自定义清单可能会非常大。

加载

类说明

每一个真正的Asset都对应一个ResBase类,这些ResBase由ResLoader管理,而总管理器ResLoadManager又会管理所有的ResLoader以及AB。

继承ResBase的类目前有三个:EditorRes、BundleRes、BundleAssetRes。

EditorRes是编辑器下用的资源类,使用AssetDatabase.LoadAssetAtPath。

BundleRes是直接加载AB的资源类。

BundleAssetRes是加载AB中资源的资源类。

为了开发的方便,我希望资源的加载可以在AB模式以及非AB下无感的切换。

扩展的编辑器菜单

为此,使用UNITY_EDITOR宏来生成不同ResBase,编辑器下使用EditorRes,其它情况则使用AB。

异步加载

本来想使用Async与Awit来做异步,但是Unity本身的异步代码都是迭代器的协程异步,没法与Async配合。所以就写了一个协程管理,迭代器都会被存到一个队列中,上一个跑完了跑下一个以此来实现异步。

AB管理

每一个AB都会有引用计数;在加载时,AB及其依赖AB的引用计数会+1,卸载时-1。当减到0时则unload此AB。

异常处理

当Resloader被回收的时候,假如资源还没加载完或者加载到一半(异步),需要处理异常。

public void OnRecycle()
    {
        //释放资源
        if (allRes.Count > 0)
        {
            foreach (IRes res in allRes)
            {
                if (res.resState == eResState.loadDone)
                {
                    res.ReleaseAsset();
                }
                else if (res.resState == eResState.wait2Load)//协程Action加到队列里了
                {
                    res.RemoveFromeWait2LoadCoroutieQueeu();
                }
                else if (res.resState == eResState.loading)//协程开始跑了
                {
                    res.StopResLoadAsyncRoutine();
                }
            }
            allRes.Clear();
        }
    }
    /// <summary>
    /// 从等待加载的队列中移除
    /// </summary>
    public void RemoveFromeWait2LoadCoroutieQueeu()
    {
        if (loadAction != null)
        {
            ResLoadManager.RemoveWait2LoadCoroutine(loadAction);
        }
    }

    /// <summary>
    /// 停止加载的协程
    /// </summary>
    public void StopResLoadAsyncRoutine()
    {
        if (loadCoroutine != null)
        {
            //直接停止可能会因为调用的不原子而产生问题
            //比如AB加载好了,但是引用的AB没加载好
            //MonoDriver.Instance.StopCoroutine(loadCoroutine);

            //替换掉LoadOver
            finishAction = null;
            loadOverCallback = () => {
                ReleaseAsset();
            };
        }
    }

场景加载

这里不太清楚原理,还需要再学习。目前的用法是一个场景一个AB,在场景管理器里每当要加载场景就新获取一个ResLoader然后将他们存入字典;监听unloadScene这个事件,当这个场景被回收时,同时回收对应的ResLoader来保证AB的回收。

图集加载

图集的主动加载需要先手动加载图集,然后从这个图集里获取sprite。直接在场景里的sprite可以正确的读取到对应的图集。

总结

加载场景2
加载场景3
卸载UI

经过测试,在加载场景、切换场景、加载预制体、删除预制体时能够正确的读取AB以及释放AB。同时图集的合批也没有问题。

使用AssetBundle Browser对AB进行检视,图集也没有出现资源重复的问题。

目前来说,这个打包加载模块足够我自己的开发使用了。

参考:

[官网资源工作流]https://docs.unity3d.com/cn/current/Manual/AssetWorkflow.html

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇