Hello all,
I’m trying to make a custom inspector editor for quests which would have different types of tasks that the user can add dynamically, reorder, and so on. However there is no easy way for me to accomplish this, I’ve tried property drawers, reflection,etc.
Below I’ve pasted my quest class and the editor Ive gotten closest to creating so far. I tried brute forcing it and looping through all the tasks, checking the type, and drawing based on type, but even this has an issue: when reloading scripts, the inspector stops drawing them for some reason. My only assumption is that the reload causes the List to revert all its elements to the base class instead of the derived classes added by the user, thus not being considered as their derived classes in the foreach loop in
OnInspectorGUI()
anymore.
Quest class
public class Quest : ScriptableObject
{
public List<QuestTaskBase> Tasks = new List<QuestTaskBase>();
[System.Serializable]
public class QuestTaskBase
{
}
[System.Serializable]
public class KillEnemyTask : QuestTaskBase
{
public EnemyData Enemy;
public int Quantity;
}
[System.Serializable]
public class CollectItemTask : QuestTaskBase
{
public ItemBase Item;
public int Quantity;
}
}
The editor I got so far
#if UNITY_EDITOR
[CustomEditor(typeof(Quest))]
public class QuestEditor : Editor
{
private Quest _quest;
private GenericMenu _menu;
private void OnEnable()
{
_quest = (Quest)target;
_menu = new GenericMenu();
_menu.AddItem(new GUIContent("Kill Enemy Task"), true, () => AddTask(typeof(Quest.KillEnemyTask)));
_menu.AddItem(new GUIContent("Collect Item Task"), true, () => AddTask(typeof(Quest.CollectItemTask)));
}
private void AddTask(Type type)
{
_quest.Tasks.Add((Quest.QuestTaskBase)Activator.CreateInstance(type));
EditorUtility.SetDirty(_quest);
AssetDatabase.SaveAssets();
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Add task..."))
{
_menu.ShowAsContext();
}
if (!_quest.Tasks.HasElements())
{
GUILayout.Label("No tasks assigned.");
return;
}
foreach (var task in _quest.Tasks)
{
EditorGUILayout.Space();
GUILayout.Label($"TASK {_quest.Tasks.IndexOf(task)}:", EditorStyles.boldLabel);
if (task is Quest.KillEnemyTask killEnemyTask)
DrawKillEnemyTask(killEnemyTask);
else if (task is Quest.CollectItemTask collectItemTask)
DrawCollectItemTask(collectItemTask);
}
}
void DrawCollectItemTask(Quest.CollectItemTask collectItemTask)
{
GUILayout.Label($"Collect {collectItemTask.Quantity} {collectItemTask.Item?.name ?? "[Item]"}s");
collectItemTask.Item = EditorGUILayout.ObjectField("Item", collectItemTask.Item, typeof(ItemBase), true) as ItemBase;
collectItemTask.Quantity = EditorGUILayout.IntField("Quantity", collectItemTask.Quantity);
}
void DrawKillEnemyTask(Quest.KillEnemyTask killEnemyTask)
{
GUILayout.Label($"Kill {killEnemyTask.Quantity} {killEnemyTask.Enemy?.name ?? "[Enemy]"}s");
killEnemyTask.Enemy = EditorGUILayout.ObjectField("Enemy", killEnemyTask.Enemy, typeof(EnemyData), true) as EnemyData;
killEnemyTask.Quantity = EditorGUILayout.IntField("Quantity", killEnemyTask.Quantity);
}
}
#endif