如何在Unity Editor中绘制继承同一类的非MonoPhavior对象列表



我有一个List<AbilityEffect> effects和AbilityEffect的许多子类,如DamageEffect、HealEffect e.t.c.,它们具有[System.Serializable]属性。如果我用DamageEffect这样的字段创建类,默认编辑器会完美地绘制它!(还有其他效果!(

我在AbilityData.cs 中为该函数添加了ContextMenu属性

[ContextMenu(Add/DamageEffect)]
public static void AddDamageEffect()
{
effects.Add(new DamageEffect());
}

但默认的Unity Editor会绘制它,如果它是AbilityEffect,而不是DamageEffect

我已经为类编写了一些自定义编辑器,其中包含List<AbilityEffect> effects = new List<AbilitiEffect>(),编写了绘制自定义列表的代码!但是,我如何告诉编辑器具体绘制DamageEffect,而不是AbilityEffect

我会在下面放一些代码:

能力数据类

using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "New Ability", menuName = "ScriptableObject/Ability")]
public class AbilityData : ScriptableObject
{
public int cooldown = 0;
public int range = 1;
public List<AbilityEffect> effects = new List<AbilityEffect>();
public bool showEffects = false;
[ContextMenu("Add/DamageEffect")]
public void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
}

能力数据编辑器类

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
public override void OnInspectorGUI()
{
var ability = (AbilityData)target;
DrawDetails(ability);
DrawEffects(ability);
}
private static void DrawEffects(AbilityData ability)
{
EditorGUILayout.Space();
ability.showEffects = EditorGUILayout.Foldout(ability.showEffects, "Effects", true);
if (ability.showEffects)
{
EditorGUI.indentLevel++;
List<AbilityEffect> effects = ability.effects;
int size = Mathf.Max(0, EditorGUILayout.IntField("Size", effects.Count));
while (size > effects.Count)
{
effects.Add(null);
}
while (size < effects.Count)
{
effects.RemoveAt(effects.Count - 1);
}
for (int i = 0; i < effects.Count; i++)
{
DrawEffect(effects[i], i);
}
EditorGUI.indentLevel--;
}
}
private static void DrawDetails(AbilityData ability)
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Cooldown", GUILayout.MaxWidth(60));
ability.cooldown = EditorGUILayout.IntField(ability.cooldown);
EditorGUILayout.LabelField("Range", GUILayout.MaxWidth(40));
ability.range = EditorGUILayout.IntField(ability.range);
EditorGUILayout.EndHorizontal();
}
private static void DrawEffect(AbilityEffect effect, int index)
{
//if (effect is DamageEffect)
//    effect = EditorGUILayout
// HOW??
}
}

能力效果等级(非摘要(

[System.Serializable]
public class AbilityEffect
{
public virtual void Affect() { }
}

损伤影响等级

[System.Serializable]
public class DamageEffect : AbilityEffect
{
public int damageAmout = 1;
public override void Affect() { ... }
}

由于序列化的工作方式,一旦反序列化某些数据,Unity将尝试根据类定义中指定的类型填充对象实例。如果你有List<AbilityEffect>,Unity将无法区分你之前序列化的特定AbilityEffect。实际上有一个解决方案,将AbilityEffect更改为ScriptableObject,这样Unity就不会将它们实际序列化为原始数据,而是作为GUID引用,这样被引用的资产就可以自己知道它们是AbilityEffect的什么子类型。缺点是,这样一来,所有效果都必须是"资源"文件夹中的资源。

首先:请注意,自Unity 2021以来,所有列表和数组的折页都是内置默认的,所以实际上我认为完全不需要自定义编辑器(至少对于列表部分(;(


您的方法有几个问题。

但默认的Unity编辑器会绘制它,如果它是AbilityEffect,而不是DamageEffect。

是的,因为它只序列化为AbilityEffect对于Serializer,该列表中的所有项都属于AbilityEffect类型,它不会再继续下去了。

所以,即使你能设法添加子类项目,它也只是暂时的!在保存、关闭Unity和重新打开所有子类型之后,应该将其转换为AbilityEffect,因为这是Serializer实际看到的唯一类型。


我建议您的AbilityEffect也是ScriptableObject型。通过这种方式,你甚至不必为它们定制抽屉,并且可以根据需要拥有不同类型和配置的任意多个实例,重复使用它们等。


这句话现在说了一件普通的事情:不要直接在编辑器中通过target(除非你清楚自己在做什么(

这不会将该对象标记为";"脏";,不适用于Undo/Redo,最糟糕的是,它不会持久保存这些更改!

总是通过serializedObjectSerializedProperty

[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
SerializedProperty cooldown;
SerializedProperty range;
SerializedProperty effects;
SerializedProperty showEffects;
private void OnEnable ()
{
// Link up the serialized fields you will access
cooldown = serializedObject.FindProperty(nameof(AbilityData.cooldown)); 
range = serializedObject.FindProperty(nameof(AbilityData.range)); 
effects = serializedObject.FindProperty(nameof(AbilityData.effects));
showEffects = serializedObject.FindProperty(nameof(AbilityData.showEffects));
}
public override void OnInspectorGUI()
{
// refresh current actual values into the editor
serializedObject.Update();
DrawDetails();
DrawEffects();
// write back any changed values from the editor back to the actual object
// This handles all marking dirty, saving and handles Undo/Redo
serializedObject.ApplyModifiedProperties();
}
private void DrawEffects()
{
// Now always ever only read and set values via the SerializedPropertys
EditorGUILayout.Space();
showEffects.boolValue = EditorGUILayout.Foldout(showEffects.boolValue, effects.displayName, true);
if (showEffects.boolValue)
{
EditorGUI.indentLevel++;
// This already handles all the list drawing by default
EditorGUILayout.PropertyField(effects, GUIContent.none, true);
EditorGUI.indentLevel--;
}
}

private void DrawDetails()
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(cooldown.displayName, GUILayout.MaxWidth(60));
cooldown.intValue = EditorGUILayout.IntField(cooldown.intValue);
EditorGUILayout.LabelField(range.displayName, GUILayout.MaxWidth(40));
range.intValue = EditorGUILayout.IntField(range.intValue);
EditorGUILayout.EndHorizontal();
}
}

现在,如果你真的想自定义列表绘图的行为,你可以使用ReorderableList,然后可以为每个元素实现一个抽屉,在那里你确实可以执行类型检查。

但正如前面所说,我根本不会走这条路,因为序列化程序无论如何都不支持它。

最新更新