Controlling ScriptableObject instantiation

ScriptableObjects become (unfortunately) necessary when you start developing editor tools. That, and every other class that inherits from UnityEngine.Object, has the unique ability to maintain proper references after deserialization. Every other class creates a copy per reference (as if it were a struct) after deserialization, which can be very troublesome.

One of the least pleasant aspects of ScriptableObject is that you cannot control their instantiation. Using their constructor to create them does not work as expected. The only proper way to create a ScriptableObject from scratch is to call the ScriptableObject.CreateInstance<DerivedType>() static method, where DerivedType is a type which derives from ScriptableObject. Unfortunately, This method does not take any parameters. I will show you an easy way to help control their instantiation, so that the users of your ScriptableObject classes will be less prone to use them incorrectly.

The method is actually very simple:

I call this the flag lock mechanism design pattern, and I’ve found this increasingly useful in Unity. The only way you can create a new ControllableObject  is via the two static New methods, which you can rename and/or overload with anything you wish.

Notice how I set the constructor to protected. Setting it to either private or protected is a good practice with ScriptableObjects, as no one should ever manually create instances of them via regular C# means. They should be actively prevented from doing so, and this is an easy way to ensure that it happens. The difference between protected and private, in this case, is that protected also allows our class to be inherited. So, one good practice to remember: Always implement the default constructor, and only that, and set its accessibility to either protected or private.

The reason why we call DestroyImmediate and not Destroy is that we’d want the instantiation methods to return a (fake) null value. Unfortunately, this value – like all destroyed UnityEngine.Objects – isn’t really null, so it can still be used afterwards. However, the user does get an error log following that, so any behaviour that follows it is undefined and the blame lies heavily on the user in the event that he keeps using it.

Finally, the reason why I’m logging an error instead of throwing an exception is that throwing an exception immediately after DestroyImmediate causes Unity to crash. Yikes!

Unfortunately, there is no easy way to control the destruction of a ScriptableObject. The only thing you can do is to log a warning (or an error) when the user tries to destroy it at inappropriate times.

Leave a Reply