- Home /
Custom Inspector - choosing subtype for a field
I'm trying to set up a custom inspector that lets me choose and expose subtypes of a custom class like how the default inspector does for Serializable classes.
I set up a ZooEnclosure
class with an Animal animalInside
variable. Animal is an abstract base; what I want to do is pick the concrete implementations Giraffe
or Dog
from a drop-down list in ZooEnclosure
's inspector, and then be able to edit the subclass' specific variables.
I screwed around with making the Animal classes ScriptableObjects, and with the Serializable and SerializeField attributes, but didn't manage to get it working; in most cases, values get reset to their defaults on hitting play, and never get saved when trying to make a ZooEnclosure
prefab.
I've spent ages looking through similar problems, with no luck. The best solution seems to be to make one big factory class that contains fields for both Giraffe
and Dog
and let it instantiate the selected subtype, and make a custom inspector that hides fields from the subtype that isn't selected.
That probably isn't a bad solution, but I definitely want to know whether this can work without having to make a monolothic class to handle it; if anyone can shed some light on this, I'd really appreciate it.
My current code is:
ZooEnclosure (this version only exposes the age
field of Animal
, but I haven't managed to get that to save):
using UnityEngine;
public enum AnimalType {Giraffe, Dog}
public class ZooEnclosure : MonoBehaviour {
public float height = 100f;
public float width = 100f;
public AnimalType animalType;
public Animal animalInside = new Dog();
}
ZooEnclosure's custom editor:
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
[CustomEditor(typeof(ZooEnclosure))]
public class ZooEnclosureEditor : Editor {
public override void OnInspectorGUI() {
ZooEnclosure script = (ZooEnclosure) target;
DrawDefaultInspector();
if (script.animalType == AnimalType.Dog && script.animalInside.GetType() != typeof(Dog)) {
script.animalInside = new Dog();
} else if (script.animalType == AnimalType.Giraffe && script.animalInside.GetType() != typeof(Giraffe)) {
script.animalInside = new Giraffe();
}
EditorGUILayout.Space();
DrawCustomFields(script);
}
private void DrawCustomFields(ZooEnclosure script) {
script.animalInside.age = EditorGUILayout.FloatField("Age: ", script.animalInside.age);
EditorUtility.SetDirty(script);
}
}
And the Animal classes:
public abstract class Animal {
public string animalName = "Default Animal Name";
public float age = 0f;
}
public class Giraffe : Animal {
public float neckHeight = 10f;
public Giraffe() { }
}
public class Dog : Animal {
public float barkLoudness = 200f;
public Dog() { }
}
Answer by PlanBTom · May 11, 2014 at 11:18 PM
The current serializer doesn't handle inheritance that way.
So far I've found 2 solutions either make ZooEnclosure and and Animal inherit from ScriptableObject or make a holder class for your animal with fields for each type.
Making them scriptable objects requires more work and each object essentially becomes an asset at that point which you may not want. ScriptableObject does know how to handle serializing polymorphic objects.
The other option is simpler, but requires you to know all possible subclasses when you write the code. Keep the references to types you're not using set to null, remember which type your current value is, and wrap all access to it from what would be inherited methods so you can check which type is held and thus which object to pass the method call to.
public class ZooEnclosure{ public AnimalHolder animal; }
public class AnimalHolder{ public AnimalType type = AnimalType.Invalid; public Dog dog = null; public Cat cat = null; public Giraffe giraffe = null;
public int GetHealth(){
if(type == AnimalType.Dog){
return dog.health;
}else if(type == AnimalType.Cat){
return cat.health;
}else if(type == AnimalType.Giraffe){
return giraffe.health;
}else{
throw new NotImplementedException();
}
}
public void SetAnimal(Animal animal){
Type animalType = typeof(animal);
if(animalType == typeof(Cat)){
type = AnimalType.Cat;
}else if(animalType == typeof(Dog)){
type = AnimalType.Dog;
}else if(animalType == typeof(Giraffe)){
type = AnimalType.Giraffe;
}else{
type = AnialType.Invalid; // or throw new NotImeplementedException();
}
cat = animal as Cat;
dog = animal as Dog;
giraffe = animal as Giraffe;
}
public enum AnimalType{Invalid, Dog, Cat, Giraffe};
public abstract class Animal{ public int health; }
public class Cat: Animal{ }
public class Dog: Animal{ }
public class Giraffe: Animal{ }
Your answer
Follow this Question
Related Questions
Showing properties from a base class in inspector? 0 Answers
Unable to draw propertyfield in inspector for some classes, findProperty returns null. 0 Answers
Dictionary in inspector 4 Answers
How should I serialize data that is also editable in the Inspector? 2 Answers
Large serializable field in a MonoBehaviour causing poor performance in Inspector 1 Answer