Unity serializefield что это
Script Serialization
Serialization of “things” is at the very core of Unity. Many of our features build ontop of the serialization system:
Inspector window. The inspector window doesn’t talk to the C# api to figure out what the values of the properties of whatever it is inspecting is. It asks the object to serialize itself, and then displays the serialized data.
Prefabs. Internally, a prefab is the serialized data stream of one (or more) game objects and components. A prefab instance is a list of modifications that should be made on the serialized data for this instance. The concept prefab actually only exists at editor time. The prefab modifications get baked into a normal serialization stream when Unity makes a build, and when that gets instantiated, the instantiated gameobjects have no idea they were a prefab when they lived in the editor.
Loading. Might not seem surprising, but backwards compatible loading is a system that is built on top of serialization as well. In-editor yaml loading uses the serialization system, but also the runtime loading of scenes, assets and assetbundles uses the serialization system.
Now you’d say that none of this very much concerns you, you’re just happy that it works and want to get on with actually creating some content.
Where it will concern you is that we use this same serializer to serialize MonoBehaviour components, which are backed by your scripts. Because of the very high performance requirements that the serializer has, it does not in all cases behave exactly like what a c# developer would expect from a serializer. In this part of the docs we’ll describe how the serializer works, and some best practices on how to make best use of it.
What does a field of my script need to be in order to be serialized?
Which fieldtypes can we serialize?
What are these situations where the serializer behaves differently from what I expect?
Custom classes behave like structs
If you populate the animals array with three references to a single Animal object, in the serialization stream, you will find 3 objects. when it’s deserialized, there are now three different objects. If you need to serialize a complex object graph with references, you cannot rely on Unity’s serializer doing that all automagically for you, and have to do some work to get that object graph serialized yourself. See the example below on how to serialize things Unity doesn’t serialize by itself.
No support for null for custom classes
Pop quiz. How many allocations are made when deserializing a MonoBehaviour that uses this script:
It wouldn’t be strange to expect 1 allocation. That of the Test object. It also wouldn’t be strange to expect 2 allocations. One for the Test Object, one for a Trouble object. The correct answer is 729. The serializer does not support null. If it serializes an object, and a field is null, we just instantiate a new object of that type, and serialize that. Obviously this could lead to infinite cycles, so we have a relatively magical depth limit of 7 levels. At that point we just stop serializing fields that have types of custom classes/structs and lists and arrays.
Since so many of our subsystems build on top of the serialization system, this unexpectedly large serialization stream for the Test monobehaviour will cause all these subsystems to perform more slowly than necessary. When we investigate performance problems in customer projects, almost always do we find this problem. We added a warning for this situation in Unity 4.5.
No support for polymorphism
if you have a public Animal[] animals and you put in an instance of a dog, a cat and a giraffe, after serialization, you will have three instances of Animal.
One way to deal with this limitation is to realize that it only applies to “custom classes”, which get serialized inline. References to other UnityEngine.Objects get serialized as actual references, and for those polymorphism does actually work. You would make a ScriptableObject derived class or another MonoBehaviour derived class, and reference that. The downside of that is that you need to store that monobehaviour or scriptable object somewhere, and cannot serialize it inline nicely.
The reason for these limitations is that one of the core foundations of the serialization system is that the layout of the datastream for an object is known ahead of time, and depends on the types of the fields of the class, instead of what happens to be stored inside the fields.
I want to serialize something that Unity’s serializer doesn’t support. What do I do?
In many cases the best approach is to use serialization callbacks. They allow you to be notified before the serializer reads data from your fields and after it is done writing to them. You can use this to have a different representation of your hard-to-serialize data at runtime than when you actually serialize. You would use these to transform your data into something Unity understands right before unity wants to serialize it, and you use it to transform the serialized form back into the form you like to have your data in at runtime right after unity has written the data to your fields.
Suppose you want to have a tree datastructure. If you let Unity directly serialize the data structure, the “no support for null” limitation would cause your datastream to become very big, leading to performance degratations in many systems:
Instead, you tell Unity not to serialize the tree directly, and you make a seperate field to store the tree in a serialized format, suited for unity’s serializer:
Beware that the serializer, including these callbacks coming from the serializer usually happen not on the main thread, so you are very limited in what you can do in terms of invoking Unity API. You can however to the necessary data transformations do get your data from a non-unity-serializer-friendly format to a unity-serializer-friendly-format.
Script serialization errors
When scripts call the Unity API from constructors or field initializers, or during deserialization (loading), they can trigger errors. This section demonstrates the poor practise that causes these errors.
In Unity 5.4 these errors do not, in most cases, throw a managed exception, and do not interrupt the execution flow of your scripts. This eases the process of upgrading your project to Unity 5.4. However, these errors will throw a managed exception in subsequent releases of Unity. You should therefore fix any errors as soon as possible when upgrading to 5.4.
Calling Unity API from constructor or field initializers
When Unity creates an instance of a MonoBehaviour or ScriptableObject derived class, it calls the default constructor to create the managed object. This happens before entering the main loop, and before the scene has been fully loaded. Field initializers are also called from the default constructor of a managed object. In general, do not call the Unity API from a constructor, as this is unsafe for the majority of the Unity API.
Examples of poor practice:
Both these cases generate the error message: “Find is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call in in Awake or Start instead.”
Calling Unity API during deserialization
When Unity loads a scene, it recreates the managed objects from the saved scene and populates them with the saved values (deserializing). In order to create the managed objects, call the default constructor for the objects. If a field referencing an object is saved (serialized) and the object default constructor calls the Unity API, you will get an error when loading the scene. As with the previous error, it is not yet in the main loop and the scene is not fully loaded. This is considered unsafe for the majority of the Unity API.
Example of poor practice:
This generates the error: “Find is not allowed to be called during serialization, call it from Awake or Start instead.”
SerializeField
class in UnityEngine
Success!
Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable.
Submission failed
For some reason your suggested change could not be submitted. Please try again in a few minutes. And thank you for taking the time to help us improve the quality of Unity Documentation.
Description
Force Unity to serialize a private field.
When Unity serializes your scripts, it will only serialize public fields. If in addition to that you also want Unity to serialize one of your private fields you can add the SerializeField attribute to the field.
The serialization system used can do the following:
— CAN serialize public nonstatic fields (of serializable types)
— CAN serialize nonpublic nonstatic fields marked with the [SerializeField] attribute.
— CANNOT serialize static fields.
— CANNOT serialize properties.
Your field will only serialize if it is of a type that Unity can serialize:
Serializable types are:
— All classes inheriting from UnityEngine.Object, for example GameObject, Component, MonoBehaviour, Texture2D, AnimationClip.
— All basic data types like int, string, float, bool.
— Some built-in types like Vector2, Vector3, Vector4, Quaternion, Matrix4x4, Color, Rect, LayerMask.
— Arrays of a serializable type
— List of a serializable type)
— Enums
— Structs
Note: if you put one element in a list (or array) twice, when the list gets serialized, you’ll get two copies of that element, instead of one copy being in the new list twice.
Hint: Unity won’t serialize Dictionary, however you could store a List<> for keys and a List<> for values, and sew them up in a non serialized dictionary on Awake(). This doesn’t solve the problem of when you want to modify the dictionary and have it «saved» back, but it is a handy trick in a lot of other cases.
For UnityScript users: Fields in c# is a script variable in UnityScript, and [SerializeField] becomes @SerializeField. [Serializable] on a class becomes @script Serializable in a UnityScript.
Did you find this page useful? Please give it a rating:
SerializeField
class in UnityEngine
Success!
Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable.
Submission failed
For some reason your suggested change could not be submitted. Please try again in a few minutes. And thank you for taking the time to help us improve the quality of Unity Documentation.
Description
Force Unity to serialize a private field.
When Unity serializes your scripts, it only serializes public fields. If you also want Unity to serialize your private fields you can add the SerializeField attribute to those fields.
The serialization system can do the following:
— CAN serialize public non-static fields (of serializable types)
— CAN serialize nonpublic non-static fields marked with the SerializeField attribute.
— CANNOT serialize static fields.
— CANNOT serialize properties.
Serializable types
Unity can serialize fields of the following types:
— All classes inheriting from UnityEngine.Object, for example GameObject, Component, MonoBehaviour, Texture2D, AnimationClip.
— All basic data types, such as int, string, float, bool.
— Some built-in types, such as Vector2, Vector3, Vector4, Quaternion, Matrix4x4, Color, Rect, LayerMask.
— Arrays of a serializable type
— Lists of a serializable type
— Enums
— Structs
For more information on serialization, see Script Serialization.
Note: If you put one element in a list (or array) twice, when the list gets serialized, you’ll get two copies of that element, instead of one copy being in the new list twice.
Note: If you want to serialize a custom Struct field, you must give the Struct the [System.Serializable] attribute.
Hint: Unity won’t serialize Dictionary, however you could store a List<> for keys and a List<> for values, and sew them up in a non serialized dictionary on Awake(). This doesn’t solve the problem of when you want to modify the dictionary and have it «saved» back, but it is a handy trick in a lot of other cases.
Did you find this page useful? Please give it a rating:
SerializeField
class in UnityEngine
Success!
Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable.
Submission failed
For some reason your suggested change could not be submitted. Please try again in a few minutes. And thank you for taking the time to help us improve the quality of Unity Documentation.
Description
Force Unity to serialize a private field.
When Unity serializes your scripts, it will only serialize public fields. If in addition to that you also want Unity to serialize one of your private fields you can add the SerializeField attribute to the field.
The serialization system used can do the following:
— CAN serialize public nonstatic fields (of serializable types)
— CAN serialize nonpublic nonstatic fields marked with the SerializeField attribute.
— CANNOT serialize static fields.
— CANNOT serialize properties.
Your field will only serialize if it is of a type that Unity can serialize:
Serializable types are:
— All classes inheriting from UnityEngine.Object, for example GameObject, Component, MonoBehaviour, Texture2D, AnimationClip.
— All basic data types like int, string, float, bool.
— Some built-in types like Vector2, Vector3, Vector4, Quaternion, Matrix4x4, Color, Rect, LayerMask.
— Arrays of a serializable type
— List of a serializable type)
— Enums
— Structs
Note: if you put one element in a list (or array) twice, when the list gets serialized, you’ll get two copies of that element, instead of one copy being in the new list twice.
Hint: Unity won’t serialize Dictionary, however you could store a List<> for keys and a List<> for values, and sew them up in a non serialized dictionary on Awake(). This doesn’t solve the problem of when you want to modify the dictionary and have it «saved» back, but it is a handy trick in a lot of other cases.
Did you find this page useful? Please give it a rating:
Unity Editor Adventures: Сериализованная Матрёшка
Краткое вступление
Как правило, для того чтобы добраться до интересующего нас поля сериализованного свойства, мануальная терапия советует нам использовать метод FindPropertyRelative(), в который прокидывается название переменной.
По определённым причинам такой подход не всегда удобен. Причины могут быть самыми разнообразными. Например, название переменной может смениться, нам кровь из носу нужен доступ к несериализованному свойству, нам необходимо иметь доступ к геттерам-сеттерам или вообще методам сериализованного объекта. Не будем задавать вопросов «зачем вам это вообще нужно» и «почему вы не могли обойтись традиционными путями». Положим, нужно – и всё тут.
Итак, давайте же разберёмся как из сериализованного свойства получить объект, с которым мы работаем, а также все его родительские объекты, и не навернуться по дороге сериализации, полной подводных камней.
Внимание. Данная статья подразумевает, что вы уже умеете работать с UnityEditor’ом, хотя бы раз писали кастомные PropertyDrawer’ы и хотя бы в общих чертах понимаете, чем сериализованное свойство отличается от сериализованного объекта.
Путь сериализации
Для начала расставим все точки над O.
В самом примитивном случае у нас есть некий класс-наследник от MonoBehaviour, и у него есть некое поле, принадлежащее сериализованному классу, очевидно не являющемуся наследником от священной юнитевской коровы а.к.а. UnityEngine.Object.
В коде выше SandboxField – это класс с аттрибутом Serializable.
Получить доступ к хозяйскому MonoBehaviour’у не составляет проблем:
По желанию можно взять его через as, но нам сейчас это не нужно. Нас интересует само сериализованное поле, чтобы его отрисовать со всем блекджеком как на рисунке ниже.
Путь сериализации мы можем взять следующим образом:
В нашем случае путь сериализации будет состоять из одного нашего поля и вернёт “sandboxField”, от которого нам пока что ни холодно, ни жарко, поскольку для первого уровня вложенности нам необходимо знать только имя переменной (которое, впрочем, нам и вернули).
Обратите внимание, родительского MonoBehaviour в пути нет. Сейчас это неважно, но станет важно, когда мы начнём разбирать матрёшку, выглядящую примерно вот так:
Чтобы не навернуться впоследствии, когда свойства будут вложенными, загодя сделаем следующее:
Теперь мы имеем все узлы пути сериализации. Но в самом примитивном случае нам нужен только нулевой узел. Возьмём его:
Включим немного рефлексии и получим отсюда само поле:
Оставим за кадром вопрос быстродействия этой затеи. Для небольшого количества таких полей и малой вложенности затраты на рефлексию будут существенно меньше, чем на всё то, что происходит под капотом UnityEditor’a за время отрисовки. Если хотите пруфов – на гитхабе у разрабов Unity есть такая интересная штука, UnityCsReference, посмотрите на досуге, как реализована отрисовка ObjectField, например.
Собственно, на этом ещё не (ха-ха) всё. Мы получили поле, мы довольны, мы можем творить с ним что вздумается и даже попытаться написать собственный UnityEvent со всеми кнопочками и важными действиями, затрагивающими исключительно наше поле, независимо от того, на каком объекте оно висит.
По крайней мере, пока оно висит в корне этого объекта, всё будет хорошо, а вот дальше – уже не очень. Вниз по пути сериализации нас ждут массивы и всякие списки, чьё главное желание – подгадить нам в тапки, своевременно изменив количество элементов в большую сторону. Но чёрт бы с этим, нам бы сначала подкопаться под сам массив.
Если бы у нас в пути сериализации не было массивов, задача решалась бы тривиально: мы бы в цикле перебирали узлы сериализации, пока не дошли бы до конца цепочки. Что-то вроде нижеследующего кода:
Здесь у нас ждут сразу две неприятные новости. Начну со второй.
Шаг со взятием nextObject может внезапно нам вернуть null вместо ожидаемого объекта. Обычно это случается, когда мы впервые создаём родительский объект в инспекторе, и путь сериализации уже существует, а соответствующее ему поле – нет (это нам ещё дальше доставит приятностей).
На этот случай нам неплохо бы сразу дописать выход из метода с возвращением null:
«Погодите! – скажете вы. – А что делать тогда в OnGUI, если нам вернули нуль?»
Ответ: ничего. В буквальном смысле ничего. Просто сделать return и таким образом пропустить этот шаг отрисовки, дождавшись, пока поле создастся. Ничего страшного от этого не произойдёт.
здесь GetTarget() – соответствующая функция, берущая сериализованный объект из проперти.
Кстати, я посоветую брать интересующее нас поле не здесь, а в GetPropertyHeight. Это понадобится на тот случай, если мы будем писать сворачивающиеся-разворачивающиеся поля с разным размером в зависимости от содержимого. GetPropertyHeight() вызывается до OnGUI(), так что если мы возьмём поле там и запишем его в поле нашего PropertyDrawer’a, то нам не придётся брать его повторно в OnGUI.
Обратите внимание, что экземпляр кастомного PropertyDrawer’a создается один для отрисовки всех в данный момент видимых сериализованных свойств, и в него по очереди сверху-вниз кидаются новые свойства. Это стоит учесть, чтобы не напортачить с вычислением высоты очередного свойства, иначе можете получить неприятную ситуацию, когда у вас по нажатию на foldout разворачивается не то поле, которое вы ожидаете.
Также всю мишуру, которая отвечает за отображение поля в редакторе и которую вы хотите сериализовать, сериализовать вам стоит на стороне сериализуемого класса, а не PropertyDrawer’a, а для верности – обнести скобками условной компиляции, чтобы весь этот испанский стыд не попытался пойти в билд:
Ещё один подводный камень, который нас здесь поджидает: все созданные через эдитор поля плевать хотели на конструктор класса и на значения по умолчанию, заданные в классе. Если вы сделаете, например, вот так (пример из моего проекта, где это было значение узлов водяной поверхности):
Это значение будет проигнорировано сериализацией в упор, как только вы добавите новый элемент в список. Звать на помощь конструктор не менее бесполезно: всё, что вы там понаписали, будет проигнорировано. Новый объект гол как сокол, все его значения – действительно значения по умолчанию, вот только не те, которые вы хотите там увидеть, а всякие null, false, 0, Color.clear и прочие непотребные вещи.
Есть тривиальный костыль. Создаём класс NonUnitySerializableClass, от которого наследуем все наши сериализованные плюшки. В самом классе делаем виртуальную функцию, DefaultEditorObject(), которую при необходимости перегружаем.
Дальше пишем что-то вроде:
В дальнейшем просто переписываем по необходимости для наследников DefaultEditorObject(), чтобы задать дефолтные значения, а к EditorCreated стучимся при валидации полученного поля.
Результат починки: адекватная обработка значений только что созданного объекта.
Вернёмся к нашим баранам, а точнее – массивам. Другая проблема, которая может возникнуть на ещё более раннем этапе, кроется вот в этой строчке:
Правая часть может вернуть нам нуль, если напорется на массив в пути сериализации (причём массивом “Array” будет любой объект IList). Неприятно.
Что делать? Постараться не попадать в такие ситуации писать обработчик:
Да, мы можем даже здесь впаяться в неприятную ситуацию, когда путь сериализации уже имеет, например, элемент data[0] или data[1], а массив его ещё не реализовал. Например, мы создали пустой список List. Мы задаём ему N элементов – и без этой красивой строчки:
…получаем кучу исключений в мурчало. А всего-то надо было пропустить шаг отрисовки, дождавшись, пока будут созданы интересующие нас поля.
Мне пока не попадались другие случаи, когда objectFieldInfo == null, но при этом узел сериализации не обозначен как Array, поэтому на такую гипотетическую исключительную ситуацию стоит выкидывание страшного исключения – чтобы впоследствии его расколупать.
В общем и целом, мы получили более-менее рабочую функцию, позволяющую извлечь поле по его сериализованному свойству. В дальнейшем эту функцию можно модифицировать, заставив извлекать все объекты в пути сериализации, а также искать ближайшего «родителя», включая или исключая массивы по пути.
Чтобы отрисовать свойства с учётом смещения из-за вложенности списка, можно вместо Rect position использовать Rect indentedPosition = EditorGUI.IndentedRect(position). Обратите внимание только на то, что если вы будете рисовать внутренние поля через EditorGUI, вам необходимо будет использовать исходный position свойства, а если вы будете рисовать через GUI – тогда вам понадобится indentedPosition. Не используйте EditorGUILayout внутри OnGUI, ничего хорошего из этого не выйдет (чаще всего не выходит, во всяком случае).
Если вам необходимо рисовать поле, ссылающееся на MonoScript соответствующего класса (класса самого сериализуемого объекта или класса-рисовашки, не суть важно), желательно кешировать его в static-поле, чтобы не лезть постоянно в AssetDatabase, поскольку это затратно по ресурсам и при очень большом количестве свойств может привести к заметным тормозам редактора.