简介

AssetBundle是将资源使用Unity提供的一种用于存储资源的压缩格式打包后的集合,它可以存储任何一种Unity可以识别的资源,如模型,纹理图,音频,场景等资源。也可以加载开发者自定义的二进制文件。他们的文件类型是.assetbundle/.unity3d,他们先前被设计好,很容易就下载到我们的游戏或者场景当中。

拓展阅读:

资源类型

  • Resources
    • 一定是包内资源,直接加载,打包后不会被压缩,商业项目几乎不用
    • 接口:Resources.Load,除此之外还有一些泛型的重载
  • AssetBundle
    • 可能是包内资源也可能是包外资源,可以放在资源服务器上下载
    • 接口:
      • AssetBundle(在使用那一章详细说)
      • WWW(被弃用了)
      • UnityWebRequest(5.3版本后使用)
  • Raw,未经处理的资源
    • mage\Movie等
    • 可能是包内资源也可能是包外资源

用途

  • 版本热更新,关于热更新,可以参考这篇文章
  • 制作DLC
  • 减少初始包大小
  • 加载为用户平台优化的资源
  • 减少运行时的内存压力

使用

  1. 创建:在Unity窗口内创建AssetBundle
  2. 打包:使用BuildAssetBundles的API打包
    1. (参数1)打包的目标目录
    2. (参数2)压缩格式:
      1. LZMA格式(压缩率最高)
      2. LZ4格式
      3. 不压缩
    3. (参数3)打包平台
    4. 打包出来的文件,其中manifest是给人看的,里面有这个AssetBundle的基本信息以及资源列表。
  3. 加载和卸载:
    1. Unity热更新:AssetBundleBrowser打包及资源加载
    2. 其他的加载方式可以参考简介的两篇文章,下面也简单讲了
    3. 接口:
      1. AssetBundle.LoadFromMemory
        1. 从内存加载
        2. 用于如果要对Bundle做加密,做自定义格式的情况
      2. AssetBundle.LoadFromMemoryAsync(带Async的都是异步方法)
      3. AssetBundle.LoadFromFile
        1. 基于文件加载
        2. 性能最好的方式,如果没有特殊需求用这个就好
      4. AssetBundle.LoadFromFileAsync
      5. AssetBundle.LoadFromStream
        1. 基于流的数据记载
        2. 比如要从网络上实时加载数据用这个方式比较好
      6. AssetBundle.LoadFromStreamAsync

依赖资源与分配

依赖资源关系

  • 如果同一个材质、纹理等资源被多个bundle包所依赖,就会造成重复打包,造成资源的冗余
    • 解决:将被依赖的资源单独打一个bundle包,被其他bundle引用,就不会造成重复的打包
  • 如果按照上面解决1的方法去打包,可能会造成资源(材质)的丢失,因为依赖资源没有被加载
  • 如果按照上一个的方法,极端情况下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安装成功后创建的,所以无法将文件在打包前放入此文件夹。

来源

各个平台路径的特点:

image-20221004220222810

存放资源目录的路径规划:

image-20221004222155428

Unity中的资源目录在不同平台下的路径

设计

模块设计:

image-20221004230425391

Resloader:管理两种资源Resources、AssetBundle的加载

AppPackage:存放不需要热更新的资源

CachePath:存放热更新的资源

Patch:存放补丁资源

Temp:先下载到Temp文件夹后再存在Ptach文件夹中

流程设计:

  1. 加载资源
    1. 判断是否是Bundle资源(通过manifest进行检索)
      1. 如果不是Bundle资源,就直接Resources.Load加载即可
      2. 如果是Bundle资源则继续判断是否有补丁资源(比如版本更新了,这个角色有了新的资源放在补丁目录,资源是否在filelist里面可以判断是否为补丁资源,filelist记录了当前版本的版本号和补丁资源的hash值/md5码之类的来校验)
        1. 如果不是说明在AppPackage中则LoadBundle,LoadAsset即可
        2. 如果是则在Patch所在目录或者从服务器中下载资源

自动更新

版本信息管理

版本号:

  • 客户端版本号:用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.防止分支大幅偏离主干 : 如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成

什么是持续集成CI?

平台:

  • Jenkins
  • Bamboo
  • Gitlab CI

DevOps: DevOps到底是什么意思?

更新服务器:

平台:

  • IIS
  • Apache
  • Nginx

更新系统的组成

  1. 版本构建系统
    1. 游戏引擎
      1. Unity
      2. Unreal
      3. Cocos
    2. 构建工具
      1. Visual Studio
      2. XCode
      3. Ant
      4. Maven
      5. Gradle
  2. 版本生成工具(一般是自己做):编写一个程序,编写UpdateList的内容,最终发送到更新服务器上
  3. 更新服务器,需要包含以下内容:
    1. 主目录
      1. Version.txt(当前版本,存放版本号,更新类型等信息)
      2. UpdateList.txt(当前版本的文件清单,存放文件路径是什么,文件大小是多少,hash值/md5码)
      3. Updates(更新内容本身)
        1. 1.0.0.0
        2. 1.0.0.1
        3. 1.0.0.2
        4. 1.0.0.3
  4. 更新逻辑
    1. 客户端逻辑的更新
      1. 客户端为主
        1. 优点:
          1. 服务器仅作为储存容器,压力小
          2. 无服务器逻辑,无开发成本
        2. 缺点:
          1. 难以支持动态策略,不灵活
          2. 一次更新到最新版本的成本较高
          3. 适合逐版本的增量更新
      2. 服务器为主,服务端有逻辑存在
        1. 优点:
          1. 动态生成实际需要更新的版本清单
          2. 一次更新多个版本
          3. 动态更新策略(渠道、所在区域、比例(灰度测试)),运营更灵活
        2. 缺点:
          1. 需要机制及其完善的更新系统,开发成本高
          2. 服务器压力大,运营成本高
    2. 服务器逻辑的更新(一般是停机维护的时候更新)

方案设计

决定更新策略:

  • 二进制更新
    • 应用商店更新/系统内置更新
    • 自主更新:启动器/更新程序
  • 资源更新
  • 热更新

设计版本配置:

  • 版本信息(Version.txt):版本号,更新类型
  • 更新清单(UpdateList.txt)
  • 版本目录(Updates):储存更新版本内容

设计简单更新流程:

  1. 启动
  2. 下载版本信息
  3. 对比本地信息
  4. 如果需要更新
    1. 下载更新清单
    2. 执行更新
  5. 结束

设计详细更新流程:

  1. 热更新首先是检查是否初次安装,如果是就要把资源文件从只读目录解压到可读写目录。
    1. 检测是否为初次安装:streamingassets只读目录有filelist且可读写目录没有filelist,注意filelist文件要最后写入,以防止中断情况。
    2. ReleaseResource:释放资源就是从只读目录(streamingassets)到可读写目录(persistentdatapath)
  2. 然后开始检查是否要热更新,先从服务器获取到最新版本信息,然后与本地对比是否需要热更新,也有可能版本过于落后需要整包更新
  3. 如果需要更新,就从服务器下载file文件,里面有资源文件的名称和md5码,再和本地的file对比md5码,把需要更新的资源文件从服务器下载到可读写目录,然后校验并且版本文件修改或覆盖。
  4. 开始更新,通过unity提供api来加载bundle里面的资源在更新客户端。

下面是我做的一个热更新流程练习

AB包打包流程

热更新流程

设计更新特性(锦上添花):

  • 断点续传
  • 多线程下载
  • 离线下载(启动器预下载)