Debugging is a really fun activity, isn't it? Often we require some extra tools to aid in this process. Maybe by dumping a bunch of text into the screen or drawing some extra lines. This addon is meant to bring some little scripts to help with those.
Activating the "Debug Helpers" addon will automatically add the scripts into the auto-load list. The default name to reference them will be mentioned in the specific "sub-addon" topics. Nevertheless, if you prefer you can manually add those scripts to the singleton list instead of activating the plugin.
This script is meant to be part of the auto-load list, meaning that its functionality will be accessible from wherever in the code. Basically it provides a few functions to add/remove text into/from the screen using the
Label Control widget. Those are added into a panel that expands/shrinks according to the contents. This panel is, by default, black with some transparency set just so text is more easily readable. This color (and transparency) can be changed.
By default this script will be named
OverlayDebugInfo and it's the name that will be used throughout the rest of this topic.
Once the script is in the auto-load list, you can then easily use the functionality. Note that by default the panel is set to be shown, which will result in a tiny black square on the top left corner of the screen. From code you can control the visibility of the entire panel using
set_visibility() function, which requires a single parameter,
true to show and
false to hide. So, if you don't want the panel to be visible in you main menu (or anywhere else, actually), you can add this into the
There is also a "shortcut" function that you can use to toggle the visibility. This can be useful to be called from a key press event:
# Toggle the visibility of the overlay debug info panel
When you want to add text to the screen, there are two options:
set_label(): adds a label to the screen, which must be associated with some ID, so you can then manipulate it or even remove.
add_timed_label(): add a label that will remain on screen during some time then will be deleted. Those don't require any ID.
Let see the
set_label() first. It requires two arguments, the label ID and the text that will be set into that label. That said, suppose you want a label that will display the position of a character, which changes during the game. Assume we have a variable named
character_position holding this information. Then we can call the function during the
Note that you don't have to first create a label with that name (
char_pos in this example). The script will take care of creating the Label object if it doesn't exist.
Now suppose you want to see some information being shown on the screen for a short amount of time, mostly because the value you want to check doesn't change too frequently (if at all), but you still want to verify if the value is correct. For that, there is the
add_timed_label() function, which also requires two arguments, the text and the amount of seconds the information will remain on screen.
For the next script, suppose you have one plant object that holds a
grow_stage property which isn't updated too frequently. You then want to check this value when you press a key and keep this information for only 2 seconds:
OverlayDebugInfo.add_timed_label("Grow Stage: %s" % grow_stage, 2.0)
With this, the label will be removed from the panel after 2 seconds.
After you have set a "permanent" label you can remove it, with the
remove_label() function. It requires a single argument, the ID of the label. So, going back to the character position example, we can remove that information with this:
You can clear all non timed labels at once like this:
The position of the panel can be changed, into "9 spots" by setting the vertical and horizontal alignment:
# Put the panel on the left horizontal alignment
# Put the panel on the center horizontal alignment
# Put the panel on the right horizontal alignment
# Put the panel on the top vertical alignment
# Put the panel on the center vertical alignment
# Put the panel on the bottom vertical alignment
With those, suppose you want the panel to be placed on the bottom-right corner of the screen. Those two can be called in sequence:
Finally, to change the color of the panel, there is the function
set_panel_color(), which requires a single argument, the color itself. Suppose we wanted to change the panel to white with 50% transparency:
OverlayDebugInfo.set_panel_color(Color(1.0, 1.0, 1.0, 0.5))
And that's all for this script!
The first thing to note in this script is the containers. There is the
_haligner (which is one
HBoxContainer) and inside of it the
_valigner (which is one
_haligner is used to help position the panel horizontally, while the
_valigner is used to position the panel vertically. Check the
set_*_align_*() functions to see exactly which property is changed.
anchor property of the
_haligner is set in a way so this container uses the entire available space.
_valigner there is the
_background, which is one
PanelContainer widget. When it is created, the
self_modulate property is used to change the panel's color (defaulting to black with some transparency). Also, the
size_flags_vertical is completely cleared as the default one expands the panel to use the entire available height every time a new label is added (which is not exactly the desired behavior).
Lastly, inside the
_background there is yet another
VBoxContainer widget, named
_label_box. This time it is used to place the labels one bellow the other.
Note that all four containers have been configured to ignore the mouse (mouse filter is set to
MOUSE_FILTER_IGNORE). If that is not done (specially on the
_valigner) it becomes impossible to interact with HUD/Widgets when the panel is visible.
There is one script property named
_label_node, which is a dictionary holding the created non-timed labels. When the
set_label() function is called, it first checks if the label exists within this dictionary. If so, only update the
text property, otherwise, create the label, add into the
_label_box container and the dictionary, then set the
The timed label function (
add_timed_label()) contains a bit more code, but it's not that complicated. Since labels added in this way are not meant to have ID's, it immediately create a new
Label object and add into the
_label_box container. After that a timer is created and setup. The function connected to its
timeout signal receives two payload arguments, the timer itself and the newly created label object.
_on_timeout() function is connected to the
timeout Timer signal. As mentioned, it receives, as payload, two arguments. Those are used to perform the necessary cleanup, which is basically call the
queue_free() of both the Timer and the Label nodes.
And that's it! This script is simple, really!
As with the
overlayinfo.gd script, once this one is in the auto-load list, the functionality can be accessed.
By default this script will be named
DebugLine3D and it's the name that will be used throughout the rest of this topic.
To draw a line, there is the
add_line() function, which requires 2 arguments plus one optional. The first two are the global positions of the end points. Perhaps this should have been named
add_line_segment() but, really, this would become really tedious, right? The optional argument specifies the color of the line, which defaults to white. That said, suppose you have two positions stored in
Vector3 variables (
p2) and you want to draw a line connecting those two points using the red color:
DebugLine3D.add_line(p1, p2, Color.red)
Doing so will create a line that will be drawn during a single physics iteration frame. In other words, on the next update the line will be removed. This is done in this way because the idea is to show lines for things that do change frequently. With that in mind, if you want the line connecting those two points to remain more time on screen you either keep calling the
add_line() function as part of the
_physics_process() function or call the
add_timed_line() requires 3 arguments plus the optional color. The first two arguments are the end points of the line segment while the third one is the amount of time (in seconds) the line will remain on screen. So, let's add a green line that will disappear after 2.5 seconds:
DebugLine3D.add_timed_line(p1, p2, Color.green, 2.5)
There is one detail, this function returns a value, which is the "ID" of the created line. This ID can be used to manually remove a timed line. The thing is, if you specify a 0 (or negative) value as the amount of time, the line will remain there and will not be automatically removed. To have more control over when the line is to be removed, you can use
remove_timed_line(), which requires the ID returned from the
add_timed_line() function. So, you can use something like this:
var timed_line: int = DebugLine3D.add_timed_line(p1, p2, some_color, 0)
# ... later, maybe even somewhere in the code, remove the line
Godot will probably warn you about a return value that was ignored if you used the
add_timed_line() function without using what was returned. It's possible to append a comment to tell Godot you are aware of that and you don't want to be warned, like this:
DebugLine3D.add_timed_line(p1, p2, some_color, some_time)
It's possible to disable the functionality of the addon (basically its processing) while still being able to access it. The idea here is to not clash too much with the game logic code (having to remove debug code then adding it back) all the time but also allowing the script to do nothing when disabled. Basically, you can the
set_enabled() function, which requires a single boolean argument:
# Disable the line3d processing
# Or enable it
This can be called somewhere on the main menu script or during initialization of another auto-load script prior to exporting the project. Then any call to add debug lines will basically do nothing. You can check its state by calling
# Do something if the functionality is enabled.
And that's it for this sub-addon!
The core of this script is to extend the
ImmediateGeometry. You can ready about it in the documentation. Basically, every time the
add_line() or the
add_timed_line() is called, an instance of the inner class
Line3D is created. This object holds the end points of the line segment as well as the color.
During initialization (
_enter_tree()), a material override is set, otherwise the line color will not work. In this case it's configured to be unshaded (so lighting will not affect the line) and to use the vertex color as albedo (so the specified color works).
In the case of the common line, it is added into the internal
_onef_line array. As for the timed lines, the instance is added into the
_timed_line dictionary. In this case, the entry is keyed by one internal "ID" (which is just an incrementing integer, really) and contains both the
Line3D and a
When the Timer object is created, the
_on_timeout() function is connected to its
timeout signal, giving the line ID and a container as payloads. While writing this I realize that the container payload is not necessary, but was used because I did try to add other drawing shapes. That ended up being too much work for something that is meant to be "quick and dirty" so I abandoned the idea while forgetting to update the code. Nevertheless, within this function the
remove_timed_line() is called, providing the ID of the line.
remove_timed_line() function first retrieves a line from the
_timed_line dictionary and, if valid, takes the Timer object, stops it, remove it from the tree and then erase the entry from the dictionary.
Finally, from within the
_physics_process() function, the previous drawing operation is cleared then the
begin() function is used to start the line drawing process. This is done by iterating through the entries in the line containers and adding the vertices using
add_vertex() function (inherited from
ImmediateGeometry) for each of the end points of the line segment. Once that iteration is done, the
end() function is called to end the process of adding vertices (and possibly other properties - please see the above linked documentation). Finally, the
_onef_line array is cleared just so the lines in it are not drawn in the next loop iteration.
And that's it for this script!