Search Unity

Некоторые из вас, возможно, знают, что мы создали большую часть новой системы 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 этот “контроль” невозможен. В этом случае вам придется оставить это атрибут до тех пор, пока вы хотите, чтобы люди с любой версией кода могли обновиться до новой версии кода без потери данных.

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

Комментарии закрыты.

  1. Steffano (Stiffix)

    Март 19, 2015 в 11:33 пп

    I can’t say enough how many times I needed it. Thank you

  2. Thanks, this is awesome addition! Every Asset Store developer should keep it in mind.

  3. 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?

  4. Excellent new feature. Ohh, how can I do not keep loving the Unity? :D

  5. Very useful small feature indeed !

  6. [FormerlySerializedAs(«Prince»)]
    [SerializeField]
    private string TheArtist;

    Seriously useful feature!

  7. 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.

  8. 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!

  9. 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)

    1. I think you could implement this yourself if you use OnAfterDeserialize and OnBeforeDeserialize. You would need to keep your old serialized fields around until all the data was converted but I think you could do it.

      Either that or live dangerously by forcing all your assets to text serialization and then process the text assets to your new format.

      http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html

      1. 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…

  10. 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.

    1. I don’t really understand what you mean. Can you explain a bit more in detail what you would like to see?

  11. 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!

  12. 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?

    1. Not really, this attribute enables developers to change field names in their code, without losing data, giving our users more flexibility to refactor their code.

  13. 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.

    1. I think we have something very similar already. I’m not sure if it’s exactly what you want but you can get really far with: ISerializationCallbackReceiver.OnBeforeSerialize.html

      I would also recommend reading this blog post

      1. Thanks for the links!

  14. Also, which Unity version is this going to be in? I hope in 4.6…

    1. It’s already in there :)

  15. 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.

    1. Yes, that would work just fine :)
      I guess the post is not clear on this but yeah, this is in 4.6 already!

  16. Nice.

    Just went back to my own blog post on Unity attribute (http://www.tallior.com/unity-attributes/) this seems to have been there already when I wrote it (in 4.6 RC2).

    Although the name is pretty bad to my taste :)

  17. 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.

    1. Emil "AngryAnt" Johansen

      Февраль 3, 2015 в 4:56 пп

      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.

      1. I agree that the ability to specify the a deprecated ID of a class in a dll or old name would be very useful.

    2. 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.