Unity не сериализует int? поле

У меня есть класс, свойства которого я хочу изменить в редакторе. Поэтому я сделал свой класс System.Serializable и сделал общедоступными переменные, которые я хочу изменить.
Вот так:

[System.Serializable]
public class UIOptionsRing
{
    public float Radius, DistanceBetweenPoints, StartOffset, GapInDegrees;
    public int? GapAfterElementNumer = 3; //this var doesnt show up
    public Vector3 CircleCenter;
    public GameObject CircleElementsContainer;

}

Но проблема, с которой я сталкиваюсь, заключается в том, что GapAfterElementNumer не отображается в редакторе во всех остальных полях. Как я могу сделать так, чтобы int? также отображалось?


person FutureCake    schedule 17.10.2018    source источник
comment
Если это работает только с обычным int, то, вероятно, это потому, что Unity не поддерживает сериализацию int?   -  person Vitulus    schedule 17.10.2018
comment
Я не думаю, что Unity может сериализовать пустые поля в инспекторе. Хотя я могу ошибаться.   -  person bolkay    schedule 17.10.2018


Ответы (3)


Типы, допускающие значение NULL, не сериализуются в редакторе Unity, поскольку его сериализатор не поддерживает null. Есть небольшой обходной путь, если вы не собираетесь сериализовать этот класс в json, используя JsonUtility. Основная идея заключается в том, что вы должны создать свой собственный обнуляемый int. Что-то вроде

public class IntNullable 
{
     public int Value;
     public bool HasValue;
 }

Так же, как это делается внутри .NET. Затем вы можете создать пользовательский редактор для IntNullable или вашего UIOptionsRing. В этом редакторе можно сделать поле для значения int и кнопку "Set Null", которая изменит значение переменной HasValue. И дальше вам нужно работать с этим кастомным IntNullable в вашем коде.

person vmchar    schedule 17.10.2018
comment
Возможно, было бы лучше назначить наибольшее значение int в качестве заполнителя для null, а в классе есть функция IsNull(). Это будет более эффективно, так как будет всего 4 байта, а не 5 байтов. (На самом деле логические значения занимают не один бит, а байт, а иногда и больше). - person Vitulus; 18.10.2018
comment
@CowNecromancer, это зависит от твоего вкуса. Когда вы используете int? в .NET или Unity вы по-прежнему работаете с 5 байтами. Использование вашего подхода означает наличие некоторого магического числа, которое вы должны иметь в виду, и 1 байт накладных расходов, я думаю, не имеет значения. - person vmchar; 18.10.2018
comment
Согласен, это не такая уж и проблема. Но в некоторых очень специфических случаях дополнительный байт может быть не нужен. - person Vitulus; 18.10.2018

Улучшение ответа vmchar, которое допускает нулевое назначение:

[Serializable]
public struct NullableInt
{
    public int Value;
    public bool HasValue;

    public NullableInt(int value)
    {
        Value = value;
        HasValue = true;
    }

    public static implicit operator NullableInt(int value) => new NullableInt(value);

    public static implicit operator NullableInt(NullableNull value) => new NullableInt();

    public static implicit operator int(NullableInt value) => value.Value;

    public static implicit operator int? (NullableInt value) => value.HasValue ? value.Value : new int?();
}

public sealed class NullableNull
{
    private NullableNull()
    { }
}
person Mark Bamford    schedule 15.03.2021

Unity не только не может отображать в инспекторе поля, допускающие значение NULL, но и не может их сериализовать. Чтобы поддерживать это, нам нужно сделать пользовательскую версию System.Nullable (как объясняет @vmchar), которая сериализуема, а затем предоставить ей ящик свойств. Сделать бесшовную замену System.Nullable не всегда очевидно, поэтому я включил этот пример. Это должна быть замена для nullable ( int? можно заменить на SN<int>, а все остальное должно работать из-за неявных приведений) вместе с базовым настраиваемым ящиком свойств.

using UnityEngine;
#if UNITY_EDITOR
  using UnityEditor;
#endif

/// <summary>
/// Serializable Nullable (SN) Does the same as C# System.Nullable, except it's an ordinary
/// serializable struct, allowing unity to serialize it and show it in the inspector.
/// </summary>
[System.Serializable]
public struct SN<T> where T : struct {
  public T Value { get {
    if (!HasValue)
      throw new System.InvalidOperationException("Serializable nullable object must have a value.");
    return v;
  } }

  public bool HasValue { get { return hasValue; } }

  [SerializeField]
  private T v;

  [SerializeField]
  private bool hasValue;

  public SN(bool hasValue, T v) {
    this.v = v;
    this.hasValue = hasValue;
  }

  private SN(T v) {
    this.v = v;
    this.hasValue = true;
  }

  public static implicit operator SN<T>(T value) {
    return new SN<T>(value);
  }

  public static implicit operator SN<T>(System.Nullable<T> value) {
    return value.HasValue ? new SN<T>(value.Value) : new SN<T>();
  }

  public static implicit operator System.Nullable<T>(SN<T> value) {
    return value.HasValue ? (T?)value.Value : null;
  }
}

#if UNITY_EDITOR
  [CustomPropertyDrawer(typeof(SN<>))]
  internal class SNDrawer : PropertyDrawer {

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
      EditorGUI.BeginProperty(position, label, property);

      // Draw label
      position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

      // Don't make child fields be indented
      var indent = EditorGUI.indentLevel;
      EditorGUI.indentLevel = 0;

      // Calculate rects
      var setRect = new Rect(position.x, position.y, 15, position.height);
      var consumed = setRect.width + 5;
      var valueRect = new Rect(position.x + consumed, position.y, position.width - consumed, position.height);

      // Draw fields - pass GUIContent.none to each so they are drawn without labels
      var hasValueProp = property.FindPropertyRelative("hasValue");
      EditorGUI.PropertyField(setRect, hasValueProp, GUIContent.none);
      bool guiEnabled = GUI.enabled;
      GUI.enabled = guiEnabled && hasValueProp.boolValue;
      EditorGUI.PropertyField(valueRect, property.FindPropertyRelative("v"), GUIContent.none);
      GUI.enabled = guiEnabled;

      // Set indent back to what it was
      EditorGUI.indentLevel = indent;

      EditorGUI.EndProperty();
    }
  }
#endif

Его производительность может быть ниже, чем у System.Nullable, но для большинства целей этого должно хватить. До сих пор он хорошо служил мне в Unity 2021.1 с включенным C# 4.

person Semimono    schedule 12.07.2021