The Wrong Null!
tl;dr:
- You can check whether a Unity.Object (including Component, GameObject, MonoBehaviour, etc.) is null using
if (variableName)
, because it has an implicit bool operator that checks != null. - If you’re trying to expose generic-typed properties in the inspector, you should constrain them with
where T : Component
or similar, or strange things may happen when you check whether they’re assigned. - I probably shouldn’t rely on generics so much. It’ll haunt me if I ever need to build for mobile or console.
And now the post…
When I worked on RuneScape, we had various jokes about the ‘other null’. The in-house scripting language wasn’t always consistent in its handling of nulls and uninitialised values and the Engine team were too busy to explain it, leading some of the data types to behave a little strangely, at least to some scripters’ intuitions.
This morning I’ve been fighting with the equivalent uncertainties in Unity. You may be interested in the results.
if (thing)
I’d noticed before that you can sometimes get away with checking for non-null-ness with
if (variableName) ...
This doesn’t work on all types, and even after discussing it with a couple of people I still had no idea which ones. At the time I didn’t look into it much further.
Having since learned a bunch more about C#, it occurred to me today that those classes must have an implicit bool operator that performs the null check for you. And lo, here it is, inherited from UnityEngine.Object.
The Wrong Null
I’ve got a reusable class for a meter- or progress-bar. It’s a bit lighter than using a hacked-up Slider for the job, and I added optional lag on value change, using an extra image either behind the first (for going down) or in front of it (for going up). My original attempt managed the bar size by stretching it with its RectTransform; I had a hard time imagining a reasonable use case for non-images, but since RectTransform was all I needed that was the type I exposed for the inspector properties.
The optionality of the lag is easily implemented: just use the nullness of one of the inspector properties (a nullable one; not int etc.) as a check for wrapping. I was unknowingly using the implicit bool operator, above.
Then I needed a version that worked without squishing the image (I was trying to fill the inside of a non-rectangular parent image). The Image class has parameters to control that kind of fill, so the new version of the bar has Image as the type of its bar variables. These two bar classes share a lot of code, but I was in too much of a hurry to resolve those basic variables into a shared base class, so I just copied and edited the file.
Today I came back to them, wanting to add more functionality. I don’t want to write (and maintain) it twice, so I set about moving duplicated code into a common base. Initially I tried to write around these typed members, but eventually I decided to make the base class generic for the type that its bars should be.
A while later, all the duplicated code is in the base class, and only two methods need overriding by the children. A job well done. Then the problems start:
- My null checks no longer parse, because there isn’t a cast from T to bool. I rewrite them as explicit != expressions.
- I run it, and get UnassignedReferenceExceptions for the optional properties, from lines that are inside the null checks. I throw in some debug, and find that unassigned Images are null but unassigned RectTransforms are in some kind of unassigned empty state that nonetheless is != null.
- I search the web, and huff and sigh a lot.
I also notice at this stage that one of the other properties – an enum – is showing up in the inspector as an int. More about that later.
After a while of useless searching (and I assume therefore that the huffing and sighing was what solved it), I go back to the generic base and add
where T : Component
By this stage I’m guessing that the bool operator belongs to Component or higher (it’s only when I started writing this that it occurred to me to look it up), so that might be what fixes it. I change the != null checks back to implicit bool ones, and it all works.
Thing is, I hadn’t yet removed my debug, and it showed me something odd. The unassigned Image had always behaved properly when checked for null, but the RectTransform that had previously misbehaved was now also playing ball. So not only does RectTransform behave differently to Image when unassigned in an inspector field, but RectTransform as explicitly derived from Component behaves different to RectTransform free of constraints.
About Inner things
The original class had an enum defined outside it. A variable of that type was exposed to the inspector in that class, and then another in the other class. It all worked fine, obviously.
When I was tidying these classes I moved the enum into the base class. I’m now much more comfortable with inner classes/structs/enums than I used to be, and I like the way they reduce namespace clutter.
But then I noticed that the inspector for a component of the derived class was drawing the enum property as though it were an int. Not very helpful. I put the enum back outside the classes, and it went back to normal.
Now I’ve definitely exposed properties of an inner class type in the inspector, and I’m pretty sure I must have done it with an inner enum. I think what it objects to is using an inner thing from another class, even though it’s this class’s base.
Some things that do work with inner things:
- They’re a great way of getting name-value-pairs or other structure data into the inspector (since Unity doesn’t like serialising dictionaries). Make sure the inner class has the [System.Serializable] attribute; Unity needs to serialise things in order to show them in the inspector. You can foreach the array of inner classes during Awake() to fill a runtime dictionary.
- They’re ugly as hell in the inspector, but that’s easily fixed with property drawers. Even when applied with an attribute, property drawers (since about 4.3) affect each element of an array rather than the array itself. It’s much more hassle to give the array custom drawing, but once you’ve made the presentation of the class as compact as possible, the array will be much less cumbersome.
- You can use the CustomPropertyDrawer attribute to bind a property drawer to an inner class, you just need to qualify it properly (as you’d expect to). I don’t think (but haven’t extensively investigated) that you can use it on a struct; this may be a side-effect of how SerializedObject works. But see if you can generalise your property drawer for attribute use, by putting parameters in the attribute class. (I’ve got some examples of this that I’m pretty pleased with; I could show you them another day.)
A Caveat
I like generics. My current project is distinctively PC: it lends itself to mouse/keyboard controls and its potential market is mostly on Windows or Linux.
If you’re working on mobile or console, you should read up on AOT before you commit to generics (unfortunately the most useful source I can currently find is a small question halfway down this FAQ), because there are some restrictions that I’ve never tried dealing with.
Sooner or later I’m going to want to build something for mobile or console. I only hope that my reusable library full of elegant generics doesn’t come back to haunt me when I do.
In any case, you’ll probably need to use generic base classes and make sure that your derived MonoBehaviours are non-generic, because Unity doesn’t like generics as much as I do.