Unity 6.3
0 онлайн 106 гостей 3 в системе
Вход
Ассеты и медиа Шаг 94 из 148

Обработка зависимостей между AssetBundles

AssetBundles становятся зависимыми от других, когда их объекты ссылаются на объекты в другом AssetBundle. Если объект, на который сделана ссылка, не назначен ни одному AssetBundle, Unity встроит его в зависимый AssetBundles во время сборки. Если несколько AssetBundles ссылаются на один и тот же неназначенный объект, каждый AssetBundle содержит свою собственную копию, увеличивая использование памяти.

Чтобы загрузить зависимый AssetBundles, убедитесь, что зависимости загружены в память до доступа к их зависимому AssetBundles. Например, если Bundle 1 содержит материал, который ссылается на текстуру в Bundle 2, загрузите Bundle 2 в память до доступа к материалу из Bundle 1. Unity не разрешает зависимости автоматически. Для управления зависимостями во время выполнения вы можете использовать AssetBundleManifest. Дополнительные сведения см. в Загрузка ассетов из AssetBundles.

Как Unity отслеживает ссылки

Unity отслеживает ссылки между AssetBundles следующим образом:

  • Зависимости на уровне манифеста: Зависимости между AssetBundles находятся в корневом файле .manifest папки сборки и доступны через AssetBundleManifest.GetAllDependencies.
  • Ссылки низкого уровня SerializedFile: Прямые ссылки на объекты между AssetBundles появляются в SerializedFile Эти низкоуровневые ссылки не записывают имя файла AssetBundle, а только имена серийных файлов внутри AssetBundles. Они работают только тогда, когда соответствующий файл AssetBundles уже загружен. Например:
    • Material в bundle0 ссылается на Shader в bundle1.
    • Файл SerializedFile внутри bundle0 содержит в заголовке таблицу внешних ссылок. В этой таблице есть запись, указывающая на путь SerializedFile в bundle1.
    • m_Shader.m_FileID объекта Material записывает индекс внешней таблицы ссылок, соответствующей SerializedFile внутри bundle1.
  • AssetBundle объект: Объект AssetBundle консолидирует информацию о зависимостях в своих m_Container и m_PreloadTable. Когда запрашивается загрузка ассета, m_PreloadTable обеспечивает идентификацию для загрузки всех необходимых объектов, включая объекты из других AssetBundles. Это может включать очень большие структуры данных для AssetBundles со многими ассетами и зависимостями.

Представление типа скрипта в AssetBundles

MonoScript объект в Unity представляет собой MonoBehaviour-производный класс. Это касается и классов, производных от ScriptableObject. MonoScript объект записывает сборку, пространство имён и класс в виде строк, однозначно определяющих тип.

Когда Unity сериализирует MonoBehaviour объекта, он регистрирует GUID и LFID из MonoScript класс, который затем напрямую записывает имя класса.

При строительстве AssetBundles Unity включает MonoScript объекты для каждого класса, производного от MonoBehaviour, который входит в сборку. Эти MonoScript объекты могут находиться внутри того же SerializedFile, что и MonoBehavior (локальная ссылка), или во внешнем Сериализованном файле. В обоих случаях MonoScripts ссылается точно таким же механизмом, как и другие объекты. прямые ссылки между объектами.

Следующие действия могут привести к изменению данных MonoScript:

  • Перемещение файла скрипта в расположение, которое находится под другим файл определения сборки.
  • Изменение имени файла определения сборки, содержащего класс.
  • Добавление или изменение пространства имён класса.
  • Изменение имени класса.

Когда эти изменения произойдут, перестроите AssetBundles в вашем проекте.

Избегайте дублирования информации в AssetBundles

По умолчанию Unity не оптимизирует дубликаты данных в AssetBundles. Например, если два AssetBundles содержат префаб, ссылающийся на один и тот же неназначенный материал, Unity встраивает копию материала в оба AssetBundles. Это увеличивает размер установки, использование памяти выполнения и влияет на пакетирование, поскольку Unity рассматривает каждую копию как уникальную.

Назначайте общие ресурсы общему AssetBundle, чтобы избежать дублирования. Во время сборки Unity автоматически включает зависимости в назначенный AssetBundle. Это значительно уменьшает размер других AssetBundles. Например:

  • Извлечение общего Материала и его зависимостей в modulesmaterials AssetBundle.
  • Префаб AssetBundles затем только ссылаются на modulesmaterials AssetBundle, уменьшая их размер.

Дополнительные сведения см. в Избежание дублирования активов.

Загрузка в режиме выполнения

При использовании общего AssetBundle для общих ассетов загрузите его в память до загрузки AssetBundles, которые зависят от него. В следующем примере общий Материал загружается правильно, потому что его AssetBundle (materialsAB) загружается первым:

using 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 разгрузка

Правильно управляйте зависимостями при разгрузке AssetBundles, чтобы предотвратить сбои или неопределенное поведение. Зависящий AssetBundles не должен оставаться загруженным после разгрузки его зависимостей. Перезагрузка зависимостей отдельно также может вызвать проблемы. Когда загружается AssetBundle, он создает структуры данных, которые конкретно указывают на объекты внутри зависимого AssetBundles. Если ссылка на AssetBundle разгружается и затем перегружается, его объекты назначаются новому InstanceIDs и не подключаются к зависимому AssetBundle, что может привести к сбоям или ошибкам сериализации.

Чтобы избежать этого, следите за зависимостями и никогда не выгружайте AssetBundle, если на него ссылается другой AssetBundle, если только вы также не выгружаете ссылающийся AssetBundle. Реализация системы подсчета ссылок является безопасным способом управления выгрузкой AssetBundle.

Пример стратегии разгрузки: подсчет ссылки

Внедрить систему подсчета ссылок для отслеживания и безопасной разгрузки AssetBundles только тогда, когда они больше не используются.

Следующий пример отслеживает зависимости и безопасно выгружает неиспользуемый 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);
        }
    }
}

Примечание: При использовании LZ4 сжатых и несжатых AssetBundles, AssetBundle.LoadFromFile только загружает каталог содержимого в память, но не само содержимое. Чтобы проверить, происходит ли это, используйте пакет Память Profiler для проверки использования памяти.

Дополнительные ресурсы