Search Unity

Interate through an class array with SerializedProperty (class contains arrays as well)

Discussion in 'Scripting' started by codxr, Jun 19, 2019.

  1. codxr

    codxr

    Joined:
    Jun 19, 2019
    Posts:
    11
    Does someone have an idea how I can iterate through an array with a SerializedProperty.
    Coding an editor script at the moment and I have to use SerializedProperties for Undo and Prefabs etc.

    For a normal variable in my class its quite easy.
    Thats what i figured out so far:
    Code (CSharp):
    1. [CustomEditor(typeof(WaveSpawnerScript))]
    2. public class CustomWaveSpawnerInspector : Editor {
    3.     SerializedProperty spawnPointProp;
    4.     public void OnEnable() {
    5.         spawnPointProp = serializedObject.FindProperty("spawnPoint");
    6.     }
    7.     public override void OnInspectorGUI() {
    8.         serializedObject.Update();
    9.         EditorGUILayout.PropertyField(spawnPointProp, new GUIContent("Spawn Point"), GUILayout.Width(300));
    10.         serializedObject.ApplyModifiedProperties();
    11.     }
    That's what my WaveSpawnerScript looks like (only the necessary part, no methods):
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. [System.Serializable]
    5. public class Wave {
    6.     public string waveName;
    7.     public GameObject enemyPrefab;
    8.     public float countdown;
    9.     public int runs;
    10.     public List<int> enemyPerRun = new List<int>();
    11.     public List<float> timeBetweenRuns = new List<float>();
    12. }
    13. public class WaveSpawnerScript : MonoBehaviour {
    14.     public Transform spawnPoint;
    15.     public List<Wave> waves = new List<Wave>();
    16.     public float timeNeeded = 0;
    17.     private int currentWaveIndex = 0;
    18.     private int currentRun = 0;
    19.     private int currentRunIndex = 0;
    20. }
    I want to add a way to edit the waves-list in the inspector via SerializedProperties
    Code (CSharp):
    1. public List<Wave> waves = new List<Wave>();
    But I don't know how to iterate through a list with a SerializedProperty Especially when the waves-list has the type Wave
    Code (CSharp):
    1. [System.Serializable]
    2. public class Wave {
    3.     public string waveName;
    4.     public GameObject enemyPrefab;
    5.     public float countdown;
    6.     public int runs;
    7.     public List<int> enemyPerRun = new List<int>();
    8.     public List<float> timeBetweenRuns = new List<float>();
    9. }
    And the class wave it-self has two more lists (enemyPerRun and timeBetweenRuns).

    I managed to implement the editor without SerializedProperties(Only for the spawnpoint I have a SerializedProperty, but that wasn't a big deal):
    Code (CSharp):
    1. using System.Linq;
    2. using UnityEngine;
    3. using UnityEditor;
    4. [CustomEditor(typeof(WaveSpawnerScript))]
    5. public class CustomWaveSpawnerInspector : Editor {
    6.     //Declaring waveSpawnerScript variable
    7.     private WaveSpawnerScript waveSpawnerScript;
    8.     SerializedProperty spawnPointProp;
    9.     public void OnEnable() {
    10.         //Assigning waveSpawnerScript variable to target
    11.         waveSpawnerScript = (WaveSpawnerScript)target;
    12.         spawnPointProp = serializedObject.FindProperty("spawnPoint");
    13.     }
    14.     public override void OnInspectorGUI() {
    15.         serializedObject.Update();
    16.         GUILayout.Space(10);
    17.         /*Enemy- and SpawnPoint-ObjectField*/
    18.         EditorGUILayout.PropertyField(spawnPointProp, new GUIContent("Spawn Point"), GUILayout.Width(300));
    19.        
    20.         GUILayout.Space(20);
    21.         /*WaveSpawnerEditor-LabelField and AddWave-Button*/
    22.         EditorGUILayout.BeginHorizontal();
    23.         EditorGUILayout.LabelField("WaveSpawner Editor");
    24.         if(GUILayout.Button("Add Wave", GUILayout.Width(100))) {
    25.             AddWave();
    26.         }
    27.         EditorGUILayout.EndHorizontal();
    28.         /*Wave-Settings*/
    29.         for (int i = 0; i < waveSpawnerScript.waves.Count; i++) {
    30.             EditorGUILayout.BeginVertical("box");
    31.             /*WaveName-LabelField and DeleteWave-Button*/
    32.             EditorGUILayout.BeginHorizontal();
    33.             EditorGUILayout.LabelField(waveSpawnerScript.waves[i].waveName);
    34.             if (GUILayout.Button("Delete Wave", GUILayout.Width(100))) {
    35.                 RemoveWave(i);
    36.                 return;
    37.             }
    38.             EditorGUILayout.EndHorizontal();
    39.             GUILayout.Space(10);
    40.             /*Name-Attribute*/
    41.             EditorGUILayout.BeginHorizontal();
    42.             EditorGUILayout.LabelField("Name");
    43.             waveSpawnerScript.waves[i].waveName = EditorGUILayout.TextField(waveSpawnerScript.waves[i].waveName, GUILayout.Width(150));
    44.             EditorGUILayout.EndHorizontal();
    45.             /*Enemy-Attribute*/
    46.             EditorGUILayout.BeginHorizontal();
    47.             EditorGUILayout.LabelField("Enemy");
    48.             waveSpawnerScript.waves[i].enemyPrefab = (GameObject) EditorGUILayout.ObjectField(waveSpawnerScript.waves[i].enemyPrefab, typeof(GameObject), true, GUILayout.Width(150));
    49.             EditorGUILayout.EndHorizontal();
    50.             /*Countdown-Attribute*/
    51.             EditorGUILayout.BeginHorizontal();
    52.             EditorGUILayout.LabelField("Countdown");
    53.             waveSpawnerScript.waves[i].countdown = EditorGUILayout.FloatField(waveSpawnerScript.waves[i].countdown, GUILayout.Width(150));
    54.             EditorGUILayout.EndHorizontal();
    55.             /*Runs-Attribute*/
    56.             EditorGUILayout.BeginHorizontal();
    57.             EditorGUILayout.LabelField("Runs");
    58.             waveSpawnerScript.waves[i].runs = EditorGUILayout.IntField(waveSpawnerScript.waves[i].runs, GUILayout.Width(150));
    59.                 /*Resizes the enemyPerRun List to the size of runs*/
    60.                 int countEPRList = waveSpawnerScript.waves[i].enemyPerRun.Count;
    61.                 int sizeEPRList = waveSpawnerScript.waves[i].runs;
    62.                 int capacityEPRList = waveSpawnerScript.waves[i].enemyPerRun.Capacity;
    63.                 if (sizeEPRList < countEPRList) {
    64.                     waveSpawnerScript.waves[i].enemyPerRun.RemoveRange(sizeEPRList, countEPRList - sizeEPRList);
    65.                 }
    66.                 else if (sizeEPRList > countEPRList) {
    67.                     if (sizeEPRList > capacityEPRList) {
    68.                         waveSpawnerScript.waves[i].enemyPerRun.Capacity = sizeEPRList;
    69.                     }
    70.                     waveSpawnerScript.waves[i].enemyPerRun.AddRange(Enumerable.Repeat(1, sizeEPRList - countEPRList));
    71.                 }
    72.                 /*Resizes the timeBetweenRuns List to the size of runs*/
    73.                 int countTBRList = waveSpawnerScript.waves[i].timeBetweenRuns.Count;
    74.                 int sizeTBRList = waveSpawnerScript.waves[i].runs;
    75.                 int capacityTBRList = waveSpawnerScript.waves[i].timeBetweenRuns.Capacity;
    76.                 if (sizeTBRList < countTBRList) {
    77.                     waveSpawnerScript.waves[i].timeBetweenRuns.RemoveRange(sizeTBRList, countTBRList - sizeTBRList);
    78.                 }
    79.                 else if (sizeTBRList > countTBRList) {
    80.                     if (sizeTBRList > capacityTBRList) {
    81.                         waveSpawnerScript.waves[i].timeBetweenRuns.Capacity = sizeTBRList;
    82.                     }
    83.                     waveSpawnerScript.waves[i].timeBetweenRuns.AddRange(Enumerable.Repeat(1f, sizeTBRList - countTBRList));
    84.                 }
    85.             EditorGUILayout.EndHorizontal();
    86.             GUILayout.Space(10);
    87.             /*EnemyPerRun-Attribute*/
    88.             EditorGUILayout.LabelField("Enemy Per Runs");
    89.             EditorGUILayout.BeginHorizontal();
    90.             for (int j = 0; j < waveSpawnerScript.waves[i].enemyPerRun.Count; j++) {
    91.                 waveSpawnerScript.waves[i].enemyPerRun[j] = EditorGUILayout.IntField(waveSpawnerScript.waves[i].enemyPerRun[j], GUILayout.Width(30));
    92.             }
    93.             EditorGUILayout.EndHorizontal();
    94.             GUILayout.Space(10);
    95.             /*TimeBetweenRuns-Attribute*/
    96.             EditorGUILayout.LabelField("Time Between Runs");
    97.             EditorGUILayout.BeginHorizontal();
    98.             for (int j = 0; j < waveSpawnerScript.waves[i].timeBetweenRuns.Count; j++) {
    99.                 waveSpawnerScript.waves[i].timeBetweenRuns[j] = EditorGUILayout.FloatField(waveSpawnerScript.waves[i].timeBetweenRuns[j], GUILayout.Width(30));
    100.             }
    101.             EditorGUILayout.EndHorizontal();
    102.             /*Text*/
    103.             EditorGUILayout.LabelField("You should calculate TimeBetweenRuns as the following:");
    104.             EditorGUILayout.LabelField("TimePerWave > EnemyPerWave * 2 + x");
    105.             EditorGUILayout.EndVertical();
    106.         }      
    107.         GUILayout.Space(10);
    108.         EditorGUILayout.LabelField("Time Needed: " + waveSpawnerScript.timeNeeded + " s");
    109.         GUILayout.Space(20);
    110.         serializedObject.ApplyModifiedProperties();
    111.     }
    112.     private void AddWave() {
    113.         //Adds new Wave to the waves-list
    114.         waveSpawnerScript.waves.Add(new Wave());
    115.         //Sets the name of the new Wave to the list-count
    116.         waveSpawnerScript.waves[waveSpawnerScript.waves.Count - 1].waveName = "Wave" + waveSpawnerScript.waves.Count;
    117.     }
    118.     private void RemoveWave(int index) {
    119.         //Removes wave at index
    120.         waveSpawnerScript.waves.RemoveAt(index);
    121.     }
    122. }
    But as I said when it comes to undo or creating a prefab of an object containing that script nothing works, so I have to implement SerializedProperties for the list somehow.
    I have literally no clue, searched through the internet but found nothing near to my problem. Appreciate every attempt, cheers
     
  2. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    SerializedProperty.GetArrayElementAtIndex(int index) will return you another instance of SerializedProperty for array element.
     
    codxr likes this.
  3. codxr

    codxr

    Joined:
    Jun 19, 2019
    Posts:
    11
    Alright so that's the solution I figured out:

    First of all, you have to declare your SerializedProperty
    Code (CSharp):
    1. SerializedProperty wavesProp;
    And initialize it with the array property
    Code (CSharp):
    1.  private void OnEnable() {
    2.         wavesProp = serializedObject.FindProperty("waves");
    3.     }
    Secondly, you have to declare SerializedProperties for the class member variables
    Code (CSharp):
    1. /*Waveclass elements*/
    2.         SerializedProperty waveNameProp;
    3.         SerializedProperty enemyPrefabProp;
    4.         SerializedProperty countdownProp;
    5.         SerializedProperty runsProp;
    6.         SerializedProperty enemiesPerRunProp;
    7.         SerializedProperty timeBetweenRunsProp;
    Then you can access all array members as the following
    Code (CSharp):
    1. public override void OnInspectorGUI() {
    2.         serializedObject.Update();
    3.  
    4.         for (int i = 0; i < wavesProp.arraySize; i++) {
    5.  
    6.             /*Initialization*/
    7.             waveNameProp = wavesProp.GetArrayElementAtIndex(i).FindPropertyRelative("waveName");
    8.             enemyPrefabProp = wavesProp.GetArrayElementAtIndex(i).FindPropertyRelative("enemyPrefab");
    9.             countdownProp = wavesProp.GetArrayElementAtIndex(i).FindPropertyRelative("countdown");
    10.             runsProp = wavesProp.GetArrayElementAtIndex(i).FindPropertyRelative("runs");
    11.             enemiesPerRunProp = wavesProp.GetArrayElementAtIndex(i).FindPropertyRelative("enemiesPerRun");
    12.             timeBetweenRunsProp = wavesProp.GetArrayElementAtIndex(i).FindPropertyRelative("timeBetweenRuns");
    And then you can simply create PropertyFields
    Code (CSharp):
    1. /*PropertyFields*/
    2.             EditorGUILayout.PropertyField(waveNameProp, new GUIContent("Wave name"), GUILayout.Width(295));
    3.             EditorGUILayout.PropertyField(enemyPrefabProp, new GUIContent("Enemy prefab"), GUILayout.Width(295));
    4.             EditorGUILayout.PropertyField(countdownProp, new GUIContent("Countdown"), GUILayout.Width(295));
    For the array in the array (enemiesPerRun and timeBetweenRuns) I did the following
    Code (CSharp):
    1. for (int j = 0; j < enemiesPerRunProp.arraySize; j++) {
    2.                 EditorGUILayout.PropertyField(enemiesPerRunProp.GetArrayElementAtIndex(j), new GUIContent(""),GUILayout.Width(30));
    3.             }
    4. for (int j = 0; j < timeBetweenRunsProp.arraySize; j++) {
    5.                 EditorGUILayout.PropertyField(timeBetweenRunsProp.GetArrayElementAtIndex(j), new GUIContent(""), GUILayout.Width(30));
    6.             }
    Attention: Those two for-loops are inside the first for loop.
    I hope this helps for anyone who has the same question

    Here is my complete editor script: https://paste.myst.rs/b5q
     
    palex-nx likes this.
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    I'd recommend a property drawer for the wave class, then you can use EditorGUILayout.Propertyfield(waveListProperty, true);
     
    palex-nx likes this.