包含此页的版本:
不含此页的版本:
当资产包的对象引用另一个包中的对象时,资产包就会依赖于其他对象。如果引用的对象未分配给任何 AssetBundle,Unity 会在构建期间将其复制并嵌入到依赖的 Bundle 中。如果多个捆绑包引用同一个未分配的对象,则每个捆绑包包含自己的副本,从而增加内存使用量。
要加载依赖的 AssetBundle,请确保在访问依赖 Bundle 之前将依赖项加载到内存中。例如,如果 Bundle 1 包含引用 Bundle 2 中纹理的材质,请先将 Bundle 2 加载到内存中,然后再从 Bundle 1 访问材质。Unity 不会自动解析依赖项。要在运行时管理依赖项,您可以使用AssetBundleManifest.有关更多信息,请参阅从AssetBundle加载资产。
默认情况下,Unity 不会优化 AssetBundle 中的重复数据。例如,如果两个 AssetBundle 每个 AssetBundle 都包含预制件:一种资产类型,允许您存储包含组件和属性的游戏对象。预制件充当模板,你可以从中在场景中创建新的对象实例。更多信息
请参阅术语表引用相同的未分配材质时,Unity 会在两个捆绑包中嵌入该材质的副本。这会增加安装大小、运行时内存使用量并影响批处理,因为 Unity 将每个副本视为唯一。
将共享资产分配给公共资产包以避免重复。在构建过程中,Unity 会自动在分配的 AssetBundle 中包含依赖项。这大大减小了其他资产包的大小。例如:
modulesmaterialsAssetBundle 中。modulesmaterialsAssetBundle,减小其大小。将通用AssetBundle用于共享资产时,请先将其加载到内存中,然后再加载依赖于它的AssetBundle。在以下示例中,共享材质被正确加载,因为其资产包(materialsAB) 首先加载:
using System.IO;
using UnityEngine;
public class InstantiateAssetBundles : MonoBehaviour
{
void Start()
{
var materialsAB = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, Path.Combine("AssetBundles", "modulesmaterials")));
var moduleAB = AssetBundle.LoadFromFile(Path.Combine(Application.dataPath, Path.Combine("AssetBundles", "example-prefab")));
if (materialsAB == null || moduleAB == null)
{
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = moduleAB.LoadAsset<GameObject>("example-prefab");
Instantiate(prefab);
}
}
卸载 AssetBundle 时正确管理依赖项,以防止崩溃或未定义的行为。卸载依赖项资产包后,依赖资产包不得保持加载状态。单独重新加载依赖项也可能导致问题。
实现引用计数系统,仅在资产包不再使用时跟踪并安全卸载它们。
以下示例跟踪依赖项并安全地卸载未使用的 AssetBundle:
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class AssetBundleManager
{
private string assetBundlesDirectory;
private AssetBundleManifest assetBundleManifest;
private Dictionary<string, int> assetBundleReferenceCounts = new Dictionary<string, int>();
private Dictionary<string, AssetBundle> loadedAssetBundles = new Dictionary<string, AssetBundle>();
public void Initialize(string manifestBundlePath, string assetBundlesDirectory)
{
this.assetBundlesDirectory = assetBundlesDirectory;
AssetBundle manifestBundle = AssetBundle.LoadFromFile(manifestBundlePath);
assetBundleManifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
manifestBundle.Unload(false);
}
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;
}
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;
}
public void UnloadBundle(AssetBundle bundle)
{
string[] dependencyBundleNames = assetBundleManifest.GetAllDependencies(bundle.name);
DecrementRefCount(bundle.name);
foreach (string dependency in dependencyBundleNames)
{
DecrementRefCount(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);
}
}
private string getAssetBundlePathFromName(string name)
{
return Path.Combine(assetBundlesDirectory, name);
}
private void IncrementReferenceCount(string bundleName)
{
if (assetBundleReferenceCounts.ContainsKey(bundleName))
{
assetBundleReferenceCounts[bundleName]++;
}
else
{
assetBundleReferenceCounts[bundleName] = 1;
}
}
private void DecrementRefCount(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);
}
}
}
注意:当使用 LZ4 压缩和未压缩的 AssetBundles 时,AssetBundle.LoadFromFile仅在内存中加载其内容的目录,而不加载内容本身。要检查是否发生这种情况,请使用内存分析器包检查内存使用情况。