Gizmos allow you to draw lines and shapes in the editor window to visually represent an object’s values.
For example, imagine you want to create a component that implements a vision cone. A vision cone is modeled with two parameters:
- Range: This is the distance between the observer and the observed object beyond which the latter becomes invisible.
- Aperture: This is the angle of the cone. It is generally calculated as an angle from the vector marking the observer’s gaze direction. If the object is at an angle relative to the observer greater than the aperture angle, then the object is not visible.
Thus, your component can have these two parameters as fields, and you can edit them from the component’s inspector. The problem is that it’s very difficult to set the ideal values for these parameters without a visual reference of the result. It’s easier to understand what falls within the vision cone if, when setting the aperture in the inspector, a triangle representing the vision cone with that angle appears in the scene editor.
To represent these geometric shapes, which help us visualize the values of our component’s fields, is exactly what Gizmos are for.
Of course, Gizmos will only appear in the editor. They are aids for the developer and level designer but will be invisible when the final game starts.
In fact, if you’ve been practicing with Godot, you’ve likely already used Gizmos, for example, when setting the shape of a CollisionShape. The boxes, circles, and other geometric shapes that appear in the editor when you configure a CollisionShape are precisely Gizmos, drawn as we will see here.
In fact, if you look at CollisionShapes, you’ll notice that, in addition to the Gizmo, there are points you can click and drag to change the component’s shape. These points are called Handles and are the “grabbers” for manipulating what we represent in the editor. In this article, we’ll also see how to implement our own Handles.
Side view of a CollisionShape with a box shape. The blue edges are the Gizmo, representing the shape, and the red points are the Handles, for changing the shape. |
Gizmos are associated with the nodes they complement, so Godot knows which Gizmos to activate when a specific node is included in the scene. As we’ve seen, many of Godot’s nodes already have associated Gizmos (as seen with CollisionShape nodes).
Creating a Custom Node
Since I don’t want to mess with the default Gizmos of Godot’s nodes, we’ll start by adding a custom node to Godot’s node list. We’ll associate a Gizmo with its respective Handles to this custom node to serve as an example.
In our example, to create this custom node, I’ve created a folder called nodes/CustomNode3D inside the project folder. In that folder, we can create the script for our custom node by right-clicking the folder and selecting Create New > Script.... A pop-up window like the one below will appear, where I’ve filled in the values for this example:
The script creation window |
Once the script is generated, we only need it to implement two public exported Vector3 properties. I’ve called them NodeMainPoint and NodeSecondaryPoint:
[Export] public Vector3 NodeMainPoint { get; set; }
[Export] public Vector3 NodeSecondaryPoint { get; set; }
I’m not including a screenshot because we’ll add code to the setter part later.
The idea is that dragging the Handles in the editor updates the values of the two properties above. The reverse should also work: if we change the property values in the inspector, the Handles should reposition to the locations indicated by the properties. Additionally, we’ll draw a Gizmo in the form of a line, from the node’s origin to the positions of the properties.
This should be enough to illustrate the main mechanics: representing a node’s properties with Gizmos and modifying those properties using Handles.
The next step will be to create an addons folder inside the Godot project. Custom Gizmos and Handles are considered plugins, so the consensus is to place them in an addons folder within the project.
Once that’s done, go to Project > Project Settings ... > Plugins and click the Create New Plugin button. A window like the one below will appear, where I’ve already filled in the values for the example:
The plugin creation window |
Note that the folder specified in the Subfolder field of the previous window will be created inside the addons folder we mentioned earlier. The same applies to the plugin script, defined in the Script Name field.
Also, notice that I’ve unchecked the Activate now? checkbox. GDScript plugins can be activated immediately with the generated template code, but C# plugins require some prior configuration, so they will throw an error if activated with the default template code. The error won’t break anything, but it displays an error window that needs to be closed, which looks messy. So, it’s best to uncheck that box and leave the activation for a later step, as we’ll see below.
After doing this, a folder will be generated inside addons, containing a plugin.cfg file and the C# script from the previous window. The purpose of this script is to register the type represented by CustomNode3D in Godot so that we can select it from the engine’s node list. Remember that CustomNode3D inherits from Node3D, so it makes sense to include it alongside other nodes.
As with any other plugin, CustomNode3DRegister will need to inherit from the EditorPlugin class and implement the _EnterTree() and _ExitTree() methods. In the first method, we’ll register CustomNode3D as an eligible node in the node list, and in the second, we’ll deregister it so it no longer appears in the list. The implementation is straightforward:
addons/custom_node3D_register/CustomNode3DRegister.cs |
As you can see, in the _EnterTree() method, we load two things: the script associated with the custom node and the icon we want to use to represent the node in Godot’s node list. For the icon, I’ve used the one included in all Godot projects, copying it from the root into the custom node’s folder.
Then, we associate these elements with a base node using the AddCustomType() method, which registers the custom node in the node list. Since the custom node’s script inherits from Node3D, we’ve used that as the base class in the AddCustomType() call. With this call, when we select CustomNode3D from the node list, a Node3D will be created in the scene, and the script we defined will be associated with it.
The implementation of _ExitTree() is the opposite: we use the RemoveCustomType() method to remove the custom node from the node list.
To execute the registration, we’ll compile the game to apply the changes to CustomNode3DRegister.cs. After that, go to Project > Project Settings ... > Plugins and ensure the Enable checkbox for the CustomNode3DRegister plugin is checked. This will trigger its logic and register our custom node in the node list. From there, we can locate our node in the list and add it to the scene:
The node list with our custom node |
Add it to the scene before proceeding.
Creation of a Gizmo for the Custom Node
Now that we have our custom node, let’s create a Gizmo to visually represent its properties.
Gizmos are considered addons, so it makes sense to create a folder addons/CustomNode3DGizmo to house their files. These files will be two: a script to define the Gizmo, which will be a class inheriting from EditorNode3DGizmoPlugin, and another script to register the Gizmo, which will inherit from EditorPlugin and will be quite similar to the one we used to register the custom node.
The Gizmo script is where the real substance lies. I’ve called it CustomNode3DGizmo.cs. As I mentioned, it must inherit from EditorNode3DGizmoPlugin and implement some of its methods.
The first of these methods is _GetGizmoName(). This method simply returns a string with the Gizmo’s name:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
Somewhat more intriguing is the _HasGizmo() method, which is passed all the nodes in the scene until the method returns true for one of them, indicating that the Gizmo should be applied to that node. Therefore, in our case, the method should return true when a node of type CustomNode3D is passed:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
Here, we need to consider a specific issue that occurs in C# but not in GDScript. Although the comparison with is is syntactically correct, in practice, it doesn’t work in Godot C# unless the class we’re comparing against is marked with the [Tool] attribute. So, this is a good time to add that attribute to the header of the CustomNode3D class:
nodes/CustomNode3D/CustomNode3D.cs |
In reality, this is an anomaly. We shouldn’t need the [Tool] attribute to make that comparison work. In fact, the equivalent GDScript code (which appears in the official documentation) doesn’t require it. This is a bug reported multiple times in Godot’s forums and is still pending resolution. Until it’s fixed, the workaround in C# is to use the [Tool] attribute.
The next method to implement is the constructor of our Gizmo class. In GDScript, we would use the _init() method, but in C#, we’ll use the class constructor:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
In this constructor, we’ll create the materials to apply to our Gizmo and its Handles. These aren’t full materials like those created with a shader but rather a set of styles to apply to the lines we draw for our Gizmo. They are created using the CreateMaterial() method for the Gizmo and CreateHandleMaterial() for the Handle. Both accept a string as the first parameter, which is the name we want to give the material. This name is used with the GetMaterial() method to obtain a reference to that material. This reference can be useful, for example, to assign it to a StandardMaterial3D variable for in-depth customization by setting the values of its properties. However, it’s common not to need that level of customization and to simply set the line color using the second parameter of the CreateMaterial() method. However, the CreateHandleMaterial() method doesn’t accept this second parameter, so we have no choice but to use the GetMaterial() method (lines 19 and 20 of the previous screenshot) to obtain references to the material and set the value of its AlbedoColor property (lines 21 and 22).
In the example constructor, I’ve configured the lines drawn from the coordinate origin to the position marked by the NodeMainPoint property to use the color red. The lines going to the position of the NodeSecondaryPoint property will use green. I’ve configured the materials for the respective Handles to use the same color.
Finally, we have the _Redraw() method. This is responsible for drawing the Gizmos every time the UpdateGizmo() method, available to all Node3D nodes, is called:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
The _Redraw() method is like our canvas, and it’s common to clear a canvas at the start before drawing on it. That’s why the Clear() method is typically called at the beginning of the method (line 29 of the previous screenshot).
Then, we collect the positions of the lines we want to draw in a Vector3 array. In this case, we want to draw a line from the coordinate origin to the position marked by the NodeMainPoint property, so we store both points in the array (lines 33 to 37 of the previous screenshot).
For the Handles, we do the same, storing the points where we want a Handle to appear in another array. In this case, since we want a Handle to appear at the end of the line, marked by the NodeMainPoint position, we only add that position to the Handles array (lines 38 to 41 of the previous screenshot).
Finally, we use the AddLines() method to draw the lines along the positions collected in the array (line 42) and the AddHandles() method to position Handles at the positions collected in its array (line 43). Note that, in both cases, we pass the material defining the style with which we want the elements to be drawn.
I didn’t include it in the previous screenshot, but the process for drawing the line and Handle for a second point (in this case, NodeSecondaryPoint) would be the same: we’d confirm their position arrays and pass them to the AddLines() and AddHandles() methods.
Manipulating a Gizmo Using Handles
At this point, our Gizmo will draw lines and Handles based on the values stored in the properties of the node it’s associated with (in this example, CustomNode3D). However, if we click on the Handles, nothing will happen. They will remain static.
To interact with the Handles, we need to implement a few more methods from EditorNode3DGizmoPlugin in our CustomNode3DGizmo class. However, Godot’s official documentation doesn’t cover these implementations. If you follow the official documentation tutorial, you’ll stop at the previous section of this article. It’s bizarre, but there’s nothing in the official documentation explaining how to manipulate Handles. Everything that follows from here is deduced from trial and error and interpreting the comments of each function to implement. Perhaps, based on this article, I’ll contribute to Godot’s documentation to address this gap.
Let’s see which methods need to be implemented in CustomNode3DGizmo to manipulate the Handles we placed in the _Redraw() method.
The first is _GetHandleName(). This method must return a string with the identifying name of the Handle. It’s common to return the name of the property modified by the Handle:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
Two things stand out in the previous screenshot.
First, we could have returned the property name as a hardcoded string, but using the nameof() method ensures that if we refactor the property name using our IDE, this part of the code will update as well.
Second, each Handle is identified by an integer, so we can know which Handle’s name is being requested based on the handleId parameter passed to _GetHandleName(). The integer for each Handle depends on the order in which we added the Handles when calling AddHandles() in _Redraw(). By default, if you leave the ids parameter of AddHandles() empty, the first Handle you pass will be assigned ID 0, the second ID 1, and so on. However, if you look at the _Redraw() screenshot earlier, I didn’t leave the ids parameter empty. Instead, I passed an array with a single element, an integer defined as a constant, to force that Handle to be assigned that integer as its ID, allowing me to use that constant as an identifier throughout the code:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
Once we’ve implemented how to identify each Handle, the next step is to define what value the Handle returns when clicked. This is done by implementing the _GetHandleValue() method:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
Like _GetHandleName(), _GetHandleValue() is passed the Handle’s identifier for which the value is being requested. With the gizmo parameter, we can obtain the node associated with the Gizmo using the GetNode3D() method (line 130 of the previous screenshot). Once we have a reference to the node, we can return the value of the property associated with each Handle (lines 133 to 136).
When you click on a Handle, look at the bottom-left corner of the scene view in the editor; a string will appear, formed by what _GetHandleName() and _GetHandleValue() return for that Handle.
Now comes what may be the most challenging part of this tutorial: using the Handle to assign a value to the associated node’s property. This is done by implementing the _SetHandle() method:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
This method is passed a gizmo parameter, which allows access to the associated node using GetNode3D(), as we did in _GetHandleValue(). It’s also passed the Handle’s identifier for which the value is being set. Most importantly, it’s passed the camera viewing the scene and the Handle’s screen position.
In this method, we need to interpret the Handle’s screen position to set the node’s property value associated with the Handle based on that position. In this case, it seems simple: the Handle’s position should be the value stored in the associated property, since both NodeMainPoint and NodeSecondaryPoint are positions. The problem is that Handles are dragged on the two-dimensional surface of the screen, which is why the screenPos parameter is a Vector2, so it’s not immediately clear which three-dimensional scene coordinate corresponds to that screen point.
When we add a Camera3D node to a scene, that node is represented with the following Gizmo:
Camera3D Gizmo |
I find it very clarifying to think of our head being at the tip of the Gizmo’s pyramid, looking at a screen at the base. The scene in front of the camera is back-projected onto that screen.
Suppose we have an object A in the scene. The screen position where A is drawn (let’s call this position Ap) is the result of drawing a straight line from the object to the camera’s focus and seeing where it intersects the camera’s back-projection plane:
3D object projection onto the flat surface of the screen |
So far, this is straightforward. In fact, the Camera3D class has the UnprojectPosition() method, which takes a three-dimensional position (Vector3) of an object in the scene and returns its two-dimensional position (Vector2) on the screen. In our case, if we passed A’s position to UnprojectPosition(), the method would return Ap (understood as a two-dimensional screen position).
Now suppose we have a Handle at the screen position Ap representing A’s position, and we drag the Handle to the screen position Bp. How would we calculate the object’s new position in three-dimensional space? (Let’s call this new position B.) The logical approach is to apply the inverse process of back-projecting the object onto the camera’s plane. To do this, we’d draw a line from the camera’s focus through Bp. Following this reasoning, the object’s new position would lie along that line—but where? At what point on the line?
The key is to realize that the object moves in a plane (Pm) parallel to the camera’s plane. The intersection of that plane with the line from the camera’s focus passing through Bp will be the object’s new position (B):
Object moved across the screen |
The Camera3D node has a ProjectPosition() method, which is used to convert two-dimensional screen coordinates into three-dimensional scene coordinates. The method accepts two parameters. The first is a two-dimensional screen position (in our example, Bp). With this parameter, the method draws a line from the camera’s focus through the two-dimensional camera coordinate (Bp). The second parameter, called zDepth, is a float indicating the distance from the camera’s focus at which the plane Pm should intersect the line.
zDepth |
This distance is the length of the line from the camera’s focus that intersects perpendicularly with the plane Pm. In the previous diagram, it’s the distance between the focus (F) and point D.
But how do we calculate this distance? Using trigonometry. If we recall our high school lessons, the cosine of the angle between segments FA and FD equals the ratio of FD divided by FA. So, FA multiplied by the cosine gives us FD.
FD distance calculation |
This calculation is so common that game engine vector math libraries include it under the name Dot Product. With this operator, we can transform the previous formula into:
Dot product |
This formula means that if we compute the Dot Product of vector FA onto the normalized vector of FD, we get the full vector FD.
It’s common to visualize the Dot Product as a projection of one vector onto another. If you placed a powerful light behind FA, shining perpendicularly onto the normalized vector FD, the shadow FA would cast onto FD would be exactly FD.
Therefore, to obtain the distance FD to use as the zDepth parameter, we only need to compute the Dot Product of FA onto the normalized FD, which is the Forward vector of the Camera3D node (by default, the inverse of its local Z-axis).
All this reasoning boils down to a few lines in the GetZDepth() method:
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
In this method, the variable vectorToPosition corresponds to FA, and cameraForwardVector to FD. The zDepth result returned by the method is FD and is used in the calls to ProjectPosition() in _SetHandle() to set the new positions of NodeMainPoint and NodeSecondaryPoint.
Having resolved _SetHandle(), the only method left to implement is _CommitHandle(). This method is responsible for building the history of modifications we make to our Handles, so we can navigate through it when performing undo/redo (Ctrl+Z or Ctrl+Shift+Z):
addons/custom_node3D_gizmo/CustomNode3DGizmo.cs |
The history is built on an object of type EditorUndoRedoManager (in this case, _undoRedo), which is obtained from the EditorPlugin object that registers the Gizmo (in this example, CustomNode3DGizmoRegister, which we’ll discuss soon) and passes an instance of EditorUndoRedoManager through its constructor.
With the EditorUndoRedoManager, each history entry is created with the CreateAction() method (line 78 of the previous screenshot). Each entry must include actions to execute for Undo and Do (called during Redo). These actions can involve setting a property with the AddDoProperty() and AddUndoProperty() methods or executing a method with AddDoMethod() and AddUndoMethod(). If the direct action only involved changing a property’s value, it’s usually sufficient to set the property back to its previous value to undo it. However, if the direct action triggered a method, in addition to changing the property, you’ll likely need to call another method to undo what the first did.
In this example, I only change the values of customNode3D’s properties, so for the action history, it’s enough to use the Add...Property() methods. These methods require as parameters the instance owning the properties to modify, the string with the property’s name to manipulate, and the value to set the property to. Each action captures the value we pass to the Add...Property() method. For AddDoProperty(), we pass the property’s current value (lines 84 and 91); for AddUndoProperty(), we pass the restore parameter’s value, which contains the value retrieved from the history when performing an Undo.
When _CommitHandle() is called with the cancel parameter set to true, it’s equivalent to an Undo on the Handle, so we restore the restore value to the property (lines 101 to 106).
Finally, but no less important, once we’ve shaped the property changes and method calls that make up the history entry, we register it with CommitAction() (line 109).
Updating the Gizmo After Changes
The visual representation of a Gizmo may need to be updated for two reasons:
- Because we have modified the fields of the represented node from the inspector.
- Because we have manipulated the Gizmo’s Handles.
The Gizmo is updated by calling the UpdateGizmos() method, which is available to all Node3D nodes.
The question is where to call this method to ensure that both types of changes mentioned above are updated. In the previous code screenshots, you’ll notice several commented-out calls to UpdateGizmos(). These were tests of possible places to execute that method. All the commented-out calls had issues: either they didn’t trigger for one of the two cases above, or they updated in a choppy manner, as if there were some performance problem.
In the end, my tests led me to conclude that, in my case, the best place to call UpdateGizmos() is from the properties of CustomNode3D that we’re modifying. For example, in the case of NodeMainPoint:
nodes/CustomNode3D/CustomNode3D.cs |
By calling UpdateGizmos() from the setter of the property, which is exported, we ensure that the method is called both when the property is modified from the inspector and from the _SetHandle() method of the Gizmo.
Registering the Gizmo
Just like with the custom node, our Gizmo also needs to be registered in the editor so that it knows to account for it. For this, we’ll again use a plugin to handle the registration:
addons/custom_node3D_gizmo/CustomNode3DGizmoRegister.cs |
We’ll create the plugin using the same method we used to create the CustomNode3DRegister plugin, but this time, the plugin will be called CustomNode3DGizmoRegister and will be based on a C# script of the same name.
In this case, we load the C# script where we configured the Gizmo and instantiate it, passing an instance of EditorUndoRedoManager to its constructor by calling the GetUndoRedo() method (lines 13 and 14).
Once that’s done, we register the plugin instance by passing it to the AddNode3DGizmoPlugin() method (line 15).
Similarly to how we handled the registration of the custom node, we also use the _ExitTree() method here to deregister the Gizmo using the RemoveNode3DGizmoPlugin() method (line 21).
Once the script is complete, we can activate the plugin from Project > Project Settings... > Plugins, and the next time we add a CustomNode3D to the scene, we’ll be able to use the Gizmo’s Handles.
Initially, the Handles might not be clearly distinguishable because both will coincide at the origin of the coordinates:
Handles at coordinate origin |
However, they are there. They’re the small points visible at the origin of the coordinate axis. If we click and drag these points, we’ll see them move, altering the values of the CustomNode3D properties:
Dragging Handles |
Conclusions
This article has been extremely long, but I wanted to address a glaring gap in the official documentation on this topic.
My previous experience was with Unity, which also has its own Gizmos and Handles API. Compared to Unity, Godot’s approach seems more compact and straightforward, as it centralizes both Gizmo and Handle configuration in a single class inheriting from EditorNode3DGizmoPlugin. In contrast, to achieve the same in Unity, you need to spread the Gizmo and Handle code across different classes.
That said, Unity’s documentation on this topic seems much more comprehensive.
It’s also worth noting that Unity’s Gizmos and Handles API covers both 2D and 3D games, whereas in Godot, everything we’ve covered in this article applies only to 3D games. There is no EditorNode2DGizmoPlugin class, or at least I’m not clear on what the equivalent of this code would be for a 2D Godot game. I’ll investigate this, and when I figure it out, I’ll likely write another article. However, at first glance, the official documentation doesn’t make it clear how to do this in 2D.
Code for This Tutorial
The code for the project used as an example is available for download in my GitHub repository GodotCustomGizmoAndHandleExample. Feel free to download it to examine the code in detail and try it out for yourself.