Search Unity

Переименование сериализованых полей

, 3 февраля, 2015

Некоторые из вас, возможно, знают, что мы создали большую часть новой системы UI как пользовательские скрипты. Мы столкнулись с большинством ограничений, с которыми сталкиваются наши пользователи при разработке в Unity и расширении редактора. В процессе разработки мы исправили проблемы, с которыми столкнулись. Сегодня мы хотели бы рассказать об одной из них.

Проблема заключалась в том, что, когда мы переименовывали поля, наши пользователи теряли данные. Во время бета-тестирования мы хотели изменить наш код, не портя при этом проекты пользователей бета-версии, чтобы они могли продолжить тестирование каждой новой версии, не беспокоясь о потере данных. Чтобы решить эту проблему, мы ввели атрибут [FormerlySerializedAs].

Что это вам дает? Давайте рассмотрим некоторые варианты использования!

Переименование переменной

Допустим, у вас есть следующий класс:

[csharp]
using UnityEngine;
class MyClass : MonoBehaviour
{
[SerializeField]
private string m_MyVariable;
}
[/csharp]

Но вы хотели бы переименовать m_MyVariable, например, на m_ABetterName, и вы не хотите, чтобы ваши пользователи повторно заполняли данные для этого скрипта во всех своих сценах и/или префабах. Сделать это можно следующим образом:

[csharp]
using UnityEngine;
using UnityEngine.Serialization;
class MyClass : MonoBehaviour
{
[FormerlySerializedAs("m_MyVariable")]
[SerializeField]
private string m_ABetterName;
}
[/csharp]

Инкапсуляция публичного API

В этом примере у вас есть публичное поле, которое является частью вашего API, но хотелось бы оформить его как метод доступа. Итак, давайте предположим у нас есть класс MyClass:

[csharp]
using UnityEngine;
class MyClass : MonoBehaviour
{
public string myValue;
}
[/csharp]

Для инкапсуляции этого значения в метод доступа без потери существующих данных в ваших ассетах вы можете сделать что-то вроде этого:

[csharp]
using UnityEngine;
using UnityEngine.Serialization;
class MyClass : MonoBehaviour
{
[FormerlySerializedAs("myValue")]
[SerializeField]
private string m_Value;
public string myValue
{
get { return m_Value; }
set { m_Value = value; }
}
}
[/csharp]

Несколько переименований

Поддерживается переименование полей несколько раз, просто добавьте атрибут для каждого предыдущего имени поля:

[csharp]
using UnityEngine;
using UnityEngine.Serialization;
class MyClass : MonoBehaviour
{
[FormerlySerializedAs("m_MyVariable")]
[FormerlySerializedAs("m_ABetterName")]
[SerializeField]
private string m_EvenBetterName;
}
[/csharp]

Когда можно удалить атрибут?

Вы можете удалить атрибуты после того, как вы повторно сохранили все сцены и ассеты, после переименования. Конечно, это означает, что вы должны “контролировать” ваших пользователей. Но, например, для продавцов в Asset Store этот “контроль” невозможен. В этом случае вам придется оставить это атрибут до тех пор, пока вы хотите, чтобы люди с любой версией кода могли обновиться до новой версии кода без потери данных.

Надеюсь, вы найдете полезной эту новую маленькую особенность!

30 replies on “Переименование сериализованых полей”

This new renaming attribute is sounding like a great direction! One related thing that often happens is wanting to change the units of a particular serialized property — like a float representing milliseconds suddenly wanting to represent seconds at an artists request. It would be awesome if these rename attributes (or maybe a new attribute) let you supply an optional «upgrade» function that can actually convert the old data to the new data, so you could supply conversions in code where necessary. Is this something other people have wanted?

Awesome feature guys! Especially the fact this work on Serializable classes!

Any chance we’re eventually going to get full ‘dynamic’ keyword support for classes? Then we can safely remove fields from our SaveFiles with worrying about them blowing up on Deserialize.

These «small changes» or «small additions» are really what I like most about Unity. Not being able to do that kind of refactoring was a rather significant limitation and I think adding this new attribute fixes that limitation quite nicely. Right on!

Simple renaming is nice and fun but basically it is far not enough in bigger projects that spans over longer development time. We need re-typing, changing of default values, changing of whole data structures, mergability, human readability, external-tool-readability, easier to parse in random access order etc.

(Of course, some of these points are conflicting. We want different aspects in different types of assets)

We start to abandon unity save format here, for its lack of extendability in any of these points and switch to our own format :(. Unfortunately, that’s also a pain in the neck, as Unity does not provide any kind of helping hands in this direction either..

Here is my 2c idea that I posted in a somewhat related issue report (665302):
—-

I would recommend to have a post-save in the editor. This step gets the save data as structured input (like key/value pairs. The SerializedObject data format seems perfect — if its not bound to concrete object Types?) and can start to optimize the structure of the data for several things like «mergability».

Mirrored to that, there is a pre-load step that first «restores» the data before it is passed to the current deserialization data mapping.

This approach makes it far more easier to optimize the save file format for various different things independend of the internal data representation. There are many conflicting goals that an export format should look like. Speed, size, human readability, mergability, random-access-reading, easiness to analyse with other tools…

(What would be even better: Add some hooks to give editor plugins the chance to alter the save format. That would make it possible to upgrade scene files and prefabs during the lifetime of a project so much easier)

Yes. This is what we are doing. We encode everything into a single list of strings, which gets encoded into separate lines by YAML most of the time. The only other thing we need is a «list of objects» to hold references so Unity’s reference-system still works. Its a pain, e.g. since you cannot access even the name of any asset during OnAfterDeserialize, but relying on Unity mergability is even more pain, so…

Can we expect in the future better solution for rename the fields without losing data? (automatically without user intervention). Another solution not use connections between fields and assets via inspector, instead use Resourses.Load, but this uncomfortably if you rename folders or assets in project often.

I think a «variable name» attribute could work too. So if I had a variable and I wanted to have a specific «weird» name on the inspector but not the one from the variable…
e.g.
public int myVar; (and the name I wanted would be «1. My Var»)

I would go like…

[VariableName(«1. My Var»)]
public int myVar;

or even on a private variable
[VariableName(«1. My Var»)]
[SerializeField]
private int myVar;

So changing the name wouldn’t lose its value reference on the inspector. It would change just the «label».
Of course the attribute (FormerlySerializedAs) you have created works too!

I’m speaking out of some ignorance here, so apologies if this is a silly suggestion. It seems like this would be better if it somehow threw a warning on compile when developers are using a «formally serialized field». This is similar to when a method is deprecated, is it not?

This is great. As a suggestion, I would say that next time it might also be worth offering a way for user to write code to modify the behavior that is affecting them. In this case maybe being able to write callback script code during the serializing/deserializing would be more useful for people, especially since it would allow people to write Asset Store utilities to solve other problems you might not have noticed.

This is a really welcome feature. Nice job!

In the past I’ve done something like use OnValidate to check if data has been migrated to new version, but that required keeping tabs of things — and duplicate variables. Quite awkward.

BTW, how far can we stretch this? Would this work:

Before:

[System.Serializable] public class ItemA { public int value; }
public ItemA a;

After:

[System.Serializable] public class ItemB { public int value; }
[FormerlySerializedAs(«a»)]
public ItemB b;

that is, change the type as well where the new type is compatible with the old one. Because changing the type already works as long as the type is compatible.

Will something similar be done for MonoBehaviours inside assemblies?

Right now the fileID of MonoBehaviour inside assemblies is based somewhat on MD4 of the class name, so it is not possible to refactor them.

An attribute to set fileID explicitly would be very helpful.

Generally, I’d say that serialising based on assembly classes doesn’t inspire a lot of confidence.

To the point where I would indeed not mind having to be completely explicit about it. Unfortunately that has not been an option so far.

Agreed that this would be really useful. Not being able to rename classes inside custom-built DLLs (without losing script references in my scenes & prefabs) is currently the only big reason why I don’t use DLLs.

Comments are closed.