External - AssetBundles FAQ
External - AssetBundles FAQ
ote: Comments and fixes are welcome. Please help point out any incorrect information, or to
N
suggest further topics to cover
Introduction
3
What is the scope of this document? 3
General Questions 4
What is a Scene AssetBundle? 4
How do I create an AssetBundle? 4
Can you build AssetBundles individually, then use them together at runtime? 4
Can I load AssetBundles built on an older version of Unity? 4
Can I load AssetBundles built on a newer version of Unity? 5
What happens if an AssetBundle is out of date? 6
Some users of my game are having crashes with AssetBundles, what is going on? 6
How do I ship AssetBundles with my Player build? 7
How do Prefabs work in AssetBundles? 7
How do Scenes share assets in AssetBundles? 8
How can I log the time taken during an AssetBundle build? 10
Do AssetBundles have a file extension? 11
How does Unity treat uppercase letters in an AssetBundle name? 11
Why are my files reimporting or builds different between Windows and Mac? 12
Are AssetBundle Builds Deterministic? 12
Tips and Tricks 14
Should AppendHashToAssetBundleName be used? 14
How can I build AssetBundles with different compression settings? 14
How can I perform partial builds and still use the inspector to assign Assets to
AssetBundles? 5
1
Are there issues with Animation Override Controllers and AssetBundles? 16
Are there issues with Scriptable Render Pipelines and AssetBundles? 16
Inside AssetBundles 17
What is the format of a Unity Archive file? 17
What is inside an AssetBundle Archive? 17
What is the naming convention for SerializedFiles inside an AssetBundle? 19
What is inside an AssetBundle SerializedFile? 20
What is a .ResS file? 20
How do References To ResS Files work? 21
What is a .Resource file? 21
How can I see what is inside an AssetBundle? 22
ption 1: WebExtract + Binary2Text
O 22
Option 2: UnityDataTools 23
Option 3: AssetBundles Browser 23
Option 4: Via BuildReport 23
How can I see what Unity Version was used to build an AssetBundle? 24
How can I check the Compression for an AssetBundle 24
What are the different “Manifests” generated by an AssetBundle Build? 25
Do AssetBundle builds generate a BuildReport? 30
Where can I find a breakdown of the size of data inside an AssetBundle 31
CRC and Hash Topics 32
How do CRCs work with AssetBundles? 32
What is the AssetBundle Hash generated by a build? 32
How do Incremental Builds use the AssetBundle Hash? 35
How is the AssetBundle Input Hash calculated? 36
What is the TypeTreeHash? 36
Downloading AssetBundles 38
What built-in mechanism does Unity offer for downloading AssetBundles? 38
What recompression happens to AssetBundles when entering the cache? 38
Which signatures of [Link] support caching? 39
What happens when [Link] is used without
caching? 39
How does [Link] determine the bundle name for
the cache? 40
What is the Hash used by the AssetBundle cache? 40
How can I use the AssetBundle hash as a version hash when downloading an
AssetBundles? 41
How can I generate my own version hash when downloading an AssetBundles? 42
What is the structure of the Asset Bundle Cache? 43
Loading Local AssetBundles 44
What is the performance problem with LZMA compression and local AssetBundles? 44
What does the Assert “PersistentManager: File …. Was expected to exist…” Mean? 45
Performance 46
What memory is used when loading Asset Bundles? 46
When does Unity load the entire AssetBundle into memory? 47
How can I reduce runtime memory usage in AssetBundles with many Assets? 48
Platform Topics 49
Can I load the same AssetBundles on different platforms? 49
How are new AssetBundles downloaded to consoles? 49
Should CRC checks be performed on Consoles? 49
What platforms do not support AssetBundle caching? 50
Related Technology and Other References 51
his document also has a lot of relevant content for Addressables and the Scriptable Build
T
Pipeline, because those packages also create and load the exact same Asset Bundle file
format. But this document does not attempt to cover all the details of those packages.
nity also supports assigning Scene files to an AssetBundle. However you cannot mix Scenes
U
with other assets inside a single AssetBundle. In the API this type of bundle is called a
“streaming scene” AssetBundle, see[Link]
he process of building scenes into AssetBundles is similar to what happens during a Player
T
build, in fact a lot of the same code is reused.
lternatively you can use the Addressables package, which has a user interface. That is
A
outside the scope of this document.
In practice, if you understand the dependencies between assets sufficiently, and have your
assets grouped into independent “islands”, then you do not need to build them all with a single
call.
If the serialization format for an object has changed then AssetBundle load code will read that
object using the “safe binary read” deserialization method. This code path uses type trees to
dditionally, we do not guarantee backward compatibility for all Objects and Unity features. We
A
attempt to maintain it, but individual teams might change their serialization code without
considering that it could cause a backward compatibility issue. It is also possible that individual
objects can load, but the expected structuring of the objects has changed, e.g. if expected
objects are completely missing, or the references between objects do not establish an expected
hierarchy. As the version gap between the building and loading of AssetBundles widens, it is
more likely that some objects will not load correctly.
lso one specific case to mention. There was a change related to padding in the AssetBundle
A
header made around March 2022. This fix is backward compatible, but could mean that older
versions of Unity, prior to that backport landing, cannot load AssetBundles generated after that
change (e.g. not forward compatible).
lso worth mentioning is that several Shader and Terrain related backward compatibility bug
A
reports and forum posts about shaders not working properly when loading AssetBundles
created in earlier versions of Unity.
he serialization definition of built-in objects can change when Unity is upgraded, and C# class
T
within a project may also change as the project evolves.
he Safe Binary Read approach works for individual objects. If there are major changes in the
T
expected state of an object or structural changes in object hierarchies, then old serialized data
may not load properly. So it can still make sense to always rebuild AssetBundles along with
Player releases to keep them in sync.
he fix for this is to make sure there is data verification happening as part of the assetbundle
T
distribution system. The assetbundle should be checked after download, or prior to load, and if
the content is not correct it can be erased and re-downloaded immediately.
his can be done by calculating a CRC or content hash of the file, and checking it against
T
expected value. A customer might create their own system for tracking and publishing
expecting hash values. One complexity is that Unity will recompress AssetBundles when
UnityWebRequestAssetBundle and AssetBundle cachingare used, so a hash of the file content
will be invalidated. For that reason the CRC support provided by Unity is recommended
because it is independent of the compression.
ecause of the cost of checking CRC or content hashes it is best done once, as the file is
B
downloaded, rather than repeating it at every single load. See later section for more details
about hashing, CRC and download support.
ithout some sort of validation layer then it is pretty much guaranteed that, sooner or later,
W
there will be corruption leading to random crashes, or bad behavior, that are quite difficult to
pinpoint.
hen a scene (or prefab) is built all the prefabs instances are fully expanded. So rather than a
W
reference to an external prefab file + property modifications, each prefab instance is replaced by
all the contents of the prefab, with the property modifications “baked in”. So the scene files are
self-contained, with prefab structuring removed and not relevant at runtime.
It is also possible to put a prefab explicitly into an AssetBundle. In that case any nested prefabs
are expanded, just as happens in a scene, and the full hierarchy of gameobjects from the prefab
is included in the AssetBundle. The prefab can be loaded at runtime, which returns the root
game object of the prefab. Multiple instances can be created by instantiating the loaded Prefab
multiple times.
s with other Asset types it is possible to place multiple Prefabs in the same AssetBundle.
A
However if the prefabs are very large there can be some overhead if you typically only load a
small portion of the prefabs in a large AssetBundle. So grouping prefabs that are typically
loaded together into a single AssetBundle can be better than grouping unrelated prefabs
together.
In a player build, all assets that are referenced by scenes are built into sharedAsset files, and
data referenced from multiple scenes is automatically deduplicated so it is only stored once.
This works because each scene will typically have its own sharedAsset file, but it can also
reference the sharedAsset files of other scenes.
or the purpose of illustrating this concept, imagine Scene1 and Scene2 both have
F
MonoBehaviours that reference the same Scriptable Object, “[Link]”.
he resulting player build will share the [Link], because both level files point to the same
T
object. Specifically, the [Link] becomes a MonoBehaviour object inside
[Link] file.
If you build multiple scenes into a single AssetBundle, then the same build logic is applied to
create sharedAsset files inside the bundle. So referenced data is only stored once, and multiple
scenes can reference the same data.
If, instead, you build scenes into separate bundles, then there can be no sharing of implicitly
referenced content. This is by design because it would be unexpected for scene bundles to
depend on each other. However it also means that data can be duplicated, and storing scenes
in separate bundles can result in a larger build output than the equivalent scenes built in the
Player or as a single AssetBundle. It can also mean that objects like ScriptableObjects that
might be intended as singletons could have multiple instances.
o in the case of our example, the MonoBehaviour object from [Link] will be duplicated in
S
both bundles, which also means that data won’t be shared at runtime.
ote: The only way to duplicate the ScriptableObject in this case is avoid assigning it explicitly
N
to any AssetBundle. If the same Asset is explicitly listed in more than bundle definition then we
fail the build. Some users have requested the ability to intentionally duplicate an asset, for
example in thisforum post.
For example:
sing UnityEditor;
u
using System;
using [Link];
using UnityEngine;
public class NewBehaviourScript
{
[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
string assetBundleDirectory = "Assets/AssetBundles";
if ()
{
[Link](assetBundleDirectory,
[Link],
[Link]);
[Link]();
w
var t = [Link]([Link]);
[Link]($"Build time: {[Link]()}");
}
}
or a breakdown of time taken by individual steps that comprise the build, you can look at the
F
Build Report Inspectortool.
In the past “.unity3d” was often used as an extension for AssetBundles.
his behavior is not ideal, but is long established, so it could be disruptive to change it to
T
respecting the input file name..
o avoid potentially hitting issues with these different behaviors it is recommended to use
T
lowercase names for your AssetBundle names.
However sometimes the results are not deterministic. Here are some example causes:
arger projects will often use one or more dedicated Build Machines (or Virtual Machines) that
L
have consistent and predictable hardware configurations for their official builds. This can reduce
the risk of non-determinism in the output. Custom build scripts can also include hashing
comparisons, and periodically use that to sanity check that their builds are consistent between
different build machines.
In some cases this feature can be useful, for example it can be convenient for web hosting of a
game when different devices are running different versions at the same time. With this naming
convention different versions of the same assetbundle can exist side by side in the same folder,
and each device would download the correct one if they have a version specific manifest or
catalog file.
here are some downsides to this. This means that the built-in AssetBundle caching will not
T
work to automatically replace older versions of an AssetBundle with newer versions. So you will
end up with multiple versions of the same AssetBundle sitting in the cache using up disk space.
The older AssetBundles can be removed eventually based on cache size limits.
he flag will also foil file-level patching, which is built in on many console platforms. E.g. a one
T
byte change to a 100MB file would end up treated as an entirely new 100MB file rather than a
small patch file. A more sophisticated cross-file patching solution could still work but would
probably need to be a custom solution specifically for AssetBundles, so really seems hard to
justify compared to not using the flag.
In some more advanced situations it can be desirable to have different compression settings for
different bundles (e.g. to leave some bundles uncompressed because they do not benefit from
compression). But because the compression setting covers the whole build this is not
supported directly using the API.
ne workaround is to run the build two or more times, with different output folders and different
O
compression settings. Then the results can be merged back together, picking and choosing
which output is desired for each bundle. The AssetBundles built this way should have identical
CRC and hash values so basically be interchangeable.
ow can I perform partial builds and still use the inspector to
H
assign Assets to AssetBundles?
he built-in UI at the bottom of the Inspector window is useful for assigning Assets to
T
AssetBundles. That UI stores the assignments in the AssetDatabase. However the signature of
[Link] which reads those settings will always build ALL bundles. This
may not be convenient in cases where you only want to build a single bundle or group of
bundles. Or if you want to do a mix of UI-assigned and programmatic assignments for your
bundles.
useful way to get the best of both alternatives is to use these APIs to populate the
A
AssetBundleBuild Array:
A
● [Link]
● [Link]
his gives an opportunity for the script to potentially build just a subset of the bundles returned
T
by GetAllAssetBundleNames, or to extend or modify the list of Assets beyond what has been
assigned in the UI. Once the array of AssetBundleBuild structures has been tweaked it can be
provided to [Link].
ote: in projects that use AssetBundle Variants it is more involved to build the correct
N
AssetBundleBuild structures. There are some methods to help, e.g.
[Link]().
ut it also should be possible to split Animation assets into different Assetbundles, so long as
B
they are all built at the same time and all the Assets are explicitly assigned to an AssetBundle.
This is important because like all other Asset times, any implicit reference from multiple
AssetBundle is resolved by embedding the referenced object directly in each bundle, e.g.
duplicating them. That can lead to clips being duplicated and hence break the behaviour of the
AnimationOverrideController.
or HDRP and URP the compilation of shaders can be the longest step in a build and problems
F
can pop up either with too many shader variants (impossible build times) or missing shader
variants (shaders missing when the Shader is used in the runtime).
Some recommendations:
● W hen we build the player, we build with SRP settings (HDRP or URP). This causes
the stripping of various shader variant
● Then you can build an AssetBundle with some Material, Shader Graph in it then it
can use the same stripping process than the player.
● If you change SRP settings in the project, your AssetBundle will break, unless you
rebuild them as well as your player.
● The SRP settings should not be included directly inside an AssetBundle.
And if you try to move any SRP settings inside an AssetBundle, thing will not
work either as the player will not contain the variant for it
his file format is not specific to AssetBundles and is used in a few other contexts such as
T
“Content Files”.
here are also other possible variations of the Archive format that can be loaded, presumably
T
maintained for backward compatibility with data that was written in the past. The signature and
flags in the header are used to determine the expected layout. E.g. Current AssetBundle builds
use signature “UnityFS”, but “UnityWeb”, “UnityRaw” and “UnityArchive” files are also loadable.
here is always at least one “SerializedFile”. This is Unity’s binary format, e.g. the same format
T
used when saving Scenes or Assets in Binary instead of YAML. It is also the same format as
the “level” files produced in a Player Build.
egular (non-scene) AssetBundles contain one Serialized File, with a name like
R
“CAB-cc6c60ef8808e0fc6663136604321554”.
bjects from assets that are referenced by any of the Scenes end up in the “.sharedAssets”
O
files. To avoid duplication the Scene and [Link] files can reference objects inside
other .sharedAssets files. But there is never any external reference to an object inside a Scene
SerializedFile.
These files are named after the serialized file that holds the associated objects.
he naming of directories and files inside an AssetBundle is rather technical. These paths are
T
visible when looking inside an AssetBundle, so this section gives some details to demystify how
the names are generated. However for regular usage of AssetBundles it is not necessary to
understand anything about the internal naming.
or a regular AssetBundle the name of the SerializedFile is based on the hash of the
F
assetbundle name. E.g. If the assetbundle is called “mybundle” then the internal file will be
“cab-”+ <Hash of“mybundle”>). The hash uses theMD4 algorithm, which is not the same as
the Hash128 object in Unity C# API (which is spooky hash).
ip: There is C# code in the Scriptable Build Pipeline and BuildPipelineModuleTests that
T
implements the precise hashing that the native build implementation uses. But for normal user
scenarios this should not be needed.
ote: There is an option to include the AssetBundle hash in the bundle’s filename. That hash is
N
not included in the string when the SerializedFile name is determined.
● B [Link] uses a name based on the Scene’s file name (without
path and extension). So “Assets/[Link]” becomes “PlayerBuild-Scene1” . This
can hit conflicts if the same scene filename exists in different project directories.
● The new Multi-Process Build Pipeline feature (2023.1 and later) uses “cab-” +
<AssetDatabase GUID of the scene>.
● Scriptable Build Pipeline uses the hash of the path, e.g. “cab-” + <hash of scene path>)
hen an Archive is loaded its contents are mounted into the Unity virtual file system (VFS). To
W
keep the content of each AssetBundle segregated each bundle has a unique directory name.
The convention is for the directory to match the name of the first file.
So, putting it together, the full virtual path to a Serialized file inside a bundle ends up like this:
Example:
“archive:/cab-9fc0d4010bbf28b4594072e72b8655ab/cab-9fc0d4010bbf28b4594072e72b8655ab”
Scene Bundle:
In the case of a regular (non-scene) AssetBundle then the objects are all the objects from the
explicitly specified assets, plus all the objects that are implicitly included via dependencies. For
example, when putting multiple ScriptableObject assets together in an AssetBundle then there
will be one object per ScriptableObject asset file, plus additional objects for the MonoScripts
referenced by those assets.
here are several “special” objects specific to Asset Bundles that are stored along with the other
T
objects in the SerializedFiles. See the Advanced section for details about theAssetBundle
object and thePreloadDataobject.
ote: Mesh vertex data will remain inside the SerializedFile if CPU access is required, e.g.
N
read/write enabled on the importer.
hen generated, this file will have the same name as the associated SerializedFile, with the
W
.ResS extension added on. For example: “CAB-296c403ed80cf6a3f663cb1ae70bdd62.resS”
(inside an Asset Bundle).
ote: This type of file can also be generated by Player Builds. E.g. “[Link]” or
N
“[Link]”.
hen the build produces a .resS file there will be 1 or more references from Objects inside the
W
associated serialized file to that file.
hese references can be determined by expanding the Serialized File with Binary2Text (or
T
UnityDataTools) and searching for “.resS”.
That says that the first 2880 bytes of the .ResS file contain the mesh data for that object.
o it would be possible to use the binary2text output to determine exactly what is inside a
S
.ResS, and what ranges of bytes correspond to each object
In the case above the total size of the .ResS file is 2880 bytes. So there is no header or footer
structure to the .ResS file.
TBD: is there any sort of padding/alignment inserted between the contents inside a ResS file?
very install of Unity includes two tools that let you expand the content of an AssetBundle.
E
They are rather low level tools but can be used from the command line or invoked from scripts.
The precise location varies depending on platform and version of Unity - if you search for
“WebExtract*” from the root Unity installation folder you will find the precise location.
hey are also available if you build Unity yourself (Source Access is available to Enterprise
T
customers via[Link]) (e.g. for example ona Windows dev machine they can be found
in <MySourceRoot>/build/WindowsEditor/x64/Debug/Data/Tools) There is also “Binary2Text”
and “WebExtract” options exposed on thejambuildtool
WebExtract
his tool creates a folder based on the assetbundle name, and populates it with the files that
T
were embedded inside the AssetBundle, similar to expanding a zip file. The folder with the
expanded contents is created inside the folder where the AssetBundle is located.
In Windows Explorer you can also drag your AssetBundle onto the [Link] file.
Binary2Text
his tool creates a text dump of the contents of a serialized file (e.g. a Unity binary-format file).
T
The output file is similar to Unity’s yaml format but it is not a true yaml file.
hat creates a .txt file in the same folder as the serialized file, e.g.
T
<path_to_asset_bundle>_data/[Link]
ote: Binary2Text requires that you are running the exact same version of Unity as was used to
N
create the SerializedFile. This restriction makes it more limited than UnityDataTools. Also for
large AssetBundles, especially with large hierarchies or shaders the output can be absolutely
massive (in the gigabytes) and hard to manipulate.
Related: this externalSupport Article also givesan example of using these tools.
his is a newer alternative to WebExtract and Binary2Text that can potentially be a better basis
T
for building tools that look inside AssetBundles. The text format it outputs when dumping
serialized files is not the same as Binary2Text but overall it is very similar.
tarting with recent version of Unity the native dll that it relies on is included in the Unity
S
installation, so UnityDataTools is an “official” tool.
An example usage (once you have downloaded the git repo and built the tool):
his opens the AssetBundle and produces text dumps of each Serialized file in the current
T
working folder (without requiring to also expand out all the binary files).
his tool also has the “analyse” option that populatesan sqlite database. This is extremely
T
useful for looking at the contents of large builds - it runs much faster than Binary2Text and can
handle object and Asset counts that the UI tools like Build Report Inspector and AssetBundle
browser cannot handle. After running the tool you can open the resulting “.db” file in a sqlite
browser, there are freeware tools like “DB Browser” on all platforms. There are useful “views”
defined that show the content of the AssetBundles in useful ways.
ssetBundles are binary files, we have created somesample codeshowing how to read the
A
header, including the unity version.
sing
u [Link];
using
[Link];
using
UnityEditor;
using
UnityEngine;
if ([Link] == [Link])
throw new [Link]($"Failed to load {archivePath}");
ChunkBasedCompression Lz4HC
UncompressedAssetBundle None
2. .manifest file with the same name as the build folder
○ This is effectively the “root” manifest file
○ This lists the generated AssetBundles and their dependencies
○ Unlike the AssetBundle .manifest files, this file lists AssetBundles with paths
relative to the build directory, instead of absolute paths.
○ It has the CRC of the Manifest AssetBundle
○ Can be used to avoid Player builds from stripping code that is needed by
bundles.
3. “ Manifest” AssetBundle, named after the build folder. This is a real AssetBundle file. It
only contains a single SerializedFile, and that serializes the AssetBundleManifest object
○ This object is exposed by theAssetBundleManifestobject. That object is
returned by[Link]()
○ But it is useful to have the information inside an actual AssetBundle because it
can be distributed in the same way as other AssetBundles, and loading inside
Unity using the same APIs as other AssetBUndles. For example, typically a
project might download this file first withUnityWebRequestAssetBundleand then
runtime code can load the AssetBundleManifest object and determine the other
asset bundles and their hash values so they can also be downloaded. Note:
when using Addressables the catalog serves a similar purpose.
○ This file is more difficult to read with external tools than the .manifest files.
// Here is example code for reading the AssetBundleManifest object from the Manifest AssetBundle.
[Link] ab =
[Link]("mybuild/mybuild");
AssetBundleManifest manifest =
[Link]<AssetBundleManifest>("AssetBundleManifest");
[Link](false);
hese are examples of the 3 types of manifest files, based on a simple build that has two
T
AssetBundles. bundle_prefab contains a MonoBehaviour that references bundle_sobject, which
contains a single serialized ScriptableObject.
anifestFileVersion: 0
M
CRC: 159838143
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: d6a8bfb48aa045a5e6da1958119abd44
TypeTreeHash:
serializedVersion: 2
Hash: 77b6acfcdc0d79b7c5de2be5fee85e4d
HashAppended: 0
ClassTypes:
- Class: 1
Script: {instanceID: 0}
- Class: 4
Script: {instanceID: 0}
- Class: 114
Script: {fileID: 11500000, guid: 0652a8087db49d248a409593b2b5624b,
type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 6de340f165a154543a19c1972f8b57fa,
type: 3}
- Class: 115
Script: {instanceID: 0}
SerializeReferenceClassIdentifiers: []
Assets:
- Assets/[Link]
Dependencies:
-
C:/unityprojects/BundleIncrementalBuild2021/Assets/StreamingAssets/bu
ndle_sobject
anifestFileVersion: 0
M
CRC: 4095657361
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 01c155e080f1c19eab7eacdf3723cae7
TypeTreeHash:
serializedVersion: 2
Hash: 67ef303608757ae63abd4d22b7aff22d
HashAppended: 0
ClassTypes:
- Class: 114
Script: {fileID: 11500000, guid: 0652a8087db49d248a409593b2b5624b,
type: 3}
- Class: 115
Script: {instanceID: 0}
SerializeReferenceClassIdentifiers: []
Assets:
- Assets/[Link]
Dependencies: []
2) T
his is an example “root” .manifest file, named after the build directory with .manifest
extension. Lists all the AssetBundles and their dependencies.
anifestFileVersion: 0
M
CRC: 2309754985
AssetBundleManifest:
AssetBundleInfos:
Info_0:
Name: bundle_prefab
Dependencies:
Dependency_0: bundle_sobject
Info_1:
Name: bundle_sobject
Dependencies: {}
ou can view the Build Report in YAML if you copy the binary file somewhere into the project’s
Y
Assets folder. Or you can have it generated directly as text if you enable the diagnostic flag
(Preferences->Diagnostics->BuildReportingEditor->SerializeBuildReportAsText).
heBuild Report Inspectorcan be used to visuallyview the contents in the Inspector. Also the
T
Project Auditorpackage. Warning: both tools arecurrently unable to display results for builds
that involve many Objects because they try to populate list views with thousands or millions of
entries. For such large builds you may have to write your own tools to use the Unity
BuildReport object, or parse through the YAML format of the file directly.
ote: some information in the build report is more oriented to Player builds and may be a bit
N
confusing for Asset Bundles, especially when scene files are included in the build. Update:
Most of these bugs have been fixed and backported.
or example, after each build the BuildReport is scanned and a large log message is generated
F
and written to the [Link]. It breaks down the total size by content type and gives a view of
what media files and other resources are included in the bundle. (Note: Scenes are referred to
as “Levels” in this output)
Here is an example:
runcation or other file corruptions that can happen during asset bundle download (just like any
T
other web download) is one of the common causes of crashes on user devices. This can be
more prone to happen on older devices which may have less local resources (e.g. running out
of memory or storage during a download). Such crashes are hard to diagnose because they
only appear later on during game play and only randomly. Therefore some sort of validation of
downloaded content, such as the CRC check, is very important to confirm that correct file
content is actually downloaded. When this check fails then some retry logic will typically fix the
problem.
● T here is a performance cost, especially when the CRC check is being repeated at each
AssetBundle load. For best performance, doing the CRC check at download time but
not at load time can be a good compromise. This is also discussed in the “Platform
Topics” section.
● As mentioned elsewhere about hashes: by default the Unity Editor version is included in
the header of SerializedFiles inside the AssetBundle. So doing a rebuild with a new
version of Unity will change the CRC, even if everything else in the bundle is identical.
E
● xposed by[Link]
● Serialized inside the Manifest AssetBundle
● Recording inside the .manifest file (asAssetFileHash)
he Native AssetBundle pipeline uses the hash for incremental builds, to determine whether an
T
AssetBundle needs to be regenerated. The hash is also sometimes used as a version hash, in
conjunction withUnityWebRequestAssetBundle.
The hashing of AssetBundles is somewhat more complicated than one might initially expect.
● T he compression of AssetBundles can change, so hashing the content of the file itself
might not give the desired results. For example AssetBundles are typically transferred
with LZMA compression, which is optimal for file size, and then dynamically
recompressed by the download cache code into LZ4 Runtime format, which is optimized
for fast decompression.
● By default AssetBundles record the exact Unity Editor version in both the AssetBundle
header and in the header of the SerializedFile(s) inside the bundle. That means that the
AssetBundle file (and CRC) will change after upgrading Unity, even if nothing has
changed in the actual built output data. The
[Link]flag can be passed to
[Link] to avoid this issue.
The different implementations of AssetBundle building calculate the hash in different ways:
n unfortunate side effect of skipping the SerializedFile header can occur if a change occurs in
A
the headers (e.g. a padding change). If that happens the content hash is not changed even
though the rebuilt AssetBundle file is different.
his hashing approach also does not include the bundle directory info in the hash, so two
T
AssetBundles containing different files that had identical contents could produce the same hash.
In practice that is very unlikely to be an issue, because Unity controls the internal file names
inside an AssetBundle archive based on the AssetBundle name or name of Scene files.
In the Multi-Process Asset Bundle Building implementation (available starting in 2023.1), the
hash calculation is simply the Hash of all the uncompressed file content, using the “spooky
hash” algorithm. It matches closely how the CRC is calculated. It is recommended to use
AssetBundleStripUnityVersionto avoid the hash changingwhen the version changes. It does
not include the names of the file inside the archive as part of the hash because the directory
information is stored separately from the data inside a Unity Archive file.
ashes for AssetBundle also appear as a way of tracking file versions in the API for
H
downloading AssetBundles. This is covered in the next section.
In the Native AssetBundle implementation the AssetBundle hash is used to capture the contents
and dependencies of the AssetBundle.
This is an example Manifest, showing the data that supports the incremental build.
anifestFileVersion: 0
M
CRC: 2088487739
Hashes:
AssetFileHash
:
serializedVersion: 2
Hash:
01c155e080f1c19eab7eacdf3723cae7
TypeTreeHash
:
serializedVersion: 2
Hash:
55501093163a37cf23c863ea4050548f
HashAppended: 0
ClassTypes:
- Class: 114
Script: {fileID: 11500000, guid: 0652a8087db49d248a409593b2b5624b, type: 3}
- Class: 115
Script: {instanceID: 0}
SerializeReferenceClassIdentifiers: []
Assets:
- Assets/[Link]
Dependencies: []
lthough a pretty exhaustive calculation, this does not capture every possible influence that can
A
impact the build. Some known limitations include:
● If a script class keeps the same class name, but moves to a new assembly or
namespace then the hash does not change
● If an asset inside a dependent AssetBundle moves to another AssetBundle the hash
does not change.
hese cases of incomplete hashing can have serious impact with the potential of null
T
references, crashes or other unexpected failures on end user devices. If a rebuild is forced with
[Link]flag,then the correct new content would be
generated, but any code depending on the hash as a “version” for the AssetBundle would not
recognize that the AssetBundle has changes.
he Native AssetBundle build implementation uses a second hash value during Incremental
T
build calculations called theTypeTreeHash.
This hash is also recorded in the .manifest file for the AssetBundle.
he hash is derived from all the types involved in the AssetBundle. These types are listed in the
T
ClassTypes section of the manifest file. For each type the hash of the typetree is feed into the
TypeTreeHash.
For Script types the ClassName, Namespace and Assembly are also hashed.
he purpose of this hash is to detect whether any objects used in the AssetBundle have newer
T
serialization formats. For example adding new fields to a MonoScript or updating to a new
version of Unity that changes some built-in objects. A change in serialization format means that
the AssetBundle should be regenerated to reflect the latest serialized schema for those objects.
arning: this typetree hash is not part of the AssetBundle hash. So while changes in this hash
W
can force an incremental build, it doesn’t force a change in the overall hash value for the
AssetBundle. This is one of the reasons that the AssetBundle hash is not an ideal value for
tracking file versions.
ssetBundle files can be distributed anyway that the customer wishes, for example hosting
A
them somewhere on the internet and using custom http code to download them, or using a
devices built in DLC mechanism.
ote: When using Addressables, the same UnityWebRequestAssetBundle API is used under
N
the hoods, hiding most of the details. But because of the potential memory usage or
performance costs it is useful to understand how it works and to configure certain settings like
compression and CRC checking properly.
his is done because LZMA is optimal for file size, e.g. for fast transfers, but LZ4 is optimized
T
for fast decompression, e.g. for faster load times.
ven uncompressed AssetBundles will be recompressed to LZ4 when downloaded and cached
E
via UnityWebRequestAssetBundle.
hese signatures use the cache. In the case a uint version is provided the cache generates the
T
equivalent Hash128 value for use in the cache.
hese signatures do not support caching, because they do not accept a Hash or version
T
argument:
nityWebRequest
U GetAssetBundle(string uri);
UnityWebRequest
GetAssetBundle(Uri uri);
UnityWebRequest
GetAssetBundle(string uri, uint crc);
UnityWebRequest
GetAssetBundle(Uri uri, uint crc);
s mentioned more in the “Platform Topics” section, some platforms do not have caching
A
enabled, so there is potentially significant memory usage when AssetBundles are loaded.
ote: As mentioned in the reference the full URL is not taken into account for the caching, only
N
the assetbundle name. This is different from general http caching, which would typically work
based on the full URL.
Important:Unity does not perform hash calculationson AssetBundles during the download and
load action. So providing a version hash argument is NOT a way to validate that the contents
match the expected hash. Instead the AssetBundle CRC is available for validation purposes.
The version hash is just an identifier to distinguish different builds of a particular AssetBundle
filename.
ne widely used hash is the “AssetFileHash” hash value generated by the build (as described in
O
an earlier section).
arning:This section describes how to use that hashas a version for the AssetBundle cache,
W
but it may be more prudent to use an alternative method (as described in more detail in the next
section). However the hash generated by the native AssetBundle API has flaws and sometimes
a new build of an AssetBundles could have the same hash as an older build with slightly
different content. See“What is the AssetBundle Hashgenerated by a build?”for more
information. The Multi-Process option introduced in 2023.1 does not have this problem, nor
does the Scriptable Build Pipeline, so it is safer to use hashes from those pipelines as a version
hash.
hen doing an AssetBundle build there are various output files, including .manifest files and the
W
Manifest AssetBundle. The hash values for each individual asset bundle is recorded in its
.manifest file.
For example:
anifestFileVersion: 0
M
CRC: 2210572199
Compression: None
Hashes:
AssetFileHash:
serializedVersion: 2
Hash:
ef053dac9adb3c2f517c180a27afc865
he UnityWebRequestAssetBundle gives users the ability to specify any value as the hash,
T
which is only used to check if an existing cached file matches the requested file. The file
downloaded from the provided URL is not automatically checked in any way to see if it matches
the specified file. (But the CRC, if provided, is checked).
● H ash of the CRC. The CRC itself can serve as a version identifier. Because it is
calculated based on the uncompressed content it is resilient to compression changes,
and it is based on the actual built content, so does not have the flaws of the Native
AssetBundle hash calculation.
● Content hash of the AssetBundle file (e.g. MD5) This is simple to generate and check.
But users need to be aware that LZMA AssetBundles are recompressed to LZ4 when
being added to the cache, which would change the content hash.
● AssetBundle hash + the CRC. Combining the CRC value with the hash value generated
by Unity would address the risk of hash collisions possible in native AssetBundle
heuristic.
● Hash of the .manifest file.
● A numeric versioning scheme, e.g. 1, 2, 3. 4…. , with the version updated each time the
content changes or for each new build (if caching is not important).
● etc.
The cache exists at a root cache folder, with the following structure:
RootCacheFolder
/ BundleName1
/Hash1
__data
__info
….
/BundleName2
…
T
● he hash is used to uniquely identify the assetbundle version.
● The __data file is the assetbundle content.
● The __info file records cache meta-data, e.g. last time the file was accessed.
uring a download, a temporary directory is used for the data as downloaded, then the
D
recompressed asset is established into the structure described above.
herefore unless UnityWebRequestAssetBundle and the caching support is used, it can be
T
better to build with LZ4 or uncompressed formats, so that the file can be loaded incrementally
and efficiently with [Link]. Alternatively the file can be built and distributed
in with LZMA format and then converted on the client device using
[Link] to mimic the behavior of the
UnityWebRequestAssetBundle.
ote: UnityWebRequestAssetBundle can be used to load local files (e.g. using an URL of the
N
form “[Link] If caching is used then the file will be automatically converted to LZ4
(if conversion is required) and saved in the cache. Then later requests will load the cached
version instead of the original file. This can be convenient for writing code that deals with local
files exactly the same as remotely hosted files, but the downside is that it ends up with two
copies of the file.
When running developer player builds you may see Asserts logged like:
ersistentManager: File
P
'archive:/CAB-f9439276f830d384c35f627354aecbef/CAB-f9439276f830d384c35
f627354aecbef' was expected to exist at absolute path
'archive:/CAB-f9439276f830d384c35f627354aecbef/CAB-f9439276f830d384c35
f627354aecbef'
his very technical message can be a sign that an attempt is being made to load an Asset is
T
being loaded which depends on an object inside another AssetBundle that wasn’t loaded. E.g.
it can be fixed by making sure the dependent AssetBundles have been loaded.
his is useful for testing as caching, compression conversion and other internal features can be
T
triggered, without the need for an actual webserver.
In all cases during read:The Persistent Manager loadseach object by reading from a
SerializedFile inside the AssetBundle. There is a special cache with pages of data from
SerializedFiles. It is shared between all loaded AssetBundles, called the
PooledFileCacherManager. Its default size is1MB(16 pages of 64KB), but an internal C# API
can be used to adjust it. This layer sits above the mounted Archive and uses an abstract
interfaceCacheReaderBase,so it is not involved withthe underlying file range mapping and
decompression that happens inArchiveStorageReader.
here are some additional supporting data structures for mounted Unity Archives and
T
AssetBundles that will use some modest memory. And the Assets and Scenes loaded from an
AssetBundle will use memory that is owned by the instantiated Unity objects.
verall the RAM usage for an AssetBundle can be small. But in some cases the entire
O
AssetBundle also gets copied into RAM, that is more of a concern and is covered in the next
section.
[Link]
C
d = true
nityWebRequestAssetBun D
U ownload AssetBundle ownload and stream to
D ownload and stream to
D
dle.
and stream to cache file, the cache file, no cache file, converting to
GetAssetBundle()
converting LZMA to LZ4 compression conversion LZ4
on the fly required
With Empty Cache: oad the file from the
L
● Version Argument Load the file from the oad the file from the
L cache
provided cache cache
● File not cached yet,
● [Link]
abled = true (this is
default)
nityWebRequestAssetBun R
U ead content directly from ead content directly from
R ead content directly from
R
dle.
cached LZ4 file cached LZ4 file cached LZ4 file
GetAssetBundle()
nityWebRequestAssetBun D
U ownload, Convert and ownload and stream to
D ownload and stream to
D
dle.
Open in-memory file memory-based temp file memory-based temp file,
GetAssetBundle()
(no conversion) (no conversion)
O
● pen source to check format, close again
● Convert to LZ4 as in-memory archive file (or uncompressed when
[Link] is false)
● Open the in-memory file
● When unloaded and no more readers: Delete in-memory file
nity also supports loading the asset by just its filename, or filename without extension. But
U
that is implemented by building extra string tables to support those lookups. In AssetBundles
with many loadable Assets these extra string tables can start to use some noticeable memory.
o avoid this it is best practice to always load Assets using the exact key and to turn off the
T
extra matching capability at build time:
B
● [Link]
● [Link]
In the runtime you can only load AssetBundles that have been built for the correct target. E.g.
Android can only load Android AssetBundles. That is because AssetBundles may contain
platform specific formats for and not work properly, possibly leading to hard to diagnose failures
such as missing textures. The loading prevention is primarily meant to help detect possible
problems in a user’s build and release pipeline, e.g. to make sure that content is not accidentally
being shipped or delivered to the wrong platform.
ote: in the case of audio assetbundle and other core formats this may be overly restrictive, but
N
it is not clear how software could judge if a particular bundle has contents that are safe between
platforms.
here is no check at load time to make sure that the subtarget matches between the player and
T
the assetbundle.
onsole CPUs take time to validate the CRC for a newly-opened bundle. Load times are very
C
important on consoles. On consoles with fast SSD storage, a CPU bound CRC check will slow
down loading.
ecause AssetBundles are typically included in the title installation on local storage, or
B
downloaded securely with DLC mechanisms, there is no need to perform CRC checks. The
only case where it can be prudent is when doing a UnityWebRequestAssetBundle download, at
the time of download and not repeated at each load.
n WebGL it was disabled in recent versions of Unity because there is a built-in download
O
cache that can be used to cache responses from requested URLs. The downside of this
approach is much more memory usage - AssetBundles are always copied fully into memory to
load them. If the file was compressed with LZMA this also includes an on-demand conversion
to LZ4 format.
uch of the details in this document about the content of AssetBundles remains true when
M
using Addressables. However the APIs/UI for building AssetBundles is different and a higher
level API is used to load the content of AssetBundles.
It produces the same file format as the “built-in” AssetBundle support, and offers an API similar
to [Link]() for developers who want to migrate their code to that
“pipeline”. But primary usage of the Scriptable Build Pipeline is from Addressables.
e recommend using the Build Settings UI to change targets, then build based on the current
W
target. The reason for this is because switching current build target results in a recompilation of
the Editor scripts to reflect the new platform and a reload of the domain. That is done because
the Unity Editor attempts to simulate the target platform as closely as possible. The problem is
that domain reloads only happen after the current script finishes running, so you cannot reload
the mono domain in the middle of a build script. On the other hand if you change the target in
the UI then the domain will reload as part of the UI refresh and be properly loaded when you
then launch a build asset bundles script.
ften this subtle difference doesn’t matter, for example callback code could check the target
O
dynamically instead of using platform conditional compilation. So scripts that build for many
targets from a single script may work properly.
In the case of command line builds there is a target argument available which should be used
so that Unity loads with the correct target that already matches the target passed to
[Link]. See also the note about command line builds in
[Link]
he AssetBundle object is rather low level, but is used by the Load code to determine what
T
assets are available inside the assetbundle, and how to load them.
It contains:
m
● _Name. Name of the AssetBundle.
● m_Container. List of all the “loadable” objects, e.g. Assets and “SubAssets”. Each entry
records:
○ the name of the asset. The name is typically the project relative path of the
original asset.
○ The root object itself (e.g. the LFID of another object within the same serialized
file, e.g. m_FileID=0)
○ A range of the preload table, specifying all the objects that need to be preloaded
when the root object is loaded.
○ Note: In the case of a scene bundle, the name is the Scenes path, and the root
object and preload table ranges are null.
● m_PreloadTable. A combined list of all objects needed by the entries in m_Container.
These can include objects outside of the SerializedFile (m_FileID > 0)
● m_IsStreamedSceneAssetBundle. Set to true if the AssetBundle contains scenes.
● m_MainAsset. Obsolete
● m_Dependencies
● …a few other flags and values
ut this section explains a bit of inner workings for how loading actually works and how you can
B
poke around a bit inside a Binary2Text output file.
s mentioned earlier, the SerializedFile inside a regular AssetBundle always has a single
A
AssetBundle object that can be examined if you dump out the file to text format.
hat object includes them_Containermap, which listsall the assets that can be loaded by
T
name and which object that corresponds to. For some assets there could be more than one
object that can be directly loaded (e.g. a “Main” object and additional “Visible” Objects)
or example in the case of an AssetBundle containing a single Mesh, “[Link]”, the container
F
section might look like this:
his means that you can load any of those three objects directly (without actually needing to
T
write code that does anything with those path values):
he SerializedFile also contains other objects, for example Shaders, MeshRenders etc. Those
T
are also available, but not directly from a LoadAsset<> call - instead you would reference them
from one of the main objects.
/ NULL!!!!
/
var renderer = [Link]<MeshRenderer>("[Link]");
/ This works
/
var gameObject = [Link]<GameObject>("[Link]");
var renderer = [Link]<MeshRenderer>();
o make loading within AssetBundles more efficient the build will calculate what objects are
T
needed to accompany each the root object for each loadable object. For example for a Prefab
the root is a GameObject and all the children GameObjects and components should also be
loaded together with it.
he information about what objects to load at the same time is recorded in the
T
PreloadData.m_Assets list. This is a flat listing of all the Object references needed by any of
the loadable root objects. The AssetBundle m_Containers structure (mentioned in the previous
section) will reference a range of indexes within the PreloadData array. For example if an
AssetBundle contained two Prefabs then the objects for one Prefab would be listed first, then all
the objects needed in the second Prefab would be listed next.
s an example, here is a binary2text dump from the sharedAssets file of a scene (which
A
references objects inside another scene’s sharedAsset file)
xternal References
E
path(1): "Resources/unity_builtin_extra" GUID:
0000000000000000f000000000000000 Type: 0
path(2): "Library/unity default resources" GUID:
0000000000000000e000000000000000 Type: 0
path(3):
"archive:/BuildPlayer-BakedLightsRealtime/[Link]
aredAssets" GUID: 00000000000000000000000000000000 Type: 0
m_Dependencies (vector)
size 0 (int)
m_ExplicitDataLayout 0 (bool)
he PreloadData list can reference objects that exist in the same Serialized File (m_FileID==0)
T
or other Serialized Files (m_FileID > 0). When it is another SerializedFile then it is either a file
inside another AssetBundle or a built-in object.
or Assets that contain SubAssets, then the same range of objects might be referenced multiple
F
times. For example for an FBX which can be loaded as a GameObject, Mesh or Material then
the objects created by the FBX importer will only appear once in the PreloadData and three
entries in the AssetBundle.m_Containers will reference the same range. Returning to the
example used in a previous section, notice how all three entries use range 0-7 of the preload
table.
The dependency between AssetBundles is visible inside the build folder .manifest file.
AssetBundleInfos:
Info_0:
Name: [Link]
Dependencies:
Dependency_0: [Link]
Info_1:
Name: [Link]
Dependencies: {}
ithin the SerializedFile text dumps it is possible to see the dependency at a much lower level,
W
down to the individual objects.
or example the dump for testbundle0 has an external reference listed at the top of the
F
Binary2Text dump:
External References
ath(1):
p
"archive:/CAB-772d3b012d144d3fced8206712901558/CAB-772d3b012d144d3fce
d8206712901558" GUID: 00000000000000000000000000000000 Type: 0
his asset bundle reference is at path “1”, and the serialized data of the Material references that
T
file in m_Shader.m_FileID.
he AssetBundle object also records this information in a centralized way that Unity’s load code
T
can use.
ID: 1 (ClassID: 142) AssetBundle
m_Name "[Link]" (string)
….
m_Container (map)
size 1 (int)
data (pair)
first "
assets/shadervariants/[Link]
"
(string)
second (AssetInfo)
preloadIndex 0 (int)
preloadSize 2 (int)
asset (PPtr<Object>)
m_FileID 0 (int)
m_PathID
7395359030061443441
(SInt64)
he above data structure records that, when a load of “[Link]” is requested, then the
T
object 7395359030061443441 should be created.
m_PreloadTable (vector)
size 2 (int)
data (PPtr<Object>)
m_FileID 1 (int)
m_PathID 8028051458226250111 (SInt64)
data (PPtr<Object>)
m_FileID 0 (int)
m_PathID 7395359030061443441 (SInt64)
he preload information says that the external object 8028051458226250111 is needed. Using
T
that extra info Unity would be able to also load the shader as the material is required.
In practice the m_PreloadTable and m_Container can be very large, because there can be
many assets and many associated objects that need to be loaded together.
hen building AssetBundles the MonoScript objects must be included in the generated
W
SerializedFiles. This is where the actual assembly, namespace and class name are recorded.
onoScripts are lightweight objects. Sometimes the same MonoScript may be duplicated in
M
different AssetBundles, or they may be shared by reference from one AssetBundle to another.
For example if the MonoBehaviour references a MonoScript in another AssetBundle then the
reference records the SerializedFile inside another AssetBundle as well as the LFID for the
MonoScript object in that file.
he behavior for whether MonoScripts are duplicated or shared between bundles varies
T
depending on the build pipeline implementation. The Scriptable Build Pipeline (and
Addressables) supports an optional feature to create a special AssetBundle with all the
MonoScripts accumulated in a single dedicated bundle.
lass SerializationUtility
c
{
public static string ReadNullTerminatedString(BinaryReader r)
{
StringBuilder builder = new StringBuilder();
while (true)
{
byte b = [Link]();
if (b == 0)
break;
[Link]((char)b);
}
return [Link]();
}
public static int ReadInt32BigEndian(BinaryReader r)
{
byte[] b = [Link](4);
[Link](b);
return [Link].ToInt32(b);
}
public static long ReadInt64BigEndian(BinaryReader r)
{
byte[] b = [Link](8);
[Link](b);
return [Link].ToInt64(b);
}
}