- Home /
Drawing a property in the inspector whose type could be one of several custom classes
Suppose I have a class called TestClass
that looks like this:
[System.Serializable]
public class TestClass {
public int myInt = 0;
}
I also have two subclasses of TestClass
called TestSubclass1
and TestSubclass2
with their own unique properties.
All TestClass
types are serializable, made up of normal types like ints, strings, and GameObjects, and do not derive from MonoBehaviour or ScriptableObject.
( TestSubclass2
will need to reference a GameObject that only exists in the scene, so we can't make them ScriptableObjects).
Now let's make a TestComponent
that utilizes these types:
using UnityEngine;
public class TestComponent : MonoBehaviour {
public TestClass myProperty;
}
When we add the TestComponent
to an object, this is what the inspector gives us:
When set from the inspector, myProperty
can only be a TestClass
. Obviously, I didn't go to the trouble of making all these different classes for this. I want to make a custom editor that allows me to select between TestClass
and all its subtypes, and then set myProperty
to a new instance of the chosen type.
What's the best way to do this?
Answer by Bunny83 · Sep 27, 2019 at 02:21 AM
At the point
I also have two subclasses
I stopped reading because Unity's serialization system does not support inheritance for custom serializable classes. Read the documentation about script serialization carefully. Especially everything following the section
When might the serializer behave unexpectedly?
Note that components / MonoBehaviour derived classes do support inheritance. However custom serializable classes are treated like structs and are always serialized based on the field type. No type information is serialized. Just have a look at how the serialized data actually looks like. You can open your own scene file or prefab file in a text editor as well (as long as the editor serialization mode is set to force text which is the default now as far as I know).
Oof, looks like I'll be using a workaround similar to $$anonymous$$outon's answer. Thanks for the info.
Answer by Mouton · Sep 26, 2019 at 11:27 PM
You are adding complexity to a simple problem. What you want to achieve with inheritance can be easily solved with components. Components are small, mixable classes designed to have "Aggregation-oriented programming". Don't fear to use multiple components on your GameObject, it's okay. If you want to disallow to add 2 different components subclassing the same class, use [DisallowMultipleComponent]
attribute on the parent class.
Here is a simple implementation:
using UnityEngine;
[DisallowMultipleComponent]
public class TestClass : MonoBehaviour
{
public int myInt = 0;
public void DoSomething()
{
Debug.Log("Original method");
}
}
public class TestSubClassA : TestClass
{
// inherits myInt, so we don't need to repeat it
// overrides DoSomething method
public new void DoSomething()
{
Debug.Log("SubClass A method");
}
}
public class TestSubClassB : TestClass
{
// inherits myInt, so we don't need to repeat it
// overrides DoSomething method
public new void DoSomething()
{
Debug.Log("SubClass B method");
}
}
public class TestComponent : MonoBehaviour
{
private TestClass _component;
private void Start()
{
_component = GetComponent<TestClass>();
}
public void SomeMethod()
{
_component.DoSomething();
}
}
Of course you can use any overriding solution you want, you are not restricted to the new
keyword.
If however you want to stick with your solution, you will have to use a CustomInspector, with a Drop down menu to select the subclass and you will have to manager the Serializer stuff by yourself since you will create the instance dynamically and Unity don't handle that.
Thanks for the answer!
So, obviously what I posted was an abstraction of a considerably more complicated problem. In the real version, my equivalent of TestComponent
takes an array of TestClass
objects ins$$anonymous$$d of just a single one. Often, this array would be 20+ elements long, and adding that many components, though it would work, it would be super messy.
What I'm looking for is a more detailed version of your last sentence, preferably with examples. I've gotten as far as having a drop down menu to select the type and having the corresponding editor appear under it, but for some reason I can't get it to save the values to the target object, even if I use serializedObject.Update()
and EditorUtility.SetDirty()
. It always goes back to an empty instance of TestClass
after I leave the scene and come back.
Show your editor code. You want serializedObject.Apply$$anonymous$$odifiedProperties() I suspect.
I was using serializedObject.Apply$$anonymous$$odifiedProperties()
at the end of OnInspectorGUI()
. I don't currently have access to the code I was working on, but I'm trying to get an older version back up to where I was so I can post it here.
The instance is not saved because Unity cannot serialize dynamically allocated class instances. To be serialized, dynamically allocated instances must need to be attached to a GameObject or a Prefab, or derive from UnityEngine.Object
, which in your case is not. When you create the instance from the custom inspector, Unity create the instance in the current context, so your custom inspector looks like the class is serialized, but it isn't, the data will be wiped out on the next context change. To avoid that, you have to manage the serialization yourself, but it's not an easy task and goes out of the scope of the question.
Another solution would be to use Scriptable Objects, which inherits from UnityEngine.Object
Your answer
Follow this Question
Related Questions
CustomScroll inherited from RectScroll, executing in edit mode 0 Answers
Is there an event being fired off when the Inspector is being resized? 1 Answer
OnInspectorGUI - Using the default Object Selection popup. 1 Answer
How do I implement Draggable Properties with custom Labels in Editor or PropertyDrawer? 1 Answer
Create an Editor Window like Mecanim 1 Answer