Что такое serializefield в unity
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 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:
Serializable types
Unity can serialize fields of the following types:
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.
Is something described here not working as you expect it to? It might be a Known Issue. Please check with the Issue Tracker at issuetracker.unity3d.com.
Copyright ©2021 Unity Technologies. Publication Date: 2021-12-23.
Script Serialization
How you organize data in your Unity Project affects how Unity serializes that data and can have a significant impact on the performance of your Project. This page outlines serialization in Unity and how to optimize your Project for it.
Understanding hot reloading
Hot reloading is the process of creating or editing scripts A piece of code that allows you to create your own Components, trigger game events, modify Component properties over time and respond to user input in any way you like. More info
See in Glossary while the Editor is open and applying the script behaviors immediately. You do not have to restart your application or the Editor for changes to take effect.
When you change and save a script, Unity hot reloads all the currently loaded script data. It first stores all serializable variables in all loaded scripts and, after loading the scripts, it restores them. All data that is not serializable is lost after a hot reload.
Saving and loading
Many of the features in the Unity Editor build on top of the core serialization system. Two things to be particularly aware of with serialization are the Inspector window, and hot reloading.
The Inspector window
When you view or change the value of a GameObject The fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
See in Glossary ’s component field in the Inspector window, Unity serializes this data and then displays it in the Inspector window. The Inspector window does not communicate with the Unity Scripting API when it displays the values of a field.
If you use properties in your script, any of the property getters and setters are never called when you view or change values in the Inspector windows as Unity serializes the Inspector window fields directly. This means that: While the values of a field in the Inspector window represent script properties, changes to values in the Inspector window do not call any property getters and setters in your script
Serialization rules
Serializers in Unity run in a real-time game environment. This has a significant impact on performance. As such, serialization in Unity behaves differently to serialization in other programming environments. The following section outlines how to use serialization in Unity. To use field serialization you must ensure it:
Simple field types that can be serialized
Container field types that can be serialized
Note: Unity does not support serialization of multilevel types (multidimensional arrays, jagged arrays, and nested container types). If you want to serialize these, you have two options: wrap the nested type in a class or struct, or use serialization callbacks ISerializationCallbackReceiver to perform custom serialization. For more information, see documentation on Custom Serialization.
How to ensure a custom class can be serialized
To ensure the fields of a custom class or struct are serialized, see How to ensure a field in a script is serialized, above.
When might the serializer behave unexpectedly?
Custom classes behave like structs
With custom classes that are not derived from UnityEngine.Object Unity serializes them inline by value, similar to the way it serializes structs. If you store a reference to an instance of a custom class in several different fields, they become separate objects when serialized. Then, when Unity deserializes the fields, they contain different distinct objects with identical data.
When you need to serialize a complex object graph with references, do not let Unity automatically serialize the objects. Instead, use ISerializationCallbackReceiver to serialize them manually. This prevents Unity from creating multiple objects from object references. For more information, see documentation on ISerializationCallbackReceiver.
No support for null for custom classes
Consider how many allocations are made when deserializing a MonoBehaviour that uses the following script.
It wouldn’t be strange to expect one allocation: That of the Test object. It also wouldn’t be strange to expect two allocations: One for the Test object and one for a Trouble object.
However, Unity actually makes more than a thousand allocations. The serializer does not support null. If it serializes an object, and a field is null, Unity instantiates a new object of that type, and serializes that. Obviously this could lead to infinite cycles, so there is a depth limit of seven levels. At that point Unity stops serializing fields that have types of custom classes, structs, lists, or arrays.
Since so many of Unity’s subsystems build on top of the serialization system, this unexpectedly large serialization stream for the Test MonoBehaviour causes all these subsystems to perform more slowly than necessary.
No support for polymorphism
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 this is that you need to store that Monobehaviour or scriptable object somewhere, and that you cannot serialize it inline efficiently.
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; it depends on the types of the fields of the class, rather than what happens to be stored inside the fields.
Optimal use of serialization
You can organise your data to ensure you get optimal use from Unity’s serialization.
Organise your data with the aim to have Unity serialize the smallest possible set of data. The primary purpose of this is not to save space on your computer’s hard drive, but to make sure that you can maintain backwards compatibility with previous versions of the project. Backwards compatibility can become more difficult later on in development if you work with large sets of serialized data.
Organise your data to never have Unity serialize duplicate data or cached data. This causes significant problems for backwards compatibility: It carries a high risk of error because it is too easy for data to get out of sync.
Avoid nested, recursive structures where you reference other classes.The layout of a serialized structure always needs to be the same; independent of the data and only dependent on what is exposed in the script. The only way to reference other objects is through classes derived from UnityEngine.Object. These classes are completely separate; they only reference each other and they don’t embed the contents.
Making Editor code hot reloadable
When reloading scripts, Unity serializes and stores all variables in all loaded scripts. After reloading the scripts, Unity then restores them to their original, pre-serialization values.
Unity never restores static variables, so do not use static variables for states that you need to keep after reloading a script.
Script Serialization
How you organize data in your Unity Project affects how Unity serializes that data and can have a significant impact on the performance of your Project. This page outlines serialization in Unity and how to optimize your Project for it.
Understanding hot reloading
Hot reloading is the process of creating or editing scripts A piece of code that allows you to create your own Components, trigger game events, modify Component properties over time and respond to user input in any way you like. More info
See in Glossary while the Editor is open and applying the script behaviors immediately. You do not have to restart your application or the Editor for changes to take effect.
When you change and save a script, Unity hot reloads all the currently loaded script data. It first stores all serializable variables in all loaded scripts and, after loading the scripts, it restores them. All data that is not serializable is lost after a hot reload.
Saving and loading
Many of the features in the Unity Editor build on top of the core serialization system. Two things to be particularly aware of with serialization are the Inspector window, and hot reloading.
The Inspector window
When you view or change the value of a GameObject The fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
See in Glossary ’s component field in the Inspector window, Unity serializes this data and then displays it in the Inspector window. The Inspector window does not communicate with the Unity Scripting API when it displays the values of a field.
If you use properties in your script, any of the property getters and setters are never called when you view or change values in the Inspector windows as Unity serializes the Inspector window fields directly. This means that: While the values of a field in the Inspector window represent script properties, changes to values in the Inspector window do not call any property getters and setters in your script
Serialization rules
Serializers in Unity run in a real-time game environment. This has a significant impact on performance. As such, serialization in Unity behaves differently to serialization in other programming environments. The following section outlines how to use serialization in Unity. To use field serialization you must ensure it:
Simple field types that can be serialized
Generic field types can also be directly serialized, without the need to declare a non-generic subclass.
Container field types that can be serialized
Note: Unity does not support serialization of multilevel types (multidimensional arrays, jagged arrays, and nested container types). If you want to serialize these, you have two options: wrap the nested type in a class or struct, or use serialization callbacks ISerializationCallbackReceiver to perform custom serialization. For more information, see documentation on Custom Serialization.
How to ensure a custom class can be serialized
To ensure the fields of a custom class or struct are serialized, see How to ensure a field in a script is serialized, above.
When might the serializer behave unexpectedly?
Custom classes behave like structs
With custom classes that are not derived from UnityEngine.Object Unity serializes them inline by value, similar to the way it serializes structs. If you store a reference to an instance of a custom class in several different fields, they become separate objects when serialized. Then, when Unity deserializes the fields, they contain different distinct objects with identical data.
When you need to serialize a complex object graph with references, do not let Unity automatically serialize the objects. Instead, use ISerializationCallbackReceiver to serialize them manually. This prevents Unity from creating multiple objects from object references. For more information, see documentation on ISerializationCallbackReceiver.
No support for null for custom classes
Consider how many allocations are made when deserializing a MonoBehaviour that uses the following script.
It wouldn’t be strange to expect one allocation: That of the Test object. It also wouldn’t be strange to expect two allocations: One for the Test object and one for a Trouble object.
However, Unity actually makes more than a thousand allocations. The serializer does not support null. If it serializes an object, and a field is null, Unity instantiates a new object of that type, and serializes that. Obviously this could lead to infinite cycles, so there is a depth limit of seven levels. At that point Unity stops serializing fields that have types of custom classes, structs, lists, or arrays.
Since so many of Unity’s subsystems build on top of the serialization system, this unexpectedly large serialization stream for the Test MonoBehaviour causes all these subsystems to perform more slowly than necessary.
No support for polymorphism
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 this is that you need to store that Monobehaviour or scriptable object somewhere, and that you cannot serialize it inline efficiently.
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; it depends on the types of the fields of the class, rather than what happens to be stored inside the fields.
Optimal use of serialization
You can organise your data to ensure you get optimal use from Unity’s serialization.
Organise your data with the aim to have Unity serialize the smallest possible set of data. The primary purpose of this is not to save space on your computer’s hard drive, but to make sure that you can maintain backwards compatibility with previous versions of the project. Backwards compatibility can become more difficult later on in development if you work with large sets of serialized data.
Organise your data to never have Unity serialize duplicate data or cached data. This causes significant problems for backwards compatibility: It carries a high risk of error because it is too easy for data to get out of sync.
Avoid nested, recursive structures where you reference other classes.The layout of a serialized structure always needs to be the same; independent of the data and only dependent on what is exposed in the script. The only way to reference other objects is through classes derived from UnityEngine.Object. These classes are completely separate; they only reference each other and they don’t embed the contents.
Making Editor code hot reloadable
When reloading scripts, Unity serializes and stores all variables in all loaded scripts. After reloading the scripts, Unity then restores them to their original, pre-serialization values.
Unity never restores static variables, so do not use static variables for states that you need to keep after reloading a script.
Лайфхаки редактора Unity 3D. Часть 1: Атрибуты
Содержание
Предисловие
Здравствуйте, друзья! Заметил, что многие программисты пропускают богатые возможности Unity кастомизации редакторского интерфейса по тем или иным причинам. В этом цикле статей я распишу несколько совсем простых примеров, позволяющих облегчить жизнь геймдизайнерам и художникам, а также парочку примеров посложнее, но также легко реализуемых.
Большая часть взята из опыта использования, куда попала из родной документации движка. Вы легко можете сами найти необходимую информацию, поворошив документацию Unity 3D. Просто, по собственному опыту скажу, что у многих программистов либо нет времени, либо нет желания копаться в мантрах. Поэтому и выкладываю максимально краткое руководство по основным редакторским возможностям, которые я использовал на работе и в своих проектах.
Встроенные атрибуты
Я не буду расписывать все атрибуты, распишу лишь кратко те, которыми самому приходилось пользоваться.
Атрибуты к методам
Позволяет создать меню для доступа к статическому методу. Через “/” указывается иерархия. Можно располагать новые кнопки в стандартном главном меню движка, указывая путь, например “File/Create New Asset”.
Всего может содержать три параметра.
Также, если использовать элементы главного меню, то дополнительная кнопка будет появляться не только там, но и в контекстном меню на правую кнопку мыши. Например, в своем проекте, я добавил копирование пути к ассету.
Кроме того, на методы можно назначить горячие клавиши. Для этого, прямо в пути к меню нужно написать необходимую комбинацию. Для этого нужно использовать один из служебных символов+буква.
% — ctrl в Windows или cmd в OSX
# — shift
& — alt
В моем проекте, с копированием пути к ассету это выглядит так
Атрибуты к переменным
Можно сказать, это кастомный редактор для атрибута, который позволяет задать границы задаваемого значения через инспектор. Не клампит в реалтайме — только в инспекторе. Полезно, если задаете, например, вероятность выпадения предметов от 0 до 1 или от 0 до 100.
Задает подпись над сериализуемым полем, которая отображается в инспекторе.
Задает отступ в инспекторе.
Задает подсказку в инспекторе при наведении на сериализуемую переменную.
Позволяет сериализовать переменные вне зависимости от их области видимости. Очень полезный атрибут, который позволяет сделать все переменные класса приватными, но настраиваемыми в инспекторе.
Позволяет убирать сериализацию у паблик переменных. Очень не рекомендую данных подход. Уж лучше определить свойство get;set; и получать данные по нему. Кроме того, свойство можно сделать виртуальным и перегрузить, при необходимости, в классах наследниках. А тот факт, что оно публичное, позволяет использовать его в интерфейсах.
Позволяет скрыть сериализуемое поле в инспекторе. Неважно, будет оно публичным или приватным/протектным с атрибутом SerializeField.
Атрибуты к классам
ScriptableObject — очень полезный класс, который позволяет хранить условную базу данных в проекте, не прибегая к префабам. В проекте создаются объекты с созданным вами типом. Можно работать также, как с префабами, имеют свои преимущества и недостатки. Вообще, этот класс — тема для отдельной статьи, небольшой, но информативной.
Указанный выше атрибут позволяет создать в проекте объект с вашим типом, по тому пути, где вы открыли контекстное меню.
Позволяет работать скрипту в редакторе. В основном, полезно для постэффектов, поскольку позволяет сразу оценить результат в камере без запуска проекта. Но иногда можно использовать и для других целей.
Например, в качестве инициализатора сериализуемых полей встроенных типов, типа transform, renderer, rectTranform и т.п. Не рекомендовал бы повсеместно, лучше требовать ручной инициализации, либо написать редакторский скрипт, но иногда удобно.
Заставляет редактор требовать наличие определенного компонента на том же объекте, на котором висит скрипт с данным атрибутом. При добавлении сразу создает на том же объекте компонент указанного типа. Также запрещает удалять уже добавленный компонент.
Добавляет подменю в выпадающий список в меню Components →… и AddComponent. Удобно, если у вас большая библиотека кода и нужно организовать её в редакторе.
На этом, простая часть заканчивается и добавлю совсем немного в меру сложной.
Кастомные атрибуты (CustomPropertyDrawer)
Unity — Scripting API: PropertyAttribute
Unity — Scripting API: PropertyDrawer
Если вам недостаточно атрибутов приведенных выше, вы всегда можете воспользоваться API для написания собственных настраиваемых атрибутов. Реализация данного инструмента также достаточно проста и заключается в нескольких шагах. В данном примере, я опишу создание
собственного атрибута к переменной.
Во-первых, нужно определить класс-наследник от стандартного класса PropertyAttribute. Я сразу создам его с конструктором, в котором входящим параметром будет путь к списку того, что нам нужно использовать в атрибуте.
Во-вторых, после этого создаем скрипт редактора, в котором будем рисовать этот самый новый класс. Его нужно унаследовать от PropertyDrawer, а также написать к нему атрибут CustomPropertyDrawer.
Я называю классы наиболее общими наименованиями, дабы просто показать принцип использования настраиваемых.
База готова, теперь нам нужно нарисовать данный атрибут в том виде, в котором он нам нужен. В основном, атрибуты я использую в тех случаях, когда возможностей перечисления (enum) недостаточно, но нужно отрисовать выпадающий список с выбором.
Например, у вас есть база эффектов, у которой есть соответствие id → эффект. Вы храните где-то эту базу, неважно в ScriptableObject’e или на каком-то префабе. Вот простейшая реализация “хранилища”
Примечание — всегда создавайте в классах первое сериализуемое поле строковым. Из-за этого в списках элементы будут именоваться не как element 1, element 2. а таким образом, каким вы назначите переменную в инспекторе.
Для классов, с которыми я взаимодействую “извне”, я всегда пишу интерфейс. У каждого свой подход к этому моменту, но данный подход легко позволит, в случае чего, подменить класс только в одном месте на другой, а остальные так и будут работать с интерфейсом. Тем более, юнити поддерживает работу с интерфейсами в таких методах, как GetComponent(s)…, GetComponent(s)InChildren и т.п.
Интерфейс и класс эффекта
Интерфейс и класс контейнера
Обычно, объекты с такими данными я располагаю в ресурсах, потом беру оттуда. Можно расположить и просто в проекте и где необходимо определить ссылки. Но я иду по более простому и уже проверенному на не одной платформе пути.
Редактор
Осталось дописать редактор:
Заключение
Все приведенные выше «лайфхаки» могут упростить не только вашу работу (особенно когда проект разрабатывается несколько месяцев или лет), но и работу новичков, а также художников и геймдизайнеров. Не каждый специалист полезет в код. Конечно, хорошая организация и дисциплина может помочь и так документировать каждый компонент, но не всегда это получается, особенно у независимых разработчиков.
P.S.: Позже, напишу еще пару статей по другим типам апгрейдов редактора, в которые включу:
CustomEditor;
CustomPropertyDrawer;
EditorWindow;
Класс Debug и как его едят;
Класс Gizmos.
А также дополню примеры окном и пользовательским редактором. Пишите в комментариях, нужны ли подобные статьи или можно обойтись тем, что уже есть на Хабре.