top of page

Introduction

 About Editor-Side Reference

Editor-Side Reference is at the same time, a tutorial and a live reference for the key aspects of Editor-Side programming.

Each sample is the most simple/illustrative as possible. Some parts, however, includes some tips that can help even experienced programmers.

This doc cover all the topics sequentially adding complexity along the topics, this way I hope you can take the overall picture without pain, however, All codes are fully documented so you can understand each piece of code without read it all.

Serialization

When I start to search for Unity-side at Internet, “Serialization” was word that crossed my way multiple times. The first hundred times I've read this word, I don't have any idea what it was about. It sounds like an advanced topic of programming far from my current knowledge.

In my opinion, the most complete source about Unity-side until now is a video called Serialization in-depth  and the first time I saw this presentation I don't understand a single sentence of it. Even now, after years, I'm not sure if I really understand what it means but let's see a little bit about that topic.

If we look at Wiki: we'll find: "Serialization is the process of translating data structures or object state into a format that can be stored (…)  and reconstructed later in the same or another computer environment."

It explain something but this isn't enough to understand how this is related to the Unity-Editor side. 

Let see at MSDN: "Serialization is the process of converting an object into a stream of bytes in order to store the object or transmit it to memory, a database, or a file. The main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization."

Ok, MSDN give us a similar definition from Wiki and was more helpful. We now, knows that something is stored somewhere and then read to be used in other context. The only question that remains is: What stored data isn't? Well, I'm not sure if any stored data isn't a serialization but something is very clear now: It's a huge and complicated word to describe a very simple and common process.

Let's do our last shot in order to link this definition with Unity documentation. Well, unfortunately, Unity doesn’t give us this definition but it says: "Serialization of “things” is at the very core of Unity. Many of our features build on top of the serialization system": Inspector window, Prefabs, Instantiation, Saving, Loading, Hot reloading of editor code, Resource.GarbageCollectSharedAssets().

Ok, so there's a system to store and read data that is used all over the editor. So how this can be useful? Let's take a look to an attribute called "Serializable" at Unity Script Docs. This class gives the last piece of information: "The Serializable attribute lets you embed a class with sub properties in the inspector". Eureka!

Everything that inherits from UnityEngine.Object can be serialized, In other words, if you're working only with Unity stuff, everything can be used inside our editor.

Well… Everything but… properties, static, const, readonly and abstract. Also serialized fields don't support polymorphism and don't support null for custom Classes. Interfaces can be serialized but this is not working as is supposed to do (Unity only deserialize if the object is selected).

Ok. Now that you know our key word let see some use for it.

Comparing our main Classes

Most of what we going to see happens inside Three main Classes: Editor,Property Drawer and Decorator Drawer so, before we continue, let compare these classes.

  • "Editor" inherits from "ScriptableObject" that inherits from "Object"

  • "Property Drawer" and "Decorator Drawer" inherits from "GUIDrawer"

Looking to their variables and functions, we notice the main difference: More than a simple drawer for serialized fields, Editor class is actually a huge functional class.

Putting it in less fancy words, Decorator Drawer is just to draw some random decorative stuff, while Property Drawer can actually handle the way that some property is draw. Editor Class, is not just a Drawer but a do-what-you-want class that can change and aggregate features to any part of Unity Editor.

Desired Attribute

Editor class has and easy access to the object being inspected:

  • (targets if multi-selection is possible): An Object type that can be converted for our class type.

  • : The serialized object here is often an entire component, a custom menu or a scene object

The callback method that we will use more often is:

  • : Used for all Editor window including Inspector. Return void. Receives nothing.

Decorator Drawer and Property Drawer has:

  • : A Property Attribute who specifies the property being draw.

Decorator Drawer and Property Drawer have some minor differences on theirs more often used callback method:

  • (DecoratorDrawer): Return void. Receives a Rect.

  • (PropertyDrawer): Return void. Receives a Rect, a SerializedProperty and a GUIContent.

In order to make the things easier, when writing a Property Drawer or a Decorator Drawer you can wrap the attribute variable inside a property.

 

I explain: Deal with the mysterious "Property Attribute" type is not just confusing but limited. The "Property Attribute" class is just a wrap for some well know type that we love. Luckily, this class implements an explicit cast to the type that we want.

Ex: If we are writing a class called "Whatever" we need to use

Whatever whatever = (Whatever) attribute;

A good pattern is to put this conversion inside a property.

Whatever whatever

{

    get

    {

        return ((Whatever)attribute);

    }

The same can be used to target variable from Editor class.

Sometimes, we also need to deal with another mysterious type called "Serialized Property", as we saw, another wrapper. There's several methods inside that class to parse for primitive types but there's a very important method that you should use in order to access what you want in non-primitive classes called FindPropertyRelative("myString").

 Inspector

Native Serialization

Arrays, List<T>, primitive types (int,float,dougle,bool,string, etc) and everything that inherits from UnityEngine.Object can be serialized if public. 

 

using UnityEngine;

using System.Collections.Generic;

 

public class BuiltIn : MonoBehaviour

{

      public bool myPrimitiveType;

      public GameObject inheritedFromObject;

      public float[] myArray;

      public List<int> myList;

Let see some complex example...

Custom classes should receive the property: System.Serializable, in order to be Serializable. Structs and Enums are also serializable. A Serializable property can serialize other property inside it up to 7 steps. (AKA:A serialized field inside a serialized field inside a serialized field insider a serialized field inside a serialized field inside a serialized field inside a serialized field).

 

 

using System;

using UnityEngine;

using UnityEngine.Events;

using System.Collections.Generic;

 

public class NativeSerialization : MonoBehaviour

{

       public bool myBool;

       public int myInteger;

       public float myFloat;

       public string myString;

       public Vector2 myVector2;

       public Vector3 myVector3;

       public Quaternion myQuaternion;

       public Vector4 myVector4;

       public Rect myRect;

       public AnimationCurve myAnimationCurve;

       public Color myColor;

       public Sprite mySprite;

       public Texture myTexture;

       public Material myMaterial;

       public AudioClip myAudioClip;

       public GameObject myGameObject;

       public LayerMask myLayerMask;

       public GameObject[] myGameObjectArray;

       public List<GameObject> myGameObjectList;

       public SomeEnum mySomeEnum;

       public SomeStruct mySomeStruct;

       public SomeClass mySomeClass;

       public List<SomeClass> mySomeClassList;

       public UnityEvent myUnityEvent;

}

 

public enum SomeEnum

{

       SomeOption,

       SomeOtherOption,

       SomeOtherOptionAgain

}

 

[Serializable]

public struct SomeStruct

{

       public string someString;

       public int someInt;

}

 

[Serializable]

 public class SomeClass

{

       public GameObject myGameObject;

       public SomeOtherClass mySomeOtherClass;

}

 

[Serializable]

public class SomeOtherClass

{

       public string myString;

       public int myInteger;

Property Drawer/Decorator Drawer

Beside the default way that a built-in serialization is done, there are some special properties that give us additional features. Some are just decorative and are called Decorator Drawer, others, changes default serialization behavior and are called Property Drawer:

 

 

using UnityEngine;

 

public class PropertyDrawers : MonoBehaviour

{

       [Header("A Header")]

       [Tooltip("A tip when Mouse Over")]

       public bool myBool;

       [Space(20)]

       [SerializeField]

       private int myInteger;

       [Range(0,100)]

       public float myFloat;

       [HideInInspector]

       public float myOtherFloat;

       [Header("Another Header.")]

       [Multiline]

       public string myString;

       [TextArea]

       public string myOtherString;

 

 

This looks good enough to create some fancy Inspector without the use of an elaborated editor-side code but you want to create your customized Inspector. First let's create a class:

using UnityEngine;

 

public class MyInspectorGUI : MonoBehaviour

{

}

Usually, to script on editor-side you should create another class that are responsible for serialize the one you want. Unity manual tells to create this class into a folder with the special name: "Editor". Everything inside “Editor” folder will not be used in runtime. Editor folder is really a place to “editor codes” and that's where you should put your “editor codes”.

//Important: The code below should be into a folder called "Editor".

using UnityEngine;

using UnityEditor;

 

 [CustomEditor(typeof(MyInspectorGUI))]

public class TestOnInspector : Editor

{

    public override void OnInspectorGUI()

    {

        GUILayout.Label("OnInspectorGUI example");

    }

}

We used the property "CustomEditor" telling what class we are talking about and inherits our class from "Editor" class.

Usually, I like to avoid custom Classes for my classes that are exposed into the editor. If you create a class for those ever-changing classes you'll need a constant maintenance in your editor-side code, what is very annoying. Instead, I like to use Custom Decorators and Custom Properties to teach my inspector smart ways to draw what I want.

That said, sometimes, as when we're building a code to be offered at Asset Store, we really need a Custom Inspector for our exposed class. If this is the case, I don't like to use the “Editor” folder for my Editor-Side script. I like to think at my classes with their serialized side. Split into different folders creates a non-explicit dependence which adds an additional step into code reuse. So I write the Editor-Side code in the same file as the Runtime Class. I admit, for most people, that’s not the best way.

On the traditional way, if you forgot to copy the editor-side file, your component will be a mess. If you put everything into the same file, the responsibility to not let any editor stuff extrapolate to your final release is entirely up to you. I re-write the previous example in that way to illustrate the difference.

 

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

]public class CustomInspector : MonoBehaviour

{

}

 

#if UNITY_EDITOR

[CustomEditor(typeof(CustomInspector))]

public class TestOnInspector : Editor

{

    public override void OnInspectorGUI()

    {

        GUILayout.Label("OnInspectorGUI example");

        GUIContent guiContentSample = new GUIContent("GUIContent Sample", "That's a fast way to add an Tooltip when doing your Editor Code. Can also add an image!");

        GUILayout.Label(guiContentSample);

    }

}

#endif

*A pretty cool Editor class is the one called GUIContent. GUIContent is like a container for a string and possible a tooltip and possible an image to be used as an icon or whatever.

Custom Property Drawer

 

If we want to create a property drawer for a class, we inherit our class from PropertyDrawer and use "CustomPropertyDrawer" to tell what class we are talking about.

Again, Editor-Side code with runtime code can be messier. So if you're unsure, just split your code and put the editor-side code inside your "Editor" folder.

 

[Serializable]

public class CustomizedClass

{

       public string myString;

       public SomeEnum myEnum;

}

 

#if UNITY_EDITOR

[CustomPropertyDrawer(typeof(CustomizedClass))]

public class CustomizedClassEditorSide : PropertyDrawer

{

       public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)

       {

              SerializedProperty myString = property.FindPropertyRelative("myString");

              SerializedProperty myEnum = property.FindPropertyRelative("myEnum");

             

              Rect myStringRect = new Rect (position.x,position.y,position.width/2,position.height);

              Rect myEnumRect = new Rect (position.x + position.width/2,position.y,position.width/2,position.height);

             

             

              EditorGUI.PropertyField(myStringRect, myString);

              EditorGUI.PropertyField(myEnumRect, myEnum);

       }

}

#endif 

*Unity says that you can’t use EditorGUILayout class inside Property Drawer due “performance reason”. There’s a workaround for that (with no apparent impact on “performance”) on Extra Session.

Custom Decorator Drawer

 

A Custom Decorator is very similar to the previous one. Just inherit from DecoratorDrawer instead of PropertyDrawer.

On the example below I did a separator bar. (The implementation I used here are inspired by that one).

First I write a class in the way I want it to work:

 

using UnityEngine;

 

public class DecoratorExample : MonoBehaviour

{

       public bool beforeDecorator;

       [SeparatorAttribute("Something")]

       public bool AfterDecorator;

Then… let the magic happen...

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

public class SeparatorAttribute:PropertyAttribute

{

       public readonly string title;

 

       public SeparatorAttribute()

       {

       }

      

       public SeparatorAttribute(string _title)

       {

              this.title = _title;

       }

}

 

#if UNITY_EDITOR

[CustomPropertyDrawer(typeof(SeparatorAttribute))]

public class separatorDrawer:DecoratorDrawer

{

       SeparatorAttribute separatorAttribute

       {

              get

              {

                     return ((SeparatorAttribute)attribute);

              }

       }

      

      

       public override void OnGUI(Rect _position)

       {

              if(separatorAttribute.title == null)

              {

                     _position.height = 1;

                     _position.y += 10;

                     GUI.Box(_position, "");

              }

              else

              {

                     Vector2 textSize = GUI.skin.label.CalcSize(new GUIContent(separatorAttribute.title));

                     float separatorWidth = (_position.width - textSize.x) / 2.0f - 5.0f;

                     _position.y += 10;

                    

                     GUI.Box(new Rect(_position.xMin, _position.yMin, separatorWidth, 1), "");

                     GUI.Label(new Rect(_position.xMin + separatorWidth + 5.0f, _position.yMin - 8.0f, textSize.x, 20), separatorAttribute.title);

                     GUI.Box(new Rect(_position.xMin + separatorWidth + 10.0f + textSize.x, _position.yMin, separatorWidth, 1), "");

              }

       }

      

       public override float GetHeight()

       {

              return 21.0f;

       }

}

#endif 

Context Menu Item

That's pretty much what you should know to start to scratch the Inspector but there's some additional tricks that you can use. Shouldn't be awesome if you can add some option on the right click over a variable name?

In order to achieve that you can use Context Menu Item, which is a property that receive a label and an function to call.

 

using UnityEngine;

 

public class ContextMenuItem : MonoBehaviour

{

       [ContextMenuItem("Add", "AddMethod")]

       [ContextMenuItem("Subtract", "SubtractMethod")]

       public int someInteger;

      

       void AddMethod ()

       {

              someInteger++;

       }

      

       void SubtractMethod ()

       {

              someInteger--;

       }

Context Menu

 Maybe your interest isn't so much in your code-part but in the component itself. You can call some methods from the component options (The little gear at the right upper corner of your component).

In order to achieve that you should use the attribute Context Menu as showed below.

 

using UnityEngine;

 

public class ContextMenuExample : MonoBehaviour

{

       [ContextMenu("Extra Option")]

       void ExtraOption ()

       {

              Debug.Log ("An extraOption was Selected");

       }

Alternatively, you can add this same feature using the special path “CONTEXT”. In that case, you should also want to pass a Menu Command parameter in order to take some references to the target object.

 

using UnityEngine;

using UnityEditor;

 

public class ContextMenuExample2

{

       [MenuItem("CONTEXT/ContextMenuExample/Another Extra Option")]

       private static void AnotherExtraOption (MenuCommand menuCommand)

       {

           Debug.Log ("You can find more about this one at ContextMenuExample2.cs");

       }

    }

 

Default Inspector

Sometimes we just want to draw the default Inspector and add some custom stuff to it. There's a very useful method to do that in Editor class called "DrawDefaultInspector".

If you want to draw a property in the way that it's usually done, EditorGUI.PropertyField receives a serializedProperty (no matter what type of the property being serialized) and do what is should be done.

 

 

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

public class DefaultInspector : MonoBehaviour

{

    public CustomizedClass mySomeClass;

}

 

#if UNITY_EDITOR

[CustomEditor(typeof(DefaultInspector))]

public class DefaultInspectorDrawer : Editor

{

    public override void OnInspectorGUI()

    {

        GUILayout.Label("This is above DrawDefaultInspector();");

        DrawDefaultInspector();

        GUILayout.Label("This is below DrawDefaultInspector();");

        EditorGUILayout.Space();

        EditorGUILayout.Space();

        GUILayout.Label("I used EditorGUILayout.PropertyField to serialize the class below");

        SerializedProperty serializedProperty = serializedObject.FindProperty("mySomeClass");

        EditorGUILayout.PropertyField(serializedProperty, true);

    }

}

#endif

Organizing Add Component

Your script is already a component as it inherits from MonoBehavior (MonoBehavior inherits from Behavior that inherits from Component) and works exactly like any built-in component as long as you drag and drop it inside a Game Object.

But if you try to add your component using Add Component option you'll see your Component inside a generic Script Folder. So in our way to a cool integrated component, we need to organize our Component using something before your class declaration:

 

[AddComponentMenu("My Custom Scripts/My Custom Component")]

 

Add Requirenment

If your component has some requirement you can put before your class declaration:

 

[RequireComponent(typeof(Rigidbody))] 

 

Avoiding the same components multiple times

If you're dealing with Unity for a while, you already have added the same script by mistake a few times. Well, your problems can be solved using:

 


[DisallowMultipleComponent]

Add Icon

But my objective is build something that looks like a built-in Unity component, so we really need an icon. Unfortunately, Unity don't provide a good solution for that. You can assign a small image (64x64) manually as you can see below but that isn't a permanent solution for your component looks like a build-in component.. In the Part 3 of this post I'll put some workaround.

For add an ICON, you first will need one. After that, you go to your script, click over his actual Icon, Select Other, Select your new Icon and that's it.

 

 

 

Window

Unity's Editor isn't just an Inspector, actually, several other windows have useful functionalities as The Animation Window, the Animator Window, Sprite Packer Window or even the Console Window. Several plugins expand Unity functionalities by creating new Windows. And so we also should see how to do it.

Toolbar

 

First let's put a simple Method in our Toolbar.

 

using UnityEngine;

using UnityEditor;

 

public class ToolbarOption : MonoBehaviour

{

    [MenuItem("Something/Whatever/DO #%w")]

    static void DoSomething()//Need to be static and UnityEditor is needed

    {

        Debug.Log("Something");

    }

}

 

The method above can now be called from the toolbar at the specified path "Something/Whatever/DO #%w" or by the specified shortcut combination Shift + Ctrl + W.

You can see Item Menu Shortkeys in MenuItem description from Unity Script Docs.

New Window 

We'll use this same technique to call a new Window. In order to achieve that, our code should be on Editor Folder. We'll use a regular OnGUI method but we need to specify that this OnGUI has the special ability to draw a window. To do that, use EditorWindow.GetWindow or EditorWindow.GetWindowWithRect (used to specify a the window size).

 

 

using UnityEngine;

using UnityEditor;

 

public class MyWindow : EditorWindow

{

       [MenuItem ("Window/My Window")]

       public static void  ShowWindow ()

       {

              EditorWindow.GetWindow(typeof(MyWindow)); //Or using GetWindowWithRect

       }

 

       void OnGUI ()

       {

              GUILayout.Label("Some new Window", EditorStyles.boldLabel);

              EditorGUILayout.LabelField("Use GUI, GUILayout, EditorGUI, EditorGUILayout and Others Editor Classes to put something cool here");

              if (GUILayout.Button("Do something amazing"))

                     Debug.Log("Amazing Log");

       }

 

 

 Viewport

Until now, we saw how to change the gray part of Unity-Editor (supposing that you didn't change the default gray layout), now it's time to do something much more fun. Let's deal with the Viewport. Yes! Our adorable and colored viewport can be much more than a place to see your game Objects. We can create Gizmos and Handles and really achieve the state of art of Unity Editor-Side programming.

Gizmos

First let's understand what a Gizmo is. A Gizmos is a sign, a non-interactive representative structure of something else. Usually Gizmos are rendered above everything and there's no particular function for a Gizmo than represent something (Useful on Debug). Unity has a class called Gizmos where you'll found everything that you need. This class should be used inside one of two special mono-behavior methods called OnDrawGizmos and OnDrawGizmosSelected.

 

Unity script explains: "All gizmo drawing has to be done in either OnDrawGizmos or OnDrawGizmosSelected functions of the script.OnDrawGizmos is called every frame. All gizmos rendered within OnDrawGizmos are pickable. OnDrawGizmosSelected is called only if the object the script is attached to is selected."

 

using UnityEngine;

 

public class DrawGizmos : MonoBehaviour

{

       void OnDrawGizmosSelected()

       {

        Gizmos.DrawIcon(transform.position, "YICON");

        Gizmos.color = Color.yellow;

              Gizmos.DrawSphere(transform.position, 1);

       }

}

 

 Handles

Handles are interactive so more than just a representation, handles are tools to visually manipulate variables. You see that fancy arrows used to move/rotate/scale an object in Unity. That is a Handle.

Unity has a class called Handles and a special class with some additional stuff called HandlesUtility. Handle should be manipulated inside OnSceneGUI method.

 

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

public class DrawHandles : MonoBehaviour

{

       public float areaOfEffect = 5;

}

 

#if UNITY_EDITOR

[CustomEditor (typeof(DrawHandles))]

class DrawSolidDisc:Editor

{

       DrawHandles drawHandles

       {

              get

              {

                     return ((DrawHandles)target);

              }

       }

 

       void OnSceneGUI ()

       {

              Handles.color = new Color(1f,0f,0f,0.1f);

              Handles.DrawSolidDisc(drawHandles.transform.position, Vector3.up, drawHandles.areaOfEffect);

              Handles.color = Color.red;

              drawHandles.areaOfEffect = Handles.ScaleValueHandle(drawHandles.areaOfEffect,drawHandles.transform.position + new Vector3(drawHandles.areaOfEffect,0,0),Quaternion.identity,2,Handles.CylinderCap,2);

 

 

        // start of 2D UI section

        Handles.BeginGUI();

        Handles.Label(drawHandles.transform.position + Vector3.right * 2, "Area Of Effect: " + drawHandles.areaOfEffect);

        GUILayout.BeginArea(new Rect(Screen.width - 100, Screen.height - 80, 90, 50));

        if (GUILayout.Button("Reset Area")) { drawHandles.areaOfEffect = 5; }

            GUILayout.EndArea();

        Handles.EndGUI();

    }

}

#endif

 

 

 

 Extras

PING

If you are trying to build a component that really looks like an build-in solution, you've notice that a very useful tool has gone when we do our Editor-side script: The Asset Ping.

The Asset Ping is a representation of your code that call for your code in the asset folder with one click. To be more specific, is a guy similar to the one below.

 

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

public class Ping : MonoBehaviour

{

}

 

#if UNITY_EDITOR

[CustomEditor(typeof(Ping))]

public class UserControllerEditor : Editor

{

    public override void OnInspectorGUI()

    {

        //EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((MonoBehaviour)target), typeof(Ping)); //Obsolete

        EditorGUILayout.ObjectField("Script",MonoScript.FromMonoBehaviour((MonoBehaviour)target), typeof(Ping), true);

    }

}

Using EnumMask (AKA: Flag)

I must confess, before I understand how to use Property Drawer,  most of my Editor-side scripts was created just because of that: EnumMasks. I never was too comfortable with bitwise and enum flag operations but the benefits of such structures are great.

 

First you should create an enum but giving potency of two as values so each option will be represented by a single '1'.

public enum MaskedEnum

{

    Something = 1,

    SomethingElse = 2,

    OtherOption = 4,

    AnotherOption = 8,

    MoreOption = 16,

    SoMuchOption = 32,

    AlmostEndingMyOption = 64,

    AlmostThere = 128,

    Ending = 256,

    TheLastOne = 512

 

Create a enum variable and by default your enum are on Inspector working as a regular enum.

The magic happens on editor-side script by using EnumMaskField.


 In that example you will see how to compare and switch on/off each flag.

 

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

public class EnumMask : MonoBehaviour

{

    public MaskedEnum maskedEnum;

 

    void Update ()

    {

        if (Input.GetKeyDown(KeyCode.Plus) || Input.GetKeyDown(KeyCode.KeypadPlus))

            maskedEnum |= MaskedEnum.Something;

 

        if (Input.GetKeyDown(KeyCode.Minus) || Input.GetKeyDown(KeyCode.KeypadMinus))

            maskedEnum &= ~MaskedEnum.Something;

 

        if ((maskedEnum & MaskedEnum.Something) == MaskedEnum.Something)

        {

            Debug.Log("MaskedEnum.Something is selected");

        }

    }

 

}

 

#if UNITY_EDITOR

[CustomEditor(typeof(EnumMask))]

public class EnumMaskInspector : Editor

{

    public override void OnInspectorGUI()

    {

        EnumMask enumMask = (EnumMask)target;

        enumMask.maskedEnum = (MaskedEnum)EditorGUILayout.EnumMaskField("Options:", enumMask.maskedEnum);

        EditorGUILayout.LabelField("Use \"+\" and \"-\" at runtime to turn on/off Something");

    }

}

#endif

 

public enum MaskedEnum

{

    Something = 1,

    SomethingElse = 2,

    OtherOption = 4,

    AnotherOption = 8,

    MoreOption = 16,

    SoMuchOption = 32,

    AlmostEndingMyOption = 64,

    AlmostThere = 128,

    Ending = 256,

    TheLastOne = 512

}

 

GUILayout on Custom Property Drawer

You can`t use GUILayout on Custom Property Drawer, they said. It`s about performance, they said. An error will be presented if you do it, they said.

Well… as usual, unity guys are right, however, as developers, we hate “don`t” and so that`s HOW we can achieve that: Simple create an Editor-Side code for your class. That`s it. You don`t even need to implement specific methods.

I`ve created a Custom Property Drawer and a sample Class below.

 

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

public class Workaround : MonoBehaviour

{

    [TextWithGUILayout()] //Print "Workaround using EditorGUI" into inspector. Ignore the parameter below.

    public float TextWithGUILayout;

}

 

#if UNITY_EDITOR

[CustomEditor(typeof(Workaround))]

public class EmptyEditorClass : Editor

{

    //This way enables UnityEditor to use GUILayout when not allowed. We don't even need to override OnInspectorGUI for that!

}

#endif

using UnityEngine;

#if UNITY_EDITOR

using UnityEditor;

#endif

 

/// <summary>

/// A simple Custom Property Atribute using EditorGUILayout can only be used by classes with Editor-Side code.

/// </summary>

public class TextWithGUILayout : PropertyAttribute

{

    public TextWithGUILayout()

    { }

}

 

#if UNITY_EDITOR

[CustomPropertyDrawer(typeof(TextWithGUILayout))]

public class TextWithGUILayoutDrawer : PropertyDrawer

{

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)

    {

        EditorGUILayout.LabelField("Workaround using EditorGUI");

    }

}

#endif

 

Editor Extension (Extending Renderer with Sort Layer Renderer)

Warning: This code uses some advanced stuff as reflection and makes reference to UnityEditorInternal namespace.

You can add functionalities to default components by writing your own CustomEditor for those classes. Your code should be in “Editor” folder and you can use “DrawDefaultInspector()” to avoid rewrite the default inspector all over.

For this example, I’ll extend the Renderer class in order to expose Sort Layer and Order in Layer, very useful if you are creating a 2D or 2.5D game. This is also useful to optimizer render passes.

 

using System;

using System.Collections.Generic;

using UnityEngine;

using UnityEditor;

using UnityEditorInternal; //Deal with this namespace is a little risky since it's not supported by Unity for User usage.

using System.Reflection; //Reflection should be avoid in real time. Here, it's used only on Editor so It's ok. If you don't know what reflection does, please refer to https://msdn.microsoft.com/en-us/library/f7ykdhsy(v=vs.110).aspx

 

[CanEditMultipleObjects()] //So you can set multiple layers at the same time.

 

#if UNITY_5

[CustomEditor(typeof(LineRenderer),true)]

public class SortLayerLineRendererExtension : SortLayerRendererExtension{}

 

[CustomEditor(typeof(TrailRenderer),true)]

public class SortLayerTrailRendererExtension : SortLayerRendererExtension{}

 

[CustomEditor(typeof(SkinnedMeshRenderer),true)]

public class SortLayerSkinnedMeshRendererExtension : SortLayerRendererExtension{}

 

[CustomEditor(typeof(ParticleRenderer),true)]

public class SortLayerParticleRendererExtension : SortLayerRendererExtension{}

#endif

[CustomEditor(typeof(Renderer),true)] //In Unity 4, we can override almost all renderers at once overriding Renderer class. In Unity 5 you'll need more more specific approuch.

public class SortLayerRendererExtension : Editor

{

    Renderer renderer;

    Renderer[] childsRenderer;

    string[] sortingLayerNames;

 

    int selectedOption;

    bool applyToChild = false;

    bool applyToChildOldValue = false;

 

    void OnEnable()

    {

        sortingLayerNames = GetSortingLayerNames();

        renderer = (target as Renderer).gameObject.GetComponent<Renderer>();

        if ((target as Renderer).transform.childCount > 1)

            childsRenderer = (target as Renderer).transform.GetComponentsInChildren<Renderer>();

 

        for (int i = 0; i<sortingLayerNames.Length;i++)

        {

            if (sortingLayerNames[i] == renderer.sortingLayerName)

                selectedOption = i;

        }

    }

 

    public override void OnInspectorGUI()

    {

        DrawDefaultInspector();

        if (!renderer)

        {

            return;

        }

 

        EditorGUILayout.LabelField("\n");

 

        selectedOption = EditorGUILayout.Popup("Sorting Layer", selectedOption, sortingLayerNames);

        if (sortingLayerNames[selectedOption] != renderer.sortingLayerName)

        {

            Undo.RecordObject(renderer, "Sorting Layer");

            if (!applyToChild)

                renderer.sortingLayerName = sortingLayerNames[selectedOption];

            else

            {

                for (int i = 0; i<childsRenderer.Length;i++)

                {

                    childsRenderer[i].sortingLayerName = sortingLayerNames[selectedOption];

                }

            }

            EditorUtility.SetDirty(renderer);

        }

 

        int newSortingLayerOrder = EditorGUILayout.IntField("Order in Layer", renderer.sortingOrder);

        if (newSortingLayerOrder != renderer.sortingOrder)

        {

            Undo.RecordObject(renderer, "Edit Sorting Order");

            renderer.sortingOrder = newSortingLayerOrder;

            EditorUtility.SetDirty(renderer);

        }

 

        applyToChild = EditorGUILayout.ToggleLeft("Apply to Childs", applyToChild);

        if (applyToChild != applyToChildOldValue)

        {

            for (int i = 0; i<childsRenderer.Length;i++)

            {

                childsRenderer[i].sortingLayerName = sortingLayerNames[selectedOption];

            }

            Undo.RecordObject(renderer, "Apply Sort Mode To Child");

            applyToChildOldValue = applyToChild;

            EditorUtility.SetDirty(renderer);

        }

    }

 

    // Get the sorting layer names

    public string[] GetSortingLayerNames()

    {

        Type internalEditorUtilityType = typeof(InternalEditorUtility);

        PropertyInfo sortingLayersProperty = internalEditorUtilityType.GetProperty("sortingLayerNames", BindingFlags.Static | BindingFlags.NonPublic);

        return (string[])sortingLayersProperty.GetValue(null, new object[0]);

    }

   

    // Get the unique sorting layer IDs -- tossed this in for good measure

    public int[] GetSortingLayerUniqueIDs()

    {

        Type internalEditorUtilityType = typeof(InternalEditorUtility);

        PropertyInfo sortingLayerUniqueIDsProperty = internalEditorUtilityType.GetProperty("sortingLayerUniqueIDs", BindingFlags.Static | BindingFlags.NonPublic);

        return (int[])sortingLayerUniqueIDsProperty.GetValue(null, new object[0]);

    }

}

 

 

 

.

.

.

.

.

.

.

.

.

.

.

More to come…

ESR is free and should remain as it is.

Any donation will be converted on updates and improvements.

PayPal ButtonPayPal Button
bottom of page