Skip to content

Instantly share code, notes, and snippets.

@t0chas
Last active March 5, 2024 16:47
Show Gist options
  • Save t0chas/34afd1e4c9bc28649311 to your computer and use it in GitHub Desktop.
Save t0chas/34afd1e4c9bc28649311 to your computer and use it in GitHub Desktop.
Default Custom Inspector-Editor for Unity3D with ReorderableLists for arrays handling
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System.Collections.Generic;
using UnityEditor.AnimatedValues;
[CustomEditor(typeof(UnityEngine.Object), true, isFallback = true)]
[CanEditMultipleObjects]
public class CustomEditorBase : Editor
{
private Dictionary<string, ReorderableListProperty> reorderableLists;
protected virtual void OnEnable()
{
this.reorderableLists = new Dictionary<string, ReorderableListProperty>(10);
}
~CustomEditorBase()
{
this.reorderableLists.Clear();
this.reorderableLists = null;
}
public override void OnInspectorGUI()
{
EditorGUILayout.LabelField("Custom Editor", EditorStyles.centeredGreyMiniLabel);
Color cachedGuiColor = GUI.color;
serializedObject.Update();
var property = serializedObject.GetIterator();
var next = property.NextVisible(true);
if (next)
do
{
GUI.color = cachedGuiColor;
this.HandleProperty(property);
} while (property.NextVisible(false));
serializedObject.ApplyModifiedProperties();
}
protected void HandleProperty(SerializedProperty property)
{
//Debug.LogFormat("name: {0}, displayName: {1}, type: {2}, propertyType: {3}, path: {4}", property.name, property.displayName, property.type, property.propertyType, property.propertyPath);
bool isdefaultScriptProperty = property.name.Equals("m_Script") && property.type.Equals("PPtr<MonoScript>") && property.propertyType == SerializedPropertyType.ObjectReference && property.propertyPath.Equals("m_Script");
bool cachedGUIEnabled = GUI.enabled;
if (isdefaultScriptProperty)
GUI.enabled = false;
//var attr = this.GetPropertyAttributes(property);
if (property.isArray && property.propertyType != SerializedPropertyType.String)
this.HandleArray(property);
else
EditorGUILayout.PropertyField(property, property.isExpanded);
if (isdefaultScriptProperty)
GUI.enabled = cachedGUIEnabled;
}
protected void HandleArray(SerializedProperty property)
{
var listData = this.GetReorderableList(property);
listData.IsExpanded.target = property.isExpanded;
if ((!listData.IsExpanded.value && !listData.IsExpanded.isAnimating) || (!listData.IsExpanded.value && listData.IsExpanded.isAnimating))
{
EditorGUILayout.BeginHorizontal();
property.isExpanded = EditorGUILayout.ToggleLeft(string.Format("{0}[]", property.displayName), property.isExpanded, EditorStyles.boldLabel);
EditorGUILayout.LabelField(string.Format("size: {0}", property.arraySize));
EditorGUILayout.EndHorizontal();
}
else {
if (EditorGUILayout.BeginFadeGroup(listData.IsExpanded.faded))
listData.List.DoLayoutList();
EditorGUILayout.EndFadeGroup();
}
}
protected object[] GetPropertyAttributes(SerializedProperty property)
{
return this.GetPropertyAttributes<PropertyAttribute>(property);
}
protected object[] GetPropertyAttributes<T>(SerializedProperty property) where T : System.Attribute
{
System.Reflection.BindingFlags bindingFlags = System.Reflection.BindingFlags.GetField
| System.Reflection.BindingFlags.GetProperty
| System.Reflection.BindingFlags.IgnoreCase
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Public;
if (property.serializedObject.targetObject == null)
return null;
var targetType = property.serializedObject.targetObject.GetType();
var field = targetType.GetField(property.name, bindingFlags);
if (field != null)
return field.GetCustomAttributes(typeof(T), true);
return null;
}
private ReorderableListProperty GetReorderableList(SerializedProperty property)
{
ReorderableListProperty ret = null;
if (this.reorderableLists.TryGetValue(property.name, out ret))
{
ret.Property = property;
return ret;
}
ret = new ReorderableListProperty(property);
this.reorderableLists.Add(property.name, ret);
return ret;
}
#region Inner-class ReorderableListProperty
private class ReorderableListProperty
{
public AnimBool IsExpanded { get; private set; }
/// <summary>
/// ref http://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/
/// </summary>
public ReorderableList List { get; private set; }
private SerializedProperty _property;
public SerializedProperty Property
{
get { return this._property; }
set
{
this._property = value;
this.List.serializedProperty = this._property;
}
}
public ReorderableListProperty(SerializedProperty property)
{
this.IsExpanded = new AnimBool(property.isExpanded);
this.IsExpanded.speed = 1f;
this._property = property;
this.CreateList();
}
~ReorderableListProperty()
{
this._property = null;
this.List = null;
}
private void CreateList()
{
bool dragable = true, header = true, add = true, remove = true;
this.List = new ReorderableList(this.Property.serializedObject, this.Property, dragable, header, add, remove);
this.List.drawHeaderCallback += rect => this._property.isExpanded = EditorGUI.ToggleLeft(rect, this._property.displayName, this._property.isExpanded, EditorStyles.boldLabel);
this.List.onCanRemoveCallback += (list) => { return this.List.count > 0; };
this.List.drawElementCallback += this.drawElement;
this.List.elementHeightCallback += (idx) => { return Mathf.Max(EditorGUIUtility.singleLineHeight, EditorGUI.GetPropertyHeight(this._property.GetArrayElementAtIndex(idx), GUIContent.none, true)) + 4.0f; };
}
private void drawElement(Rect rect, int index, bool active, bool focused)
{
if (this._property.GetArrayElementAtIndex(index).propertyType == SerializedPropertyType.Generic)
{
EditorGUI.LabelField(rect, this._property.GetArrayElementAtIndex(index).displayName);
}
//rect.height = 16;
rect.height = EditorGUI.GetPropertyHeight(this._property.GetArrayElementAtIndex(index), GUIContent.none, true);
rect.y += 1;
EditorGUI.PropertyField(rect, this._property.GetArrayElementAtIndex(index), GUIContent.none, true);
this.List.elementHeight = rect.height + 4.0f;
}
}
#endregion
}
#pragma strict
var myInt : int = 5;
var myFloat: float = 10.56;
public var players : GameObject[];
public var floats : float[];
public var numbers : int[];
public var vectors : Vector3[];
function Start () {
}
function Update () {
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//using System;
[System.Flags]
public enum Stuff{
Coke = 1 << 0,
Hamburguer = 1 << 1,
Pizza = 1 << 2,
Hotdog = 1 << 3,
Pepsi = 1 << 4,
Beer = 1 << 5,
BuffaloWings = 1 << 6,
IceCream = 1 << 7,
}
[System.Serializable]
public struct SomeOtherData
{
public int aNumber;
[Range(0f, 1f)]
public float anotherNumber;
public Vector3 position;
}
public class Tester : MonoBehaviour {
public int aNumber;
public int AFloat;
[Range(0f, 100f)]
public float AFloatRangeAttr;
public string aString;
public int[] aNumerArray;
public float[] aFloatArray;
[Range(0.0f, 100.0f)]
public float[] anotherFloatArray;
[SerializeField]
private List<Stuff> privateListOfStuff;
[SerializeField]
private SomeOtherData NestedData;
[SerializeField]
private AudioMixerClip mixerClip;
[SerializeField]
private AudioMixerClip[] mixerClips;
}
@JohannesMP
Copy link

Terrox: It sounds like you are compiling it as a runtime script, not an editor script. In Unity, any script placed in an 'Editor' directory are only compiled for the editor and so don't need to be wrapped in UNITY_EDITOR directive checks. See the special folder docs for more info.

@Phylum123
Copy link

@Casey-Hofland I tried to put in your code changes, and the list doesn't work. The part when you remove the AnimBool is where it all goes wrong. Can you please add a link or comment the actual code that you made. I really don't like clicking a bool to open my list ;). Thank you.

@hariedo
Copy link

hariedo commented Aug 11, 2018

@Casey-Hofland, I would also like to see your final edit, as it's not clear what to do with original line 59 once you start pulling out AnimBool and AnimatedValues.

@phobos2077
Copy link

phobos2077 commented Sep 16, 2018

@t0chas, this doesn't work too well with recent version of Unity.

  1. "Element 0, Element 1, etc" text is displayed in grey behind the elements themselves: https://i.imgur.com/YecUs7z.png
  2. There is a big delay when expanding/collapsing the array.
  3. Dragging objects to add new elements is not working.
  4. Nested arrays not working.

I think the main flaw is that you shouldn't use Custom Inspector to solve this problem, but PropertyDrawer instead. I know they are harder to work with but it's the only way to do it properly.

@the-mr-matt
Copy link

the-mr-matt commented Oct 28, 2018

How can I remove the foldout in the elements? I find it slows me down having to expand them and it looks kinda bad. I removed the expand button at the top but I can't find anything in the code about the foldouts. The regular reorderable lists don't use these so I assume you're either using something else or have manually added them. Any ideas?

@farshidhss
Copy link

farshidhss commented Dec 31, 2018

@t0chas, this doesn't work too well with recent version of Unity.

  1. "Element 0, Element 1, etc" text is displayed in grey behind the elements themselves: https://i.imgur.com/YecUs7z.png
  2. There is a big delay when expanding/collapsing the array.
  3. Dragging objects to add new elements is not working.
  4. Nested arrays not working.

I think the main flaw is that you shouldn't use Custom Inspector to solve this problem, but PropertyDrawer instead. I know they are harder to work with but it's the only way to do it properly.

I recommend use https://github.com/SubjectNerd-Unity/ReorderableInspector It does solve the issue with folding and provides many more features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment