Making main screen plugins¶
What this tutorial covers¶
As seen in the Making plugins page, making a basic plugin that extends the editor is fairly easy. This plugin mechanism also allows you to create new UIs in the central part of the editor, similarly to the basic 2D, 3D, Script and AssetLib views. Such editor plugins are refered as “Main screen plugins”.
This tutorial leads you through the creation of a basic main screen plugin. With this plugin example, we want to demonstrate:
- Creating a main screen plugin
- Linking the main screen to another plugin GUI element (such as a Tab panel, similar to the Inspector tab)
For the sake of simplicity, the two GUI elements of our main screen plugin will both consist in a Label and a Button. Pressing one element’s button will display some text on the other’s label node.
Initializing the plugin¶
The plugin itself is a Godot project. It is best to set its contents in an
addons/my_plugin_name/ structure. The only files that lie in the root folder
are the project.godot file, and the project icon.
addons/my_plugin_name/ folder, we create the
plugin.cfg file as
described in the Making plugins page.
[plugin] name="Main screen plugin demo" description="A plugin that adds a main screen panel and a side-panel which communicate with each other." author="Your Name Here" version="1.0.0" script="main_screen_plugin.gd"
We also initialize the file targeted by the
script= property of the
file. In our example,
tool extends EditorPlugin func _enter_tree(): pass func _exit_tree(): pass func has_main_screen(): return true func make_visible(visible): pass func get_plugin_name(): return "Main Screen Plugin"
The important part in this script is the
has_main_screen() function, which is
overloaded so it returns true. This function is automatically called by the
editor on plugin activation, to tell it that this plugin adds a new center view to
the editor. For now, we’ll leave this script as-is and we’ll come back to it
main_screen_plugin.gd file will be responsible for each of our plugin’s
UI element instanciation, and it will also manage the communication between them.
As a matter of fact, we wish to design each UI element in their own scene.
Different scenes are not aware of each other unless they are both children of a
parent scene, yet they will then require
Such practice is more likely to produce errors at runtime, especially if these
scenes do not share the same parent node. This is why, they should only be
allowed to access their children.
So, in order to communicate information to another scene, the best design is to
define signals. If a user action in a UI scene #1 has to trigger something in
another UI scene #2, then this user action has to emit a signal from scene #1,
and scene #2 will be connected to that signal. Since all of our UI scenes will
be instanced by
main_screen_plugin.gd script, this one script will also
connect each of them to the required signals.
main_screen_plugin.gd instanciates the UI scenes, won’t
they be brothers nodes then?
Not necessarily: this script may add all UI scenes as children of the same node
of the editor’s scene tree - but maybe it won’t. And the
script will not be the parent node of any instanciated scene because it is a
script, not a node! This script will only hold references to instanciated
Main screen scene¶
Create a new scene with a
Panel root node. Select this root node and, in the
viewport, click the
Layout menu and select
Full Rect. The panel now uses
all the space available in the viewport. Now, let’s add a new script on the root
node. Name it
We then add 2 children to this Panel node: first a
Button node. Place it
anywhere on the Panel.
Now we need to define a behaviour when this button is pressed. This is covered
by the Handling a signal page, so this
part will not be described in details in this tutorial.
Select the Button node and click the
Node side dock.
pressed() signal and click the
Connect button (you can also
pressed() signal instead). In the window that opened,
select the Panel node (we will centralize all behaviors in its attached
script). Keep the default function name, make sure that the
toggle is ON and hit
Connect. This creates an
function in the
main_panel.gd script, that will be called every time the
button is pressed.
As the button gets pressed, we want the side-panel’s
Label node to show a
specific text. As explained above, we cannot directly access the target scene,
so we’ll emit a signal instead. The
main_screen_plugin.gd script will then
connect this signal to the target scene. Let’s continue in the
tool extends Panel signal main_button_pressed(value) func on_Button_pressed(): emit_signal("main_button_pressed", "Hello from main screen!")
In the same way, this main scene’s Label node has to show a value when it
receives a specific signal. Let’s create a new
_on_side_button_pressed(text_to_show) function for this purpose:
func _on_side_button_pressed(text_to_show): $Label.text = text_to_show
We are done for the main screen panel. Save the scene as
Tabbed panel scene¶
The tabbed panel scene is almost identical to the main panel scene. You can
either duplicate the
main_panel.tscn file and name the new file
side_panel.tscn, or re-create it from a new scene by following the previous
section again. However, you will have to create a new script and attach it to
the Panel root node. Save it as
side_panel.gd. Its content is slightly
different, as the signal emitted and the target function have different names.
Here is the script’s full content:
tool extends Panel signal side_button_pressed(value) func on_Button_pressed(): emit_signal("side_button_pressed", "Hello from side panel!") func _on_main_button_pressed(text_to_show): $Label.text = text_to_show
Connecting the two scenes in the plugin script¶
We now need to update the
main_screen_plugin.gd script so the plugin
instances our 2 GUI scenes and places them at the right places in the editor.
Here is the full
tool extends EditorPlugin const MainPanel = preload("res://addons/my_plugin_name/main_panel.tscn") const SidePanel = preload("res://addons/my_plugin_name/side_panel.tscn") var main_panel_instance var side_panel_instance func _enter_tree(): main_panel_instance = MainPanel.instance() side_panel_instance = SidePanel.instance() # Add the main panel to the editor's main viewport. get_editor_interface().get_editor_viewport().add_child(main_panel_instance) # Add the side panel to the Upper Left (UL) dock slot of the left part of the editor. # The editor has 4 dock slots (UL, UR, BL, BR) on each side (left/right) of the main screen. add_control_to_dock(DOCK_SLOT_LEFT_UL, side_panel_instance) # Hide the main panel make_visible(false) func _exit_tree(): main_panel_instance.queue_free() side_panel_instance.queue_free() func _ready(): main_panel_instance.connect("main_button_pressed", side_panel_instance, "_on_main_button_pressed") side_panel_instance.connect("side_button_pressed", main_panel_instance, "_on_side_button_pressed") func has_main_screen(): return true func make_visible(visible): if visible: main_panel_instance.show() else: main_panel_instance.hide() func get_plugin_name(): return "Main Screen Plugin"
A couple of specific lines were added. First, we defined the constants that
contain our 2 GUI packed scenes (
SidePanel). We will use
these resources to instance both scenes.
_enter_tree() function is called before
_ready(). This is where we
actually instance the 2 GUI scenes, and add them as children of specific parts
of the editor. The side panel case is similar to the example shown in
Making plugins page: we add the scene in an editor dock. We specified
it will be placed in the left-side dock, upper-left tab.
EditorPlugin class does not provide any function to add an element in the
main viewport. We thus have to use the
get_editor_interface().get_editor_viewport() to obtain this viewport and add
our main panel instance as a child to it. We call the
function to hide the main panel so it is not directly shown when first
activating the plugin.
_exit_tree() is pretty straightforward. It is automatically called when
the plugin is deactivated. It is then important to
queue_free() the elements
previously instanced to preserve memory. If you don’t, the elements will
effectively be invisible in the editor, but they will remain present in the
memory. Multiple de-activations/re-activations will then increase memory usage
without any way to free it, which is not good.
make_visible() function is overridden to hide or show the main
panel as needed. This function is automatically called by the editor when the
user clicks on another main viewport button such as 2D, 3D or Script.
Try the plugin¶
Activate the plugin in the Project Settings. You’ll observe a new button next to 2D, 3D, Script above the main viewport. You’ll also notice a new tab in the left dock. Try to click the buttons in both side and main panels: events are emitted and caught by the corresponding target scene to change the Label caption inside it.