Unity Doesnt Serialize int? field
Asked Answered
U

3

12

I have a class i want to change the properties of in the editor. So i made my class System.Serializable and made the variables public that i want to be able to change.
Like so:

[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;

}

But the problem i am having is that the GapAfterElementNumer is not show up in the editor at all the other fields are. How i can i make it so that int? also shows up?

Ulric answered 17/10, 2018 at 11:45 Comment(2)
If this works with just a normal "int" then it is probably because unity doesn't support serialization of "int?"Adenoid
I don't think Unity can serialise null fields in the inspector. I may be wrong though.Covetous
Y
9

Nullable types are not serialized in Unity Editor because it's serializer doesn't support null. There's a small workaround if you're not going to serialize this class to json using JsonUtility. The key idea is that you have to create your own nullable int. Something like

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

Just like it's done inside .NET. Then you can create a Custom Editor for IntNullable or your UIOptionsRing. In this editor you can make a filed for int value and a button "Set Null", which will change the value of HasValue variable. And further you need to work with this custom IntNullable in your code.

Yellowstone answered 17/10, 2018 at 12:28 Comment(3)
It might be better to assign the highest int value as a place holder for null, and the class has a IsNull() function. It will be more efficient as it will only be 4 bytes as opposed to 5 bytes. (Bools don't actually take up one bit, they take up a byte and sometimes even more).Adenoid
@CowNecromancer it depends on your taste. When you use int? in .NET or Unity you still operate with 5 bytes. Using your approach means having some magical number, which you have to keep in mind and 1 byte overhead, I think, isn't significantYellowstone
I agree, it's not that much of an issue. But, in some very specific cases the extra byte may not be wanted.Adenoid
F
3

Unity not only can't show nullable fields in the inspector, it cannot serialize them. In order to support this we need to make a custom version of System.Nullable (as @vmchar explains) that is serializable and then give it a property drawer. Making a seamless replacement for System.Nullable is not necessarily obvious, so I've included this example. It should be a drop in replacement for nullable ( int? can be replaced with SN<int> and all else should work due to the implicit casts) along with a basic custom property drawer.

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

It's performance may not be on par with System.Nullable, but it should be fine for most purposes. It's been serving me well so far in Unity 2021.1 with C# 4 enabled.

Fixity answered 12/7, 2021 at 3:55 Comment(1)
Note, that property drawer only works for single-line types. Things like int, Color, Vector3 should work, but the default property drawer for custom structs will be cut off.Fixity
R
0

An improvement on vmchar's answer, which allows null assignment:

[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()
    { }
}
Renascence answered 15/3, 2021 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.