Unity构建详解(7)——AssetBundle格式解析

【文件格式】

文件可以分为文本文件、图片文件、音频文件、视频文件等等,我们常见的这些文件都有行业内的标准格式,其意味着按照一定的规则和规范去保存读取文件,可以获取我们想要的数据。

有些软件会有自己的文件格式,会按照其自己设计的规则和规范去保存读取文件。

我们自己在做开发时,也需要保存和读取一些文件,但我们需要保存的数据一般会比较简单,很少去做设计格式。

例如,要保存一系列角色和怪物的当前血量。为了读取时能区分是角色和怪物,可以先保存角色个数。读取时先读取角色个数n,随后读取n个血量数据,这些都是角色的,随后读取的是怪物的。

个数n就是文件头,血量是文件数据。

一般来说,任何文件都可以分为文件头和文件数据两个部分,文件头用于描述文件数据。如果要保存的数据很复杂,可以进一步将文件头分为文件头(描述整个文件的,例如版本号)和数据头(描述数据部分的)、将文件数据分为主要数据段和次要数据段等。

如果复杂些,例如还需要保存当前的魔力值,那么需要区分当前的值是魔力值还是血量。如果是自己做开发,自己读写,可以默认先是血量,随后的值是魔力值。如果要保存角色的更多属性,那么要保存的不能是单一的数值,而是不同数值组成的数据结构了。

如果是软件,那么采用默认的方式就不合适,随着版本的迭代,要保存的数据会发生变化,必须在文件头中加入版本号和其他信息以描述要保存的数据。要保存的数据越复杂,文件头也就越复杂。

尽管更加复杂,但只要明白保存读取的规则和规范(即文件格式),那么和我们自定义的保存读取的主要逻辑在本质上没有区别。

行业标准格式更为复杂,其会被多个软件使用,可以看看场景的文件格式了解下文件是怎么样构成的:

PNG文件解读(2):PNG格式文件结构与数据结构解读—解码PNG数据-腾讯云开发者社区-腾讯云

MP3文件结构解析(超详细)-CSDN博客

AVI文件格式详解-CSDN博客

【AssetBundle格式】

详细的请看下Unity 的AssetBundle解析视频和AssetStudio读取Bundle的源码,这里只做简要介绍

  • AssetBundleHeader
    • AssetBundleFileHeader
    • StorageBlock[] m_BlocksInfo
    • Node[] m_DirectoryInfo
  • SerializedFile
    • SerializedFileHeader 文件头
    • MetaData
      • Types 每个对象的类型是什么
        • TypeTree
      • Objects 有多少对象,分别是什么,多大,在哪开始读取
      • ScriptTypes 如果有mono脚本的话,脚本的类型是什么
      • Externals 如果包里的对象引用了其他AB里的资产,分别在哪里可以找到
      • RefTypes
    • Object(第一个Object类型为AssetBundle)
    • 一系列的其他Object (包内Object的数据)
  • ResourceFile

下面是相关的数据结构

// AssetBundle文件头结构
public class AssetBundleFileHeader {public string signature;public uint version;public string unityVersion;public string unityRevision;public long size;public uint compressedBlocksInfoSize;public uint uncompressedBlocksInfoSize;public ArchiveFlags flags;
};public class StorageBlock{public uint compressedSize;public uint uncompressedSize;public StorageBlockFlags flags;}public class Node{public long offset;public long size;public uint flags;public string path;}public class SerializedFileHeader{public uint m_MetadataSize;public long m_FileSize;public SerializedFileFormatVersion m_Version;public long m_DataOffset;public byte m_Endianess;public byte[] m_Reserved;}public class SerializedType{public int classID;//unity有个classId的映射表public bool m_IsStrippedType;public short m_ScriptTypeIndex = -1;public TypeTree m_Type;public byte[] m_ScriptID; //Hash128public byte[] m_OldTypeHash; //Hash128public int[] m_TypeDependencies;public string m_KlassName;public string m_NameSpace;public string m_AsmName;}public class TypeTree{public List<TypeTreeNode> m_Nodes;public byte[] m_StringBuffer;}public class TypeTreeNode{public string m_Type;public string m_Name;public int m_ByteSize;public int m_Index;public int m_TypeFlags; //m_IsArraypublic int m_Version;public int m_MetaFlag;public int m_Level;public uint m_TypeStrOffset;public uint m_NameStrOffset;public ulong m_RefTypeHash;
}public class ObjectInfo{public long byteStart;//相对于SerializedFileHeader的偏移public uint byteSize;public int typeID;public int classID;public ushort isDestroyed;public byte stripped;public long m_PathID;public SerializedType serializedType;}public class LocalSerializedObjectIdentifier{public int localSerializedFileIndex;public long localIdentifierInFile;}public class FileIdentifier{public Guid guid;public int type; //enum { kNonAssetType = 0, kDeprecatedCachedAssetType = 1, kSerializedAssetType = 2, kMetaAssetType = 3 };public string pathName;//custompublic string fileName;}public class  AssetBundle : NamedObject{public PPtr<Object>[] m_PreloadTable;public KeyValuePair<string, AssetInfo>[] m_Container;
}public class AssetInfo{public int preloadIndex;public int preloadSize;public PPtr<Object> asset;
}

我们要关心的核心问题是:如何从Bundle中加载需要的Asset

  • 业务上层会传入一个资源路径
  • 游戏中的资源加载模块会根据资源路径得到其所在的Bundle路径
  • 资源加载模块会调用Unity接口去加载Bundle
  • 先将整个文件头加载到内存中,其以SerializedFile 格式存储在内存中(注意保存文件时也即磁盘上的数据结构和内存中的数据结构不一定一致)
  • 根据传入的路径从Bundle内的Container中找到该Asset对应的AssetInfo
  • 从AssetInfo中拿到PreloadIndex,其是PreloadTable中的索引,PreloadSize是长度,结合两者可以知道该Asset包含了哪些Object,并获取到ObjectInfo(也叫ObjectHeader)
  • 从ObjectInfo中拿到FileID和PathID,PathID是Object在AssetBundle内的标识,如果FileID为0,表明Object在该Bundle内,如果FileID不为0,则说明需要的Object在其他AssetBundle中
  • 从Exteranls中根据FileID找到对应的FileIdentifier,拿到AssetBundle的名字,再根据PathID找到Object 。
    • 在内存中会有其他转换,例如每个SerializedFile都会有个SerializedFileIndex。FileID和SerializedFileIndex不过是同一个Bundle在磁盘和内存上的不同标识而已
  • 从ObjectInfo中拿到byteStart和byteSize即可知道Object数据在整个Bundle文件中的位置,并实现读取
  • 依次将Asset内的所有Object数据读取到内存中
  • 根据读取的Object数据反序列化得到想要加载的资源

【AssetBundle的加载和卸载】

先看一张图

加载时AssetBundleHeader、SerializedFileHeader、MetaData的数据会进入到内存中,也即途中的AssetBundle内存镜像:

  • AssetBundle.LoadFromFile(path):同步加载,path为本地路径
  • AssetBundle.LoadFromFileAsync(path):异步加载,path为本地路径
  • AssetBundle.LoadFromMemory(byte[] binary):从字节数组加载,binary为目标ab二进制流
  • AssetBundle.LoadFromMemoryAsync(byte[] binary):从字节数组异步加载,binary为目标ab二进制流
  • UnityWebRequest.GetAssetBundle(string uri):url为ab文件路径,可为本地,也可为云端,

加载Asset时会从Bundle中加载一系列的Object生成对应的Asset,每个Asset会根据FileID和PathID生成唯一的标识ID:

  • assetBundle.LoadAsset<T>(name):T为目标资产类型,name为资产名称,会返回一个T实例
  • assetBundle.LoadAsset(name,type):name为资产名,type为资产类型
  • assetBundle.LoadAllAssets<T>():T为目标资产类型,会返回一个assetBundle中所有T类型资产数组
  • assetBundle.LoadAllAssets():加载assetBundle中所有资产,返回一个assetBundle中所有资产数组

实例化时,会生成一份新的Asset,其有一个对应的InstanceID,并引用原来的Asset。

【AssetBundle解析工具】

unity官方的WebExtract和Binary2Text

WebExtract路径:

cd进入路径后 输入AssetBundle路径即可 

Binary2Text路径:

AssetStudio

【AssetBundle优化】

IO优化:先了解下读取文件的详细流程

这里说的都是很通用的IO优化方法,不光是读取AssetBundle,读取其他文件也一样:

  • 缓存文件句柄
    • 打开关闭文件是一个耗时的操作,上层要卸载Bundle时,可以先缓存文件句柄,而不是立即关闭释放
  • Object数据重排
    • IO中很大一部分时间消耗是寻找磁道和磁头移动的时间(即寻道时间),为了减少这个时间,需要数据顺序排列,这样可以顺序读取,类似CPU读取数组比List快。在加载Asset时会读取多个Object的数据,如果保证传入的流的Pos是顺序增加或减少,而不是来回横跳,那么可以大幅度减少IO时间。
  • 无锁多线程
    • 一般来说一个成熟软件的读取文件操作会在单独的IO线程中执行,但会涉及到很多加锁操作,可以考虑改成无锁的,这个难度比较大
  • 组合IO请求
    • 一次大的IO请求比多次小的IO请求好,如果多个小的IO请求的位置大致是连续的,即使中间有部分数据可能不是需要的,也可以组合成一次大的IO请求

大小和内存优化

  • 剔除重复数据
    • 保证每个Asset只在一个Bundle内
  • 字符串优化(内存优化,很大一部分是去如何优化字符串)
    • string转Id
      • 例如所有的AssetInfo中的assetName转为int,加载资源时也传入Id
      • 所有标识改为用Id,int64改为int32
  • 合并重复数据,例如TypeTree合并成一份
  • 精简冗余数据
    • 会建立各种映射关系,可以梳理下简化映射,例如PersistentManager里的remapper
    • 有些数据在加载到内存后可能不会用了,从原有的数据结构中拆开,然后释放,例如各种版本信息
    • 动态Buffer,读取文件时一般会有个Buffer缓存数据,读Object时的Buffer是FileCacherRead,可以根据需要动态调整,或者共用Buffer

【参考】

AssetBundle研究报告 | BLOG

Unity如何把一个对象从内存序列化到磁盘 | 矩阵·空间

AssetBundle热更新完整工作流与知识点解析 | 登峰造极者,殊途亦同归。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/809231.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

风储微网虚拟惯性控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 风储微网虚拟惯性控制系统simulink建模与仿真。风储微网虚拟惯性控制系统是一种模仿传统同步发电机惯性特性的控制策略&#xff0c;它通过集成风力发电系统、储能系统和其他分…

如何动态渲染HTML内容?用v-html!

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

计算机网络知识等汇总补充

计算机网络知识汇总补充 一、四次挥手1、为什么TCP要等待2MSL2、如果说一个系统中&#xff0c;有大量的time_wait和close_wait&#xff0c;会是什么原因&#xff1f; 二、你是怎么解决粘包问题&#xff1f;三、你觉得哪些场景适合redis四、redis的持久化策略五、你会怎么保证my…

4-云原生监控体系-Grafana-基本使用

1. 介绍 使用Grafana&#xff0c;您可以通过漂亮、灵活的仪表板创建、探索和共享所有数据。查询、可视化、提醒和理解您的数据&#xff0c;无论数据存储在何处。 图片出处&#xff1a; https://grafana.com/grafana/ 官方网站 2. 界面介绍 Connections 可以配置数据源&#x…

php-redis windows ,pecl 已经不维护了,解决方案:php 8.2 | 8.3+ redis extension windows

从论坛上pecl 已经不维护了&#xff0c;直接让大家到ci 去下载 https://stackoverflow.com/questions/76496488/redis-dll-not-found-for-php8-2/76496489#76496489 让我们找最新的一次commit &#xff0c;然后又action 构建&#xff0c;再下载&#xff0c;这样的话也好&#…

Redis从入门到精通(十三)Redis分布式缓存(一)RDB和AOF持久化、Redis主从集群的搭建与原理分析

文章目录 第5章 Redis分布式缓存5.1 Redis持久化5.1.1 RDB持久化5.1.1.1 执行时机5.1.1.2 bgsave原理 5.1.2 AOF持久化5.1.2.1 AOF原理5.1.2.2 AOF配置5.1.2.3 AOF文件重写 5.1.3 RDB和AOF的对比 5.2 Redis主从5.2.1 搭建主从结构5.2.2 主从数据同步原理5.2.2.1 全量同步5.2.2.…

集群开发学习(一)(安装GO和MySQL,K8S基础概念)

完成gin小任务 参考文档&#xff1a; https://www.kancloud.cn/jiajunxi/ginweb100/1801414 https://github.com/hanjialeOK/going 最终代码地址&#xff1a;https://github.com/qinliangql/gin_mini_test.git 学习 1.安装go wget https://dl.google.com/go/go1.20.2.linu…

【Ubuntu】 Github Readme导入GIF

1.工具安装 我们使用 ffmpeg 软件来完成转换工作1.1 安装命令 sudo add-apt-repository ppa:jonathonf/ffmpeg-3sudo apt-get updatesudo apt-get install ffmpeg1.2 转换命令 &#xff08;1&#xff09;直接转换命令&#xff1a; ffmpeg -i out.mp4 out.gif(2) 带参数命令&…

【洛谷 P4017】最大食物链计数 题解(深度优先搜索+动态规划+邻接表+记忆化搜索+剪枝)

最大食物链计数 题目背景 你知道食物链吗&#xff1f;Delia 生物考试的时候&#xff0c;数食物链条数的题目全都错了&#xff0c;因为她总是重复数了几条或漏掉了几条。于是她来就来求助你&#xff0c;然而你也不会啊&#xff01;写一个程序来帮帮她吧。 题目描述 给你一个…

SuperGluePretrainedNetwork调用接口版本(两个版本!)

本脚本是一个基于Python的应用&#xff0c;旨在演示如何使用SuperGlue算法进行图像之间的特征匹配。SuperGlue是一个强大的特征匹配工具&#xff0c;能够在不同的图像之间找到对应的关键点。这个工具尤其适用于计算机视觉任务&#xff0c;如立体视觉、图像拼接、对象识别和追踪…

RN封装三角形组件(只支持上下箭头)

import React from react; import { View, StyleSheet } from react-native;const Triangle ({ direction, width, height, color }) > {// 根据方向选择三角形的样式const triangleStyle direction up? {borderTopWidth: 0,borderBottomWidth: height,borderLeftWidth: …

docker完美安装分布式任务调度平台XXL-JOB

分布式任务调度平台XXL-JOB 1、官方文档 自己看 https://www.xuxueli.com/xxl-job/#1.1%20%E6%A6%82%E8%BF%B0 2、使用docker部署 本人使用的腾讯云&#xff0c;安装docker暴露一下端口&#xff0c;就很舒服的安装这个服务了。 docker pull xuxueli/xxl-job-admin:2.4.03…

Harmony鸿蒙南向驱动开发-PIN接口使用

功能简介 PIN即管脚控制器&#xff0c;用于统一管理各SoC的管脚资源&#xff0c;对外提供管脚复用功能&#xff1a;包括管脚推拉方式、管脚推拉强度以及管脚功能。 PIN接口定义了操作PIN管脚的通用方法集合&#xff0c;包括&#xff1a; 获取/释放管脚描述句柄&#xff1a;传…

Stable Diffusion之Ubuntu下部署

1、安装conda环境 conda create -n webui python3.10.6 2、激活环境 每次使用都要激活 conda activate webui 注意开始位置的变换 关闭环境 conda deactivate webui 3、离线下载SD 代码 https://github.com/AUTOMATIC1111/stable-diffusion-webui https://github.com/Stabilit…

10个大型语言模型(LLM)常见面试问题和答案解析

今天我们来总结以下大型语言模型面试中常问的问题 1、哪种技术有助于减轻基于提示的学习中的偏见? A.微调 Fine-tuning B.数据增强 Data augmentation C.提示校准 Prompt calibration D.梯度裁剪 Gradient clipping 答案:C 提示校准包括调整提示&#xff0c;尽量减少产生…

http请求头导致了dial tcp:lookup xxxx on 10.43.0.10:53 no sunch host

事实证明人有的时候也不能太偷懒&#xff0c;太偷懒容易给自己埋坑。 问题的背景&#xff1a; web端调用服务A&#xff0c;服务A异步调用服务B。服务A有四个场景需要调用服务B&#xff0c;所以&#xff0c;服务A中封装了一个公用的方法&#xff0c;唯一的区别是&#xff0c;场…

IDEA 控制台中文乱码 4 种解决方案

前言 IntelliJ IDEA 如果不进行相关设置&#xff0c;可能会导致控制台中文乱码、配置文件中文乱码等问题&#xff0c;非常影响编码过程中进行问题追踪。本文总结了 IDEA 中常见的中文乱码解决方法&#xff0c;希望能够帮助到大家。 IDEA 中文乱码 解决方案 一、设置字体为支…

手机银行客户端框架之mPaaS介绍

移动开发平台&#xff08;Mobile PaaS&#xff0c;简称 mPaaS&#xff09;是源于支付宝 App 的移动开发平台&#xff0c;为移动开发、测试、运营及运维提供云到端的一站式解决方案&#xff0c;能有效降低技术门槛、减少研发成本、提升开发效率&#xff0c;协助企业快速搭建稳定…

Docker Redis Debian服务器版

1.使用官方安装脚本自动安装docker 安装命令如下&#xff1a; curl -fsSL https://get.docker.com -o get-docker.shsudo sh get-docker.sh 如果安装提示 -bash sudo command not found 则需要 #update sudo apt-get update sudo apt-get install sudo再执行安装脚本1 安装…

c++中常用库函数

大小写转换 islower/isupper函数 char ch1 A; char ch2 b;//使用islower函数判断字符是否为小写字母 if(islower(ch1)){cout << ch1 << "is a lowercase letter." << end1; } else{cout << ch1 << "is not a lowercase lette…