Property Backing Field Drawer

I have some updates for asset store tools that have been delayed a bit, but just put up a new free one that hopefully some of you will find helpful. It’s a custom property drawer that allows you to automagically invoke your properties or Get/Set methods associated with serialized backing fields. If you’re excited, you can go ahead and download it right now! If you have no idea what I’m talking about, then the rest of this post will hopefully make you excited.

Among newcomers to Unity, there is often a conflation of public variables and what is exposed in the inspector. Consider the following script, for example:

public class Enemy : MonoBehaviour {
    public float health = 100f;
}

When you add the Enemy component to something, you’ll see an input field for “Health” in the inspector. Great! Now your designers can edit the health of enemies they place in the level. Unfortunately, I’ve also seen many a newcomer later decide they want to make a change, like this:

public class Enemy : MonoBehaviour {
    public float health = 50f;
}

Then, when they load up a level, they’re mystified that the enemies still have 100 health. The important point here is that the values being displayed and set in the inspector represent serialized data. I’m not going to harp on the distinction too much (not least because I’m sure someone more particular will point out why I’m not exactly correct), but the basic idea is that the serialized data are saved with your prefabs and scenes, and then loaded when the objects are loaded at run-time. By default, all public fields on MonoBehaviours (or ScriptableObjects) are serialized if they can be. Most basic types and some of Unity’s types can be serialized, and your own types can be as well by adding the System.Serializable attribute to them. Specifically, any of their serializable fields will be serialized. (For more info, see the documentation for SerializeField.)

Making all your fields public just so they show up in the inspector is dangerous though! You want to think about making your code safe as much as you want to make things designer friendly. Suppose you wanted a constraint on your health. You would of course set up a property to access your health:

public class Enemy : MonoBehaviour {
    [SerializeField]
    private float m_Health = 100f;
    public float Health {
        get { return m_Health; }
        set { m_Health = Mathf.Clamp(value, 0f, 100f); }
    }
}

Note that when you have a private field you want to serialize, such as the backing field for the Health property here, you can force Unity to serialize it by using the SerializeField attribute.

Great! Now you have safer, standardized code, and a field that your designers can edit in the inspector. Unfortunately, because your designers are editing the serialized field directly, they never trigger the property setter’s logic, and so the value they set could be too low (below 0) or too high (above 100). If you want to deal with this situation, you have a couple of options. Traditionally, you would have to create a custom editor that essentially replicates your setter’s logic or calls the setter directly:

[CustomEditor(typeof(Enemy))]
public class EnemyEditor : Editor {
    public override void OnInspectorGUI() {
        serializedObject.Update();
        SerializedProperty health = serializedObject.FindProperty("m_Health");
        EditorGUILayout.PropertyField(health);
        health.floatValue = Mathf.Clamp(health.floatValue, 0f, 100f);
        serializedObject.ApplyModifiedProperties();
    }
}

Thankfully, in the 4.x cycle, Unity added custom property drawers, so you can simply create a custom editor GUI input field without having to create an entire custom editor class, which you then must keep in sync with any changes to fields you might make. In our example, we could use the builtin RangeDrawer by adding the RangeAttribute to the field:

public class Enemy : MonoBehaviour {
    [SerializeField, Range(0f, 100f)]
    private float m_Health = 100f;
    public float Health {
        get { return m_Health; }
        set { m_Health = Mathf.Clamp(value, 0f, 100f); }
    }
}

In short, you can define custom property drawers that are associated with a certain field type, or that are associated with some particular PropertyAttribute that you define. PropertyDrawers are great for both creating rich editor GUIs as well as implementing basic constraints. However, apart from the issue of having to synchronize your property logic in two places for this example, what if your property setter rebuilds some kind of internal data?

public class Enemy : MonoBehaviour {
    [SerializeField, Range(0f, 100f)]
    private float m_Health = 100f;
    public float Health {
        get { return m_Health; }
        set {
            m_Health = Mathf.Clamp(value, 0f, 100f);
            // something else more complex happens here!
        }
    }
}

What can you do then to make sure all of your data stay in sync? You could go the custom editor route and call your property setter on your target object if there is a change, but that can be a bear to maintain, as I already mentioned. You could also just invoke your property setter in your Start() method or something similar (e.g., Awake(), OnEnable(), or whatever makes sense), but you might lose the potential benefit of being able to serialize whatever those other data are, and not having to rebuild them at run-time (particularly if they are time-consuming to generate). In this regard, the problem I set out to solve was to create a solution that would:

  • Not require the creation of any custom editors
  • Not require the maintenance of setter logic in multiple places
  • Be easy to implement on an as-needed basis
  • Be generalizable for any type of property setter

The solution I came up with is a custom PropertyAttribute/PropertyDrawer solution. Using my tool, the previous example would simply be written in the following way:

public class Enemy : MonoBehaviour {
    [SerializeField, Candlelight.PropertyBackingField(typeof(Enemy), "Health")]
    private float m_Health = 100f;
    public float Health {
        get { return m_Health; }
        set {
            m_Health = Mathf.Clamp(value, 0f, 100f);
            // something else more complex happens here!
        }
    }
}

When the PropertyBackingField attribute is created, it uses the supplied type and property name to locate the PropertyInfo, which is then ultimately used in the custom PropertyBackingFieldDrawer class. The system works similarly for array properties:

public class Enemy : MonoBehaviour {
    // ...
    [SerializeField, Candlelight.PropertyBackingField(typeof(Enemy), "Targets")]
    private Hero[] m_Targets = new Hero[0];
    public Hero[] GetTargets() {
        return (Hero[])m_Targets.Clone();
    }
    public void SetTargets(Hero[] targets) {
        m_Targets = (Hero[])targets.Clone();
    }
}

In this case, the PropertyBackingField attribute instead searches for methods named “GetTargets” and “SetTargets”, but otherwise ultimately works the same.

What if you have some kind of fancy drawer you like to use already? For example, you might already have a custom PropertyDrawer, TextureThumbnailDrawer, that displays a texture thumbnail by your field. My PropertyBackingFieldDrawer is also set up to allow you to specify an override drawer to use. (In fact, its underlying mechanics will automatically use whatever drawer is associated with the field type being drawn.) Assuming, in this example, you have a TextureThumbnailAttribute whose constructor takes x and y dimensions (e.g., TextureThumbnail(16f, 16f)), you can specify it as an override drawer by appending the type and any arguments its constructor takes:

public class Enemy : MonoBehaviour {
    // ...
    [SerializeField, Candlelight.PropertyBackingField(
        typeof(Enemy), "BannerTexture",
        typeof(TextureThumbnailAttribute), 16f, 16f
    )]
    private Texture2D m_BannerTexture = null;
    public Texture2D BannerTexture {
        get { return m_BannerTexture; }
        set { m_BannerTexture = value; }
    }
}

Assuming your properties don’t do super crazy things, it automatically works with undo as well. For example, if your property is on a MonoBehaviour (as opposed to, e.g., a ScriptableObject), it will record changes to any objects in its hierarchy. There are a couple of other minor limitations noted in the documentation, but for the most part is is designed to “just work.” If you download it and give it a try, I’d love any feedback you have or any changes you feel are warranted.

Download Property Backing Field Drawer right now!

9 thoughts on “Property Backing Field Drawer”

  1. Awesome— seems to work quite well, and another blatant Unity shortcoming fixed nicely. Thank you for your work.

    I notice one interesting deviation from standard C# OOP when using this — which is probably still because Unity throws C#’s access model wildly into the wind — that I’m wondering if you could talk about, since I’m sure you’re also aware of this.

    The issue is that the backing instance variable has already changed to the new value by the time the setter method or property is called— the value isn’t the new proposed value, it’ll always match your backing var.

    Adjusting one of your examples:


    public float Health {
    get { return m_Health; }
    set {
    if (m_Health == value)
    return; // issue: will always return here

    m_Health = Mathf.Clamp(value, 0f, 100f);

    // Some expensive re-calculations of other variables happen here.
    // Lets pretend it's re-calculating a Mesh or dynamically painting a Texture2D or something like that.
    }
    }

    In the example above, the initial no-change-condition return will always get called and the rest of the setter will always get skipped— one can’t effectively use property for change checking, which is one of the core reasons setters are used in OOP.

    I’ve found a solid workaround— but it isn’t pretty:


    /// Solely for Unity Editor-access; will contain the editor-updated value after change.
    /// Serialized value here is irrelevant.
    [Candlelight.PropertyBackingField(typeof(PropertyBackingFieldTest2), "Health")]
    public float m_Health = 100f;

    /// The authorative data store for Health.
    private float m_HealthStore = 100f;

    public float Health {
    get { return m_HealthStore; }
    set {
    if (m_HealthStore == value)
    return; // issue: will always return here

    m_HealthStore = Mathf.Clamp(value, 0f, 100f);

    m_Health = m_HealthStore; // sync updated value to Editor
    }
    }

    This usage approach requires two serialized fields:  The first, m_Health is serialized only so that it shows up in Unity; this is tied to the editor’s value and is only public/serialized to trick Unity into showing it in the inspector.  We could keep it from showing up in builds by wrapping it with `#ifdef UNITY_EDITOR` but it’ll still be serialized separately to scenes and prefabs, so it’s redundant either way.  The other field, m_HealthStore is the actual private data store for the class— it’s only updated when we update it.  The setter checks this against the value (per normal OOP) and only touches m_Health at the very end, when it simply updates it to what it’s settled m_HealthStore on.

    While the above works… it’s messy, and a lot of typing.  I would hope that if this is the best we can do against Unity, we could at least wrap it up a little more neatly.  Maybe a pure-Attribute approach wouldn’t work as well as once that involves a custom type like PropertyField, which would be a pain in its own right but still could circumvent a lot of this typing.

    I also discovered on the Unity wiki another approach to expose the property directly ( http://wiki.unity3d.com/index.php?title=Expose_properties_in_inspector ), though this seems to have disadvantage of drawing all the exposed properties after the rest of the fields.  It’s also a more-cumbersome setup, requiring a custom Editor for each containing class in order to override OnInspectorGUI().

    So far, my approach has been to just keep two copies of every variable I need to change-check: one private one to store the last value, and one public one for Unity.  Then I offload any expensive operations until the value is needed by other code— and do the change-check in a property getter.  Something like this (hand-written for each class, sadly not encapsulated):


    private float m_LastHealth;
    public float m_Health = 100f;

    void Awake()
    {
    m_LastHealth = float.NaN;
    }

    public float HealthSantized {
    get {
    if (m_Health != m_LastHealth) {
    m_Health = Mathf.Clamp(m_Health, 0f, 100f);

    // run important change-check updates here
    }

    m_LastHealth = m_Health;
    return m_Health;
    }
    set { m_Health = value; }
    }

    void SomeOtherMethod()
    {
    // …
    float blah = this.HealthSantized;
    // …
    }

    It’s a pain and a little wasteful, but works flawlessly.

    Thoughts?  Could PropertyBackingField be improved?  Am I missing something and just making a stupid mistake with the usage of PropertyBackingField?  Merci.

  2. Crap.  Looks like those code samples lost their indentation when posting.  Here they are again, with  s instead of tabs.

    Issue Sample:


        public float Health {
            get { return m_Health; }
            set {
                if (m_Health == value)
                    return; // issue: will always return here
                
                m_Health = Mathf.Clamp(value, 0f, 100f);
                
                // Some expensive re-calculations of other variables happen here.
                // Lets pretend it's re-calculating a Mesh or dynamically painting a Texture2D or something like that.
            }
        }

    Workaround Example:


        /// Solely for Unity Editor-access; will contain the editor-updated value after change.
        /// Serialized value here is irrelevant.
        [Candlelight.PropertyBackingField(typeof(PropertyBackingFieldTest2), "Health")]
        public float m_Health = 100f;
        
        /// The authorative data store for Health.
        private float m_HealthStore = 100f;
        
        public float Health {
            get { return m_HealthStore; }
            set {
                if (m_HealthStore == value)
                    return; // issue: will always return here
                
                m_HealthStore = Mathf.Clamp(value, 0f, 100f);
                
                m_Health = m_HealthStore; // sync updated value to Editor
            }
        }

    Manual Approach Example:


        private float m_LastHealth;
        public float m_Health = 100f;
        
        void Awake()
        {
            m_LastHealth = float.NaN;
        }
        
        public float HealthSantized {
            get {
                if (m_Health != m_LastHealth) {
                    m_Health = Mathf.Clamp(m_Health, 0f, 100f);
                    
                    // run important change-check updates here
                }
                
                m_LastHealth = m_Health;
                return m_Health;
            }
            set { m_Health = value; }
        }
        
        void SomeOtherMethod()
        {
            // …
            float blah = this.HealthSantized;
            // …
        }

  3. Hi! You’re not missing anything. This is actually a documented limitation right now (see the Readme.txt in the package, as well as my BasicPropertySetterExample.cs; the current recommendation is to simply bypass this behavior while running in the editor).

    That said, it’s just an implementation detail and something that will be improved in a future update. Basically, when you change the value of a SerializedProperty in Unity, that value is not flushed to the field until you call serializedObject.ApplyModifiedProperties(). Otherwise, you are accessing a temporary value stored in the SerializedProperty object. Right now, I apply modified properties (and thus flush the changes to the backing field) before the setter is invoked, which means its value will always be the same as the incoming value when the setter is called. The reason I do this is because when I obtain the value to assign to the setter, I am either using a) the getter method or b) reflection to access the field value, as opposed to what is in the SerializedProperty’s temporary storage. Otherwise, for non-basic object types, I need to basically construct a new instance and set up its fields. It’s something I will get to eventually.

  4. Your approach is clever, but doesn’t OnValidate() do the same trick?

    [SerializeField,Range(0,1)]
    float m_Percent;

    public float Percent
    {
    get { return m_Percent;}
    set
    {
    m_Percent=Mathf.Clamp01(value);
    }
    }

    #if UNITY_EDITOR
    void OnValidate()
    {
    Percent=m_Percent;
    }
    #endif

  5. Hi Jake,

    If all you’re doing is simple data validation, then possibly (assuming you don’t mind managing the extra code).

    One main problem that can arise though is if your setter only executes when the incoming value and the backing field value differ. In OnValidate(), they’ll always be the same.

    Another benefit of the PropertyBackingFieldDrawer is that you can rely on your test suite to ensure the integrity of inspector functionality. For example, one problem that can arise via OnValidate() is that you’re responsible for determining what, if anything changed, which can be a problem if you have properties that rely on one another. In your example, if the upper limit for the value clamp were based on some other property’s value, you’d need to make sure it was handled first in OnValidate(). In more complex situations, it’s easy to introduce errors that only exist in OnValidate(). With the PropertyBackingFieldAttribute, only the setter corresponding to the backing field whose value changed is invoked, and so there’s no need to manage any additional states or temporal aspects of execution.

    Another benefit is that the PropertyBackingFieldDrawer will monitor serialized properties on child objects. In the new GUI system, for example, you may have a model component on a top-level game object, and the display of its values is handled via a number of Text and Image components in its hierarchy. If you invoke your setter in OnValidate() to propagate value changes to your GUI components, they will update, but their fields are not marked as dirty, and hence would not be saved with the scene. The PropertyBackingFieldDrawer will correctly mark their fields as dirty.

  6. Hi does this works in latest Unity BETA? I’m having some errors related to platform defines in Build Settings.

  7. Hi Peter,

    To the best of my knowledge those messages are benign and can be safely ignored. (They come up when e.g., registering compilation symbols for platforms that you do not have installed, such as potentially tvOS.) That said, I have been bugging Unity about it for about 4 months and have yet to get a straight answer.

Leave a Reply

Your email address will not be published. Required fields are marked *