-
-
Save t0chas/34afd1e4c9bc28649311 to your computer and use it in GitHub Desktop.
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; | |
} |
Hi @mrKaizen it seems that with the suggestion by @abduelhamit it solves the problem
I am updating the code!
Thanks both for your input
why i can't see my header anymore if i use this editor script any suggestion ?
For exemple :
[Header("Static Obstacles")] public List ListOfObstaclesTypeOne = new List();
the message "Static Obstacles" before the list dont show up. Help plz
Can I get some help making this work for serialized objects? Currently only works for monobehaviours. I changed the "inspected type" property in the custom editor attribute, but still doesn't run on SOs and I'm not sure where else to start.
Welp I did it again... got SerializedObject mixed up with ScriptableObject...
Anyway to use this tool with ScriptableObjects, just replace the "inspected type" property in the "custom editor" class attribute.
Sadly doesn't work in conjonction with other custom editor we may have (because they call DrawDefaultInspector
and not yours instead and I haven't found a way around that just yet).
Also lost the ability to drag and drop a selection of multiple at once (as aforementioned).
Both would be VERY welcome.
EDIT: OP's answers: https://twitter.com/t0chas/status/976930045401882624
Because I am a very fussy person, I put in a lot of work to make the lists look a bit nicer, and these are the fruits of my labour!
Note: whenever I'm talking about a line, I am referring to the CustomEditorBase.cs as seen above. Also keep in mind it may have updated since this post.
What you'll wanna do is replace line 62 to 65 with this next bit:
EditorGUI.indentLevel++;
int indentSpace = EditorGUI.indentLevel * 15;
Rect lastRect = GUILayoutUtility.GetLastRect();
Rect rect = new Rect(indentSpace + 1f, lastRect.y + lastRect.height + 3f, Screen.width - indentSpace - 23f, 16f);
Color color = Color.white * 0.89f;
color.a = 1f;
EditorGUI.DrawRect(rect, color);
property.isExpanded = EditorGUILayout.Foldout(property.isExpanded, property.displayName);
EditorGUILayout.GetControlRect(true, 2f); // Not sure if it matters the value is true or not.
EditorGUI.indentLevel--;
If you want the size to be displayed next to the name of your list as well, remove the EditorGUILayout.Foldout (2nd from the last) and use this code in its place:
EditorGUILayout.BeginHorizontal();
property.isExpanded = EditorGUILayout.ToggleLeft(string.Format("{0}[]", property.displayName), property.isExpanded, EditorStyles.boldLabel, GUILayout.MaxWidth(Screen.width * 0.42f));
EditorGUILayout.LabelField(string.Format("size: {0}", property.arraySize), GUILayout.MaxWidth(Screen.width * 0.38f));
EditorGUILayout.EndHorizontal();
You may need to toy a bit with the MaxWidth() multiplier values, I haven't tested these sizes out after I got the box.
IMPORTANT! You will ALSO need to replace line 148 / drawHeaderCallback with this code:
this.List.drawHeaderCallback += rect => this._property.isExpanded = EditorGUI.Foldout(new Rect(rect.x + 10, rect.y, rect.width, rect.height), this._property.isExpanded, this._property.displayName);
While we're on the topic, I also had a problem with the opening and closing of my list, which seemed to not serialise until I did something else in the inspector. Here's how to fix that:
- remove all references to the AnimBool in the ReorderableListProperty (line 112, 132 and 133)
- change line 60 for this code:
if (listData.Property.isExpanded)
- remove line 68 and 70, they'll just look weird now.
- remove line 5 for good measure.
And jank, begone!
Originally I was trying to get the unexpanded list looking just like the expanded list, but listData.List.DoList(newRect); would only draw it under my other variables and there was nothing I could do to get it to work (believe me, I've tried). So if someone knows how to do it, I would love to hear it!
And because I know people are gonna ask: the Dictionaries you see are from the SerializableDictionary asset in the asset store. It is by RotaryHeart and it is 100% royalty free! So go get it! (There are currently 2 assets, make sure to get the 2.x version.)
This script needs IF EDITOR tags around it, otherwise it wont build (as far as I can tell).
#if UNITY_EDITOR .... #endif
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.
@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.
@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.
@t0chas, this doesn't work too well with recent version of Unity.
- "Element 0, Element 1, etc" text is displayed in grey behind the elements themselves: https://i.imgur.com/YecUs7z.png
- There is a big delay when expanding/collapsing the array.
- Dragging objects to add new elements is not working.
- 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.
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?
@t0chas, this doesn't work too well with recent version of Unity.
- "Element 0, Element 1, etc" text is displayed in grey behind the elements themselves: https://i.imgur.com/YecUs7z.png
- There is a big delay when expanding/collapsing the array.
- Dragging objects to add new elements is not working.
- 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.
Please add the following code into the first line of
drawElement
:It'll increase the readability of custom struct arrays a lot.