Remember that Gizmos are visual aids activated in the editor, associated with a specific node, to visually represent the magnitudes of some of a node's fields. For example, if a node had a field called Direction, we could implement a Gizmo that draws an arrow originating from the node and pointing in the direction configured in the field. This way, it would be easier to configure the field because we would see the result of the different values we input.
On the other hand, Handles are grip points drawn alongside the Gizmo that allow us to manipulate it visually and, consequently, the field it corresponds to. In the example mentioned, a Handle would be a point drawn at the end of the Gizmo's arrow. By selecting that point and dragging it with the mouse, the arrow would change direction and, in doing so, the value of the Direction field of the node associated with the Gizmo would also change.
In a previous article, I discussed how to implement Gizmos and even Handles in Godot projects. The issue with that implementation is that it only worked for 3D projects. Godot's 3D and 2D APIs are so distinct that what works in one does not work in the other. The fact that everything revolved around the EditorNode3DGizmo node should have been a clue.
Therefore, in a 2D Godot project, we won’t be able to use the EditorNode3DGizmo method to draw Gizmos and Handles on the screen. In this article, we'll explore an alternative method for including Gizmos in our 2D projects. I won’t discuss Handles for reasons I’ll explain at the end of the article.
First of all, it’s worth noting that, unlike Unity, Godot does not offer specialized nodes for drawing Gizmos in 2D. Its native drawing functionalities are powerful enough to use them when the node is executed within the editor. The key lies in how to make the node execute within the editor and distinguish when it's in the editor versus running as part of the game.
As an example, let's implement a circular distance Gizmo. It will be in the form of a node that we can associate with others that have fields related to circular distances relative to their position. To see a possible implementation, we’ll associate it with an escape agent, which defines a panic distance. If another agent gets closer than the panic distance, the escape agent will move away. The Gizmo's purpose will be to visually represent the panic distance field.
 |
A Gizmo used to represent the field PanicDistance |
As shown in the illustration, the Gizmo draws a circle around the agent, with a radius equal to the PanicDistance field's value. We need to ensure that the circle updates every time the PanicDistance value is changed.
At the level of the level scene, the Gizmo node is not visible because it's included within the FleeSteeringBehavior scene (selected in the illustration). If we open the implementation of that scene, we’ll see the Gizmo node.
 |
Our Gizmo node |
In the illustration above, our Gizmo takes the form of a custom node named CircularRange. This node offers a very simple API. In the inspector, it exports a color variable (RangeColor) so we can choose the color of the drawn circle. In the code, the node provides a public property for the circle's radius. The parent node using CircularRange only needs to update the radius property for CircularRange to redraw the circle with the updated radius.
However, you won’t find CircularRange among the available nodes in Godot. We’ll have to implement and register it in Godot so it appears in the node list. This can be done via a plugin.
Creating a Plugin
This part is very similar to the article on 3D Gizmos and Handles. You need to use Godot’s wizard to create the structure for our plugin. The wizard can be found at Project → Project Settings... → Plugins → Create New Plugin. A window like the following will appear:
 |
Plugin creation wizard |
To give you an idea, here are some of the settings I configured for mine:
 |
Plugin Configuration |
In the “Subfolder” field, I entered res://addons/InteractiveRanges so that it would create that folder and build the plugin's skeleton there.I left the “Activate now?” checkbox unchecked because in C#, you have to implement your node’s code and compile it before activating the plugin that uses it.
Once you create the plugin, the specified folder will be generated and populated with a plugin.cfg file containing the data you entered via the wizard.
 |
plugin.cfg Contents |
In my case, I plan to include several types of Gizmos in the plugin, so I created a specific folder for the example Gizmo: res://addons/InteractiveRanges/CircularRanges. In this folder, I placed all the resources needed for the node implementation.
 |
Final content for the plugin folder |
Implementing the Gizmo Node
To implement the node, we only need a script with its code and an icon to represent it in Godot's node list.
For the icon, I found an SVG file for easier scaling. The simpler and more conceptual, the better. In my case, it’s just a circle with a line from its center to its perimeter. For consistency with the rest of Godot’s icons, I gave it the typical purple color of 2D nodes.
For the Gizmo’s code, inheriting from Node2D sufficed.
 |
First Part of the Node Implementation in CircularRange.cs |
Notice that I decorated the class with the [Tool] attribute. This is essential for its implementation to execute within the editor. Otherwise, the node would be inert until the game runs, but this node is only useful as an editor aid.
As described earlier, the node exports a RangeColor property so we can configure the Gizmo's color via the inspector.
There's also another property, Radius, which is not exported to the inspector but is public so that parent nodes using the Gizmo can update the represented radius. In line 27, each time the Radius value is updated, the Gizmo is forced to redraw via QueueRedraw().
 |
Implementing the Gizmo's Drawing in CircularRange.cs |
As you can see, drawing a circle doesn’t need to be complicated. A simple call to DrawCircle on line 35, with the circle's center position (relative to the parent node), radius, and color, is enough. The "filled" parameter lets you specify whether you want a filled circle (true) or just a perimeter outline (false), which is hollow inside but colored on its edge.
Take note of line 33—it’s important. The call to Engine.IsEditorHint() returns true if the node is running within the editor and false if it’s executing in the game. Since we only want the Gizmo to be drawn in the editor, the _Draw() method will exit if Engine.IsEditorHint() returns false.
It is crucial to use Engine.IsEditorHint() in all methods of a [Tool] node. Some nodes of this type are intended to run only in the editor, so every method must start with a safeguard like the one above. There will also be cases of mixed nodes, where you want them to have one functionality in the editor and another during gameplay. For these, each method should include a safeguard (or multiple ones) to limit the execution of code depending on what Engine.IsEditorHint() returns. While this example is simple, that won't always be the case. Adding these safeguards can complicate your code, and you might encounter errors where editor-only code is executed during gameplay. For this reason, it’s recommended to decorate nodes with the [Tool] attribute only when absolutely necessary. In fact, if you have mixed nodes (with both editor and gameplay functionality), my advice is to separate their functionality into two nodes: one for gameplay and another for the editor.
Registering the Gizmo Node in Godot
Let’s revisit a step common to the article on 3D Gizmos: registering our Gizmo node so that Godot shows it in the node list.
The script responsible for the registration is the one configured in the plugin creation wizard. In my case, this is InteractiveRanges.cs, which I’ve placed in the root folder of the plugin.
 |
Registering the Gizmo Node in InteractiveRanges.cs |
As you may recall, registering the node is quite simple. A call to the AddCustomType() method (line 30) is all it takes. You pass the method the node's name, the base node it inherits from, the location of its script, and the icon to represent it.
In my case, since I plan to create more Gizmos, I consolidated the entire process into the RegisterCustomNode() method (line 22), which I call every time I want to register a new Gizmo. That call is made from the _EnterTree() method (line 8), which executes whenever the plugin is activated.
After completing this last script, our plugin has everything it needs to work. It’s time to compile it and go to Project → Project Settings... → Plugins to activate the plugin.
Once we’ve done this, we’ll be able to select our node from Godot's list.
Using Our Custom Gizmo
After including the Gizmo node in the scene hierarchy, we need to integrate it with the rest of the code.
 |
Example Scene Structure |
In our example, the node that will use the Gizmo by passing its data is the FleeSteeringBehavior node.
FleeSteeringBehavior locates its child nodes and obtains references to them in its _Ready() method.
 |
FleeSteeringBehavior Gets References to Its Children in _Ready() |
Don’t be misled by the call to FindChild(). It’s an extension method I implemented to search for child nodes by type. I had to implement it myself because the default FindChild() method in Godot only searches for nodes by name. Searching by name wouldn’t have been a big problem, but in this case, I preferred to search by type.
 |
Implementation of the Extension Method for Finding Child Nodes by Type |
Once it has a reference to the Gizmo, FleeSteeringBehavior must use it every time the value of the field represented by the Gizmo changes.
 |
Updating the Gizmo from FleeSteeringBehavior |
This kind of feature is the reason properties exist. In this case, whenever the PanicDistance property of FleeSteeringBehavior is updated, it also updates the Radius property of the Gizmo (line 50). Remember, Radius is a property that forces the Gizmo to redraw itself whenever its value changes. In this way, the Gizmo will update every time PanicDistance changes.
It’s worth mentioning that FleeSteeringBehavior must also be marked as [Tool] so that the logic tied to the PanicDistance property executes in the editor.
Conclusion
Gizmos are extremely useful when shaping your game. They allow you to see the effects of node field values without running the game, which speeds up development.
Unity has a very mature implementation for Gizmos and Handles. In fact, the APIs for these are redundant in some aspects. I hope to write an article about this soon.
In contrast, Godot’s implementation is not as developed. There seems to be some effort to reach Unity's level, but limitations are still noticeable. Gizmos and Handles, as such, only seem to exist in the 3D realm. They have limitations (e.g., only lines can be drawn) and are poorly documented, as I mentioned in my article on the subject.
The case for 2D Gizmos is even more striking because there doesn’t seem to be a specialized API. Instead, the engine’s drawing primitives are used. This isn’t inherently bad, but it’s surprising that no effort has been made to create auxiliary classes that make 3D and 2D Gizmo management similar.
The worst part comes with 2D Handles in Godot. They don’t exist as such, and their manual implementation is complex or even impossible. In my case, I managed to implement something similar to a Handle by having InteractiveRanges.cs intercept mouse clicks. This involved implementing the _ForwardCanvasGuiInput() and Handles() methods from the EditorPlugin parent class and calculating positions over a circle drawn on the Gizmo as a makeshift Handle. However, this implementation only worked when the Gizmo node was selected in the hierarchy, making it useless. Typically, the Gizmo would be hidden as an auxiliary node within other scenes, so when selecting those scenes, the root nodes of those scenes would be clicked instead of the Gizmo, preventing it from being drawn.
I spent a lot of time on this, searched online countless times, and eventually gave up. My impression is that Handles are not widely used in Godot development. Perhaps I’m wrong, but very few people ask about this topic online, and no one provides a solution or an alternative to the issues I encountered.
Hopefully, I’ll find some time to explain how to achieve very similar functionality in Unity so you can compare the support both engines offer.