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
- Types 每个对象的类型是什么
- 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
- string转Id
- 合并重复数据,例如TypeTree合并成一份
- 精简冗余数据
- 会建立各种映射关系,可以梳理下简化映射,例如PersistentManager里的remapper
- 有些数据在加载到内存后可能不会用了,从原有的数据结构中拆开,然后释放,例如各种版本信息
- 动态Buffer,读取文件时一般会有个Buffer缓存数据,读Object时的Buffer是FileCacherRead,可以根据需要动态调整,或者共用Buffer
【参考】
AssetBundle研究报告 | BLOG
Unity如何把一个对象从内存序列化到磁盘 | 矩阵·空间
AssetBundle热更新完整工作流与知识点解析 | 登峰造极者,殊途亦同归。
相关文章:
Unity构建详解(7)——AssetBundle格式解析
【文件格式】 文件可以分为文本文件、图片文件、音频文件、视频文件等等,我们常见的这些文件都有行业内的标准格式,其意味着按照一定的规则和规范去保存读取文件,可以获取我们想要的数据。 有些软件会有自己的文件格式,会按照其…...
前端对接fastGPT流式数据+打字机效果
首先在对接api时 参数要设置stream: true, const data {chatId: abc,stream: true,//这里true返回流式数据detail: false,variables: {uid: sfdsdf,name: zhaoyunyao,},messages: [{ content: text, role: user }]}; 不要用axios发请求 不然处理不了流式数据 我这里使用fetch …...
避免使用第三方工具完成电脑环境检测
0. 简介 在之前配置各种深度学习环境的时候经常需要先检测一下电脑的软硬件环境,其实整个过程比较重复和固定,所以我们是否有可能一键检测Python版本、PIP版本、Conda版本、CUDA版本、电脑系统、CPU核数、CPU频率、内存、硬盘等内容这是很多Deepper苦恼…...
vue 中 mixin 的应用场景,原理和合并规则
应用场景 多个组件的相同逻辑可以提出去来一个公共的 mixin 原理 Mixin 的工作原理是将 Mixin 中的选项合并到组件的选项中 合并规则 优先处理 mixinsprops 、method、inject、computed 同名的使用组件内的,不使用mixin 的data 进行合并生命周期和watch 先执行…...
点击按钮(文字)调起elementUI大图预览
时隔一年,我又回来了 ~ 最近在做后台,遇到一个需求,就是点击“查看详情”按钮,调起elementUI的大图预览功能,预览多张图片,如下图: 首先想到的是使用element-ui的el-image组件,但它是…...
全面学习SpringCloud框架指南
要深入学习Spring Cloud框架,你需要系统地掌握其核心组件和概念,并了解如何在实际项目中应用这些知识。以下是一些关键的学习点和相应的学习内容: 一共分为10个模块包括: 1、微服务架构基础: 理解微服务架构的概念和优势。 学习单体架构向微服务架构演进的过程。 掌握…...
5G智慧水利数字孪生可视化平台,推进水利行业数字化转型
5G智慧水利数字孪生可视化平台,推进水利行业数字化转型。随着5G技术的快速发展,越来越多的行业开始探索数字化转型的道路。水利行业作为国民经济的重要支柱,也面临着数字化转型的迫切需求。5G智慧水利数字孪生可视化平台作为水利行业数字化转…...
新手入门:大语言模型训练指南
在这个信息爆炸的时代,人工智能技术正以前所未有的速度渗透到我们生活的方方面面。从智能手机上的语音助手到自动驾驶汽车,AI的应用无处不在。而在这些令人惊叹的技术背后,大语言模型(LLM)扮演着至关重要的角色。它们不…...
Win11 WSL2 install Ubuntu20.04 and Seismic Unix
Win11系统,先启用或关闭Windows功能,勾选“适用于Linux的Windows子系统”和“虚拟机平台”两项 设置wsl默认版本为wsl2,并更新 wsl --list --verbose # 查看安装版本及内容 wsl --set-default-version 2 # 设置wsl默认版本为wsl2 # 已安装…...
rust使用print控制台打印输出五颜六色的彩色红色字体
想要在控制台打印输出彩色的字体,可以使用一些已经封装好的依赖库,比如ansi_term这个依赖库,官方依赖库地址:https://crates.io/crates/ansi_term 安装依赖: cargo add ansi_term 或者在Cargo.toml文件中加入&#…...
贪心算法|435.无重叠区间
力扣题目链接 class Solution { public:// 按照区间右边界排序static bool cmp (const vector<int>& a, const vector<int>& b) {return a[1] < b[1];}int eraseOverlapIntervals(vector<vector<int>>& intervals) {if (intervals.siz…...
C++的并发世界(七)——互斥锁
0.死锁的由来 假设有两个线程T1和T2,它们需要对两个互斥量mtx1和mtx2进行访问。而且需要按照以下顺序获取互斥量的所有权: -T1先获取mte1的所有权,再获取mt2的所有权。 -T2先获取 mtx2的所有权。再铁取 mtx1的所有权。 如果两个线程同时执行,…...
NI-LabView的DAQ缺少或丢失的解决办法(亲测有效)
DAQmx在Labview中不显示或缺失 问题:在NI Packasge Manager安装完DAQ后在labview中不显示控件解决办法 问题:在NI Packasge Manager安装完DAQ后在labview中不显示控件 在打开测量I/O时,见不到 DAQmx,或者在Express中见不到DAQ助手…...
cesium 调整3dtiles的位置 世界坐标下 相对坐标下 平移矩阵
cesium调整3dtiles的位置用到的是平移矩阵,原理是在世界坐标系中用偏移点减去原始点得到一个平移向量,再根据这个向量得到平移矩阵。 原始点:一般是模型的中心点位置,可通过模型的包围盒得到偏移点:可分为两种情况&…...
flutter跑通腾讯云直播Demo
运行示例 前提条件 要求java jdk 11版本 并且配置到了环境变量 重要 要求flutter 版本 2.8.0 并且配置到了环境变量 重要 要求dart-sdk版本2.15 并且配置到了环境变量 重要 您已 注册腾讯云 账号,并完成 实名认证。 申请 SDKAPPID 和 SECRETKEY 登录实时音视频控…...
飞机降落蓝桥杯[2023蓝桥省赛B组]
2023蓝桥省赛B组 B题 飞机降落 题解 标准深搜板子题,难度不大 #include<bits/stdc.h> using namespace std; #define MAX 10 struct node{int t,d,l;//t:飞机到达时间 d:飞机最大盘旋时间 l:飞机降落所需时间bool v;//标记此架飞机是否被搜索过 用于剪枝 };…...
如何动态渲染HTML内容?用v-html!
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
EFcore 6 连接oracle19 WinForm vs2022
用EFcore访问Oracle,终于不需要Oracle的什么安装包了,直接在VS2022中就可以轻松搞定。在csdn上看到一哥们的帖子,测试了一下,发现很方便。使用的场景是:VS2022中EFcore6。经过测试,同 Navicat Premium 16比…...
(delphi11最新学习资料) Object Pascal 学习笔记---第9章第2节(finally代码块)
9.2 finally 代码块 还有第四个用于异常处理的关键字,我已经提到过,但到目前为止还没有使用过,那就是 finally。finally块用于执行一些应始终执行的操作(通常是清理操作)。事实上,无论是否发生异常&…...
220 基于matlab的考虑直齿轮热弹耦合的动力学分析
基于matlab的考虑直齿轮热弹耦合的动力学分析,输入主动轮、从动轮各类参数,考虑润滑油温度、润滑油粘度系数等参数,输出接触压力、接触点速度、摩擦系数、对流传热系数等结果。程序已调通,可直接运行。 220直齿轮热弹耦合 接触压力…...
企业出海聘用海外员工该怎么挑选靠谱名义雇主服务商?
很多企业出海初期,都会卡在海外员工聘用这一步:没有海外实体,没法合法签合同、缴社保,想找名义雇主服务商,又怕选到不靠谱的,踩坑又不合规。结合我这几年帮出海企业对接服务商的经验,今天不玩虚…...
RFSoC开发避坑指南:手把手教你理解并配置RF数据转换器的核心结构体(以XRFdc为例)
RFSoC开发实战:深度解析XRFdc结构体配置与避坑策略 第一次打开xrfdc.h头文件时,面对密密麻麻的结构体定义,我的鼠标滚轮不由自主地滑动了三分钟才看完所有内容。作为曾经在RFSoC项目上踩过无数坑的开发者,我完全理解那种面对数十个…...
EFM8 I2C Slave外设深度解析:从SMBus思维转换到实战应用
1. 项目概述:从SMBus到I2C Slave的思维转换如果你之前主要接触的是SMBus(系统管理总线)设备,现在要上手Silicon Labs的EFM8LB1或EFM8BB3这类8位MCU的I2C Slave(从机)功能,可能会觉得有点“水土不…...
在职场上,别人对你的态度,都是你允许的:“他为什么敢这样对我?”“他为什么不怕得罪我?”“我有什么好怕的?”
当有人在公司凶你时:别问"他为什么凶我",要问"他为什么敢" 目录 当有人在公司凶你时:别问"他为什么凶我",要问"他为什么敢" 别人敢在公司得罪你,是因为他早已算清了这笔账 他不怕得罪你,说明在他眼里你"没有威胁性" …...
4.【Python】Python3 注释
第一步:分析与整理 注释1. 注释的作用 不影响程序执行,只提高可读性。帮助理解代码逻辑,方便团队协作。2. 单行注释 以 # 开头,直到行末的所有内容均为注释。 # 这是一个注释 print("Hello, World!") # 这也是注释3. 多…...
Arm DSTREAM调试接口设计与JTAG/SWD协议详解
1. Arm DSTREAM系统与调试接口设计指南1.1 调试接口技术基础1.1.1 JTAG协议架构解析JTAG(Joint Test Action Group)标准IEEE 1149.1定义了五线制调试接口:TCK:测试时钟,同步所有JTAG操作TMS:测试模式选择&a…...
从零到一:Ubuntu Server上构建生产级Slurm计算集群
1. 环境准备与系统配置 在开始构建Slurm集群之前,我们需要确保所有节点都处于干净、一致的初始状态。我建议使用Ubuntu Server 22.04 LTS版本,这个长期支持版本经过充分测试,稳定性有保障。实际部署中发现,不同Linux发行版间的软件…...
基于BLE与NeoPixel的智能眼镜控制:在ATtiny85上实现无线光效交互
1. 项目概述与核心价值几年前,当我第一次把玩Adafruit的NeoPixel灯环时,就被其绚丽的色彩和简单的控制方式所吸引。后来,一个很自然的想法冒了出来:能不能把这些灯珠集成到一副眼镜上,并且用手机来无线控制它ÿ…...
okbiye AI 写作新思路:毕业论文终稿一站式落地,不用熬夜硬熬
okbiye-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPT毕业论文 - Okbiye智能写作https://www.okbiye.com/ai/bylw 开篇引言 每到毕业季,毕业论文总会成为无数大学生最头疼的一道关卡。选题没方向、框架搭不起来、正文写不出深度、重复率居高不…...
2026 最稳高薪副业 + 主业赛道,网络安全零基础系统学习大纲,实战项目 + 证书考取 + 求职面试一站式教程
网络空间安全建设刻不容缓,已成为国家安全建设的重中之重。 随着境内外敌对势力的大规模安全事件,《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地,网络安全产业从小众产业逐步发展成为国家…...
