带有子检查器的Unity自定义检查器 [英] Unity Custom Inspector with sub-inspectors

查看:114
本文介绍了带有子检查器的Unity自定义检查器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在Unity 2017.2中开发小型ARPG.

我尝试为我的游戏的AbilityBluePrint类实现自定义编辑器.

基本上,AbilityBluePrints包含在运行时生成Ability所需的所有信息.包括一个Effect [] ScritpableObjects数组,当使用该异能时会触发该事件.

我目前拥有实现和工作所需的一切,但由于以下原因,我认为创造能力非常繁琐.

说我有一个效果类DamagePlusX:效果作为伤害修正值.如果我希望该效果对两个不同的异能具有不同的修正值,那么我将不得不在自己的Asset目录中创建它的两个实例,并将每个实例手动分配给相应异能的Effect []数组.我担心最终会出现很多效果实例,每个实例实际上都有一些不同的整数和浮点数.

因此,我认为我会使用自定义检查器,就​​像 Unity中的冒险教程.

想法是基本上创建AbilityBluePrint的实例,然后使用自定义检查器来动态实例化Effects []数组中的效果,并能够直接在AbilityBluePrint检查器中编辑每个效果的属性./p>

基本上,我想得到些类似的东西(对可怜的photoshop表示歉意):

我尝试将教程中的脚本转换为适合我的需要,但自昨天以来我一直遇到相同的错误:

NullReferenceException: Object reference not set to an instance of an object
AbilityBluePrintEditor.SubEditorSetup (.EffectEditor editor) (at Assets/Scripts/Editor/AbilityBluePrintEditor.cs:90)
EditorWithSubEditors`2[TEditor,TTarget].CheckAndCreateSubEditors (.TTarget[] subEditorTargets) (at Assets/Scripts/Editor/EditorWithSubEditors.cs:33)

我已经尝试了很多事情,我想知道我想做的事是否适用于可编写脚本的对象.在原始教程中,我的BluePrintAbility等效于Monobehaviour.

我的代码如下:

我的BluePrintAbility类:

[CreateAssetMenu(fileName = "New Ability BluePrint", menuName = "Ability BluePrint")]
public class AbilityBluePrint : ScriptableObject {
    public Effect[] effects = new Effect[0];
    public string description;
}

我的效果课:

public abstract class Effect : ScriptableObject {
    }

我的DamagePlusX效果等级:

[CreateAssetMenu(fileName = "DamagePlusX",menuName = "Effects/DamagePlusX")]
public class DamagePlusX : Effect
{
    [SerializeField]
    int modifier;

    public void ApplyModifier(){ // some logic}
}

现在是编辑器(为冗长的示例表示歉意,但是我现在不在哪里出现错误了,尽管如此,我还是减少了主要的类):

这是本教程的基础编辑器,我的错误来自:

// This class acts as a base class for Editors that have Editors
// nested within them.  For example, the InteractableEditor has
// an array of ConditionCollectionEditors.
// It's generic types represent the type of Editor array that are
// nested within this Editor and the target type of those Editors.
public abstract class EditorWithSubEditors<TEditor, TTarget> : Editor
    where TEditor : Editor
    where TTarget : Object

{
    protected TEditor[] subEditors;         // Array of Editors nested within this Editor.


// This should be called in OnEnable and at the start of OnInspectorGUI.
protected void CheckAndCreateSubEditors (TTarget[] subEditorTargets)
{
    // If there are the correct number of subEditors then do nothing.
    if (subEditors != null && subEditors.Length == subEditorTargets.Length)
        return;

    // Otherwise get rid of the editors.
    CleanupEditors ();

    // Create an array of the subEditor type that is the right length for the targets.
    subEditors = new TEditor[subEditorTargets.Length];

    // Populate the array and setup each Editor.
    for (int i = 0; i < subEditors.Length; i++)
    {
        subEditors[i] = CreateEditor (subEditorTargets[i]) as TEditor;
        SubEditorSetup (subEditors[i]); // ERROR comes inside this function HERE !!!!
    }
}


// This should be called in OnDisable.
protected void CleanupEditors ()
{
    // If there are no subEditors do nothing.
    if (subEditors == null)
        return;

    // Otherwise destroy all the subEditors.
    for (int i = 0; i < subEditors.Length; i++)
    {
        DestroyImmediate (subEditors[i]);
    }

    // Null the array so it's GCed.
    subEditors = null;
}


// This must be overridden to provide any setup the subEditor needs when it is first created.
protected abstract void SubEditorSetup (TEditor editor);

}

    [CustomEditor(typeof(AbilityBluePrint)), CanEditMultipleObjects]
public class AbilityBluePrintEditor : EditorWithSubEditors<EffectEditor, Effect>
{
    private AbilityBluePrint blueprint;          // Reference to the target.
    private SerializedProperty effectsProperty; //represents the array of effects.

    private Type[] effectTypes;                           // All the non-abstract types which inherit from Effect.  This is used for adding new Effects.
    private string[] effectTypeNames;                     // The names of all appropriate Effect types.
    private int selectedIndex;                              // The index of the currently selected Effect type.


    private const float dropAreaHeight = 50f;               // Height in pixels of the area for dropping scripts.
    private const float controlSpacing = 5f;                // Width in pixels between the popup type selection and drop area.
    private const string effectsPropName = "effects";   // Name of the field for the array of Effects.


    private readonly float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
    // Caching the vertical spacing between GUI elements.

    private void OnEnable()
    {
        // Cache the target.
        blueprint = (AbilityBluePrint)target;

        // Cache the SerializedProperty
        effectsProperty = serializedObject.FindProperty(effectsPropName);

        // If new editors for Effects are required, create them.
        CheckAndCreateSubEditors(blueprint.effects);

        // Set the array of types and type names of subtypes of Reaction.
        SetEffectNamesArray();
    }

    public override void OnInspectorGUI()
    {
        // Pull all the information from the target into the serializedObject.
        serializedObject.Update();

        // If new editors for Reactions are required, create them.
        CheckAndCreateSubEditors(blueprint.effects);

        DrawDefaultInspector();

        // Display all the Effects.
        for (int i = 0; i < subEditors.Length; i++)
        {
            if (subEditors[i] != null)
            {
                subEditors[i].OnInspectorGUI();
            }            
        }

        // If there are Effects, add a space.
        if (blueprint.effects.Length > 0)
        {
            EditorGUILayout.Space();
            EditorGUILayout.Space();
        }


        //Shows the effect selection GUI
        SelectionGUI();

        if (GUILayout.Button("Add Effect"))
        {

        }

        // Push data back from the serializedObject to the target.
        serializedObject.ApplyModifiedProperties();
    }

    private void OnDisable()
    {
        // Destroy all the subeditors.
        CleanupEditors();
    }

    // This is called immediately after each ReactionEditor is created.
    protected override void SubEditorSetup(EffectEditor editor)
    {
        // Make sure the ReactionEditors have a reference to the array that contains their targets.
        editor.effectsProperty = effectsProperty; //ERROR IS HERE !!!
    }

    private void SetEffectNamesArray()
    {
        // Store the Effect type.
        Type effectType = typeof(Effect);

        // Get all the types that are in the same Assembly (all the runtime scripts) as the Effect type.
        Type[] allTypes = effectType.Assembly.GetTypes();

        // Create an empty list to store all the types that are subtypes of Effect.
        List<Type> effectSubTypeList = new List<Type>();

        // Go through all the types in the Assembly...
        for (int i = 0; i < allTypes.Length; i++)
        {
            // ... and if they are a non-abstract subclass of Effect then add them to the list.
            if (allTypes[i].IsSubclassOf(effectType) && !allTypes[i].IsAbstract)
            {
                effectSubTypeList.Add(allTypes[i]);
            }
        }

        // Convert the list to an array and store it.
        effectTypes = effectSubTypeList.ToArray();

        // Create an empty list of strings to store the names of the Effect types.
        List<string> reactionTypeNameList = new List<string>();

        // Go through all the Effect types and add their names to the list.
        for (int i = 0; i < effectTypes.Length; i++)
        {
            reactionTypeNameList.Add(effectTypes[i].Name);
        }

        // Convert the list to an array and store it.
        effectTypeNames = reactionTypeNameList.ToArray();
    }

    private void SelectionGUI()
    {
        // Create a Rect for the full width of the inspector with enough height for the drop area.
        Rect fullWidthRect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(dropAreaHeight + verticalSpacing));

        // Create a Rect for the left GUI controls.
        Rect leftAreaRect = fullWidthRect;

        // It should be in half a space from the top.
        leftAreaRect.y += verticalSpacing * 0.5f;

        // The width should be slightly less than half the width of the inspector.
        leftAreaRect.width *= 0.5f;
        leftAreaRect.width -= controlSpacing * 0.5f;

        // The height should be the same as the drop area.
        leftAreaRect.height = dropAreaHeight;

        // Create a Rect for the right GUI controls that is the same as the left Rect except...
        Rect rightAreaRect = leftAreaRect;

        // ... it should be on the right.
        rightAreaRect.x += rightAreaRect.width + controlSpacing;

        // Display the GUI for the type popup and button on the left.
        TypeSelectionGUI(leftAreaRect);
    }

    private void TypeSelectionGUI(Rect containingRect)
    {
        // Create Rects for the top and bottom half.
        Rect topHalf = containingRect;
        topHalf.height *= 0.5f;
        Rect bottomHalf = topHalf;
        bottomHalf.y += bottomHalf.height;

        // Display a popup in the top half showing all the reaction types.
        selectedIndex = EditorGUI.Popup(topHalf, selectedIndex, effectTypeNames);

        // Display a button in the bottom half that if clicked...
        if (GUI.Button(bottomHalf, "Add Selected Effect"))
        {
            // ... finds the type selected by the popup, creates an appropriate reaction and adds it to the array.
            Debug.Log(effectTypes[selectedIndex]);
            Type effectType = effectTypes[selectedIndex];
            Effect newEffect = EffectEditor.CreateEffect(effectType);
            Debug.Log(newEffect);
            effectsProperty.AddToObjectArray(newEffect);
        }
    }
}

public abstract class EffectEditor : Editor
{
    public bool showEffect = true;                       // Is the effect editor expanded?
    public SerializedProperty effectsProperty;    // Represents the SerializedProperty of the array the target belongs to.


    private Effect effect;                      // The target Reaction.


    private const float buttonWidth = 30f;          // Width in pixels of the button to remove this Reaction from the ReactionCollection array.

    private void OnEnable()
    {
        // Cache the target reference.
        effect = (Effect)target;

        // Call an initialisation method for inheriting classes.
        Init();
    }

    // This function should be overridden by inheriting classes that need initialisation.
    protected virtual void Init() { }


    public override void OnInspectorGUI()
    {
        Debug.Log("attempt to draw effect inspector");
        // Pull data from the target into the serializedObject.
        serializedObject.Update();

        EditorGUILayout.BeginVertical(GUI.skin.box);
        EditorGUI.indentLevel++;

        DrawDefaultInspector();

        EditorGUI.indentLevel--;
        EditorGUILayout.EndVertical();

        // Push data back from the serializedObject to the target.
        serializedObject.ApplyModifiedProperties();
    }

    public static Effect CreateEffect(Type effectType)
    {
        // Create a reaction of a given type.
        return (Effect) ScriptableObject.CreateInstance(effectType);
    }
}

[CustomEditor(typeof(DamagePlusXEditor))]
public class DamagePlusXEditor : EffectEditor {}

解决方案

不确定它是否对您的实际情况有所帮助,但是我很幸运将数据存储在纯C#类中,然后嵌套其中的数组在ScriptableObject中,并且这两个控件的自定义编辑器一起工作.

例如这个纯数据类(也由其他相当简单的纯类组成):

[System.Serializable]
public class Buff
{
    public CharacterAttribute attribute;
    public CalculationType calculationType;
    public BuffDuration buffDuration;
    public bool effectBool;
    public int effectInt;
    public float effectFloat;
}

使用编辑器进行以下操作:

[CustomPropertyDrawer (typeof (Buff))]
public class BuffDrawer : PropertyDrawer
{
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    ...

,然后是包含这些"Buff"对象数组的SO:

[CreateAssetMenu (fileName = "New Buff", menuName = "Data/Buff")]
public class BuffData : ScriptableObject
{
    public new string name;
    public string description;
    public Texture2D icon;
    public Buff [] attributeBuffs;
}

最后是SO的编辑器(请参阅底部附近的PropertyField):

using UnityEngine;
using UnityEditor;

[CustomEditor (typeof (BuffData))]
public class BuffDataEditor : Editor
{
    private const int DescriptionWidthPadding = 35;
    private const float DescriptionHeightPadding = 1.25f;
    private const string AttributesHelpText = 
        "Choose which attributes are to be affected by this buff and by how much.\n" +
        "Note: the calculation type should match the attribute's implementation.";

    private SerializedProperty nameProperty;
    private SerializedProperty descriptionProperty;
    private SerializedProperty iconProperty;
    private SerializedProperty attributeBuffsProperty;

    private void OnEnable ()
    {
        nameProperty = serializedObject.FindProperty ("name");
        descriptionProperty = serializedObject.FindProperty ("description");
        iconProperty = serializedObject.FindProperty ("icon");
        attributeBuffsProperty = serializedObject.FindProperty ("attributeBuffs");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update ();

        nameProperty.stringValue = EditorGUILayout.TextField ("Name", nameProperty.stringValue);
        EditorGUILayout.LabelField ("Description:");
        GUIStyle descriptionStyle = new GUIStyle (EditorStyles.textArea)
        {
            wordWrap = true,
            padding = new RectOffset (6, 6, 6, 6),
            fixedWidth = Screen.width - DescriptionWidthPadding
        };
        descriptionStyle.fixedHeight = descriptionStyle.CalcHeight (new GUIContent (descriptionProperty.stringValue), Screen.width) * DescriptionHeightPadding;
        EditorGUI.indentLevel++;
        descriptionProperty.stringValue = EditorGUILayout.TextArea (descriptionProperty.stringValue, descriptionStyle);
        EditorGUI.indentLevel--;
        EditorGUILayout.Space ();
        iconProperty.objectReferenceValue = (Texture2D) EditorGUILayout.ObjectField ("Icon", iconProperty.objectReferenceValue, typeof (Texture2D), false);
        EditorGUILayout.Space ();
        EditorGUILayout.HelpBox (AttributesHelpText, MessageType.Info);
        EditorGUILayout.PropertyField (attributeBuffsProperty, true);

        serializedObject.ApplyModifiedProperties();
    }
}

所有这些都会导致:

示例检查器

无论如何,希望该示例为您提供一些可能对您有所帮助的想法.

I am working on a small ARPG in Unity 2017.2.

I have tried implementing a custom editor for the AbilityBluePrint class of my game.

Basically, the AbilityBluePrints contain all the information necessary to generate the Ability at run time. Including an array of Effect[] ScritpableObjects which get triggered when the ability is used.

I currently have everything I need implemented and working but I envisage creating abilities to be very tedious for the following reason.

Say I have an effect class DamagePlusX : Effect which as a damage modifier value. If I want this effect to have a different modifier value for two different abilities then I will have to create two instances of it in my Asset directory and manually assign each one to the Effect[] array of the corresponding ability. I am concerned that I will end up having lots and lots of instances of effects each with essentially a few different ints and floats.

I therefore thought I would use a custom inspector a bit like the one from the Adventure Tutorial from Unity.

The idea is to basically create the instance of the AbilityBluePrint and then use the custom inspector to be able to dynamically instantiate Effects in the Effects[] array and be able to edit the properties of each effect directly within the AbilityBluePrint inspector.

Basically I would like to get somthing a bit like that (apologies for the poor photoshop):

I tried to convert the scripts from the tutorial to fit my needs but I keep having the same error since yesterday:

NullReferenceException: Object reference not set to an instance of an object
AbilityBluePrintEditor.SubEditorSetup (.EffectEditor editor) (at Assets/Scripts/Editor/AbilityBluePrintEditor.cs:90)
EditorWithSubEditors`2[TEditor,TTarget].CheckAndCreateSubEditors (.TTarget[] subEditorTargets) (at Assets/Scripts/Editor/EditorWithSubEditors.cs:33)

I've tried so many things I am wondering if what I am trying to do is doable with scriptable objects. In the original tutorial the equivalent of my BluePrintAbility is a Monobehaviour.

The code I have is below:

My BluePrintAbility Class:

[CreateAssetMenu(fileName = "New Ability BluePrint", menuName = "Ability BluePrint")]
public class AbilityBluePrint : ScriptableObject {
    public Effect[] effects = new Effect[0];
    public string description;
}

My Effect class:

public abstract class Effect : ScriptableObject {
    }

My DamagePlusX effect Class:

[CreateAssetMenu(fileName = "DamagePlusX",menuName = "Effects/DamagePlusX")]
public class DamagePlusX : Effect
{
    [SerializeField]
    int modifier;

    public void ApplyModifier(){ // some logic}
}

And now the Editors (apologies for the long samples, but I don't now where the error is in there, I've cut down the main classes though):

This is the base editor from the tutorial, where my error comes from:

// This class acts as a base class for Editors that have Editors
// nested within them.  For example, the InteractableEditor has
// an array of ConditionCollectionEditors.
// It's generic types represent the type of Editor array that are
// nested within this Editor and the target type of those Editors.
public abstract class EditorWithSubEditors<TEditor, TTarget> : Editor
    where TEditor : Editor
    where TTarget : Object

{
    protected TEditor[] subEditors;         // Array of Editors nested within this Editor.


// This should be called in OnEnable and at the start of OnInspectorGUI.
protected void CheckAndCreateSubEditors (TTarget[] subEditorTargets)
{
    // If there are the correct number of subEditors then do nothing.
    if (subEditors != null && subEditors.Length == subEditorTargets.Length)
        return;

    // Otherwise get rid of the editors.
    CleanupEditors ();

    // Create an array of the subEditor type that is the right length for the targets.
    subEditors = new TEditor[subEditorTargets.Length];

    // Populate the array and setup each Editor.
    for (int i = 0; i < subEditors.Length; i++)
    {
        subEditors[i] = CreateEditor (subEditorTargets[i]) as TEditor;
        SubEditorSetup (subEditors[i]); // ERROR comes inside this function HERE !!!!
    }
}


// This should be called in OnDisable.
protected void CleanupEditors ()
{
    // If there are no subEditors do nothing.
    if (subEditors == null)
        return;

    // Otherwise destroy all the subEditors.
    for (int i = 0; i < subEditors.Length; i++)
    {
        DestroyImmediate (subEditors[i]);
    }

    // Null the array so it's GCed.
    subEditors = null;
}


// This must be overridden to provide any setup the subEditor needs when it is first created.
protected abstract void SubEditorSetup (TEditor editor);

}

    [CustomEditor(typeof(AbilityBluePrint)), CanEditMultipleObjects]
public class AbilityBluePrintEditor : EditorWithSubEditors<EffectEditor, Effect>
{
    private AbilityBluePrint blueprint;          // Reference to the target.
    private SerializedProperty effectsProperty; //represents the array of effects.

    private Type[] effectTypes;                           // All the non-abstract types which inherit from Effect.  This is used for adding new Effects.
    private string[] effectTypeNames;                     // The names of all appropriate Effect types.
    private int selectedIndex;                              // The index of the currently selected Effect type.


    private const float dropAreaHeight = 50f;               // Height in pixels of the area for dropping scripts.
    private const float controlSpacing = 5f;                // Width in pixels between the popup type selection and drop area.
    private const string effectsPropName = "effects";   // Name of the field for the array of Effects.


    private readonly float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
    // Caching the vertical spacing between GUI elements.

    private void OnEnable()
    {
        // Cache the target.
        blueprint = (AbilityBluePrint)target;

        // Cache the SerializedProperty
        effectsProperty = serializedObject.FindProperty(effectsPropName);

        // If new editors for Effects are required, create them.
        CheckAndCreateSubEditors(blueprint.effects);

        // Set the array of types and type names of subtypes of Reaction.
        SetEffectNamesArray();
    }

    public override void OnInspectorGUI()
    {
        // Pull all the information from the target into the serializedObject.
        serializedObject.Update();

        // If new editors for Reactions are required, create them.
        CheckAndCreateSubEditors(blueprint.effects);

        DrawDefaultInspector();

        // Display all the Effects.
        for (int i = 0; i < subEditors.Length; i++)
        {
            if (subEditors[i] != null)
            {
                subEditors[i].OnInspectorGUI();
            }            
        }

        // If there are Effects, add a space.
        if (blueprint.effects.Length > 0)
        {
            EditorGUILayout.Space();
            EditorGUILayout.Space();
        }


        //Shows the effect selection GUI
        SelectionGUI();

        if (GUILayout.Button("Add Effect"))
        {

        }

        // Push data back from the serializedObject to the target.
        serializedObject.ApplyModifiedProperties();
    }

    private void OnDisable()
    {
        // Destroy all the subeditors.
        CleanupEditors();
    }

    // This is called immediately after each ReactionEditor is created.
    protected override void SubEditorSetup(EffectEditor editor)
    {
        // Make sure the ReactionEditors have a reference to the array that contains their targets.
        editor.effectsProperty = effectsProperty; //ERROR IS HERE !!!
    }

    private void SetEffectNamesArray()
    {
        // Store the Effect type.
        Type effectType = typeof(Effect);

        // Get all the types that are in the same Assembly (all the runtime scripts) as the Effect type.
        Type[] allTypes = effectType.Assembly.GetTypes();

        // Create an empty list to store all the types that are subtypes of Effect.
        List<Type> effectSubTypeList = new List<Type>();

        // Go through all the types in the Assembly...
        for (int i = 0; i < allTypes.Length; i++)
        {
            // ... and if they are a non-abstract subclass of Effect then add them to the list.
            if (allTypes[i].IsSubclassOf(effectType) && !allTypes[i].IsAbstract)
            {
                effectSubTypeList.Add(allTypes[i]);
            }
        }

        // Convert the list to an array and store it.
        effectTypes = effectSubTypeList.ToArray();

        // Create an empty list of strings to store the names of the Effect types.
        List<string> reactionTypeNameList = new List<string>();

        // Go through all the Effect types and add their names to the list.
        for (int i = 0; i < effectTypes.Length; i++)
        {
            reactionTypeNameList.Add(effectTypes[i].Name);
        }

        // Convert the list to an array and store it.
        effectTypeNames = reactionTypeNameList.ToArray();
    }

    private void SelectionGUI()
    {
        // Create a Rect for the full width of the inspector with enough height for the drop area.
        Rect fullWidthRect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(dropAreaHeight + verticalSpacing));

        // Create a Rect for the left GUI controls.
        Rect leftAreaRect = fullWidthRect;

        // It should be in half a space from the top.
        leftAreaRect.y += verticalSpacing * 0.5f;

        // The width should be slightly less than half the width of the inspector.
        leftAreaRect.width *= 0.5f;
        leftAreaRect.width -= controlSpacing * 0.5f;

        // The height should be the same as the drop area.
        leftAreaRect.height = dropAreaHeight;

        // Create a Rect for the right GUI controls that is the same as the left Rect except...
        Rect rightAreaRect = leftAreaRect;

        // ... it should be on the right.
        rightAreaRect.x += rightAreaRect.width + controlSpacing;

        // Display the GUI for the type popup and button on the left.
        TypeSelectionGUI(leftAreaRect);
    }

    private void TypeSelectionGUI(Rect containingRect)
    {
        // Create Rects for the top and bottom half.
        Rect topHalf = containingRect;
        topHalf.height *= 0.5f;
        Rect bottomHalf = topHalf;
        bottomHalf.y += bottomHalf.height;

        // Display a popup in the top half showing all the reaction types.
        selectedIndex = EditorGUI.Popup(topHalf, selectedIndex, effectTypeNames);

        // Display a button in the bottom half that if clicked...
        if (GUI.Button(bottomHalf, "Add Selected Effect"))
        {
            // ... finds the type selected by the popup, creates an appropriate reaction and adds it to the array.
            Debug.Log(effectTypes[selectedIndex]);
            Type effectType = effectTypes[selectedIndex];
            Effect newEffect = EffectEditor.CreateEffect(effectType);
            Debug.Log(newEffect);
            effectsProperty.AddToObjectArray(newEffect);
        }
    }
}

public abstract class EffectEditor : Editor
{
    public bool showEffect = true;                       // Is the effect editor expanded?
    public SerializedProperty effectsProperty;    // Represents the SerializedProperty of the array the target belongs to.


    private Effect effect;                      // The target Reaction.


    private const float buttonWidth = 30f;          // Width in pixels of the button to remove this Reaction from the ReactionCollection array.

    private void OnEnable()
    {
        // Cache the target reference.
        effect = (Effect)target;

        // Call an initialisation method for inheriting classes.
        Init();
    }

    // This function should be overridden by inheriting classes that need initialisation.
    protected virtual void Init() { }


    public override void OnInspectorGUI()
    {
        Debug.Log("attempt to draw effect inspector");
        // Pull data from the target into the serializedObject.
        serializedObject.Update();

        EditorGUILayout.BeginVertical(GUI.skin.box);
        EditorGUI.indentLevel++;

        DrawDefaultInspector();

        EditorGUI.indentLevel--;
        EditorGUILayout.EndVertical();

        // Push data back from the serializedObject to the target.
        serializedObject.ApplyModifiedProperties();
    }

    public static Effect CreateEffect(Type effectType)
    {
        // Create a reaction of a given type.
        return (Effect) ScriptableObject.CreateInstance(effectType);
    }
}

[CustomEditor(typeof(DamagePlusXEditor))]
public class DamagePlusXEditor : EffectEditor {}

解决方案

Not sure if it will help in your exact situation, but I've had some luck with storing data in a pure C# class and then nesting an array of those inside a ScriptableObject, and the custom editors on both of those worked together.

E.g. this pure data class (which is also made up of other fairly simple pure classes):

[System.Serializable]
public class Buff
{
    public CharacterAttribute attribute;
    public CalculationType calculationType;
    public BuffDuration buffDuration;
    public bool effectBool;
    public int effectInt;
    public float effectFloat;
}

with an editor along the lines of:

[CustomPropertyDrawer (typeof (Buff))]
public class BuffDrawer : PropertyDrawer
{
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    ...

and then the SO containing an array of these "Buff" objects:

[CreateAssetMenu (fileName = "New Buff", menuName = "Data/Buff")]
public class BuffData : ScriptableObject
{
    public new string name;
    public string description;
    public Texture2D icon;
    public Buff [] attributeBuffs;
}

and finally the SO's editor (see near the bottom for PropertyField):

using UnityEngine;
using UnityEditor;

[CustomEditor (typeof (BuffData))]
public class BuffDataEditor : Editor
{
    private const int DescriptionWidthPadding = 35;
    private const float DescriptionHeightPadding = 1.25f;
    private const string AttributesHelpText = 
        "Choose which attributes are to be affected by this buff and by how much.\n" +
        "Note: the calculation type should match the attribute's implementation.";

    private SerializedProperty nameProperty;
    private SerializedProperty descriptionProperty;
    private SerializedProperty iconProperty;
    private SerializedProperty attributeBuffsProperty;

    private void OnEnable ()
    {
        nameProperty = serializedObject.FindProperty ("name");
        descriptionProperty = serializedObject.FindProperty ("description");
        iconProperty = serializedObject.FindProperty ("icon");
        attributeBuffsProperty = serializedObject.FindProperty ("attributeBuffs");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update ();

        nameProperty.stringValue = EditorGUILayout.TextField ("Name", nameProperty.stringValue);
        EditorGUILayout.LabelField ("Description:");
        GUIStyle descriptionStyle = new GUIStyle (EditorStyles.textArea)
        {
            wordWrap = true,
            padding = new RectOffset (6, 6, 6, 6),
            fixedWidth = Screen.width - DescriptionWidthPadding
        };
        descriptionStyle.fixedHeight = descriptionStyle.CalcHeight (new GUIContent (descriptionProperty.stringValue), Screen.width) * DescriptionHeightPadding;
        EditorGUI.indentLevel++;
        descriptionProperty.stringValue = EditorGUILayout.TextArea (descriptionProperty.stringValue, descriptionStyle);
        EditorGUI.indentLevel--;
        EditorGUILayout.Space ();
        iconProperty.objectReferenceValue = (Texture2D) EditorGUILayout.ObjectField ("Icon", iconProperty.objectReferenceValue, typeof (Texture2D), false);
        EditorGUILayout.Space ();
        EditorGUILayout.HelpBox (AttributesHelpText, MessageType.Info);
        EditorGUILayout.PropertyField (attributeBuffsProperty, true);

        serializedObject.ApplyModifiedProperties();
    }
}

All of which results in:

Example Inspector

Anyway hope that example gives you some ideas that might help with yours.

这篇关于带有子检查器的Unity自定义检查器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆