26 January 2025

Creating interactive Gizmos in Unity

In a previous article I explained how to use the OnDrawGizmos() and OnDrawGizmosSelected() callbacks, present in all MonoBehaviours, to draw Gizmos that visually represent the magnitudes of the fields of a GameObject. However, I already explained then that Gizmos implemented in this way were passive in the sense that they were limited to representing the values ​​that we entered in the inspector. But the Unity editor is full of Gizmos that can be interacted with to change the values ​​of the GameObject, simply by clicking on the Gizmo and dragging. An example is the Gizmo that is used to define the dimensions of the Collider. 

How are these interactive Gizmos implemented? The key is in the Handles. These are visual "handles" that we can place on our GameObject and that allow us to click and drag them with the mouse, returning the values ​​of their change in position, so that we can use them to calculate the resulting changes in the magnitudes represented. This will be better understood when we see the examples.

The first thing to note is that Handles belong to the editor namespace and can therefore only be used within it. This means that we cannot use Handles in final game builds that we run independently of the editor. For this reason, all code that uses Handles has to be placed either in the Editor folder or with #if UNITY_EDITOR ... #endif guards. In this article I will explain the first approach, as it is cleaner.

With the above in mind, Gizmo code that uses Handles should be placed in the Editor folder and not in the Scripts folder. Unity only takes into account the Scripts folder when compiling the C# code for the final executable. It is important to be rigorous with this because if you mix Handles code with MonoBehaviour code, everything will seem to work as long as you start the game from the editor, but when you try to compile it with File --> Build Profiles... --> Windows --> Build, you will get errors that will prevent you from compiling. At this point, you will have two options: either you redirect the distribution of the code that uses Handles to the structure that I am going to explain here or you fill your code with #if UNITY_EDITOR ... #endif guards around all the calls to objects in the Handles library. Anyway, I think that the structure that I am going to explain here is generic enough so that you do not need to mix your Handles code with MonoBehaviour code.

To start with the examples, let's assume we have a MonoBehaviour (in our Scripts folder) called EvadeSteeringBehavior that is responsible for initiating the escape of its GameObject if it detects that a threat is approaching below a certain threshold. That threshold is a radius, a distance around the fleeing GameObject. If the distance to the threat is less than that radius, the EvadeSteeringBehavior will start executing the escape logic. Let's assume that the property that stores that radius is EvadeSteeringBehavior.PanicDistance.

To represent PanicDistance as a circle around the GameObject, we could use calls to Gizmos.DrawWireSphere() from MonoBehaviour's OnDrawGizmos() method, but with that approach we could only change the PanicDistance value by modifying it in the inspector. We want to go further and be able to alter the PanicDistance value by clicking on the circle and dragging in the Scene window. To achieve this, we're going to use Handles via a custom editor.

For that, we can create a class in the Editor folder. The name doesn't matter too much. In my case, I've named the file DrawEvadePanicDistance.cs. Its content is as follows:

DrawEvadePanicDistance Custom Editor Code

Look at line 1, all your alarms should go on if you find yourself importing this library in a class intended to be part of the final compilation of your game, since in that case you will get the errors I mentioned before. However, by placing this file in the Editor folder we will no longer have to worry about this problem.

Line 6 is key as it allows us to associate this custom editor with a specific MonoBehaviour. With this line we are telling the editor that we want to execute the logic collected in OnSceneGUI() whenever it is going to render an instance of EvadeSteeringBehavior in the Scene tab.

As a custom editor, our class must inherit from UnityEditor.Editor, as seen on line 7.

So far, whenever I've needed to use Handles, I've ended up structuring the contents of OnSceneGUI() in the same way you see between lines 10 and 24. So in the end it's almost a template.

If we want to access the data of the MonoBehaviour whose values ​​we want to modify, we will have it in the target field to which all custom editors have access. Although it is true that you will have to cast to the type that you know the MonoBehaviour we are representing has, as can be seen in line 11.

We must place the code for our Handles between a call to EditorGUI.BeginChangeCheck() (line 13) and a call to EditorGUI.EndChangeCheck() (line 19) so that the editor will monitor whether any interaction with the Handles occurs. The call to EditorGUI.EndChangeCheck() will return true if the user has interacted with any of the Handles created from the call to EditorGUI.BeginChangeCheck().

To define the color with which the Handles will be drawn, we do it in a very similar way to how we did with the Gizmos, in this case loading a value in Handles.color (line 14).

We have multiple Handles to choose from, the main ones are:

  • PositionHandle : Draws a coordinate origin in the Scene tab, identical to that of Transforms. Returns the position of the Handle.
  • RotationHandle : Draws rotation circles similar to those that appear when you want to rotate an object in the editor. If the user interacts with the Handle, it will return a Quaternion with the new rotation value, while if the user does not touch it, it will return the same initial rotation value with which the Handle was created.
  • ScaleHandler : It works similarly to RotationHandle, but in this case it uses the usual scaling axes and cubes when you want to modify the scale of an object in Unity. What it returns is a Vector3 with the new scale, in case the user has touched the Handle or the initial one, otherwise.
  • RadiusHandle : Draws a sphere (or a circle if we are in 2D) with handles to modify its radius. In this case, what is returned is a float with said radius.

In the example at hand, the natural choice was to choose the RadiusHandle (line 15), since what we are looking for is to define the radius that we have called PanicDistance. Each Handle has its creation parameters, to configure how they are displayed on the screen. In this case, RadiusHandle requires an initial rotation (line 16), the position of its center (line 17) and the initial radius (line 18).

In case the user interacts with the Handle, its new value would be returned as a return from the Handle creation method. In our example, we have saved it in the variable newPanicDistance (line 15). In such cases, the EditorGUI.EndChangeCheck() method would return true, so we could save the new value in the property of the MonoBehaviour whose value we are defining (line 22).

To ensure that we can undo changes to the MonoBehaviour with Ctrl+Z, it is convenient to precede them with a call to Undo.RecordObject() (line 21), indicating the object that we are going to change and providing an explanatory message of the change that is going to be made.

The result of all the above code will be that, whenever you click on a GameObject that has the EvadeSteeringBehavior script, a circle will be drawn around it, with points that you can click and drag. Interacting with those points will change the size of the circle, but also the value displayed in the inspector for the EvadeSteeringBehavior's PanicDistance property.

The RadiusHandle displayed thanks to the code above
The RadiusHandle displayed thanks to the code above

What we can achieve with Handles doesn't end here. If we were Unity, the logical thing would have been to offer Handles to users, for interactive modification of script properties, and leave Gizmos for the visual representation of those values ​​from calls to OnDrawGizmos(). However, Unity did not leave such a clear separation of functions and, instead, provided the Handles library with drawing primitives very similar to those offered by Gizmos. This means that there is some overlap between the functionalities of Handles and Gizmos, especially when it comes to drawing.

It is important to know the drawing primitives that Handles can offer. In many cases it is faster and more direct to draw with Gizmos primitives, from OnDrawGizmos(), but there are things that cannot be achieved with Gizmos that can be drawn with Handles methods. For example, with Gizmos you cannot define the thickness of the lines (they will always be one pixel wide), while Handles primitives do have a parameter to define the thickness. Handles also allows you to paint dashed lines, as well as arcs or rectangles with translucent fills.

Many people take advantage of the benefits of Handles and use them not only for entering new values ​​but also for drawing, completely replacing Gizmos. The problem is that this forces the entire drawing logic to be extracted to a custom editor like the one we saw before, which implies creating one more file and saving it in the Editor folder.

In any case, it is not about following any dogma, but about knowing what the Handles and Gizmos offer in order to choose what best suits us in each occasion.