SerializeReference Improves в Unity 2021 LTS

В последнем выпуске LTS полиморфная сериализация обеспечивает улучшенную совместную работу пользователей и доступ к API, а также более гибкую обработку отсутствующих типов.
С помощью атрибута SerializeReference вы можете сериализовать объект, назначенный полю в качестве эталона, вместо того, чтобы сериализовать его по значению. Ссылаемые объекты называются «управляемыми ссылками». Поля с атрибутом SerializeReference поддерживают полиморфизм и null значения — совсем недавно в Unity 2021 LTS были представлены стабильные идентификаторы управляемых эталонов, которые обеспечивают гранулированную обработку отсутствующих типов и улучшенный доступ к API. В этом блоге мы делимся подробностями об этих изменениях и о том, чем они могут быть полезны вам напрямую.
Управляемые ссылки сохраняются в данных сериализации их хоста, где хостер является объектом Unity (например, класс, производный от MonoBehaviour или ScriptableObject). Для этого мы назначаем уникальный идентификатор каждому управляемому опорному объекту.
Поле с атрибутом SerializeReference сериализуется путем сохранения идентификатора объекта, на который делается ссылка. Сами управляемые ссылки сохраняются в списке ManagedReferenceRegistry, который входит в сериализуемые данные хоста.
В Unity 2019 и 2020 LTS идентификаторы назначались во время сохранения посредством прохождения контента с учетом первой глубины. Главный недостаток этого подхода заключается в том, что небольшие действия, например изменение порядка элементов массива, могут привести к значительному изменению затрагиваемого файла. Значительные изменения в файлах могут привести к конфликтам слияния, которые сложно разрешить в среде совместной работы.
Именно поэтому мы представили Stable ID. Стабильный идентификатор гарантирует, что после назначения объекту собственного уникального идентификатора тот же идентификатор будет сохраняться в ходе последовательных циклов сохранения и загрузки. Иными словами, изменение назначения полей управляемых ссылок на хосте с последующим повторным сохранением не изменит идентификатор.
Для иллюстрации ценности Stable ID рассмотрим следующий пример:

Этот пример создает массив управляемых эталонных объектов, заполненных переплетающимися экземплярами класса Sandwich and Fruit. Содержимое массива можно просмотреть в файле LunchBox1.asset.

Перемещая первую запись в конец списка, вы получите изменение файла ассета, лежащего в основе файла. Следующие скриншоты из инструмента diff показывают, насколько прост diff из Unity 2021.3, поскольку объекты в массиве теперь имеют идентификаторы, не зависящие от порядка массива.
2020.3:

2021.3:

Помимо сокращения изменений в файлах Unity функция Stable ID предназначена для решения распространенных задач совместной работы. В предыдущих версиях у двух пользователей, добавлявших управляемые базовые объекты на одном хосте, оставался один и тот же идентификатор, что затрудняло слияние (особенно учитывая, что на один управляемый базовый объект может ссылаться несколько полей). Начиная с Unity 2021 идентификаторы теперь практически гарантированно избегают такого конфликта, ведь они генерируются на основе хэша времени и системной информации. Для более сложных сценариев можно даже переопределить стандартную систему назначения идентификаторов, вызвав SerializationUtility.SetManagedReferenceIdForObject.
SerializeReference поддерживает полиморфизм, что означает, что поле может быть назначено экземпляру класса, происходящего из типа поля. Более того, мы поддерживаем тип поля как System.Object, который является корневым базовым классом каждого класса C#. Но это открывает возможность того, что успешно скомпилированный проект не будет соответствовать определениям классов, которые были доступны ранее и сохранены в сцену или файл ассета. В некоторых случаях классы могут пропадать при удалении исходных файлов, переименовании классов или перемещении классов в другую сборку.
При загрузке SerializedReference host объект проверяет полное имя типа каждого управляемого Reference объекта, и для его мгновенной установки ему нужно вернуть действительный тип класса. В предыдущих версиях Unity отсутствующие классы могли перевести весь «хост» объект в состояние ошибки без загрузки любого из действительных управляемых эталонных объектов. Если бы у вас был хост с массивом из 15 управляемых объектов-ссылок, но один объект не удалось бы разрешить, то вы бы не увидели ни одного из них в Inspector. В консоли регистрировалась ошибка, даже несмотря на то, что объект хоста не был визуально отмечен как находящийся в состоянии ошибки при проверке, и все сделанные изменения молча отбрасывались.
В Unity 2021 мы теперь мгновенно создаем все загружаемые управляемые референс-объекты и заменяем отсутствующие на null. Это позволяет пользователям лучше видеть состояние хост-объекта и упрощает устранение отсутствующих типов. Между тем, если отсутствующий тип будет восстановлен во время загрузки объекта хоста, то запущенная перезагрузка домена восстановит управляемые ссылочные объекты, а все поля, в которых он будет восстановлен, будут обновлены надлежащим образом.
Вот пример появления объектов с отсутствующими типами в Inspector:
В Unity 2020.3 класс Fruit отсутствует, но в окне Inspector нет элементов массива, а также нет указаний на ошибку:
В Unity 2021.3 инспектор предупреждает вас, что недостающие объекты Fruit отображаются как null entrys, в то время как объекты Sandwich продолжают отображаться:
Сообщения об ошибках в консоли, связанные с отсутствующими типами, также были обновлены, чтобы они не повторялись — они просто определяют, какие хост-объекты имеют отсутствующие типы.
Вот сообщение об ошибке в Unity 2020.3:
Сравните это с предупреждающим сообщением в Unity 2021.3:
Эти улучшения идентификаторов, а также изменения, внесенные в управляемые ссылочные объекты в префабах, теперь привязаны к управляемым ссылочным объектам. Раньше модификации свойств ориентировались на поля по первому пути свойств, ведущему к этому полю. Это означало, что при изменении пути (например, путем изменения порядка массива) система PropertyModification потеряет отслеживаемость требуемой управляемой ссылки и не сможет должным образом решить эту проблему. Начиная с Unity 2021, существуют системы PropertyModification, которые используют управляемые объекты-ссылки, используя путь, включающий Stable ID, то есть managedReferences[12345].myString. Это гарантирует сохранение переопределенных значений управляемого эталонного объекта независимо от его перемещения на хосте.
В Unity API добавлен новый класс SerializationUtility, который раскрывает функции SerializeReference. Например, SerializationUtility.ClearAllManagedReferencesWithMissingTypes() можно использовать для удаления ссылок на отсутствующие типы, например, удаление состояния предупреждения с хоста, если восстановление отсутствующего типа не планируется.
Мы улучшили API для работы с управляемыми ссылками в контексте CustomEditors, включая возможность доступа к SerializedProperty.managedReferenceValue для чтения.
Справочник по новым методам также содержит пример кода, и мы добавили более подробную информацию по темам, связанным с сериализацией.
Текущие проекты, использующие SerializeReference, должны плавно загружаться в новой версии Unity, так как код сериализации совместим со старым управляемым форматом. Часто использование SerializeReference не требует глубокого знания концепции Stable ID или новых методов API. Однако даже если оставить их «под капотом», эти улучшения полезны для типичного использования, особенно в среде совместной работы.
Надеемся, что эта статья подтолкнет вас к дальнейшему изучению функции. Поскольку наша команда сериализаторов продолжает расширять возможности для всех пользователей Unity, мы признательны вам за ваши отзывы и обсуждения по этой теме форума.