Handling dependencies between AssetBundles
Manage dependencies between AssetBundles.
読み終わるまでの所要時間 6 分最終更新 1ヶ月前
AssetBundles become dependent on others when their objects reference objects in another AssetBundle. If a referenced object isn't assigned to any AssetBundle, Unity embeds it in the dependent AssetBundles during the build. If multiple AssetBundles reference the same unassigned object, each AssetBundle contains its own copy, increasing memory usage.
To load dependent AssetBundles, ensure dependencies are loaded into memory before accessing their dependent AssetBundles. For example, if Bundle 1 contains a material that reference a texture in Bundle 2, load Bundle 2 into memory before accessing the material from Bundle 1. Unity doesn't automatically resolve dependencies. To manage dependencies at runtime, you can use the . For more information, refer to Loading assets from AssetBundles.
AssetBundleManifestHow Unity tracks references
Unity tracks references between AssetBundles in the following ways:
-
Manifest-level dependencies: Dependencies between AssetBundles are in the build folder's rootfile and accessible via
.manifest.AssetBundleManifest.GetAllDependencies -
Low-level SerializedFile references: Direct object references between AssetBundles appear intext dumps as external references. These low-level references don't record the name of the AssetBundle and only record the names of the serialized files inside the AssetBundles. They only work when the corresponding AssetBundles have already been loaded. For example:
SerializedFile- A in
Materialreferences abundle0inShader.bundle1 - The inside
SerializedFilehas an external reference table in its header. This table has an entry that points to thebundle0path ofSerializedFile.bundle1 - The of the
m_Shader.m_FileIDobject records the index of external reference table corresponding to the SerializedFile insideMaterial.bundle1
- A
-
AssetBundle object: Theobject consolidates dependency information in its
AssetBundleandm_Container. When a load is requested for an asset, them_PreloadTableensures all necessary objects, including those from other AssetBundles, are identified for loading. This can involve very large data structures for AssetBundles with many assets and dependencies.m_PreloadTable
Script type representation in AssetBundles
The object in Unity represents a specific -derived class. This also covers classes derived from ScriptableObject. The object records the assembly, namespace and class as strings that uniquely identify the type.
MonoScriptMonoBehaviourMonoScriptWhen Unity serializes a object it records the GUID and LFID of the class, which then directly records the name of the class.
MonoBehaviourMonoScriptWhen building AssetBundles Unity includes objects for each MonoBehaviour-derived class that's part of the build. These objects might be inside the same SerializedFile as the MonoBehavior (a local reference), or in an external Serialized file. In both cases MonoScripts are referenced with exactly the same mechanism as other direct references between objects.
MonoScriptMonoScriptThe following actions can result in changes to the MonoScript data:
- Moving the script file to a location that comes under another assembly definition file.
- Changing the name of the assembly definition file containing the class.
- Adding or changing the class namespace.
- Changing the class name.
When those changes happen, rebuild the AssetBundles in your project.
Avoid duplicated information across AssetBundles
By default, Unity doesn't optimize duplicate data across AssetBundles. For example, if two AssetBundles each contain a prefab referencing the same unassigned material, Unity embeds a copy of the material in both AssetBundles. This increases install size, runtime memory usage, and affects batching, because Unity treats each copy as unique.
Assign shared assets to a common AssetBundle to avoid duplication. During the build, Unity automatically includes dependencies in the assigned AssetBundle. This significantly reduces the size of other AssetBundles. For example:
- Extract a shared Material and its dependencies into a AssetBundle.
modulesmaterials - Prefab AssetBundles then only reference the AssetBundle, reducing their size.
modulesmaterials
For more information, refer to Avoiding asset duplication.
Runtime loading
When using a common AssetBundle for shared assets, load it into memory before loading AssetBundles that depend on it. In the following example, the shared Material is loaded correctly because its AssetBundle () is loaded first:
materialsABusing System.IO; using UnityEngine; public class InstantiateAssetBundles : MonoBehaviour { void Start() { // Load the AssetBundles AssetBundle materialsAB = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, Path.Combine("AssetBundles", "modulesmaterials"))); AssetBundle moduleAB = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, Path.Combine("AssetBundles", "example-prefab"))); // Check for errors if (materialsAB == null || moduleAB == null) { Debug.Log("Failed to load AssetBundle!"); return; } GameObject prefab = moduleAB.LoadAsset<GameObject>("example-prefab"); // Instantiate the prefab Instantiate(prefab); } }
AssetBundle unloading
Properly manage dependencies when unloading AssetBundles to prevent crashes or undefined behavior. Dependent AssetBundles must not remain loaded after their dependencies are unloaded. Reloading dependencies separately can also cause issues. When an AssetBundle is loaded, it establishes data structures that specifically point to objects inside dependent AssetBundles. If a referenced AssetBundle is unloaded and then reloaded, its objects are assigned new InstanceIDs and are not reconnected to the dependent AssetBundle, which can lead to crashes or serialization errors.
To avoid this, track dependencies and never unload an AssetBundle if it is referenced by another AssetBundle, unless you also unload that referencing AssetBundle. Implementing a reference-counting system is a safe way to manage AssetBundle unloading.
Example unload strategy: reference counting
Implement a reference-counting system to track and safely unload AssetBundles only when they're no longer in use.
The following example tracks dependencies and safely unloads unused AssetBundles:
using System.Collections.Generic; using System.IO; using UnityEngine; public class AssetBundleManager { // Path to the directory containing AssetBundles private string assetBundlesDirectory; // The AssetBundleManifest containing dependency information private AssetBundleManifest assetBundleManifest; // Reference counts for loaded AssetBundles private Dictionary<string, int> assetBundleReferenceCounts = new Dictionary<string, int>(); // Loaded AssetBundles cache private Dictionary<string, AssetBundle> loadedAssetBundles = new Dictionary<string, AssetBundle>(); // Initializes the AssetBundleManager with the manifest and directory path public void Initialize(string manifestBundlePath, string assetBundlesDirectory) { this.assetBundlesDirectory = assetBundlesDirectory; AssetBundle manifestBundle = AssetBundle.LoadFromFile(manifestBundlePath); assetBundleManifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); manifestBundle.Unload(false); } // Loads an AssetBundle and its dependencies, incrementing reference counts public AssetBundle LoadBundle(string bundlePath) { AssetBundle bundle = LoadAssetBundleIfNotLoaded(bundlePath); IncrementReferenceCount(bundle.name); string[] dependencyBundleNames = assetBundleManifest.GetAllDependencies(bundle.name); foreach (string dependency in dependencyBundleNames) { string dependencyBundlePath = GetAssetBundlePathFromName(dependency); LoadAssetBundleIfNotLoaded(dependencyBundlePath); IncrementReferenceCount(dependency); } return bundle; } // Loads an AssetBundle if it is not already loaded private AssetBundle LoadAssetBundleIfNotLoaded(string bundlePath) { if (!loadedAssetBundles.TryGetValue(bundlePath, out AssetBundle bundle)) { // For simplicity, this example only shows the case of synchronous loading, but support for // LoadFromFileAsync() and the other load methods could also be added with similar code. bundle = AssetBundle.LoadFromFile(bundlePath); if (bundle == null) { throw new System.Exception($"Failed to load AssetBundle at path {bundlePath}"); } loadedAssetBundles.Add(bundlePath, bundle); } return bundle; } // Unloads an AssetBundle and its dependencies if their reference counts reach zero public void UnloadBundle(AssetBundle bundle) { string[] dependencyBundleNames = assetBundleManifest.GetAllDependencies(bundle.name); DecrementReferenceCount(bundle.name); foreach (string dependency in dependencyBundleNames) { DecrementReferenceCount(dependency); } List<string> bundlesToUnload = new List<string>(); foreach (KeyValuePair<string, AssetBundle> loadedBundleEntry in loadedAssetBundles) { if (assetBundleReferenceCounts[loadedBundleEntry.Value.name] <= 0) { bundlesToUnload.Add(loadedBundleEntry.Key); } } foreach (string bundlePath in bundlesToUnload) { loadedAssetBundles[bundlePath].Unload(true); loadedAssetBundles.Remove(bundlePath); } } // Gets the full path of an AssetBundle given its name private string GetAssetBundlePathFromName(string name) { return Path.Combine(assetBundlesDirectory, name); } // Increments the reference count for a given AssetBundle private void IncrementReferenceCount(string bundleName) { if (assetBundleReferenceCounts.ContainsKey(bundleName)) { assetBundleReferenceCounts[bundleName]++; } else { assetBundleReferenceCounts[bundleName] = 1; } } // Decrements the reference count for a given AssetBundle private void DecrementReferenceCount(string bundleName) { if (assetBundleReferenceCounts.ContainsKey(bundleName)) { assetBundleReferenceCounts[bundleName]--; } else { string errorMessage = $"Attempted to decrement reference count for non-existent bundle: {bundleName}"; throw new KeyNotFoundException(errorMessage); } } }