【问题标题】:Display certain properties of array members in Unity editor depending on other properties of those array members根据数组成员的其他属性,在 Unity 编辑器中显示数组成员的某些属性
【发布时间】:2018-10-17 16:27:46
【问题描述】:

我有一个类MyClass,它有枚举和属性。根据枚举,我想在编辑器中显示某些属性。

有这样的枚举 { first, second} 和属性 health, step, position 。如果选择第一个,则在编辑器中显示名称和步骤,如果选择第二个,则显示步骤和位置。我想出了如何为单一行为类做到这一点。以及如何做到这一点,以使数组的每个元素都具有动态属性?图像突出显示了我在选择此列表时希望看到的字段。提前致谢 。对不起我的英语不好

【问题讨论】:

  • 我的问题是不同的,没有序列化的monobehavior类有隐藏属性,我有数组(由序列化类组成)
  • edit 将您的CustomEditor 代码包含在MyClass
  • 我建议你使用ReorderableList 来做类似的事情。一开始有点棘手,但是一旦你得到它,它就会使事情变得非常干净和结构化 + 添加、删除等由 SerializedProperties 覆盖,因此撤消/重做和标记为脏的都可以为你处理。这是tutorial,我关注了一次,发现它很棒。
  • “请编辑您的问题以在 MyClass 中包含您的 CustomEditor 代码” - 不幸的是,现在代码不起作用(在 customeditor 中使用了序列化属性),如果它起作用,我将分享答案“我会建议你使用 ReorderableList 来做类似的事情。一开始有点棘手,但一旦你得到它,它会让事情变得非常干净和结构化 + 添加、删除等由 SerializedProperties 覆盖,因此撤消/重做和标记为脏都可以处理你。这是我曾经学习过的一个教程,发现它很棒” - 我会尝试,非常感谢你

标签: c# unity3d unity3d-editor


【解决方案1】:

这是一个使用UnityEditorInternal 中的ReaorderableList 的示例(我基本上是使用this cool turorial 学习的),我发现它比直接在OnInspectorGUI 上执行所有操作更加灵活和简洁。

还有一个附加功能,顾名思义:可以使用拖放操作选择元素并使其可重新排序!

List 元素的类

[Serializeable]
puclic class YourClass
{
    public enum YourEnum
    {
        first,
        second
    }

    public YourEnum Enum;
    public string Name;
    public int Step;
    public Vector3 Position;
}

包含列表的类

public class YourOtherClass : MonoBehaviour
{
    public List<YourClass> YourList = new List<YourClass>();

    // It works the same for arrays if you prefere that, no need to change the inspector
    // Note that in this case you can't initalize it here yet but the Inspector does that for you
    // public YourClass[] YourList ;
}

编辑器

[CustomEditor(typeof(YourOtherClass))]
public class YourOtherClassEditor : Editor
{
    // This will be the serialized "copy" of YourOtherClass.YourList
    private SerializedProperty YourList;


    private ReorderableList YourReorderableList;

    private void OnEnable()
    {
        // Step 1 "link" the SerializedProperties to the properties of YourOtherClass
        YourList = serializedObject.FindProperty("YourList");

        // Step 2 setup the ReorderableList
        YourReorderableList = new ReorderableList(serializedObject, YourList)
        {
            // Can your objects be dragged an their positions changed within the List?
            draggable = true,

            // Can you add elements by pressing the "+" button?
            displayAdd = true,

            // Can you remove Elements by pressing the "-" button?
            displayRemove = true,

            // Make a header for the list
            drawHeaderCallback = rect =>
            {
                EditorGUI.LabelField(rect, "This are your Elements");
            },

            // Now to the interesting part: Here you setup how elements look like
            drawElementCallback = (rect, index, active, focused) =>
            {
                // Get the currently to be drawn element from YourList
                var element = YourList.GetArrayElementAtIndex(index);

                // Get the elements Properties into SerializedProperties
                var Enum = element.FindPropertyRelative("Enum");
                var Name = element.FindPropertyRelative("Name");
                var Step = element.FindPropertyRelative("Step");
                var Position = element.FindPropertyRelative("Position");

                // Draw the Enum field
                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), Enum);
                // start the next property in the next line
                rect.y += EditorGUIUtility.singleLineHeight;

                // only show Name field if selected "first"
                if ((YourClass.YourEnum)Enum.intValue == YourClass.YourEnum.first)
                {
                    EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), Name);
                    // start the next property in the next line
                    rect.y += EditorGUIUtility.singleLineHeight;
                }

                // Draw the Step field
                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), Step);
                // start the next property in the next line
                rect.y += EditorGUIUtility.singleLineHeight;

                // only show Step field if selected "seconds"
                if ((YourClass.YourEnum)Enum.intValue == YourClass.YourEnum.second)
                {
                    EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), Position);
                }
            },

            // And since we have more than one line (default) you'll have to configure 
            // how tall your elements are. Luckyly in your example it will always be exactly
            // 3 Lines in each case. If not you would have to change this.
            // In some cases it becomes also more readable if you use one more Line as spacer between the elements
            elementHeight = EditorGUIUtility.singleLineHeight * 3,

            //alternatively if you have different heights you would use e.g.
            //elementHeightCallback = index =>
            //{
            //    var element = YourList.GetArrayElementAtIndex(index);
            //    var Enum = element.FindPropertyRelative("Enum");

            //    switch ((YourClass.YourEnum)Enum.intValue)
            //    {
            //        case YourClass.YourEnum.first:
            //            return EditorGUIUtility.singleLineHeight * 3;

            //        case YourClass.YourEnum.second:
            //            return EditorGUIUtility.singleLineHeight * 5;

            //            default:
            //                return EditorGUIUtility.singleLineHeight;
            //    }
            //}

            // optional: Set default Values when adding a new element
            // (otherwise the values of the last list item will be copied)
            onAddCallback = list =>
            {
                // The new index will be the current List size ()before adding
                var index = list.serializedProperty.arraySize;

                // Since this method overwrites the usual adding, we have to do it manually:
                // Simply counting up the array size will automatically add an element
                list.serializedProperty.arraySize++;
                list.index = index;
                var element = list.serializedProperty.GetArrayElementAtIndex(index);

                // again link the properties of the element in SerializedProperties
                var Enum = element.FindPropertyRelative("Enum");
                var Name = element.FindPropertyRelative("Name");
                var Step = element.FindPropertyRelative("Step");
                var Position = element.FindPropertyRelative("Position");

                // and set default values
                Enum.intValue = (int) YourClass.YourEnum.first;
                Name.stringValue = "";
                Step.intValue = 0;
                Position.vector3Value = Vector3.zero;
            }
        };
    }

    public override void OnInspectorGUI()
    {
        // copy the values of the real Class to the linked SerializedProperties
        serializedObject.Update();

        // print the reorderable list
        YourReorderableList.DoLayoutList();

        // apply the changed SerializedProperties values to the real class
        serializedObject.ApplyModifiedProperties();
    }
}

别忘了使用

using UnityEditor;
using UnityEditorInternal;

结果:

【讨论】:

  • 非常感谢
【解决方案2】:

假设MyClass 是这样定义的:

public class MyClass {
    public enum MyEnumType {first, second} ;
    public MyEnumType enumNumber;
    public String name;
    public int step;
    public Vector3 position; 
}  

分为三个步骤:

1。创建自定义列表包装器MyCustomList

// Script name : MyCustomList.cs
using UnityEngine;
using System;
using System.Collections.Generic; // Import the System.Collections.Generic class to give us access to List<>

public class MyCustomList: MonoBehaviour { 
    //This is our list we want to use to represent our class as an array.
    public List<MyClass> MyList = new List<MyClass>(1);
}

2。包装器的自定义编辑器:MyCustomListEditor

// Script name : MyCustomListEditor.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

[CustomEditor(typeof(MyCustomList))]

public class MyCustomListEditor : Editor {
    MyCustomList t;
    SerializedObject GetTarget;
    SerializedProperty ThisList;
    int ListSize;

    void OnEnable(){
        t = (MyCustomList)target;
        GetTarget = new SerializedObject(t);
        ThisList = GetTarget.FindProperty("MyList"); // Find the List in our script and create a reference of it
    }

    public override void OnInspectorGUI(){
        //Update our list

        GetTarget.Update();

        //Resize our list
        EditorGUILayout.Space ();
        EditorGUILayout.Space ();
        ListSize = ThisList.arraySize;
        ListSize = EditorGUILayout.IntField ("List Size", ListSize);

        if(ListSize != ThisList.arraySize){
            while(ListSize > ThisList.arraySize){
                ThisList.InsertArrayElementAtIndex(ThisList.arraySize);
            }
            while(ListSize < ThisList.arraySize){
                ThisList.DeleteArrayElementAtIndex(ThisList.arraySize - 1);
            }
        }

        EditorGUILayout.Space ();
        EditorGUILayout.Space ();

        //Display our list to the inspector window

        for(int i = 0; i < ThisList.arraySize; i++){
            SerializedProperty MyListRef = ThisList.GetArrayElementAtIndex(i);
            SerializedProperty MyEnum= MyListRef.FindPropertyRelative("enumName");
            SerializedProperty MyName = MyListRef.FindPropertyRelative("name");
            SerializedProperty MyStep = MyListRef.FindPropertyRelative("step");
            SerializedProperty MyPosition = MyListRef.FindPropertyRelative("position");


            EditorGUILayout.PropertyField(MyEnum);

            int MyEnumIndex = MyEnum.enumValueIndex;

            // Show/hide the properties based on the index of the enumValue. 
            if (MyEnumIndex == (int)MyClass.MyEnumType.first) {
                EditorGUILayout.PropertyField(MyName);
            } 

            if (MyEnumIndex == (int)MyClass.MyEnumType.second) {
                EditorGUILayout.PropertyField(MyPosition);
            }

            EditorGUILayout.PropertyField(MyStep);

            EditorGUILayout.Space ();

            //Remove this index from the List
            if(GUILayout.Button("Remove This Index (" + i.ToString() + ")")){
                ThisList.DeleteArrayElementAtIndex(i);
            }
            EditorGUILayout.Space ();
            EditorGUILayout.Space ();
            EditorGUILayout.Space ();
            EditorGUILayout.Space ();
        }

        //Apply the changes to our list
        GetTarget.ApplyModifiedProperties();
    }
}

3。将BigClass 更改为使用MyCustomList 而不是List&lt;MyClass&gt;

public class BigClass : MonoBehaviour {
    MyCustomList myList = new MyCustomList();

    // .. Whatever else is in BigClass

}

这是从 Unity 论坛上的this post 采用的。

【讨论】:

  • 请注意,将 SerializedProperties 与原始目标(AddNewRemove)的直接操作混合可能会导致问题。因为对于那些您将没有重做/撤消支持
  • 好点。我似乎无法找到它们在自定义脚本中的使用位置。除非有人注意到它们在某些方面有用,否则我只会删除它们。
  • 是的,也对不起我的错。我认为这将通过来自t = (MyCustomList)target;t 在检查器中使用。你实际上并不需要它,因为你从不使用它 -> 除非在某些特殊情况下,否则你永远不应该使用它
  • 所以要清楚,现在它们已被删除,这个答案没有撤消/重做问题吗?我个人更喜欢你的,但如果值得的话,不妨记录下这个替代方案。
  • 不,因为你只是通过他们处理的 SerializedProperties 来设置东西。但我不太明白你的第 3 步,不过……那是为了什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
相关资源
最近更新 更多