Почему ссылки на вложенные ресурсы в ресурсе ScriptableObject не загружаются?

Пожалуйста, прочитайте весь вопрос и запустите пример, прежде чем отправлять ответ.


обзор

Я столкнулся с некоторым противоречивым поведением в Unity 5.6.1 при загрузке вложенных ресурсов в статический скрипт редактора (например, в статическом конструкторе класса, отмеченного [InitializeOnLoad]).

Я загружаю ScriptableObject Актив с Resources.Load и ScriptableObject имеет общедоступную ссылку на другой ресурс, предположим, префаб GameObject. С этого момента я буду называть объект ScriptableObject "Оберткой", поскольку в этом упрощенном примере это единственная цель, которой он служит.

В то время как Resources.Load правильно возвращает Wrapper, вложенная ссылка Prefab часто еще не загружается во время первого запуска, но загружается после второго запуска:

Снимок экрана, показывающий, что префаб не загружается при первом запуске, но находится на втором

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

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

Доказательство того, что актив Wrapper действительно правильно ссылается на Префаб в его сериализованных данных (с Сериализацией Актива, установленной на Принудительный Текст): Доказательство того, что сборная ссылка правильно сериализована

Я также пытался использовать AssetDAtabase.LoadAssetAtPath (по крайней мере, пока в редакторе), что не имеет значения.


Пример проекта

Вы можете скачать UnityPackage здесь, который содержит следующее:

Или воспроизведите это следующим образом:

  • Скрипты:

    ExampleWrapper.cs:

    using UnityEngine;
    public class ExampleWrapper : ScriptableObject
    {
      public GameObject Value;
    }
    

    StaticLoader.cs:

    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    [InitializeOnLoad]
    #endif
    public class Loader
    {
      static Loader()
      {
        var Wrapper = Resources.Load<ExampleWrapper>("Wrapper");
        Debug.Log(Wrapper);         // Prints the Wrapper ScriptableObject
        Debug.Log(Wrapper.Value);   // Prints the Wrapped GameObject
      }
    }
    
  • Создайте пустой GameObject "ExampleObject" в иерархии, а затем сохраните его в качестве префаба в Assets/Resources/ExampleObject.prefab

  • Создайте экземпляр актива ExampleWrapper и в Assets/Resources/Wrapper.asset

    • Поскольку Unity 5 не предоставляет пользовательский интерфейс для порождения объектов ScriptableObjects, либо создайте свой собственный элемент меню, либо используйте автоматизированное решение. Этот вопрос предполагает, что вы достаточно знакомы с ScriptableObjects, чтобы иметь свой собственный предпочтительный метод.
  • Установить актив Обертки Value Поле для префаба ExampleObject

  • Обратите внимание, что, поскольку иногда единица правильно кэширует актив,


обоснование

Приведенный здесь пример намеренно упрощен, но основан на реальных проектах, использующих ScriptableObjects для хранения / совместного использования данных конфигурации для пользовательских систем.

Пожалуйста , не отвечайте со следующим:

  • "Просто используйте Object.Instantiate " - не меняет результат, а модифицирует необработанный объект, возвращаемый Resources.Load желательно в некоторых случаях.
  • "Пропустите упаковщик и просто обратитесь к сборному напрямую / загрузите его вручную" - хотя это позволяет обойти проблему загрузки, оно также упускает суть вопроса. Добавление уровня абстракции может сделать совместное использование ресурсов между системами более легким в обслуживании. Также эта проблема не ограничивается префабами (просто используется здесь для простого примера). Более реалистичные примеры будут содержать несколько вложенных объектов, таких как спрайты, материалы, другие объекты ScriptableObject и т. Д.
  • "Не загружать во время статического построения" - статические системы поддерживаются Unity (почему иначе [InitializeOnLoad]?) и использование ресурсов на основе ScriptableObjects для хранения конфигурационной информации для таких систем является очень реальным вариантом использования. Прежде чем полностью перестроить системы, я хотел бы взглянуть на другие потенциальные альтернативы.

Что я ищу:

  • Могу ли я заставить Unity предварительно загружать актив, сериализованный в оболочку, когда я загружаю его в статическом контексте, подобном этому, без необходимости вручную загружать его содержимое по его пути?
  • Другими словами, я не хочу просто бежать Resources.Load<GameObject>("ExampleObject"), так как это сведет на нет весь смысл инкапсуляции в первую очередь. Я в порядке с изменением ExampleWrapper класса, но любое потенциальное решение должно быть достаточно автоматизировано, чтобы рабочий процесс добавления префаба к полю в инспекторе был всем, что нужно.

РЕДАКТИРОВАТЬ: Следует также отметить, что довольно странно, когда я закрываю проект и открываю его снова, я вижу следующее:

  • Во время запуска статический конструктор вызывается один раз, Wrapper загружается, и вложенный префаб фактически загружается правильно.
  • Затем (все еще в начальном запуске, поскольку это происходит до того, как я смогу ввести какие-либо действия), он снова статически создается, и на этот раз, когда загружается оболочка, вложенный префаб не загружается.

Это я действительно не понимаю.

1 ответ

Решение

Есть неправильное представление о вашем вопросе.

Ссылка передается на Loader класс, и вы можете проверить это, войдя Wrapper.Value после завершения инициализации сцены.

Скорее всего, проблема (как вы указали) в порядке выполнения / сериализации, по-видимому, это происходит примерно так:

  • Loader конструктор называется, и Wrapper ссылка передана правильно.
  • Debug.Log(Wrapper.Value) возвращается null потому что поля объекта сценария еще не были сериализованы
  • Wrapper поля сериализуются, теперь ведется логирование Wrapper.Value показывает правильно ExampleObject,

Так что, если вы не планируете что-то "особенное" делать с Wrapper полей во время инициализации, в вашем коде действительно нет проблем: я пытался запустить Debug.Log(Loader.Wrapper.Value) в течение OnEnable из ExampleWrapper и я получил правильное значение.

Что касается ваших правок, то, очевидно, это происходит "по замыслу", как четко указано в этом выпуске: https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens

Другие вопросы по тегам