FutureCake
FutureCake

Reputation: 2943

Unity Doesnt Serialize int? field

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?

Upvotes: 12

Views: 7135

Answers (3)

Semimono
Semimono

Reputation: 724

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.

Upvotes: 3

Mark Bamford
Mark Bamford

Reputation: 123

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()
    { }
}

Upvotes: 0

vmchar
vmchar

Reputation: 1323

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.

Upvotes: 9

Related Questions