Make your tools in Unity editor

The path editor for my game in progress
The path editor for my game in progress

With Unity, you can save a lot of time by creating some tools adapted to your specific needs. Right above, a small path editor I made to save me some time when creating enemy patterns in my current project, Red Skies. With just a few click, I quickly design the general shape I want, then I can perfect it using the transform coordinates.

The Unity editor offers a LOT of tools to make yours, but I will just cover some basics in this post, then it will be up to you to look at the APIs to discover hidden gems.

WARNING: This post is ALL about code, with (commented) snippets rather than theory; that’s the way I like to learn, I hope I’m not alone!


Custom inspector

The most common tool you can use to make your life easier is custom inspector.

A very basic way to improve your inspectors is to use Property Attributes, which do not require to rewrite the entire inspector, but rather make it more readable:

So simple, yet so beautiful
So simple, yet so beautiful

using UnityEngine;

 

public class MyScript : MonoBehaviour

{

    [Header(“First category”)]

    [Tooltip(“Primary name for MyScript.”)]

    public string scriptName;

    public float scriptFloat;

 

    [Header(“Second category”)]

    [Tooltip(“Secondary name for MyScript.”)]

    public string secondaryName;

}

And then you can go pretty crazy with the custom inspectors themselves. Those scripts are editor-side only (they have to inherit from Editor and be placed in a folder called Editor) and let you write what you want, how you want, in the inspector when an instance of your script is selected. There is a ton of functions to experiment with, that can be found in various editor classes such as EditorGUILayout, GUILayout, EditorGUIUtility and many others, ranging from simple to highly customizable.

I bet it could be of some use… maybe… for a weird project
I bet it could be of some use… maybe… for a weird project

using UnityEngine;

using UnityEditor;

 

//This line will automatically call your custom editor for instances of MyScript

[CustomEditor(typeof(MyScript))]

public class MyScriptInspector : Editor

{

    //Keep a reference on your style rather than regenerating them every update

    GUIStyle headerStyle;

 

    //This custom inspector will generate random ids in order to display a list to

    // the user from which he can then choose

    bool choosingNewId = false;        //Are options displayed?

    string[] newIds;                //Random ids generated to choose from

 

    public override void OnInspectorGUI()

    {

        //The member “target” allow you to grab a ref to the instance of your component

        MyScript myScriptTarget = (MyScript) target;

 

        //Init styles

        if(headerStyle == null)

        {

            InitStyles();

        }

 

        EditorGUILayout.Space();

        EditorGUILayout.LabelField(“First Category”, headerStyle);

        EditorGUILayout.LabelField(“Script id: ” + myScriptTarget.scriptName);

 

        //If player has already clicked on the “Generate new name” button,

        // then we only display the generated options

        if(choosingNewId)

        {

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

            {

                if(GUILayout.Button(newIds[i]))

                {

                    myScriptTarget.scriptName = newIds[i];

                    choosingNewId = false;

                }

            }

        }

        else if(GUILayout.Button(“Generate new name”))

        {

            newIds = new string[] { Random.Range(0, int.MaxValue).ToString(),

                Random.Range(0, int.MaxValue).ToString(),

                Random.Range(0, int.MaxValue).ToString() };

            choosingNewId = true;

        }

    }

 

    void InitStyles()

    {

        headerStyle = new GUIStyle();

        headerStyle.fontStyle = FontStyle.Bold;

        headerStyle.normal.textColor = Color.yellow;

    }

}

Note that I could have used a PopUp to display the choices in another way (there are many other solutions, I guess, but the EditorGUILayout.PopUp is pretty neat).

You just indicate the class for which you want to make your custom inspector, then the OnInspectorGUI is ready to make all your dreams come true. The EditorGUILayout and GUILayout classes are usually the more convenient to use, since you don’t have to handle the position and size of your elements, but you can use EditorGUI and GUI as well for very precise display (you could recreate, say, a color picker in the inspector by drawing a bunch of colored pixels and using the events of the mouse).

There are of course some limitations and tricky cases, but it’s up to you to find out about those according to your needs.


Editor windows

Did I speak about your custom color picker? Well why not, after all! Let’s make it in a small window, so that it doesn’t take all the space in the inspector. Editor windows use the same apis as custom inspector. To spice things up, we’ll allow the user to select the amount of color he wants to be able to choose from. (It’s not a course about color, so forgive me if my color theory isn’t perfect!)

A color picker, not the best idea for a gif… but I spent way too much time doing it to change it now
A color picker, not the best idea for a gif… but I spent way too much time doing it to change it now

using UnityEngine;

using UnityEditor;

 

public class DiscreteColorPicker : EditorWindow

{

    //Create and open a discrete color picker

    [MenuItem(“Window/Open Discrete color picker”)]

    public static void OpenDiscreteColorPicker()

    {

        DiscreteColorPicker windowInstance = ScriptableObject.CreateInstance<DiscreteColorPicker>();

        windowInstance.Init();

        windowInstance.Show();

    }

 

    int division = 1;        //Base value to calculate amount of available colors

    int colorAmount

    {

        //There are {pickerTextureHeight} base color to choose from, each generating

        // a grid of {luminosityTextureSize}*{luminosityTextureSize} tints, with

        // the last line being only black squares.

        get { return pickerTextureHeight * luminosityTextureSize * luminosityTextureSize – luminosityTextureSize + 1; }

    }

 

    Color currentBaseColor;                    //Current selected base color

 

    Vector2 currentLuminosityCoordinates;    //Coordinates of selected tint in luminosity texture

    float currentMainColorY = -1f;            //Coordinate of selected base color

 

    Texture2D pickerTexture;        //Texture containing the base colors

    Texture2D luminosityTexture;    //Texture based on the current base color with luminosity variations

    Texture2D cursorTexture;        //Texture used to display cursors

    Texture2D sampleColorTexture;    //Texture containing final color

 

    int pickerTextureHeight

    {

        get { return division * 6; }

    }

    int luminosityTextureSize

    {

        get { return division * 3; }

    }

 

    int pickerSize = 300;           //GUI object size

 

    bool draggingLuminosity;    //Is user dragging from luminosity square

    bool draggingColor;            //Is user dragging from color picker

 

    void OnGUI()

    {

        EditorGUILayout.LabelField(“Discrete color”);

 

        EditorGUI.indentLevel++;

 

        //First, a slider to decide the amount of colors

        bool needsUpdate = false;

        int newDivision = EditorGUILayout.IntSlider(“Color levels:”, division, 1, 30, GUILayout.MaxWidth(pickerSize));

        if(newDivision != division)

        {

            //If division changed, update luminosity coordinates to
adapt to new grid

            currentLuminosityCoordinates *= ((float) newDivision) / ((float) division);

            division = newDivision;

 

            //Then regenerate textures, since their size has changed

            RegenerateTextures();

            needsUpdate = true;

        }

        EditorGUILayout.LabelField(“=> “ + colorAmount + ” colors”);

        GUILayout.Space(10);

 

        //Reserve a rect in the windows, where we can draw with GUI functions, and that

        // GUILayout functions will consider as already filled

        Rect colorPickerRect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, pickerSize);

 

        //Draw luminosity texture

        Rect luminosityRect = new Rect(colorPickerRect);

        luminosityRect.x += 5;

        luminosityRect.width = pickerSize;

        GUI.DrawTexture(luminosityRect, luminosityTexture, ScaleMode.StretchToFill, false, colorPickerRect.width/((float) luminosityTextureSize));

 

        //Draw small black dot to show which color is selected

        Rect luminosityCursorRect = new Rect(luminosityRect);

        luminosityCursorRect.position += new Vector2((int) currentLuminosityCoordinates.x, (int) currentLuminosityCoordinates.y) * pickerSize / ((float) luminosityTextureSize);

        luminosityCursorRect.y = luminosityRect.height – luminosityCursorRect.y + 2*luminosityRect.y;

        luminosityCursorRect.position += new Vector2(0, -5f);

        luminosityCursorRect.size = new Vector2(5, 5);

        GUI.DrawTexture(luminosityCursorRect, cursorTexture);

 

        //Draw color picker

        colorPickerRect.x += luminosityRect.width + 10;

        colorPickerRect.width = 20;

        GUI.DrawTexture(colorPickerRect, pickerTexture);

       

        //Draw small black line to show which color is selected

        Rect colorPickCursorRect = new Rect(colorPickerRect);

        colorPickCursorRect.y = colorPickerRect.y + currentMainColorY * colorPickerRect.height;

        colorPickCursorRect.height = 1;

        GUI.DrawTexture(colorPickCursorRect, cursorTexture);

       

        //Check if user is beginning or ending a drag in one of the pickers

        if(Event.current.type == EventType.MouseDown)

        {

            if(luminosityRect.Contains(Event.current.mousePosition))

            {

                draggingLuminosity = true;

            }

            else if(colorPickerRect.Contains(Event.current.mousePosition))

            {

                draggingColor = true;

            }

            else

            {

                draggingLuminosity = false;

                draggingColor = false;

            }

        }

        else if(Event.current.type == EventType.MouseUp)

        {

            draggingLuminosity = false;

            draggingColor = false;

        }

 

        //Warning: OnGUI can be called for different event types; you only want to use some of them, since

        // during others the layout is being constructed and values will be wrong

        bool input = Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseDrag;

        if(input)

        {

            Vector2 mousePosition = Event.current.mousePosition;

            if(draggingLuminosity)

            {

                //Calculate new coordinates in luminosity texture

                Restrain(ref mousePosition, luminosityRect);

                Vector2 coordinates = mousePosition – luminosityRect.position;

                coordinates *= luminosityTextureSize / luminosityRect.width;

                currentLuminosityCoordinates = new Vector2((int) coordinates.x, luminosityTextureSize – 1 – ((int) coordinates.y));

                needsUpdate = true;

            }

            else if(draggingColor)

            {

                //Calculate new base color and regenerate luminosity texture

                Restrain(ref mousePosition, colorPickerRect);

                currentMainColorY = (mousePosition.y – colorPickerRect.position.y) / colorPickerRect.height;

                int textureY = (int) ((1f – currentMainColorY) * pickerTextureHeight);

                currentBaseColor = pickerTexture.GetPixel(0, textureY);

                RecalculateLuminosityTexture();

                needsUpdate = true;

            }

        }

 

        if(needsUpdate)

        {

            //A change has been made, sample texture has to be updated

            sampleColorTexture.SetPixel(0, 0, luminosityTexture.GetPixel((int)
currentLuminosityCoordinates.x, (
int) currentLuminosityCoordinates.y));

            sampleColorTexture.Apply();

            Repaint();

        }

 

        GUILayout.Space(10);

 

        //Draw currently selected color to sample texture

        Rect sampleColorRect = GUILayoutUtility.GetRect(1, 20);

        sampleColorRect.x += 5;

        sampleColorRect.width = pickerSize + 20;

        GUI.DrawTexture(sampleColorRect, sampleColorTexture);

 

        EditorGUI.indentLevel–;

    }

 

    //Simple helper function

    void Restrain(ref Vector2 v, Rect r)

    {

        v.x = Mathf.Min(r.x + r.width – 1f, Mathf.Max(r.x + 1f, v.x));

        v.y = Mathf.Min(r.y + r.height – 1f, Mathf.Max(r.y + 1f, v.y));

    }

 

    //Each time the base color changes, the luminosity texture has to be updated

    void RecalculateLuminosityTexture()

    {

        Color rowColor;

        Color rowGreyColor;

        for(int i = 0; i < luminosityTextureSize; i++)

        {

            //Row by row, simple lerp of two vertical gradients (white to black on the left,

            // base color to black on the right)

            rowColor = currentBaseColor * ((float) (luminosityTextureSize – i – 1)) / ((float) (luminosityTextureSize – 1));

            rowGreyColor = Color.white * ((float) (luminosityTextureSize – i – 1)) / ((float) (luminosityTextureSize – 1));

            rowColor.a = 1;

            rowGreyColor.a = 1;

            for(int j = 0; j < luminosityTextureSize; j++)

            {

                luminosityTexture.SetPixel(j, luminosityTextureSize – i – 1, Color.Lerp(rowGreyColor, rowColor, ((float) j)/((float) (luminosityTextureSize – 1))));

            }

        }

        //Don’t forget to call Apply after you change a texture, or it won’t

        // have any effect

        luminosityTexture.Apply();

    }

 

    //Regenerate all the texture, because they don’t exist yet or because

    // their size was changed

    void RegenerateTextures()

    {

        //Just create a texture and fill it with basic rainbow

        // There must be a name for that, sorry I don’t know it

        // (…and there must be a better way to do it, but it’s not the point)

        pickerTexture = new Texture2D(1, pickerTextureHeight);

        pickerTexture.filterMode = FilterMode.Point;

        Color colorInProgress = Color.red;

        float increment = 6f / ((float) pickerTextureHeight);

        for(int i = 0; i < 6; i++)

        {

            for(int j = 0; j < pickerTextureHeight / 6; j++)

            {

                int index = i * pickerTextureHeight / 6 + j;

                pickerTexture.SetPixel(0, index, colorInProgress);

                //Grab current base color to setup cursor in color picker at init

                if(colorInProgress == currentBaseColor && currentMainColorY < 0)

                {

                    currentMainColorY = 1f – (1f / (2f * pickerTextureHeight) + ((float) index) / ((float) pickerTextureHeight));

                }

                switch(i)

                {

                    case 0:

                        colorInProgress.g += increment;

                        break;

                    case 1:

                        colorInProgress.r -= increment;

                        break;

                    case 2:

                        colorInProgress.b += increment;

                        break;

                    case 3:

                        colorInProgress.g -= increment;

                        break;

                    case 4:

                        colorInProgress.r += increment;

                        break;

                    case 5:

                        colorInProgress.b -= increment;

                        break;

                }

            }

        }

        pickerTexture.Apply();

        int textureY = (int) ((1f – currentMainColorY) * pickerTextureHeight);

        currentBaseColor = pickerTexture.GetPixel(0, textureY);

 

        luminosityTexture = new Texture2D(luminosityTextureSize,  luminosityTextureSize);

        luminosityTexture.filterMode = FilterMode.Point;

        RecalculateLuminosityTexture();

 

        sampleColorTexture = new Texture2D(1, 1);

        sampleColorTexture.SetPixel(0, 0, luminosityTexture.GetPixel((int)
currentLuminosityCoordinates.x, (
int) currentLuminosityCoordinates.y));

        sampleColorTexture.Apply();

    }

 

    //Init default values and create cursor texture, since it won’t be updated after

    void Init()

    {

        currentBaseColor = Color.red;

        currentLuminosityCoordinates = new Vector2(luminosityTextureSize – 1, luminosityTextureSize – 1);

 

        RegenerateTextures();

 

        cursorTexture = new Texture2D(1, 1);

        cursorTexture.SetPixel(0, 0, Color.black);

        cursorTexture.Apply();

    }

}

Of course there must be some more convenient and/or efficient ways of doing that (like using the default color picker from Unity – random example), but you can see that there is virtually no limit to what you can do, it’s more a matter of time and perseverance.

However we won’t go very far with this window, in term of utility… Let’s say we want to change that, and have a DiscreteColor struct that can be tweaked from the inspector like a standard color. For that, we will use a Property Drawer to customize the field shown in the inspector.

I feel a bit like a father now
I feel a bit like a father now

First we need a DiscreteColor struct and an object to use it as public field:

using UnityEngine;

 

public class MyScript : MonoBehaviour

{

    public DiscreteColor[] myColors;

}

 

//You have to make the class serializable so it can be used with a custom

// property drawer

[System.Serializable]

public struct DiscreteColor

{

    //Make the private fields serializable so they are saved

    [SerializeField]

    int colorsDivision;

    [SerializeField]

    float baseColor;

    [SerializeField]

    Vector2 luminosityCoordinates;

 

    //Public field is automatically serializable

    public Color resultColor;

}

Then we need to create the custom property drawer itself, that will use a slightly changed DiscreteColorPicker:

using UnityEngine;

using UnityEditor;

 

//Indicate we are creating a custom property drawer for the DiscreteColor type

[CustomPropertyDrawer(typeof(DiscreteColor))]

public class DiscreteColorDrawer : PropertyDrawer

{

    //Override the line height used to display the property in the inspector

    const float propertyHeight = 30;

    //Texture to display a sample of the selected color

    Texture2D colorSample;

 

    // Draw the property inside the given rect

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

    {

        //Find all the serialized attributes of the DiscreteColor

        SerializedProperty serializedColorsDivision = property.FindPropertyRelative(“colorsDivision”);

        SerializedProperty serializedBaseColor = property.FindPropertyRelative(“baseColor”);

        SerializedProperty serializedLuminosityCoordinates = property.FindPropertyRelative(“luminosityCoordinates”);

        SerializedProperty serializedFinalColor = property.FindPropertyRelative(“resultColor”);

 

        //Using BeginProperty / EndProperty on the parent property means that

        // prefab override logic works on the entire property.

        EditorGUI.BeginProperty(position, label, property);

 

        //Default int value is 0, which is not an accetable value for our colors division

        serializedColorsDivision.intValue = Mathf.Max(serializedColorsDivision.intValue, 1);

 

        //The final color field was added only to avoir having to recalculate it from our weird parameters

        // By default it’s clear, so set its alpha to 1, since we don’t use alpha.

        Color finalColor = serializedFinalColor.colorValue;

        finalColor.a = 1;

 

        //Create a GUIStyle to display the selected color with the button text

        GUIStyle buttonStyle = new GUIStyle(“button”);

        GUIStyleState activeState = buttonStyle.active;

        activeState.textColor = finalColor;

        GUIStyleState normalState = buttonStyle.normal;

        normalState.textColor = finalColor;

 

        //Create the sample texture if it doesn’t exist and fill it with the final color

        if(colorSample == null)

            colorSample = new Texture2D(1, 1);

        colorSample.SetPixel(1, 1, finalColor);

        colorSample.Apply();

 

        //Display the sample as a little square on the left

        Rect sampleRect = new Rect(position.x, position.y, propertyHeight, propertyHeight);

        GUI.DrawTexture(sampleRect, colorSample);

 

        //Display a button to open the color picker and link it to the current property

        Rect buttonRect = new Rect(position.x + propertyHeight + 5, position.y + 5, position.width – 10 – propertyHeight, propertyHeight – 10);

        if(GUI.Button(buttonRect, “Set color”, buttonStyle))

        {

            DiscreteColorPicker.OpenWithColor(property.serializedObject,

                serializedColorsDivision, serializedBaseColor, serializedLuminosityCoordinates, serializedFinalColor);

        }

       

        EditorGUI.EndProperty();

    }

 

    //Override the line height used to display the property in the inspector

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)

    {

        return propertyHeight;

    }

}

 And the changes in the DiscreteColorPicker:

using UnityEngine;

using UnityEditor;

 

public class DiscreteColorPicker : EditorWindow

{

    //Opens a picker with the serialized fields of a DiscreteColor

    public static DiscreteColorPicker OpenWithColor(SerializedObject serializedObject,

        SerializedProperty serializedColorsDivision,

        SerializedProperty serializedBaseColor,

        SerializedProperty serializedLuminosityCoordinates,

        SerializedProperty serializedFinalColor)

    {

        DiscreteColorPicker windowInstance = ScriptableObject.CreateInstance<DiscreteColorPicker>();

        windowInstance.Init(serializedObject, serializedColorsDivision,
serializedBaseColor, serializedLuminosityCoordinates, serializedFinalColor);

        windowInstance.Show();

        //Picker is returned so that the custom property drawer can get values

        return windowInstance;

    }

 

    //Keep a reference on the serialized fields

    SerializedObject serializedObject;

    SerializedProperty serializedColorsDivision;

    SerializedProperty serializedBaseColor; 

    SerializedProperty serializedLuminosityCoordinates;

    SerializedProperty serializedFinalColor;

 

    //Keep the final color so that it can be given back to the DiscreteColor

    // without having to recalculate it

    Color currentFinalColor;

 

   

 

    void OnGUI()

    {

 

       

 

        if(needsUpdate)

        {

            //A change has been made, sample texture has to be updated

            currentFinalColor = luminosityTexture.GetPixel((int) currentLuminosityCoordinates.x, (int) currentLuminosityCoordinates.y);

            sampleColorTexture.SetPixel(0, 0, currentFinalColor);

            sampleColorTexture.Apply();

            Repaint();

           

            //Final color has changed, we set the new values in the serialized fields

            if(serializedObject != null)

            {

                serializedColorsDivision.intValue = division;

                serializedBaseColor.floatValue = currentMainColorY;

                serializedLuminosityCoordinates.vector2Value = currentLuminosityCoordinates;

                serializedFinalColor.colorValue = currentFinalColor;

                //Save your changes

                serializedObject.ApplyModifiedProperties();

            }

 

       

   

 

    //Init the window with the serialized properties

    void Init(SerializedObject _serializedObject,

        SerializedProperty _serializedColorsDivision,

        SerializedProperty _serializedBaseColor,

        SerializedProperty _serializedLuminosityCoordinates,

        SerializedProperty _serializedFinalColor)

    {

        serializedObject = _serializedObject;

        serializedColorsDivision = _serializedColorsDivision;

        serializedBaseColor = _serializedBaseColor;

        serializedLuminosityCoordinates = _serializedLuminosityCoordinates;

        serializedFinalColor = _serializedFinalColor;

  

        division = serializedColorsDivision.intValue;

        currentMainColorY = serializedBaseColor.floatValue;

        currentLuminosityCoordinates = serializedLuminosityCoordinates.vector2Value;

 

        RegenerateTextures();

 

        cursorTexture = new Texture2D(1, 1);

        cursorTexture.SetPixel(0, 0, Color.black);

        cursorTexture.Apply();

    }

 

    //Close the window when user clicks somewhere else. There seems to be a bug with

    // this in current Unity version (even with system windows), which sometimes causes

    // a crash… so don’t use it in your commercial production, maybe.

    void OnLostFocus()

    {

        serializedObject = null;

        Close();

    }

}

You can as well build entire systems for game settings, accessible from menus, that parse assets in your project, save data to txt or asset files, modify prefabs… You can have access to or even set the selected object in the project tab, find all subclasses of a given class to create assets from scratch, open, modify and save scenes… You’ve got it: it’s powerful.


Scene view

It can also be very useful to build tools directly in the scene view, especially for things like level design. And guess what: it’s as simple as what we’ve seen so far! You just register to the scene GUI delegate, then use the Handles to reach your goal. Here is a simple example reproducing the first gif of this post:

So convenient!
So convenient!

And the code to do that:

using UnityEngine;

 

//Import UnityEditor only when on editor, in order to be able to build project

#if UNITY_EDITOR

using UnityEditor;

#endif

 

//In order to have the code running while application is not playing

[ExecuteInEditMode]

public class Path : MonoBehaviour

{

    public Transform[] waypoints;

 

    void Start ()

    {

        //When in the editor not playing, register to callback allowing

        // to draw on the Scene view. Unregister first if the delegate

        // had previously been added

        if(!Application.isPlaying)

        {

#if UNITY_EDITOR

            SceneView.onSceneGUIDelegate -= OnSceneGUI;

            SceneView.onSceneGUIDelegate += OnSceneGUI;

#endif

        }

    }

 

    protected virtual void OnDestroy()

    {

        //Unregister if the object is destroyed

        if(!Application.isPlaying)

        {

#if UNITY_EDITOR

            SceneView.onSceneGUIDelegate -= OnSceneGUI;

#endif

        }

    }

 

#if UNITY_EDITOR

    void OnSceneGUI(SceneView sceneView)

    {

        //Check if this object or one of its children is selected

        bool isOnMe = Selection.activeGameObject == gameObject;

        if(!isOnMe)

        {

            for(int i = 0; i < transform.childCount; i++)

            {

                if(Selection.activeTransform == transform.GetChild(i))

                {

                    isOnMe = true;

                    break;

                }

            }

        }

 

        if(isOnMe)

        {

            //If there are no waypoint yet, create the array and initialize it with

            // a spawn point

            if(waypoints == null || waypoints.Length == 0)

            {

                Vector3 newSpawnPointPos = Vector3.zero;

                GameObject newSpawnPointGo = new GameObject();

                newSpawnPointGo.transform.SetParent(transform);

                waypoints = new Transform[1];

                waypoints[0] = newSpawnPointGo.transform;

                newSpawnPointGo.transform.position = newSpawnPointPos;

            }

 

            //Draw the spawn point and name it

            //Check if it isn’t null, in case user has deleted it

            if(waypoints[0] != null)

            {

                waypoints[0].gameObject.name = “Spawn”;

 

                //Display the spawn point handle in red

                Handles.color = Color.red;

 

                //The handle can be moved by the user and returns its new position,

                // so reassign it to the transform

                waypoints[0].position = Handles.FreeMoveHandle(waypoints[0].position,

                                        Quaternion.identity,

                                        0.2f, Vector3.zero,

                                        Handles.DotCap);

            }

 

            //Draw the rest of the path

            for(int i = 1; i < waypoints.Length; i++)

            {

                //Always check for a null transform

                if(waypoints[i] != null)

                {

                    waypoints[i].gameObject.name = “WP” + i.ToString();

                    Vector3 position = waypoints[i].position;

 

                    //We’ll draw a line between the waypoints, from the previous one

                    // to the current one

                    if(waypoints[i – 1] != null)

                    {

                        Handles.color = Color.blue;
Handles.DrawLine(waypoints[i-1].position,
position);

                    }

 
//If it’s the last waypoint, display it in black

                    if(i == waypoints.Length – 1)

                        Handles.color = Color.black;

                    //Otherwise, show it green

                    else

                        Handles.color = Color.green;

 

                    //Create the handle to move it

                    waypoints[i].position = Handles.FreeMoveHandle(position,

                         Quaternion.identity,

                         0.2f, Vector3.zero,

                          Handles.DotCap);

 

                }

            }

 

            //Finally, after the last waypoint, display a button to create another one

            int maxIndex = waypoints.Length – 1;

            if(waypoints[maxIndex] != null)

            {

                Vector3 newWPPosition = waypoints[maxIndex].position + new Vector3(0.5f, 0);

                Handles.color = Color.cyan;

                //Create the clickable handles button

                if(Handles.Button(newWPPosition, Quaternion.identity, 0.2f, 0.2f, Handles.DotCap))

                {

                     //If clicked, create the new waypoint

                     GameObject newWP = new GameObject();

                     newWP.transform.SetParent(transform);

                     Transform[] newPath = new Transform[maxIndex + 2];

                     for(int j = 0; j < maxIndex + 1; j++)

                     {

                         newPath[j] = waypoints[j];

                     }

                     waypoints = newPath;

                     waypoints[waypoints.Length – 1] = newWP.transform;

                     newWP.transform.position = newWPPosition;

                }

                //Add a “+” label on the button, scaled according to button size

                float handleSize = HandleUtility.GetHandleSize(newWPPosition);

                Handles.color = Color.black;

                GUIStyle buttonStyle = new GUIStyle();

                buttonStyle.fontSize = (int) (25f/handleSize);

                Handles.Label(newWPPosition + new Vector3(-0.12f, 0.2f), “+”,
buttonStyle);

            }

        }

    }

#endif

}

That’s all! As said at the beginning of the post, this was just a very light overview of the possibilities offered by Unity’s editor, but the doc is quite nice, and there are tons of questions and answers on the webz about this subject. All the examples given here are small scripts, but you can build mad interfaces with all the options your game designers have always dreamed of without thinking it could come true someday. It will save you a crazy amount of time!


 

For more articles, you can follow me on Twitter, Facebook or subscribe to this blog.

 

Further “readings”:

A great video about Editor Scripting (the one that got me into this)

THE thread to talk about serialization

Reducing memory allocations to avoid Garbage Collection

In this technical post, I’m gonna talk about optimization, and specifically about memory allocations in Unity.

Disclaimer: I’m in no way an expert in this area, and you should take anything from this article with caution, as it may not be perfectly accurate, or there may be better solutions than the ones I give. Let’s say it’s a basic overview of tips about reducing allocations.

Second disclaimer: Some methods are here to reduce allocations, but it sometimes goes against speed performances (reasonably). Just choose what you’re looking for; it is for example more important on a mobile app than on a desktop game.


Short introduction

Working on my mobile game, I didn’t care too much about performances or allocations (I usually try to write clear, flexible, readable code without doing premature optimization) until I faced issues on device. I began with some obvious fixes, like packing sprites to improve compression, which made the game a lot more playable. Still, there were frequent micro-freezes that were almost not noticeable at first, but became unbearable as soon as I took note of them.

After some digging and playing around in the profiler, I found out those were due to some line named GC.Collect. I knew what garbage collection is, but didn’t really think about it until now, since it had never been an issue since I learned about it in school. In my project, it can take up to 8.62ms (on my high-end pc), leading to 9.79ms needed for my behaviours updates on a given frame; I didn’t check on device, but it was enough to be seen.

Scary, isn't it?
Scary, isn’t it?

From Unity doc: “When an object, string or array is created, the memory required to store it is allocated from a central pool called the heap.” And the GC.Collect, which can happen anytime, as soon as the heap requires it, empties this memory by collecting garbage, looking through all the objects to see if they are in use or not. This operation can take quite a long time if there are many objects, dead or still alive.

Since I use pooling for almost everything in my game, I couldn’t really imagine where those alloc came from (the place it shows up in the profiler isn’t necessarily related to what really caused it). So I began investigating about this on the interwebz, and here are some tips on mistakes to avoid and methods to track them.


Reuse

The first obvious thing to say is: don’t create “new” things once your game is initialized/your scene is loaded. For your prefabs and even for components, use pooling and immediately instantiate everything you may need later. It also applies to arrays, list and dictionary, for example in an Update function where you have to fill an array every frame. Create one when the class starts, giving it the expected size as parameter, then clear it when you need it empty, instead of recreating it. It will keep its allocated space, and you can fill it again without extra allocation. Those are often very small allocs, but if they keep happening every frame, they will eventually cost more than the bigger ones that happen less frequently.

If you have a method filling an array or a list, pass it as an argument of the function instead of making it its return value. Have your collection be a member of the class, initialized only once, then simply cleared and filled inside your method, so you don’t have to instantiate a new one every time.

The strings create garbage as well, even when you concatenate them, so if you have to handle a lot of text somewhere and manipulate it heavily, use a StringBuilder that will take care of those memory issues alone and avoid the creation of garbage.

In the same spirit, cache everything you need to check frequently. If GetComponent is supposed to only allocate memory in the editor and not on device, other functions like GetComponentsInChildren will never be good to use on a regular basis (which is obvious since it returns an array). So call it once if you need it, then keep the result to reuse it and update it if needed.


Best practices

Another well-known garbage creator is the foreach. Because of Boxing (I’ll let you look for it if you want to go in detail), a foreach loop used on a collection containing objects creates garbage, so just use a simple for loop.

Some basic methods or functionalities provided by Unity also generate garbage for weird reasons. The best way to track them is to use the profiler after encapsulating your code in Profiler.BeginSample(“SampleName”) / Profiler.EndSample(). Those sections will have their own line in the profiler, allowing you to find exactly what line generates garbage and finding those tricky allocs.

The result of a GetComponentsInChildren
The result of a GetComponentsInChildren

Vector3.Equals, for example, generates garbage; just compare the coordinates of your vectors one by one. Also, don’t use enum values as dictionary keys, cast them to int. There must be several other tips of this kind which I did not have to deal with, so be sure to profile until you find the very line making the alloc.

Finally, you can trigger the garbage collector by yourself by calling System.GC.Collect(). I naively tried to call it every 30 frames, so that the garbage don’t have the time to grow, but the thing is that garbage collection duration will take as much time whether it has a lot to clear or not, because it scales with the total amount of objects. The best thing to do (besides not allocating anything) is to call this at the right time: just before starting the gameplay, when the player pauses the game, during loading of cutscenes… In those occasions, the collection spike will be invisible to the player (but your code still has to be clean enough so that it doesn’t need to be called outside of those circumstances).


Limits

Sadly, some of Unity’s methods generating garbage can’t really be avoided without doing some programming weird tricks. I couldn’t find anything about some obvious “leaks”, like Text.OnEnable or OnGUI functions, which can generate quite a lot of garbage if you use them frequently.

Changing game state, reenabling the HUD… That hurts.
Changing game state, reenabling the HUD… That hurts.

In those cases, if you don’t want to recode yourself the whole system, you’ll have to be smart about the way you call them. Maybe you could just put that label offscreen when it’s not used, instead of disabling it? Rather than calling Clear/Stop on your particle system when it’s not used anymore, store its end date when starting it (don’t check each frame if it’s still alive!), and if it went until the end when you don’t need it anymore, don’t bother clearing it.

There are a bunch of other methods from Unity that allocate memory on the heap, some of which you can bypass, some of which you’ll just have to deal with. Just remember that it’s not always essential to reach 0 alloc; if a function called once in a while generates some KB of garbage, maybe it’s ok, as long as it’s not every frame. Optimize wisely.


Some other perf considerations

To end this note, I’d like to point out some very easy optimizations to improve performances:

On the rendering side, there is a lot to do. Pack your sprites in atlases, use an uniform scale on all of them in game, don’t modify the shared material of only one instance. In my project for example, I used an additive shader to make the enemies blink in white when hit, but every time I did that, a new instance of the material was spawned, and the enemies couldn’t be correctly batched anymore. Instead, I added a white sprite of the hit enemy, and just enable/disable it when needed (which fortunately doesn’t have the same memory impact as UI.Text).

On the physics side, it can often be easy to optimize things as well, if you started with a naive approach. Check your collisions matrix, remove all unnecessary cases. Try to use less colliders if you have several of them on some objects, prefer circle/sphere colliders to square/box or capsule ones. If you use Unity’s UI system, disable the “UI Raycast Target” option (checked by default) on everything that doesn’t need it.

Don’t leave empty Unity callbacks, especially like Update or FixedUpdate: because of the way they are called, it can be costly if you have a lot of behaviours, even if there is no code in them.


Well, that’s all for today. Please get in touch if you see mistakes or inaccurate advices so I can fix it before more people read it!

If you liked it and want more, don’t hesitate to subscribe to this blog, or follow me on Twitter: @Grhyll.

Thanks for reading!

 


Further readings:

Unity doc: understanding automatic memory management

Unity doc: mobile profiling

Unity blog post: 1k update calls

C Memory Management for Unity Developers (a bit old)