- Home /
Draw Inspector For Most Derived Type Of Collection Elements
Alright, that title is a bit of a mouthful, but that concept is pretty straightforward. I'm working on a short project that is essentially a quiz game. The player is presented with a question and must choose the correct answer. There are different types of questions, such as multiple choice, and true/false.
public class Question { }
public class MultipleChoiceQuestion : Question { }
public class TrueFalseQuestion : Question { }
I have a base Question class, and a few derived classes MultipleChoiceQuestion and TrueFalseQuestion. For a given quiz, I have a ScriptableObject with a list of the questions.
public class QuizData : ScriptableObject
{
public Question[] questions;
}
Using a custom PropertyDrawer for QuizData I can add new Questions of any type to the array.
But here's my problem: the Inspector always draws for the base class Question, not for the derived type. So, even if element 1 in the array is MultipleChoiceQuestion, only the fields in Question are shown.
How can I get Unity to draw the Inspector for the most derived type when showing an element in an array?
Alternatively, if I have a SerializedProperty for a Question, but I know the underlying type is MultipleChoiceQuestion, how can I call the Editor for the MultipleChoiceQuestion?
EDIT: Going by "General Array Serialization" in http://forum.unity3d.com/threads/155352-Serialization-Best-Practices-Megapost, the correct method seems to be for Question to derive from ScriptableObject. Indeed, this get Unity serialize everything properly, survive assembly reloads, etc, but the Inspector is still a problem.
By default, the Inspector shows the elements of the array as 'object reference field'. It doesn't fold out and show all the fields within the Question (in fact, it just says 'type mismatch' even for the base class Question). I'm trying to get it to show the actual fields for each item.
Answer by Sycobob · May 22, 2014 at 08:24 PM
I managed to solve this is a satisfactory manner.
Create a QuizData ScriptableObject:
[Serializable] public class QuizData : ScriptableObject
{
public string quizText;
public List<Question> questions = new List<Question>();
}
Create a Manager to hold an instance of QuizData:
public class Manager : MonoBehaviour
{
public QuizData quizData;
}
And an Editor to create an instance:
[CustomEditor(typeof(Manager))]
public class ManangerEditor : Editor
{
private void OnEnable ()
{
Manager target = (Manager) base.target;
if ( target.quizData == null )
{
string projectRelativeFilePath = "Assets/" + typeof(QuizData).Name + ".asset";
//Try to load existing asset.
QuizData asset = (QuizData) AssetDatabase.LoadAssetAtPath(projectRelativeFilePath, typeof(QuizData));
//If none exists, create a new one.
if ( asset == null )
{
asset = ScriptableObject.CreateInstance<QuizData>();
AssetDatabase.CreateAsset(asset, projectRelativeFilePath);
}
target.quizData = asset;
}
}
}
Create the question types:
[Serializable] public class Question : ScriptableObject
{
[Range(0, 100)]
[SerializeField] protected int m_IntField;
}
[Serializable] public class MultipleChoiceQuestion : Question
{
[SerializeField] private float m_FloatField;
}
And finally, the meat of it. Create a custom Editor for QuizData:
[CustomEditor(typeof(QuizData))]
public class QuizDataEditor : Editor
{
public override void OnInspectorGUI ()
{
serializedObject.Update();
//QuizData GUI
DrawPropertiesExcluding(serializedObject, "questions");
serializedObject.ApplyModifiedProperties();
//Question GUI
arrayProp = serializedObject.FindProperty("questions");
EditorGUILayout.LabelField("Questions: " + arrayProp.arraySize);
for ( int i = 0; i < arrayProp.arraySize; ++i )
{
EditorGUILayout.Space();
SerializedProperty itemProp = arrayProp.GetArrayElementAtIndex(i);
Question item = (Question) itemProp.objectReferenceValue;
string label = "Question " + (i+1) + " (" + item.GetType() + ")";
itemProp.isExpanded = GUILayout.Toggle(itemProp.isExpanded, label, EditorStyles.foldout);
if ( itemProp.isExpanded )
{
Editor drawer = Editor.CreateEditor(item);
EditorGUI.indentLevel += 2;
drawer.OnInspectorGUI();
if ( DrawDeleteButton(itemProp, i) )
{
if ( --i > 0 ) continue;
else return;
}
EditorGUI.indentLevel -= 2;
EditorGUILayout.Space();
}
}
}
}
The main idea here is to draw the normal Inspector for QuizData except for the list of Questions. Then, loop over the Questions, create an Editor for each one, and call the created Editor's OnInspectorGUI. These objects may not need to be ScriptableObjects, I did that because I'm saving all this data to an .asset file.
The above doesn't account for being able to edit the collection (i.e. add and remove items). You can either display the arraySize property of the array or write a full custom array Editor.
For a more complete example, with extra goodies, I uploaded my test project to Github.
Answer by MakeCodeNow · May 22, 2014 at 12:42 AM
As far as I am aware, there is no good way to do what you're trying to do. Class is a reference type, which is why Unity displays the field as an object reference. You can make it a struct, but then it can't derive from Serializable Object. It sucks. I'm actually grappling with a very similar problem right now, and here is my solution, implemented in terms of your problem:
struct Question {
enum QuestionType {
TrueFalse,
MultipleChoice,
};
QuestionType questionType;
string questionText;
bool trueFalseAnswer;
List<string> multipleChoiceText;
int multipleChoiceAnswer;
}
And then of course you chose which fields to display in the UI based on the value of questionType.
I've considered this, but it's such a hacky solution. You have to add extra logic to decide which fields to show, then whenever you access the object you have to be careful to only use the fields defined for the current question type.
It's a terrible solution. I'm not aware of any way to get around it though within the realm of Unity's serialization logic. If you find a good way to do it, please post here. If Full Inspector can do it, then so much the better (though i don't see this issue called out in the description).
There is one solution that involves custom inspectors. It's still fairly hacky, but within the confines of Unity, it may be the best there is currently. Basically, write custom Editors for Question and its sub-classes, write a custom Editor for the Quiz$$anonymous$$ode class, and from the Quiz$$anonymous$$ode Editor, call the Question Editors manually.
If I can get that to work with DrawDefaultInspector or similar, that will actually accomplish my original goal. If I get it to work nicely, I'll post it here.
I found an acceptable solution, if you're interested @$$anonymous$$akeCodeNow. Posted above.
Answer by Clet_ · May 22, 2014 at 12:51 AM
Take a look into Jacob Dufault's FullInspector. Best 25$ I've ever invested. No more problem with serialization.
Your answer
![](https://koobas.hobune.stream/wayback/20220613145602im_/https://answers.unity.com/themes/thub/images/avi.jpg)