Unity的AB包系统使用概论

0. 名词表

资源:Resource,主要分为Prefab,Image,AudioClip等,一般在开发时为单个文件

包/ab包:AssetBundle,打包后的单个文件,内含若干资源

包名:AssetBundleName,为资源设置的归属包的名称

1. 打包与解包

打包:按一定规则或配置为资源设置包名(一般写个工具读配置遍历设置),build时资源会被打到对应的包中

解包:游戏运行时,根据资源路径加载资源,需要先找出所属包,先加载包,然后才能加载资源

两者为逆操作,故需要根据打包规则反推所属包,使用配置文件或者遍历所有AB包都不可取

1.1 打包

未设置包名的资源不会被打进包中,除非它被某包中资源引用到,如果被多个包中资源引导到,就会打到多个包中(冗余)

设置了包名的资源如果被其他包资源引用,两个包会建立引用关系

大部分项目使用的都是按文件夹打包,打包规则分为2类:

  • 该文件夹下的所有文件打成一个包
  • 该文件夹下的每个文件打成一个包

以上规则还会配置文件后缀过滤,比如只设置ui的prefab包名,引用的静态图片等未设置包名的资源会自动打进同一个包里,引用到的公共图集等设置了包名的资源,会生成一个包引用关系

一个典型的ui打包规则配置文件大致如下:

文件夹路径子层级打包规则后缀列表备注
ui/common0散包common下每个资源单独打包(多了点引用,方便更新)
ui/form1整包prefabform下每个文件夹(如xxx_form)打成一个包,只给prefab资源设置包名
ui/dynamic1散包jpg|pngdynamic下每个文件夹里(如xxx_form)的图片单独打包(一般都很大,按需加载,不打整包)

子层级:文件夹下层级,因为打包规则中文件夹不递归扫描,降低复杂度

根据项目的需要,在打包数量和冗余大小之间取舍的策略差异较大,不赘述

1.2 解包

解包即反解包名,根据资源路径得到所属包名称

在上一步的打包中,包名一般设置为文件(夹)路径,按照打包的逆操作,资源路径一定包含包名路径

通过系统api获取到所有包名,生成前缀树,按资源路径进行查询,叶子节点即为所属包名

2. 加载

资源加载流程:输入资源路径和回调,反解包名,加载包,加载资源,执行回调

no
done
no
done
yes
yes
开始
解包
包已加载
加载包
资源已加载
加载资源
执行回调

加载包的done指自身和依赖的ab包均加载完成,这些ab包的加载没有先后关系,可以同步发起

如果依赖的ab包未加载或被错误地提前回收,最常出现的就是经典的紫红色贴图丢失bug

同步实现较为简单,但是绝不符合项目需求,异步就会比较复杂,建议搭建时先实现同步版后转异步版

为了开发时的Resources模式和测试时的AB模式兼容,一套接口,两套实现是必须的,Resources模式加载逻辑简单(没有包,同步加载),不赘述

每个ab包都对应一个管理单元AbUnit,结构大致如下:

属性类型说明
namestring包名
stateEnum状态:加载中,已加载
dependsDictionary<string, AbUnit>依赖的ab包管理单元
reference countint被依赖计数,为0时回收
resourcesDictionary<string, object>ab包内已加载的资源,key为纯净资源名
callbacksDictionary<string, List>回调表,key为纯净资源名
2.1 包管理

一个ab包的生命周期大概是:

  • 因为包内资源加载请求或被其他包依赖,进行加载,状态设置加载中
  • 加载完成,状态设置为加载完成
  • 等待依赖的其他包加载完成,资源加载等操作,状态不变
  • 外部或其他包不再引用该包时,手动回收

为了防止内存爆掉,需要在适当的时候回收无用ab包,小游戏会在切场景等操作时调用系统api一键清理,不过大一点的游戏都不能这么莽,常见方案就是引用计数,引用类型分:

  • 内部引用,ab包之间的引用计数,由依赖包指向被依赖包
  • 外部引用,游戏运行对象对ab包的引用,如界面go(gameobject)对prefab所在包的引用,image组件对sprite所在包的引用
2.2 资源管理

每个包内包含若干资源,按需加载,资源没有引用计数,ab包释放时统一释放即可

较为特殊的资源是图集,不能像其他资源那样的方式加载,一次性全部加载后存到字典中即可

2.3 引用计数

引用计数是这套方案中最重要的部分

自动引用计数

  1. 添加ResRef(ResourceReference)脚本,将计数的增减操作绑定到mono的start和destroy函数中,根据资源的不同继承脚本生成goRef、SpriteRef、AudioRef等脚本
  2. 封装会产生资源加载的组件(如Image),对外接口不变,内部通过上一步的脚本进行计数增减
  3. go不属于组件,所以加载prefab时返回一个已经添加goRef脚本的go即可,这个go禁止复制

如果一个脚本自从被添加后,从未被激活过,那么start和destroy都不会执行,反之都会执行,

因此Ref脚本计数有一个临界bug如下:

资源加载完成时go是disabled,导致Ref脚本start不执行,计数未增加,ab包被回收,等go被enabled出现资源丢失

解决方案:

增加ParentResRef脚本,当go发现自己被disabled,会往上找到enabled的首个祖先节点,挂上该脚本,增加引用计数,destroy同理

手动引用计数

由程序员增减引用计数,主要用于对象池、预加载等上层应用自身对资源进行管理的情形

自动计数中,每个go对于一个类型的资源只能挂一个脚本,引用一个包,所以封装按钮这种多图片切换的情形时也要手动计数

3. 优化细节

最大并行数

同一帧发起大量资源加载时,会造成短时io堵塞,进而产生卡顿,所以需要限制加载最大并行数

超过最大并行数的请求排队等候即可

最优数值可能因机器而已,可以实际调试获得

垃圾箱

在ab包引用计数为0后,不立即回收,而是进入垃圾箱,然后等待一段时间后,仍然为0,再实际回收

避免了少量情况下的循环回收加载,如列表滚动到下方后上方图标被回收

加载优先级

可以为加载请求添加优先级参数,用于低优先级预加载某些资源,在低配手机效果会比较好

因为玩家操作的不确定性和项目组对秒开界面没有变态的要求,所以没有实装

同步加载支持

理论上是可以同步异步都支持的

但是有一个bug:

对同一个ab的异步加载发起后,再发起同步加载,会自动略过,这就很可能导致同步代码之后的业务逻辑出错,感觉Unity不太会修复

而且强行同步加载意义不大,故舍弃

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值