Last Updated: October 29, 2016
·
2.773K
· ProGM

Show a popup field to serialize string or integer values from a list of choices in unity3d

In a previous coderwall post of mine, I tried to explain how to create show a popup in the editor to select easily the sorting layer in a MonoBehavior.

My question, after that, was: how could I make this more generic?

For instance, I want that my string property to stay inside a list of values.

Here's a simple solution:

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

public class StringInList : PropertyAttribute {
  public delegate string[] GetStringList();

  public StringInList(string [] list) {
    List = list;
  }

  public string[] List {
    get;
    private set;
  }
}


#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(StringInList))]
public class StringInListDrawer : PropertyDrawer {
  // Draw the property inside the given rect
  public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
    var stringInList = attribute as StringInList;
    var list = stringInList.List;
    if (property.propertyType == SerializedPropertyType.String) {
      int index = Mathf.Max (0, Array.IndexOf (list, property.stringValue));
      index = EditorGUI.Popup (position, property.displayName, index, list);

      property.stringValue = list [index];
    } else if (property.propertyType == SerializedPropertyType.Integer) {
      property.intValue = EditorGUI.Popup (position, property.displayName, property.intValue, list);
    } else {
      base.OnGUI (position, property, label);
    }
  }
}
#endif

And here's how to use it:

public class MyBehavior : MonoBehaviour {
    [StringInList(new [] { "Cat", "Dog"})] public string Animal;
    // This will store the index of the array value
    [StringInList(new [] { "John", "Jack", "Jim"})] public int PersonID;
}

However this is not very useful, as is. I would like to create a set of useful property drawers that could show some game informations.

Here's an example: Assuming I have a Component that should change its behavior when it's loaded in a certain Scene.

public class SomeBehavior : MonoBehaviour {
  public string SceneName;

  void Start() {
      if (SceneManager.GetCurrentScene().name == SceneName) {
          Debug.Log("Special things");
      }
  }
}

It would be really cool if the scene selection could be shown as a list of scene names.

Here's how I edited the "SceneInList" property drawer:

public class StringInList : PropertyAttribute {
  public delegate string[] GetStringList();

  public StringInList(string [] list) {
    List = list;
  }

  public StringInList(Type type, string methodName) {
    var method = type.GetMethod (methodName);
    if (method != null) {
      List = method.Invoke (null, null) as string[];
    } else {
      Debug.LogError ("NO SUCH METHOD " + methodName + " FOR " + type);
    }
  }

  public string[] List {
    get;
    private set;
  }
}

Now, using reflection, I can define a static method that creates the list of scene names:

public static class PropertyDrawersHelper {
  #if UNITY_EDITOR

  public static string[] AllSceneNames()
  {
    var temp = new List<string>();
    foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes)
    {
      if (S.enabled)
      {
        string name = S.path.Substring(S.path.LastIndexOf('/')+1);
        name = name.Substring(0,name.Length-6);
        temp.Add(name);
      }
    }
    return temp.ToArray();
  }

  #endif
}

And here's how it could be used:

[StringInList(typeof(PropertyDrawersHelper), "AllSceneNames")] public string SceneName;

Here's the complete example:
https://gist.github.com/ProGM/9cb9ae1f7c8c2a4bd3873e4df14a6687

We used this code in Oh! I'm Getting Taller :D

I hope it'll be useful!