• Unity官方直播--Unity Asset的一生


    Unity Asset的一生

    https://unity.cn/projects/zhi-bo-yu-gao-unityzi-shen-ji-zhu-zhuan-jia-gao-chuan-wei-nin-xiang-jie-unity-assetde-yi-sheng

    Asset

    Asset资源分为两部分:文件本身和.meta文件
      文件本身存储原始数据;对应的.meta文件存储一些unity用到的额外信息

    Asset可分为两种:第三方工具产生的 和 Unity自身产生的
      第三方工具产生的,如:Maya、3DMax等;
      Unity自身产生的,如:Prefab、Script等
      这两者的.meta文件所存储的信息是不相同的

    Asset可分为两种:运行时Runtime Asset 和 编辑器Editor Asset
      Runtime Asset(比如纹理、声音、动画等)在最终打包时会被打入包,被玩家直接看到
      Editor Asset,比如一些数据内容,参与编辑或生成包的过程,但是最终没有被打包

    Asset文件与.meta文件

    .Meta文件

    .meta文件很重要。

    由Unity产生的资源对应的.meta文件

    prefab的.meta文件;和material的.meta文件

     

    fileFormatVersion: 无需关注,表示当前meta文件的格式(基本上一直会是2)

    guid:当导入一个asset时,unity会分配一个唯一id作为标识,这个标识也用于关联到Library中的对应资源

    PrefabImpoter:导入管理的相关信息(也是AssetImporter处理的内容,也可在Inspector中对应看到)

    Impoter下的一些键值对是可以在Inspector面板下

    .prefab文件(资源文件本身) -- (每一个Asset的数据都是这种格式)

     

    YAML文件格式

    000 !u!1 &4309454636272863991:一般称为ObjectID
      1表示类型,比如这里是1一定表示是GameObject,下面4一定是Transform(unity内置枚举)
      4309454636272863991表示该组件的fileID

    最上面是GameObject;其中的一些字段可以在Inspector面板中打开Debug进行查看
      m_Component下面有四个数据,会发现这四个数据对应的是该GameObject下挂载的四个组件自己对应的ID
      Unity找寻该fileID对应的数据段,将这个数据段填充到这个位置

    实用方法:有的时候会出现script里面引用missing的情况,多数是因为.meta文件丢失后生成了新的meta导致id对不上了
      这时如果有旧版本,就可以通过fileID来重新赋值引用(在这里改数据的优势就是批量化)

    诡异技巧:在打包时比如想要移除掉一些脚本,也可通过python这样处理(移除数据块和引用)

    Library文件夹

    所有Asset资源最终(build)都会被放入Library文件夹 -- 异常庞大的文件夹

    源文件会根据unity的导出设置进行格式转换并放入Library文件夹,这也就是为什么源文件永远是那个源文件,即使导出设置改变了源文件也不变的原因

    所以比如声音文件,放什么格式的最好呢,按道理wav格式是最好的,因为无损、原始采样率最高,unity导出后只进行了一次压缩;如果放的是mp3,最终音效质量就没那么好,进行了二次压缩

    这里提到一个Unity现在有两种版本,在ProjectSettings -> Editor -> Asset Pipeline -> Mode 里可选Version1和Version2,
      Version1和Version2的主要区别是Version1其实是一个对应索引,而Version2是一个DB

    选择Version1时是这样的

      Library/metadata 目录下为很多这种编号的文件夹,上面提到的guid数字就可以在这里被对应上

     

    比如上面第一部分提到的prefab对应的在.meta中记录的guid: 368406572aed14c9da2edd5fe4bedc67
      前两位数36表示可以在36文件夹中找到两个对应文件

    之前提到,Unity会将源文件基于一些配置设置导入到Library文件夹下,这些文件就存在这里

    这里面文件的修改时间可以被作为一些操作的参考依据,比如判断是否需要assetbundle重新打入包

    选择Version2时会发现reimport的时间大大缩短,此时是这样的

    在Library文件夹下没有了meta文件夹,但是有一个Artifacts文件夹下也都是编号文件夹,不过在36文件夹中也找不到对应guid的文件

    会发现在Library下多了很多DB文件,如ArtifactDB, SourceAssetDB等LMDB数据库文件,这也是reimport时间

    StreamingAssets文件夹

    1. 被原封不动打进包里 -- 也意味着不做压缩(Unity在打安卓包的时候会对所有SteamingAssets文件夹下的文件标记为不压缩)

    2. 在安卓系统上可以直接被读取

    害羞的波浪线

    (一个小技巧)

    在Unity中,凡是以~为后缀的文件或文件夹,都是会直接被无视跳过的,不会被导入工程

    这个小技巧在做工程管理的时候比较有用,比如某些文件夹在某些场合下不想用到,这个时候直接改名加后缀即可

    AssetBundle

    AssetBundle的原理:

    AssetBundle其实就是一个压缩包

    既然是一个压缩包,那直接用文件不行吗?是可以的,但是AssetBundle包含了资源文件依赖关系、还有一些文件查重等功能

    可以做到跨平台,对应不同平台可以打出对应的包

    可以做出快速索引

    本质上是Unity的一套虚拟文件系统

    既然是一个压缩包,那就可以分成两部分
      体:被压缩的内容
      头:对应的一些摘要信息

    加载一个AB包的时候,头会被立刻加载,而里面的内容(Asset资源本身)是按需加载的,使用到的时候才会被加载入内存

    AssetBundle的参数:

    BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions, BuildTarget)

    BuildAssetBundleOptions:

    BuildAssetBundleOptions.ChunkBasedCompression: 以chunk-based LZ4进行包体的压缩,在LZ4的基础上做了一些改良

    BuildAssetBundleOptions.DisableWriteTypeTree: 可减少AB包体大小,同时减小使用的内存大小,和加载AB包的使用时间

    BuildAssetBundleOptions.DisableLoadAssetByFileName | DisableLoadAssetByFileNameWithExtension
      在加载AB包时,AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "cubebundle")
      可以传入路径/包名,也可以只写包名,或加上扩展名,但是是有代价的,在写入的时候是需要加上哈希的,所以在寻找的时候会耗费更多的cpu时间与内存开销。如果确定加载方式是存路径加载的话,就可以把这个哈希寻找关闭掉

    做个小实验:创建一个简单场景,场景中只有一个cube
      ChunkBasedCompression: 包体大小85KB
      ChunkBasedCompression | DisableWriteTypeTree: 包体大小73KB (一个简单Cube的typetree就占了有12KB)
      ChunkBasedCompression | DisableLoadAssetByFileName: 包体大小仍为85KB,因为小场景中只有一个AB包,所以差别不大,而且这个选项更侧重的是内存和CPU上的消耗

    另一个小实验:对应以上不同打包方式,进行AB包的加载,并使用Profiler进行性能消耗的查看
      Build成可执行文件后运行,连接上Editor中的Profiler,点击按钮进行AB包的加载,并在Profiler中TakeSample,
      查看SerializedFiles与其下的archive所对应的Memory值(是AB包头的大小)
      ChunkBasedCompression: 273.5KB
      ChunkBasedComprerssion | DisableWriteTypeTree: 206.4KB (打出的包相差了67KB,就一个简单的cube对应的typetree就有这么大)

    不过一个cube所关联的有许多资源,比如material texture等等,这些都是需要被进行打包的

    AssetBundle的识别:

    有些人会去算AssetBundle打出来的包的MD5值,这种方式是不推荐的,因为在Unity打包的过程中并不是稳定的,有可能导致两次打出来的AB包的内容即使是一致的,但是Binary是有差异的。

    那怎么识别呢?算打包之前的

    可以算Library里的文件;可以算打包前的文件本体以及文件对应的meta的哈希值
    图方便的话也可以直接使用Unity打包出来的AB包对应的.manifest文件里对应的值

    AssetBundle的策略:

    不要走极端,AB包过大过小都不好。

    官方推荐大小为
      需要经过网上下载的AB包(比如手游资源)1MB~2MB一个包
      本地的包的5MB~10MB一个包,不超过10MB

    过大缺点:下载慢

    过小缺点:每个AB包内的资源很少,但是头文件大小相对应的会变大,且导致加载到内存后的有效数据变少,很多为头文件信息

    Asset的加载及管理:

    编辑器内和运行时的加载机制不同:

    因为在Editor中,unity会优先保证使用的流畅度,并且基本上都是在资源充裕的电脑上运行的,因此会尽量把许多资源都提前加载好,甚至会加载一些额外数据以方便并加速编辑和制作过程

    在Runtime时,unity遵循按需加载的加载规则,尽量减少目标设备上内存和cpu的使用

    -- 不要用Editor期的Profiler去作为最终的衡量标准,一定要去profiler真机

    序列化与反序列化:

    两个场景:
      场景1中有三个Cube gameobject
      场景2中有三个来自于同一个Cube Prefab的gameobject

    将场景文件.unity用文本方式打开,可以发现
      场景1对应的文件大小会大于场景2对应的文件大小
      打开会看到,场景1中每一个gameobject都会存储对应的信息
      而场景2中的gameobject会对应到同一个prefab里

    这就导致了unity在加载场景1时,会有更多的时间开销和内存开销

    在加载场景2时,会优先将使用到的prefab解析出来,并且让场景中对应的游戏物体gameobject的引用指向这一块内存
    而在加载场景1时,会认为这三个实际上一样的gameobject是不同的,因此会解析3次

    -- 结论,能用到prefab的地方尽量用prefab

    TypeTree:

    上面提到这个数据会使得AB包体变大许多,那它的作用是什么呢?
    为了Unity的跨版本时做兼容的

    找一个meta文件查看

    可以看到serializedVersion:6字段,表示当前格式之前,Unity至少改了5次数据格式
    Unity在打AB包的时候,如果开着TypeTree,则首先第一步会遍历所有的文件,并把对应的数据内容的字段先写一遍
      比如上图的defaultSettings中,在6这个版本里会把字段loadType, sampleRateSetting, sampleRateOverride等先写一遍
      然后在第二遍里再去写字段对应的值

    在读取的时候,如果当前Unity版本不同了,serializedVersion比如说是5,那么则会根据version5的格式进行反向解析
      先开始解析TypeTree,发现里面的loadType不认识,是version5中没有的字段,这时候这个字段就会被跳过而不去解析
      如果解析TypeTree时发现应该有的一项aabb在TypeTree里没有找到,则会用默认值去填充该字段

    好处:Unity通过TypeTree,实现了跨版本的兼容性

    缺点:如果在打包的时候使用了TypeTree,
      AB包中会额外增加TypeTree的信息(存储);
      而且在加载的时候消耗cpu时间去额外遍历TypeTree(cpu);
      并在内存中存储了TypeTree的数据结构(内存

    结论:当确认Unity版本一致时,比如打的apk和ab包都是2019.1.1版本打出来的,此时关闭typetree即可
      -- 绝大部分项目都可以关闭,除非需要做跨版本兼容

    同步与异步:

    什么时候选用更多的是策略,而没有哪一种更好

    同步意味着更快,在那一帧内,主线程所有的CPU全部都可以使用;
    但是同时,可能造成主线程卡顿

    异步的最大优点是主线程可以保持尽量不卡顿;
    但是异步永远至少比同步慢一帧 -- 这一帧发起的异步,最快也得等到下一帧才会开始执行
    异步需要一些额外的逻辑,在保证没有加载完之前,会进行一些对应情况的处理

    还有一种情况是可以手动分帧进行同步的处理

    但是,异步和同步混合使用的时候,会导致大问题:Preload与Presistent问题

    Preload与Presistent:

    Unity引擎内部,有两个模块是主要负责加载工作的:PreloadManager和PresistentManager

    PreloadManager负责调度任务,PresistentMnanager负责把数据从硬盘读取到内存中,同时给这块数据分配一个ID
      当上层有一个任务下来,形成一个option,这个option会给到PreloadManager;
      在PreloadManager中有一个队列,每一帧会从这个队列中取出一个任务(opt)去执行;
      在执行opt的过程中,会使用到PresistantManager。

    上面说到异步和同步混合使用会导致的问题就是这么来的
    当preloadManager加载了异步的任务,而下一帧加载了同步的任务,这时异步的任务也在跑,这时同步任务和异步任务会去抢着使用PresistentManager;而PresistentManager分配ID等等的操作是阻断线程的,一次只能对应操作同一块内存,对应一个ID,这时候就会被block掉(异步工作可能会被同步工作阻断,同步工作也可能被异步工作阻断)

    -- 但是在2020版本中的Unity解决了这个问题
      两个任务都需要分配ID时,需要分先后

    Asset的卸载:

    UnloadUnusedAssets:

    这个和加载一样,是归PreloadManager管理的

    unity在一次load的开始阶段,就已经确定了哪一些资源是需要被load的,但是如果在load的过程中又发生了unload操作,那么会发生一些已经确定了要用的asset而且已经load了却被unload卸载掉,最终导致出错

    -- 因此UnloadUnusedAssets是一个同步的方法,所以会造成卡顿

    而Unity在切换scene的过程中,会自动调用一次UnloadUnusedAssets。

    AssetBundle.Unload()

    这个不归PreloadManager管理

    它会遍历当前加载过的资源,并进行unload;

    如果是Unload(true),则会把AssetBundle本身和加载了的相关Asset一起卸载掉;在不合适的时机,是会导致Runtime错误的
    如果是Unload(false),则只是把AssetBundle卸载掉;而这个会导致当再次加载该AB包的时候,一些asset可能会在内存中存在两份,因为在当把AssetBundle卸载掉的时候,AB包与对应的asset之间的关系也消失了

    在Unity内部,很多时候Asset并不是大家想的是有reference的,而是靠的遍历
    这个正在解决,可以看看新的AddressableAsset

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    Android App内存优化之图片优化
    APP中的存储路径
    为什么源码中很多方法就一行throw new RuntimeException("Stub!")
    Android运行时Crash自动恢复框架-Recovery
    Android图片压缩框架-Tiny 集成
    防止APP退到被安卓系统清理
    Android开发中,那些让你觉得相见恨晚的方法、类或接口
    安卓设置沉浸式状态栏
    Euler Sums系列(四)
    一个含有Fibonacci Number的级数
  • 原文地址:https://www.cnblogs.com/FudgeBear/p/13416080.html
Copyright © 2020-2023  润新知