Почему ссылки на вложенные ресурсы в ресурсе 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