Unity 热更新学习笔记五:AssetBundle资源管理

发布时间 2023-07-08 13:59:14作者: CatSevenMillion

内容学习自编程之力大佬视频:编程之力的个人空间_哔哩哔哩_bilibili

1.资源包优化

  当我们场景中包含了某一个物体,且场景与物体都同时打包时会报错:

  图中:01场景下包含了model131这个物体,所以打包时会提示错误。

 使用Move deplicates to new bundle 复制一份新包,然后将两个资源指向这个新包可以解决报错,但是新问题来了。

这样做的话,包的大小变成了14M。如果场景中没有模型,单独将场景和模型打包的话只有7M,容量足足翻了一倍。

解决方法:

  在原始场景中不放置物体,当地图载入时再加载物体到场景中

2.分包设计

   在打包时,我们可能有这种情况,在Character目录下有Char1和Char2两个角色预制体。我们现在要进行打包,我们可以将整个Character目录都打成一个包。但是,在实际开发时我们应当分开打包,将Char1和Char2分别打包。

  这样做的原因是:

  1.方便后续更新

    例如版本1只有十个角色,版本2有二十个角色,使用分包设计时,第二个版本就只用下载增加的部分,不用把之前的部分重新下载。

  2.减少解密运算,提高游戏运行速度

    游戏中的资源包打包后是会进行加密的,使用时会先进行解密操作。如果包太大,每次调用都先要解密包所有的文件,耗时也就大了。  

3.资源加载框架

   首先我们要知道,同一个资源在Unity中是不允许被加载两次的。所以我们应当记录一下包是否被加载过。

  一下是一个加载资源的方法

// 记录
AssetBundle asset
[ContextMenu("InstantiateBundle")]
protected void InstantiateBundle()
{
    if(asset==null)
        AssetBundle asset = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath,bundleName));
    var asset_god = asset.LoadAsset<GameObject>(assetName);
    MonoBehavior.Instantiate(asset_god);
}    

  还有一种情况,就是我们现在在编辑器中添加了新的文件,但是如果按照一般流程,添加文件后要进行重新打包,那这样的话时间就会非常的长。所以我们需要一种可以直接读取编辑器资源的方法。

protected void InstantiateBundle()
{
    
    var assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(bundleName,assetName);

    var gob = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(assetPaths[0]);
    MonoBehavior.Instantiate(gob);
}  

4.同步加载还是异步加载

   同步加载更加方便,且速度更快,缺点是会给用户特别卡的感觉。

  实际使用:当我们需要使用进度条的时候采用异步加载的方式,否则则使用同步加载的方式。或者可以使用假进度条的方式看上去是异步加载实际上使用同步加载,不过是把同步加载分成了多个部分。

5.资源版本对比

   资源更新就是让本地包的资源与服务器的资源做一个版本号的比较,若版本一致则进入游戏,不一致的话就更新资源。

  更新流程为:比较文件的md5码,如果有差异就放到下载列表中,下载完毕后再写入版本到本地。

  分析过程:匹配文件时只是比较配置文件的md5码,这种做会有一定的安全性问题,因为可能由于杀毒软件或者网络问题把资源删除,但是配置文件还保留着。

  那为什么不直接比较文件的md5码呢?因为比较文件的md5码会花更多的时间,而且安全性问题发生概率其实很小。

  解决办法

            1.添加修复按钮,让用户自行操作。原理:删除游戏版本号文件,再进行一次更新,重新下载文件及配置文件。

6.资源清单生成与版本比较

  制作资源版本比较工具一般分为三步:1.生成资源及其配置文件 2.搭建文件服务器 3.客户端下载文件并且与本地作比较,若有更新则更新。

  1.生成资源及其配置文件

  直接上代码:

[MenuItem("资源部署/生成资源配置文件")]
    private static void GeneratorAssetList()
    {
        // 获取目录下所有文件
        string path = AsserGenerate();
        DirectoryInfo root = new DirectoryInfo(path);
        FileInfo[] files = root.GetFiles();
        var cfg = new VersionFileCfg();
        // 获取文件大小,名字,MD5码
        foreach (var f_file in files)
        {
            FileInFoForUpdate f = new FileInFoForUpdate();
            f.size = f_file.Length;
            f.md5 = MD5File(f_file);
            var fileName = Path.GetFileName(f_file.FullName);
            cfg.files.Add(fileName, f);
        }
        //设置软件版本,如果和服务器不一致提示到商店更新
        // 往往是框架不能满足新提出的需求
        cfg.app_version = PlayerPrefs.GetString("app_version",System.DateTime.Today.ToString() );
        // 设置资源文件版本
        cfg.files_version = System.DateTime.Now.ToString();
        string json = JsonConvert.SerialObject(cfg);
    }

  主要流程就是,先打包资源成为AB包,其次生成资源配置文件,作为开发者最后将资源配置文件上传到服务器。

  2.搭建文件服务器,这里通过后端程序员来完成。

  3.文件对比, 我们可以登录游戏时先读取本地和服务器的配置文件,在将大版本号与小版本号分别进行对比,都通过才继续。

7.文件下载(断点续传)

   断点续传的整个流程是:1.将本地版本与服务器版本做比较,对比文件差异得到下载清单。2.文件下载完毕后写入版本号

   原理:请求下载文件时,请求头包含了现有文件长度(开始位置)需要文件总长度(结束位置),每次得到数据增量写进文件。

  这样做会出现一个情况,就是假如现在是2.0版本,下载到一半时关闭了应用,此时服务器发布了3.0版本。这就导致文件会无法更新,因为版本号不一致了。

  所以在恢复下载前也必须要判断下载文件的md5码与服务器是否一致,不一致就要把本地文件给删除。

  网络中断下载:

    用户的网络有可能发生波动导致网络连接短暂的丢失,我们希望用户网络恢复时能够再次下载包。

  实现:

    在下载文件的接口中,不断地去判断网络连接是否正常。若出现异常则中断下载,并同时UI提供重新下载的选项。

8.生成最小程序包

   在构建发布时,StreamingAssets目录下的资源会和游戏绑定发布,这意味着StreamingAsset体积越大,游戏包体积也就越大。所以控制了StreamingAssets目录就意味着控制了游戏首包的体积。

  如何控制首包大小呢?

  思路也是比较直接的,先将需要更新的全部资源打包后上传到服务器。然后把StreamingAsset中的不需要加载到首包的文件都删除,在重新生成配置文件后打包就可以了。

9.不可写目录的AB包加载方式

   资源内置在StreamingAssets下,手机平台不可修改

  如果服务器ab包有版本更新,且对应客户端的内置资源如何应对?

  解决:

    我们需要一个配置文件,记录所有更新过的文件。 在读取ab包时,我们要先判断该文件是否存在更新记录,

    如果没有更新记录,从StreamingAssets加载

    有更新记录的话,从下载目录加载

10.引用计数器卸载包

   前面都在讨论包加载到内存的情况,但是加载到内存的包也需要卸载,不然可用的内存空间就会越来越少。所以我们应该使用引用计数的卸载方式。当包被使用或者被引用时就把计数器+1,反之则-1。最后当包为0时将包卸载出内存。