An Arsenal of Editor Tooling

Posted on

Tech

Hi, I’m Al and I’m one of the Senior Engineers at Hutch. As a programmer I’m always looking for ways to provide artists, designers and even other programmers with more, for less. If you’ve ever had to make a whole new editor assembly for a single custom inspector where you slightly change the colour of a label, you’ll know what I mean!

And so I approached improvements to Unity’s editor tooling with the same desire for efficiency. Keep reading to find out more, or if you want to jump straight into the solutions, all code mentioned is freely available to download at the Hutch Public Github.

Attribute Driven Inspectors

Unity supports usage of Attributes on fields to customise how things are drawn in the Inspector - you may have met Space or Header before, you may not. These are examples of Property and Decorator Drawers, and you can register your own to customise globally how your data is drawn by adding them to your serialized fields or registering them for a type - give ‘em a go!

OK, I’m assuming you’ve given them a go and realised they’re the worst, best thing ever (as opposed to the best, worst thing ever which is, of course: ‘Picholas Cage’, Google it):

Pros

  • No need for custom inspectors for basic organisation

  • More obvious what is responsible for drawing data

Cons

  • Not many implemented by Unity, so limited functionality available

  • Unity only supports ONE property drawer per field

The last con may not seem like a biggy, but it is - rather than creating modular attributes you could use to build up complex inspector functionality, you can choose only one, leading naturally to horrible inheritance based amalgamations.

Also, any list or array field will ignore your property drawers (although still respects decorator drawers) because… reasons. God knows it took long enough for us to get sensible List Inspector tools, but it’s still pretty limited.

All in all, you’ll probably sprinkle them around a bit, but have to resort back to heavy duty custom inspectors which is gonna mean a load of boilerplate and messing around with Serialized Properties.

Luckily, there’s a solution; we can make our own attribute framework and piggy back off a lot of Unity’s great editor API. Everything will revolve around a single attribute class which is going to unlock a lot of power: The Multi Attribute Property.

A lot to break down here, so let’s go (almost) line by line:

  • This class will have to live in our runtime assembly because we’ll be decorating fields in runtime classes

  • Since this is about how data is drawn in the editor, we’ve had to wrap almost all of it in a UNITY_EDITOR preprocessor directive to avoid issues when building our game. We leave just the class to keep the compiler happy, but will compile out almost everything else

  • We have this random list called ‘stored’ living in our class - it’ll become clearer later what this is for, but essentially it’s for optimisation

  • We make sure it can only be applied to fields right now and derive from Unity’s PropertyAttribute class to make sure it’ll pick this up as a custom property attribute

  • We have a bunch of GUI related functions, some of which are abstract, others of which are virtual with sensible defaults. Derived attributes can/should implement these as their needs arise

Next, we’ll need a custom property drawer to draw our custom property attribute: The Multi Property Drawer.

A lot to break down again here, so let’s do it!

  • This has to live in an Editor assembly, it’s strictly Editor time!

  • We mark this as a custom property drawer for our attribute using [CustomPropertyDrawer(typeof(MultiPropertyAttribute), true)] and ensure it will be used for all derived classes too. This is the ONLY property drawer class we’ll ever need for these attributes - they will draw themselves

  • How this property drawer works is not obvious, but we’re actually utilising Unity’s limitation of only drawing one custom property drawer per field. If I had multiple of my own multi property attributes on a field, Unity would use this drawer to draw one (it doesn’t really matter which), but we then grab all of them ourselves. This is crucial and you’ll see us using iterating through them all over the class

  • RetrieveAttributes used reflection to go through our field and grab ALL our multi property attributes and store them in the ‘stored’ list I mentioned earlier. This is our optimisation, to avoid constantly using reflection every time we draw our field

  • We override the two functions you always need to override with Property Drawers: GetPropertyHeight and OnGUI. Their bark is a lot worse than their bite, I promise!

  • GetPropertyHeight will ask all our custom attributes which are visible, then add up the total height required to draw them all. We return this, tricking Unity into thinking we’re drawing one massive property. Hehehehehehehehe

  • OnGUI calls OnPreGUI, the OnGUI and OnPostGUI for all visible attributes. This allows us to do things like adjust indentation, colour etc. draw a field, then reset our changes afterwards

If most or all of that went over your head, that’s fine, it’s not important, the TLDR: Magic. You can plop both the attribute and property drawer classes in your project (in their respective assemblies) and not give them a huge amount more thought. However, we’re not even at the best bit yet, let’s look at an example of an attribute I find very useful: The Inline Data In Inspector Attribute.

Again, a few things worthy of note before we dive into what this is doing:

  • Derive from MultiPropertyAttribute. This will give us access to all the methods we’ll need for customising the GUI, as well as having this be picked up by our custom drawing system

  • Create this class in your runtime assembly, even though it’s for Editor GUI. Runtime classes will need to access these attributes

  • Surround all the business logic in a UNITY_EDITOR preprocessor directive to avoid compiler errors when building your project

You know how structs and classes living in other classes are drawn with a foldout in the Inspector?

Sometimes it can be really annoying, especially for structs which represent settings, so you can write an attribute to circumvent this (the code linked above!). Simply add the above InlineDataInInspector attribute on the appropriate serialized field and its data will be drawn inline in the Inspector, rather than hidden in a foldout:

The above code can seem overwhelming, there’s a lot of Editor GUI wrangling, but, in truth, it figures out the total height of all properties it needs to draw, then goes through and draws them inline.

The message is clear: move away from boiler plate custom inspectors, try and leverage more generic attributes. Before long, you’ll have built up an arsenal of day-to-day Editor GUI massaging you’ll want to do to your data and deriving from the MultiPropertyAttribute should give you the power to do it. This won’t banish every custom inspector from your project, but should help you improve the overall experience of modifying data with a lot less of the work.

Homework

Here’s some stuff I’ve found useful. Go and implement it!

  • Disable Editor GUI at runtime (aka if Application.isPlaying is true, prevent editing)

  • Show a property if a serialized bool property is true e.g. I have a flag overrideValue and if this is true, I want to show value in the inspector, otherwise hide it.

  • Show a property if a serialized enum property has a particular value (aka same as above, but comparing int values - you’ll need to cast the enum value to an int)

  • Modifying the colour of the GUI for a property

  • Displaying a string property in a text area, with a button for formatting it. The most common example of this is for JSON - I would like the button to prettify the string value, assuming it represents JSON (you’ll need to research what JSON Unity API let’s you do this)

  • Displaying a helpbox with a message if a particular validation function fails (ok, this one is hard. You’ll need to pass the validation function in by name, then call it using reflection in the property drawer. Make sure the function signature is sensible and think about how to optimise some of the reflection API calls)

Github

All code here is freely available to download at the Hutch Public Github. Please do so! And if you get stuck, you’ll also find implementations there too.

Look out for more tech blog posts coming soon! Or bookmark the Hutch tech blog here: hutch.io/blog/tech/

And for our latest tech job vacancies at Hutch, head here: hutch.io/careers.



Best Places To Work 2023
Best Places To Work 2022
Best Places to Work 2021
Best Places to Work 2020
Tech Track 100