Unity Custom Drop Down selection for Arrays C#

Viewed 3267

I am trying to simplify selection of prefabs in a wave system.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WaveList : MonoBehaviour
{
    [System.Serializable]
    public class Wave
    {
        public int waveCount;
        public string[] enemies;
        public int[] enemyLVL;
        public float[] delay;
    }

    public Wave[] waves;
}

I am trying to have a drop down menu in the Unity editor to choose the value of each enemy in "enemies" via drop down menu.

All help is greatly appreciated.

Cheers

1 Answers

Usually your question is pretty broad and requires a lot of time.

However I kind of love Unity Editor scripting and honestly it is extremely complex and a bit hard to get in so consider this a little jump start into Editor scripting ;)


What you want to achieve requires four things:

  • A field to store the available values
  • A way to fill these available values
  • An additional field to store the currently selected value
  • Custom PropertyDrawer for implementing the EditorGUI.Popup

Editor scripting can get as complex as one wishes :D So I will only implement a very basic one. I hope this is a good start point for your needs:

using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class WaveList : MonoBehaviour
{
    [Serializable]
    public class Wave
    {
        public int waveCount;
        public string[] enemies;
        // Additional field for storing the currently selected enemy index
        public int selectedEnemy;
        public int[] enemyLVL;
        public float[] delay;

#if UNITY_EDITOR
        [CustomPropertyDrawer(typeof(Wave))]
        private class WavePropertyDrawer : PropertyDrawer
        {
            // This will be generated during the OnGUI call
            // Maybe not the cleanest way but it works for now ;)
            private float height;
            private string[] availableEnemies;

            // This method is required so other property drawers (like the Wave[] waves)
            // know how much space to reserve for drawing this property
            public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
            {
                return height + EditorGUIUtility.singleLineHeight;
            }

            // Draw the property inside the given rect
            public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
            {
                // reset the height
                height = 0;

                // Using BeginProperty / EndProperty on the parent property means that    
                // prefab override logic works on the entire property.
                EditorGUI.BeginProperty(position, label, property);
                {
                    // Get the rect for where to draw the label/foldout
                    var labelRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
                    position.y += EditorGUIUtility.singleLineHeight;
                    height += EditorGUIUtility.singleLineHeight;

                    // Draw the foldout
                    property.isExpanded = EditorGUI.Foldout(labelRect, property.isExpanded, property.displayName);
                    if (property.isExpanded)
                    {
                        // indent children for better readability
                        EditorGUI.indentLevel++;
                        {
                            // Get serialized Properties
                            var serializedWaveCount = property.FindPropertyRelative(nameof(waveCount));
                            var serializedEnemies = property.FindPropertyRelative(nameof(enemies));
                            var serializedSelectedEnemy = property.FindPropertyRelative(nameof(selectedEnemy));
                            var serializedEnemyLVL = property.FindPropertyRelative(nameof(enemyLVL));
                            var serializedDelay = property.FindPropertyRelative(nameof(delay));

                            // Calculate rects
                            var waveCountHeight = EditorGUI.GetPropertyHeight(serializedWaveCount);
                            var waveCountRect = new Rect(position.x, position.y, position.width, waveCountHeight);
                            position.y += waveCountHeight;
                            height += waveCountHeight;

                            var enemiesHeight = EditorGUI.GetPropertyHeight(serializedEnemies, true);
                            var enemiesRect = new Rect(position.x, position.y, position.width, enemiesHeight);
                            position.y += enemiesHeight;
                            height += enemiesHeight;

                            var selectedEnemyRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
                            position.y += EditorGUIUtility.singleLineHeight;
                            height += EditorGUIUtility.singleLineHeight;

                            var enemeiesLevelHeight = EditorGUI.GetPropertyHeight(serializedEnemyLVL, true);
                            var enemeiesLVLRect = new Rect(position.x, position.y, position.width, enemeiesLevelHeight);
                            position.y += enemeiesLevelHeight;
                            height += enemeiesLevelHeight;

                            var delayHeight = EditorGUI.GetPropertyHeight(serializedDelay, true);
                            var delayRect = new Rect(position.x, position.y, position.width, delayHeight);
                            position.y += delayHeight;
                            height += delayHeight;

                            // Draw Fields
                            availableEnemies = new string[serializedEnemies.arraySize];
                            for (var i = 0; i < serializedEnemies.arraySize; i++)
                            {
                                availableEnemies[i] = serializedEnemies.GetArrayElementAtIndex(i).stringValue;
                            }

                            EditorGUI.PropertyField(waveCountRect, serializedWaveCount);
                            EditorGUI.PropertyField(enemiesRect, serializedEnemies, true);
                            if (serializedEnemies.arraySize == 0)
                            {
                                serializedSelectedEnemy.intValue = -1;
                            }

                            serializedSelectedEnemy.intValue = EditorGUI.Popup(selectedEnemyRect, serializedSelectedEnemy.displayName, serializedSelectedEnemy.intValue, availableEnemies);
                            EditorGUI.PropertyField(enemeiesLVLRect, serializedEnemyLVL, true);
                            EditorGUI.PropertyField(delayRect, serializedDelay, true);
                        }
                        EditorGUI.indentLevel--;
                    }
                }
                EditorGUI.EndProperty();
            }
        }
#endif
    }

    public Wave[] waves;
}

So later in order to get the actual selected enemy you would access

var enemy = someWave.enemies[someWave.selectedEnemy];

now you can add, edit and remove enemies and then select a specific one via dropdown enter image description here


As to your question of how to define the available enemies globally for all waves:

You should probably move the available array(s) into the WaveList class and define them only once there like.

Again this here will work for now but is quite dirty

public class WaveList : MonoBehaviour
{
    public string[] availableEnemies;
}

Then in the Wave only store the selected index like

[Serializable]
public class Wave
{
    public int waveCount;
    public int selectedEnemy;
    public int[] enemyLVL;
    public float[] delay;

    ...

Then in the property drawer instead of

var serializedEnemies = property.FindPropertyRelative(nameof(enemies));

You would rather get it directly from the WaveList class using

var serializedEnemies = property.serializedObject.FindProperty(nameof(WaveList.availableEnemies));

Note: Typing on the phone but I hope the idea gets clear

Related