简介
AssetBundle是将资源使用Unity提供的一种用于存储资源的压缩格式打包后的集合,它可以存储任何一种Unity可以识别的资源,如模型,纹理图,音频,场景等资源。也可以加载开发者自定义的二进制文件。他们的文件类型是.assetbundle/.unity3d,他们先前被设计好,很容易就下载到我们的游戏或者场景当中。
拓展阅读:
资源类型
- Resources
- 一定是包内资源,直接加载,打包后不会被压缩,商业项目几乎不用
- 接口:Resources.Load,除此之外还有一些泛型的重载
- AssetBundle
- 可能是包内资源也可能是包外资源,可以放在资源服务器上下载
- 接口:
- AssetBundle(在使用那一章详细说)
- WWW(被弃用了)
- UnityWebRequest(5.3版本后使用)
- Raw,未经处理的资源
- mage\Movie等
- 可能是包内资源也可能是包外资源
用途
- 版本热更新,关于热更新,可以参考这篇文章
- 制作DLC
- 减少初始包大小
- 加载为用户平台优化的资源
- 减少运行时的内存压力
使用
- 创建:在Unity窗口内创建AssetBundle
- 打包:使用BuildAssetBundles的API打包
- (参数1)打包的目标目录
- (参数2)压缩格式:
- LZMA格式(压缩率最高)
- LZ4格式
- 不压缩
- (参数3)打包平台
- 打包出来的文件,其中manifest是给人看的,里面有这个AssetBundle的基本信息以及资源列表。
- 加载和卸载:
- Unity热更新:AssetBundleBrowser打包及资源加载
- 其他的加载方式可以参考简介的两篇文章,下面也简单讲了
- 接口:
- AssetBundle.LoadFromMemory
- 从内存加载
- 用于如果要对Bundle做加密,做自定义格式的情况
- AssetBundle.LoadFromMemoryAsync(带Async的都是异步方法)
- AssetBundle.LoadFromFile
- 基于文件加载
- 性能最好的方式,如果没有特殊需求用这个就好
- AssetBundle.LoadFromFileAsync
- AssetBundle.LoadFromStream
- 基于流的数据记载
- 比如要从网络上实时加载数据用这个方式比较好
- AssetBundle.LoadFromStreamAsync
- AssetBundle.LoadFromMemory
依赖资源与分配
依赖资源关系
- 如果同一个材质、纹理等资源被多个bundle包所依赖,就会造成重复打包,造成资源的冗余
- 解决:将被依赖的资源单独打一个bundle包,被其他bundle引用,就不会造成重复的打包
- 如果按照上面解决1的方法去打包,可能会造成资源(材质)的丢失,因为依赖资源没有被加载
- 解决:先加载依赖资源,具体可以看我这篇文章Unity热更新:AssetBundleBrowser打包及资源加载
- 如果按照上一个的方法,极端情况下model、materials、texture分别只打一个bundle,使用的时候只用一个模型就需要加载所有的bundle,造成内存的浪费
- 解决:关注打包的粒度,制定合理的分配策略,且看下一章
分配策略
- 按类型分组
- 强类型关联,比如全都是字体,全都是Shader等等
- 平台相关,比如安卓打一个包,苹果打一个包
- 本地化相关,比如日文一个包,中文一个包
- 要求:无交叉依赖,不会依赖其他的资源
- 按并发分组
- 要求1:加载时机,生命周期几乎一致,比如一个关卡的资源
- 要求2:无交叉依赖
- 按逻辑单元分组
- 逻辑功能划分,比如UI只是用来显示的,不会用到场景里,就可以单独打一个包
- 逻辑对象划分,比如一个人物,他的材质,模型,都是一对一的,可以打在一个包
- 共享对象划分,多个场景都会用到的资源打一个包,比如多个场景都会用到同样的怪物,就把这些怪物打在一个包内。
- 原则:
- 对频繁更新的对象进行拆分
- 同时加载的对象打包到一起
- 拆分加载时机不一致的Bundle
- 合并频繁加载的小粒度Bundle
加载模块
需求分析
- 发布平台:PC/IOS/Android(资源路径不同)
- 更新方式:动态更新/流式加载/实时更新
- 压缩:压缩加密/无加密
- 数据包:自定义/无自定义
资源路径
对于加载模块的设计,一定会有区别的就是发布的平台的资源路径不同。
Unity提供了以下四种目录:
DataPath : 返回程序的数据文件所在的文件夹的路径(只读)。返回路径为相对路径,一般是相对于程序安装目录的位置。不同游戏平台的数据文件保存路径不同。
StreamingAssetsPath: 此属性用于返回数据流的缓存目录,返回路径为相对路径,适合设置一些外部数据文件的路径。(只读)。
PersistentDataPath: 返回一个持久化数据存储目录的路径(读写),可以在此路径下存储一些持久化的数据文件。对应同一平台,在不同程序中调用此属性时,其返回值是相同的,但是在不同的运行平台下,其返回值会不一样。
temporaryCachePath: 此属性用于返回一个临时数据的缓冲目录(只读)。对于同一平台,在不同程序中调用此属性时,其返回值是相同的,但是在不同的运行平台下,其返回值是不一样的。
persistentDataPath和temporaryCachePath的返回值一般是程序所在平台的固定位置,适合程序在运行过程中产生的数据文件。
streamingAssetsPath是可以跟随Unity打包存在的文件夹,WWW和IO都可以访问这个文件夹下的文件,但是这个文件夹是只读文件夹,不支持修改文件和写入等操作。
persistentDataPath是可读写的文件夹,但是这个文件夹是在APK安装成功后创建的,所以无法将文件在打包前放入此文件夹。
各个平台路径的特点:
存放资源目录的路径规划:
设计
模块设计:
Resloader:管理两种资源Resources、AssetBundle的加载
AppPackage:存放不需要热更新的资源
CachePath:存放热更新的资源
Patch:存放补丁资源
Temp:先下载到Temp文件夹后再存在Ptach文件夹中
流程设计:
- 加载资源
- 判断是否是Bundle资源(通过manifest进行检索)
- 如果不是Bundle资源,就直接Resources.Load加载即可
- 如果是Bundle资源则继续判断是否有补丁资源(比如版本更新了,这个角色有了新的资源放在补丁目录,资源是否在filelist里面可以判断是否为补丁资源,filelist记录了当前版本的版本号和补丁资源的hash值/md5码之类的来校验)
- 如果不是说明在AppPackage中则LoadBundle,LoadAsset即可
- 如果是则在Patch所在目录或者从服务器中下载资源
- 判断是否是Bundle资源(通过manifest进行检索)
自动更新
版本信息管理
版本号:
- 客户端版本号:用4位来标识,假设是X.Y.Z.W
- X:这一-位其实就是1.除非有太巨大的变化。否则不要变动;
- Y:商店版本,游戏-般一个月会有一个比较大的版本迭代,这种版本会走商店,每次提交Y值+1;
- Z:也是商店版本,一个版本周期内,如果SDK有问题或者C#层有发现Bug,需要更新商店,这一位会+1.这里单独留一个Z处理这种商店版本号,是因为不想影响Y值,而商店提交新包要求版本号必须有增加。
- W:热更新版本号.每次热更新都+1,
- 比如:目前商店版本号是1.1.0.0,这个版本我们热更新了3次后,版本号就变成1.1.0.3, 这时候发现好像C#层有一点Bug必须要修复,那打个1.1.1.3提交商店,1.1.1.3包里的资源和1.1.0.3的资源是一模一样的,这之后如果有第4次热更新,那热更新包的版本号就是1.1.1.4。
- 强更版本号:客户端版本号<强更版本号会提醒玩家去商店下载。
- 推荐版本号:建议玩家去商店下载,可以跳过。
版本记录: 发布每一个版本都需要记录这个版本的信息,比如更新了什么内容
资源清单: 也被称为filelist,每个版本包含文件名是多少,文件大小是多少,hash值/md5码(做资源校验)是多少
版本内容: 所有版本的版本内容,在更新服务器应该有每个版本的版本完整镜像,方便跨版本运营
更新策略
发布途径:
- 应用商城更新
- 自主更新(比如官网更新、启动器更新)
更新内容:
- 二进制更新(可执行程序更新)
- 资源更新
- 热更新,如果不是项目十分复杂,一定要依赖热更新,直接提交商店更新是最佳选择
更新环境
持续集成CI:
背景:
举例来说,比如当前迭代,开发时间为两周。项目开始后,开发人员会从代码管理工具(SVN 或 GIT)的主干代码上复制一份代码到自己本地电脑。然后在自己的本地电脑上进行开发,假设这个项目有三个开发人员,分别是 狗蛋、翠花、大锤
现在三个开发分别在自己电脑上开发。开发任务完成后,一次性提交自己本地代码到代码管理工具的主干代码上。
这个过程就叫集成,也就是代码集成,将自己本地的代码集成到主干代码。此时发生了所谓的集成地狱。三位开发有可能会改到同一个代码文件、同一个方法,这样就会出现代码冲突。为了处理集成过程中的冲突,会花费非常多的时间、精力和成本,并且提高了发布风险。
为了降低这种风险和成本,于是持续集成就被提出了。强调的是不再一次性把代码集成到主干,而是高频率的持续集成。一天集成1次,甚至多次。同时在集成过程中,进行自动化测试,保证主干代码一直可用。
概念:
频繁的将代码集成到主干上
持续集成强调开发人员提交了新代码之后,立刻进行构建、(单元)测试。根据测试结果,我们可以确定新代码和原有代码能否正确地集成在一起。
目的:
就是让产品可以快速的更新迭代,同时保证高质量(代码提交到主干之前必须通过自动化检测,只要有一个测试用例失败,就不能集成)
好处:
1.快速发现错误 :每完成一点更新,就集成到主干上,可以快速发现错误,定位错误也比较容易
2.防止分支大幅偏离主干 : 如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成
平台:
- Jenkins
- Bamboo
- Gitlab CI
DevOps: DevOps到底是什么意思?
更新服务器:
平台:
- IIS
- Apache
- Nginx
更新系统的组成
- 版本构建系统
- 游戏引擎
- Unity
- Unreal
- Cocos
- 构建工具
- Visual Studio
- XCode
- Ant
- Maven
- Gradle
- 游戏引擎
- 版本生成工具(一般是自己做):编写一个程序,编写UpdateList的内容,最终发送到更新服务器上
- 更新服务器,需要包含以下内容:
- 主目录
- Version.txt(当前版本,存放版本号,更新类型等信息)
- UpdateList.txt(当前版本的文件清单,存放文件路径是什么,文件大小是多少,hash值/md5码)
- Updates(更新内容本身)
- 1.0.0.0
- 1.0.0.1
- 1.0.0.2
- 1.0.0.3
- 主目录
- 更新逻辑
- 客户端逻辑的更新
- 客户端为主
- 优点:
- 服务器仅作为储存容器,压力小
- 无服务器逻辑,无开发成本
- 缺点:
- 难以支持动态策略,不灵活
- 一次更新到最新版本的成本较高
- 适合逐版本的增量更新
- 优点:
- 服务器为主,服务端有逻辑存在
- 优点:
- 动态生成实际需要更新的版本清单
- 一次更新多个版本
- 动态更新策略(渠道、所在区域、比例(灰度测试)),运营更灵活
- 缺点:
- 需要机制及其完善的更新系统,开发成本高
- 服务器压力大,运营成本高
- 优点:
- 客户端为主
- 服务器逻辑的更新(一般是停机维护的时候更新)
- 客户端逻辑的更新
方案设计
决定更新策略:
- 二进制更新
- 应用商店更新/系统内置更新
- 自主更新:启动器/更新程序
- 资源更新
- 热更新
设计版本配置:
- 版本信息(Version.txt):版本号,更新类型
- 更新清单(UpdateList.txt)
- 版本目录(Updates):储存更新版本内容
设计简单更新流程:
- 启动
- 下载版本信息
- 对比本地信息
- 如果需要更新
- 下载更新清单
- 执行更新
- 结束
设计详细更新流程:
- 热更新首先是检查是否初次安装,如果是就要把资源文件从只读目录解压到可读写目录。
- 检测是否为初次安装:streamingassets只读目录有filelist且可读写目录没有filelist,注意filelist文件要最后写入,以防止中断情况。
- ReleaseResource:释放资源就是从只读目录(streamingassets)到可读写目录(persistentdatapath)
- 然后开始检查是否要热更新,先从服务器获取到最新版本信息,然后与本地对比是否需要热更新,也有可能版本过于落后需要整包更新
- 如果需要更新,就从服务器下载file文件,里面有资源文件的名称和md5码,再和本地的file对比md5码,把需要更新的资源文件从服务器下载到可读写目录,然后校验并且版本文件修改或覆盖。
- 开始更新,通过unity提供api来加载bundle里面的资源在更新客户端。
下面是我做的一个热更新流程练习
设计更新特性(锦上添花):
- 断点续传
- 多线程下载
- 离线下载(启动器预下载)