Skip to content

Instantly share code, notes, and snippets.

@tarocco
Last active March 11, 2022 05:05
Show Gist options
  • Save tarocco/639a7ef738a8c08d01b84ebd15c762c7 to your computer and use it in GitHub Desktop.
Save tarocco/639a7ef738a8c08d01b84ebd15c762c7 to your computer and use it in GitHub Desktop.
Best practices for making your serialized classes code & editor-friendly!

Unity Encapsulation Tutorial

by Tarocco

Fields and Properties

Unity does not serialize properties or any static members. You have some options for serializing member fields.

Field-only

  • Public fields of serializable types are automatically serialized and show in the Unity Editor
  • Non-public fields of serializable types are only serialized when using the [SerializeField] attribute

Here are some examples of different data-hiding options:

public int ClipsRemaining; // Serialized, shown in the inspector editor window, and accessible to other objects

[HideInInspector]
public int AmmoCount; // Serialized, hidden in the inspector editor window, and accessible to other objects

[SerializeField]
private float BulletSpeed; // Serialized, shown in the inspector editor window, and inaccessible from other objects

[SerializeField]
[HideInInspector]
private float FireRate; // Serialized, hidden from the inspector editor window, and inaccessible from other objects

private float FlashBrightness; // Non-serialized, hidden from the inspector editor window, and accessible from other objects

[NonSerialized]
[HideInInspector]
public float Holdover; // Non-serialized, hidden from the inspector editor window, and accessible to other objects

// There are no combinations for Non-serialized + shown in the inspector editor window

Properties with Backing Fields

Unity does not serialize auto-properties. Use the property backing field pattern to make properties with serialized field values. My recommendation for backing field nomenclature is to use a prefix underscore.

[SerializeField]
private float _Speed = 5f;

public float Speed
{
  get { return _Speed; }
  set { _Speed = Mathf.Max(0f, value); } // Only positive values allowed
}

By having the declaring class implement the ISerializationCallbackReceiver interface, it is possible to define constraints, or better yet, use polymorphism in the Unity Editor with editor-side preconditions.

IFooBarLike.cs

public interface IFooBarLike
{
  int Foos { get; set; }
  int Bars { get; set; }
}

FooBarBazBongo.cs

using UnityEngine;

public class FooBarBazBongo : MonoBehavior, IFooBarLike
{
  [SerializeField]
  private int _Foos = 1;
  public int Foos { get { return _Foos; } set { _Foos = value } }
  
  [SerializeField]
  private int _Bars = 1;
  public int Bars { get { return _Bars; } set { _Bars = Mathf.Max(Foos, _value) } }
}

MyBehavior.cs

using UnityEngine;

public class MyBehavior : MonoBehaviour, ISerializationCallbackReceiver
{
  [SerializeField]
  private MonoBehaviour _FooBar;
  public IFooBarLike FooBar
  {
    get { return (IFooBarLike)_FooBar; }
    private set { _FooBar = (MonoBehaviour)value; }
  }
  
  public void OnBeforeSerialize()
  {
    // "Round-trip" properties to enforce preconditions
    // during serialization in the Unity Editor
    FooBar = FooBar;
  }
  
  public void OnAfterSerialize() { } // A vestigial organ
}

This allows you to have inspector editor fields that can be assigned to any MonoBehaviour as long as it implements the IFooBarLike interface, and is represented with the correct interface type by the property, accessible by other objects. The get accessor is public, but the set accessor has been set private. This is an advantage of using properties instead of plain fields.

Events

To allow Unity to serialize events in the editor, (and conveniently show them in the inspector editor window), you can use the property backing field technique. Nope that you will need to use the [Serializable] attribute on your UnityEvent-derived class.

HopEvent.cs

using System;
using UnityEngine;
using UnityEngine.Events;

public class HopEventArgs : EventArgs
{
    public Vector3 Impulse;
}

[Serializable]
public class HopEvent : UnityEvent<object, HopEventArgs> { }

MyBunny.cs

using UnityEngine;
using UnityEngine.Events;

public class MyBunny : MonoBehaviour
{
    [SerializeField]
    private HopEvent _Hop;

    public event UnityAction<object, HopEventArgs> Hop
    {
        add { _Hop.AddListener(value); }
        remove { _Hop.RemoveListener(value); }
    }
}

Now if you have a script to handle the behavior of bunny ears when the bunny hops...

BunnyEar.cs

using UnityEngine;

public class BunnyEar : MonoBehaviour
{
    public Transform[] WiggleBones;

    public void HandleHop(object sender, HopEventArgs args)
    {
        // Do something
    }
}

...you can assign the HandleHop methods to the Hop event in the Unity Editor as a UnityEvent, or at runtime as a regular C# event!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment