456 lines
15 KiB
Text
456 lines
15 KiB
Text
|
.. _doc_making_plugins:
|
|||
|
|
|||
|
Making plugins
|
|||
|
==============
|
|||
|
|
|||
|
About plugins
|
|||
|
~~~~~~~~~~~~~
|
|||
|
|
|||
|
A plugin is a great way to extend the editor with useful tools. It can be made
|
|||
|
entirely with GDScript and standard scenes, without even reloading the editor.
|
|||
|
Unlike modules, you don't need to create C++ code nor recompile the engine.
|
|||
|
While this makes plugins less powerful, there are still many things you can
|
|||
|
do with them. Note that a plugin is similar to any scene you can already
|
|||
|
make, except it is created using a script to add editor functionality.
|
|||
|
|
|||
|
This tutorial will guide you through the creation of two simple plugins so
|
|||
|
you can understand how they work and be able to develop your own. The first
|
|||
|
will be a custom node that you can add to any scene in the project and the
|
|||
|
other will be a custom dock added to the editor.
|
|||
|
|
|||
|
Creating a plugin
|
|||
|
~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
Before starting, create a new empty project wherever you want. This will serve
|
|||
|
as a base to develop and test the plugins.
|
|||
|
|
|||
|
The first thing you need for the editor to identify a new plugin is to
|
|||
|
create two files: a ``plugin.cfg`` for configuration and a tool script with the
|
|||
|
functionality. Plugins have a standard path like ``addons/plugin_name`` inside
|
|||
|
the project folder. Godot provides a dialog for generating those files and
|
|||
|
placing them where they need to be.
|
|||
|
|
|||
|
In the main toolbar, click the ``Project`` dropdown. Then click
|
|||
|
``Project Settings...``. Go to the ``Plugins`` tab and then click
|
|||
|
on the ``Create`` button in the top-right.
|
|||
|
|
|||
|
You will see the dialog appear, like so:
|
|||
|
|
|||
|
.. image:: img/making_plugins-create_plugin_dialog.png
|
|||
|
|
|||
|
The placeholder text in each field describes how it affects the plugin's
|
|||
|
creation of the files and the config file's values.
|
|||
|
|
|||
|
To continue with the example, use the following values:
|
|||
|
|
|||
|
.. tabs::
|
|||
|
.. code-tab:: ini GDScript
|
|||
|
|
|||
|
Plugin Name: My Custom Node
|
|||
|
Subfolder: my_custom_node
|
|||
|
Description: A custom node made to extend the Godot Engine.
|
|||
|
Author: Your Name Here
|
|||
|
Version: 1.0.0
|
|||
|
Language: GDScript
|
|||
|
Script Name: custom_node.gd
|
|||
|
Activate now: No
|
|||
|
|
|||
|
.. code-tab:: ini C#
|
|||
|
|
|||
|
Plugin Name: My Custom Node
|
|||
|
Subfolder: my_custom_node
|
|||
|
Description: A custom node made to extend the Godot Engine.
|
|||
|
Author: Your Name Here
|
|||
|
Version: 1.0.0
|
|||
|
Language: C#
|
|||
|
Script Name: CustomNode.cs
|
|||
|
Activate now: No
|
|||
|
|
|||
|
.. warning::
|
|||
|
|
|||
|
Unchecking the ``Activate now?`` option in C# is always required because,
|
|||
|
like every other C# script, the EditorPlugin script needs to be compiled which
|
|||
|
requires building the project. After building the project the plugin can be
|
|||
|
enabled in the ``Plugins`` tab of ``Project Settings``.
|
|||
|
|
|||
|
You should end up with a directory structure like this:
|
|||
|
|
|||
|
.. image:: img/making_plugins-my_custom_mode_folder.png
|
|||
|
|
|||
|
``plugin.cfg`` is a simple INI file with metadata about your plugin.
|
|||
|
The name and description help people understand what it does.
|
|||
|
Your name helps you get properly credited for your work.
|
|||
|
The version number helps others know if they have an outdated version;
|
|||
|
if you are unsure on how to come up with the version number, check out `Semantic Versioning <https://semver.org/>`_.
|
|||
|
The main script file will instruct Godot what your plugin does in the editor
|
|||
|
once it is active.
|
|||
|
|
|||
|
The script file
|
|||
|
^^^^^^^^^^^^^^^
|
|||
|
|
|||
|
Upon creation of the plugin, the dialog will automatically open the
|
|||
|
EditorPlugin script for you. The script has two requirements that you cannot
|
|||
|
change: it must be a ``tool`` script, or else it will not load properly in the
|
|||
|
editor, and it must inherit from :ref:`class_EditorPlugin`.
|
|||
|
|
|||
|
.. warning::
|
|||
|
|
|||
|
In addition to the EditorPlugin script, any other script that your plugin uses
|
|||
|
must *also* be a tool.
|
|||
|
* Any GDScript without ``tool`` imported into the editor will act like an empty file!
|
|||
|
* Any C# class without ``[Tool]`` won't be reloaded when the project is built forcing you to re-enable the plugin!
|
|||
|
|
|||
|
It's important to deal with initialization and clean-up of resources.
|
|||
|
A good practice is to use the virtual function
|
|||
|
:ref:`_enter_tree() <class_Node_method__enter_tree>` to initialize your plugin and
|
|||
|
:ref:`_exit_tree() <class_Node_method__exit_tree>` to clean it up. Thankfully,
|
|||
|
the dialog generates these callbacks for you. Your script should look something
|
|||
|
like this:
|
|||
|
|
|||
|
.. _doc_making_plugins_template_code:
|
|||
|
.. tabs::
|
|||
|
.. code-tab:: gdscript GDScript
|
|||
|
|
|||
|
tool
|
|||
|
extends EditorPlugin
|
|||
|
|
|||
|
|
|||
|
func _enter_tree():
|
|||
|
# Initialization of the plugin goes here.
|
|||
|
pass
|
|||
|
|
|||
|
|
|||
|
func _exit_tree():
|
|||
|
# Clean-up of the plugin goes here.
|
|||
|
pass
|
|||
|
|
|||
|
.. code-tab:: csharp
|
|||
|
|
|||
|
#if TOOLS
|
|||
|
using Godot;
|
|||
|
using System;
|
|||
|
|
|||
|
[Tool]
|
|||
|
public class CustomNode : EditorPlugin
|
|||
|
{
|
|||
|
public override void _EnterTree()
|
|||
|
{
|
|||
|
// Initialization of the plugin goes here.
|
|||
|
}
|
|||
|
|
|||
|
public override void _ExitTree()
|
|||
|
{
|
|||
|
// Clean-up of the plugin goes here.
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
This is a good template to use when creating new plugins.
|
|||
|
|
|||
|
A custom node
|
|||
|
~~~~~~~~~~~~~
|
|||
|
|
|||
|
Sometimes you want a certain behavior in many nodes, such as a custom scene
|
|||
|
or control that can be reused. Instancing is helpful in a lot of cases, but
|
|||
|
sometimes it can be cumbersome, especially if you're using it in many
|
|||
|
projects. A good solution to this is to make a plugin that adds a node with a
|
|||
|
custom behavior.
|
|||
|
|
|||
|
.. warning::
|
|||
|
|
|||
|
Nodes added via an EditorPlugin are "CustomType" nodes. While they work
|
|||
|
with any scripting language, they have fewer features than
|
|||
|
:ref:`the Script Class system <doc_gdscript_basics_class_name>`. If you
|
|||
|
are writing GDScript or NativeScript, we recommend using Script Classes instead.
|
|||
|
|
|||
|
To create a new node type, you can use the function
|
|||
|
:ref:`add_custom_type() <class_EditorPlugin_method_add_custom_type>` from the
|
|||
|
:ref:`class_EditorPlugin` class. This function can add new types to the editor
|
|||
|
(nodes or resources). However, before you can create the type, you need a script
|
|||
|
that will act as the logic for the type. While that script doesn't have to use
|
|||
|
the ``tool`` keyword, it can be added so the script runs in the editor.
|
|||
|
|
|||
|
For this tutorial, we'll create a simple button that prints a message when
|
|||
|
clicked. For that, we'll need a simple script that extends from
|
|||
|
:ref:`class_Button`. It could also extend
|
|||
|
:ref:`class_BaseButton` if you prefer:
|
|||
|
|
|||
|
.. tabs::
|
|||
|
.. code-tab:: gdscript GDScript
|
|||
|
|
|||
|
tool
|
|||
|
extends Button
|
|||
|
|
|||
|
|
|||
|
func _enter_tree():
|
|||
|
connect("pressed", self, "clicked")
|
|||
|
|
|||
|
|
|||
|
func clicked():
|
|||
|
print("You clicked me!")
|
|||
|
|
|||
|
.. code-tab:: csharp
|
|||
|
|
|||
|
using Godot;
|
|||
|
using System;
|
|||
|
|
|||
|
[Tool]
|
|||
|
public class MyButton : Button
|
|||
|
{
|
|||
|
public override void _EnterTree()
|
|||
|
{
|
|||
|
Connect("pressed", this, "clicked");
|
|||
|
}
|
|||
|
|
|||
|
public void clicked()
|
|||
|
{
|
|||
|
GD.Print("You clicked me!");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
That's it for our basic button. You can save this as ``my_button.gd`` inside the
|
|||
|
plugin folder. You'll also need a 16×16 icon to show in the scene tree. If you
|
|||
|
don't have one, you can grab the default one from the engine and save it in your
|
|||
|
`addons/my_custom_node` folder as `icon.png`, or use the default Godot logo
|
|||
|
(`preload("res://icon.png")`). You can also use SVG icons if desired.
|
|||
|
|
|||
|
.. image:: img/making_plugins-custom_node_icon.png
|
|||
|
|
|||
|
Now, we need to add it as a custom type so it shows on the **Create New Node**
|
|||
|
dialog. For that, change the ``custom_node.gd`` script to the following:
|
|||
|
|
|||
|
.. tabs::
|
|||
|
.. code-tab:: gdscript GDScript
|
|||
|
|
|||
|
tool
|
|||
|
extends EditorPlugin
|
|||
|
|
|||
|
|
|||
|
func _enter_tree():
|
|||
|
# Initialization of the plugin goes here.
|
|||
|
# Add the new type with a name, a parent type, a script and an icon.
|
|||
|
add_custom_type("MyButton", "Button", preload("my_button.gd"), preload("icon.png"))
|
|||
|
|
|||
|
|
|||
|
func _exit_tree():
|
|||
|
# Clean-up of the plugin goes here.
|
|||
|
# Always remember to remove it from the engine when deactivated.
|
|||
|
remove_custom_type("MyButton")
|
|||
|
|
|||
|
.. code-tab:: csharp
|
|||
|
|
|||
|
#if TOOLS
|
|||
|
using Godot;
|
|||
|
using System;
|
|||
|
|
|||
|
[Tool]
|
|||
|
public class CustomNode : EditorPlugin
|
|||
|
{
|
|||
|
public override void _EnterTree()
|
|||
|
{
|
|||
|
// Initialization of the plugin goes here.
|
|||
|
// Add the new type with a name, a parent type, a script and an icon.
|
|||
|
var script = GD.Load<Script>("MyButton.cs");
|
|||
|
var texture = GD.Load<Texture>("icon.png");
|
|||
|
AddCustomType("MyButton", "Button", script, texture);
|
|||
|
}
|
|||
|
|
|||
|
public override void _ExitTree()
|
|||
|
{
|
|||
|
// Clean-up of the plugin goes here.
|
|||
|
// Always remember to remove it from the engine when deactivated.
|
|||
|
RemoveCustomType("MyButton");
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
With that done, the plugin should already be available in the plugin list in the
|
|||
|
**Project Settings**, so activate it as explained in `Checking the results`_.
|
|||
|
|
|||
|
Then try it out by adding your new node:
|
|||
|
|
|||
|
.. image:: img/making_plugins-custom_node_create.png
|
|||
|
|
|||
|
When you add the node, you can see that it already has the script you created
|
|||
|
attached to it. Set a text to the button, save and run the scene. When you
|
|||
|
click the button, you can see some text in the console:
|
|||
|
|
|||
|
.. image:: img/making_plugins-custom_node_console.png
|
|||
|
|
|||
|
A custom dock
|
|||
|
^^^^^^^^^^^^^
|
|||
|
|
|||
|
Sometimes, you need to extend the editor and add tools that are always available.
|
|||
|
An easy way to do it is to add a new dock with a plugin. Docks are just scenes
|
|||
|
based on Control, so they are created in a way similar to usual GUI scenes.
|
|||
|
|
|||
|
Creating a custom dock is done just like a custom node. Create a new
|
|||
|
``plugin.cfg`` file in the ``addons/my_custom_dock`` folder, then
|
|||
|
add the following content to it:
|
|||
|
|
|||
|
.. tabs::
|
|||
|
.. code-tab:: gdscript GDScript
|
|||
|
|
|||
|
[plugin]
|
|||
|
|
|||
|
name="My Custom Dock"
|
|||
|
description="A custom dock made so I can learn how to make plugins."
|
|||
|
author="Your Name Here"
|
|||
|
version="1.0"
|
|||
|
script="custom_dock.gd"
|
|||
|
|
|||
|
.. code-tab:: csharp
|
|||
|
|
|||
|
[plugin]
|
|||
|
|
|||
|
name="My Custom Dock"
|
|||
|
description="A custom dock made so I can learn how to make plugins."
|
|||
|
author="Your Name Here"
|
|||
|
version="1.0"
|
|||
|
script="CustomDock.cs"
|
|||
|
|
|||
|
Then create the script ``custom_dock.gd`` in the same folder. Fill it with the
|
|||
|
:ref:`template we've seen before <doc_making_plugins_template_code>` to get a
|
|||
|
good start.
|
|||
|
|
|||
|
Since we're trying to add a new custom dock, we need to create the contents of
|
|||
|
the dock. This is nothing more than a standard Godot scene: just create
|
|||
|
a new scene in the editor then edit it.
|
|||
|
|
|||
|
For an editor dock, the root node **must** be a :ref:`Control <class_Control>`
|
|||
|
or one of its child classes. For this tutorial, you can create a single button.
|
|||
|
The name of the root node will also be the name that appears on the dock tab,
|
|||
|
so be sure to give it a short and descriptive name.
|
|||
|
Also, don't forget to add some text to your button.
|
|||
|
|
|||
|
.. image:: img/making_plugins-my_custom_dock_scene.png
|
|||
|
|
|||
|
Save this scene as ``my_dock.tscn``. Now, we need to grab the scene we created
|
|||
|
then add it as a dock in the editor. For this, you can rely on the function
|
|||
|
:ref:`add_control_to_dock() <class_EditorPlugin_method_add_control_to_dock>` from the
|
|||
|
:ref:`EditorPlugin <class_EditorPlugin>` class.
|
|||
|
|
|||
|
You need to select a dock position and define the control to add
|
|||
|
(which is the scene you just created). Don't forget to
|
|||
|
**remove the dock** when the plugin is deactivated.
|
|||
|
The script could look like this:
|
|||
|
|
|||
|
.. tabs::
|
|||
|
.. code-tab:: gdscript GDScript
|
|||
|
|
|||
|
tool
|
|||
|
extends EditorPlugin
|
|||
|
|
|||
|
|
|||
|
# A class member to hold the dock during the plugin life cycle.
|
|||
|
var dock
|
|||
|
|
|||
|
|
|||
|
func _enter_tree():
|
|||
|
# Initialization of the plugin goes here.
|
|||
|
# Load the dock scene and instance it.
|
|||
|
dock = preload("res://addons/my_custom_dock/my_dock.tscn").instance()
|
|||
|
|
|||
|
# Add the loaded scene to the docks.
|
|||
|
add_control_to_dock(DOCK_SLOT_LEFT_UL, dock)
|
|||
|
# Note that LEFT_UL means the left of the editor, upper-left dock.
|
|||
|
|
|||
|
|
|||
|
func _exit_tree():
|
|||
|
# Clean-up of the plugin goes here.
|
|||
|
# Remove the dock.
|
|||
|
remove_control_from_docks(dock)
|
|||
|
# Erase the control from the memory.
|
|||
|
dock.free()
|
|||
|
|
|||
|
.. code-tab:: csharp
|
|||
|
|
|||
|
#if TOOLS
|
|||
|
using Godot;
|
|||
|
using System;
|
|||
|
|
|||
|
[Tool]
|
|||
|
public class CustomDock : EditorPlugin
|
|||
|
{
|
|||
|
Control dock;
|
|||
|
|
|||
|
public override void _EnterTree()
|
|||
|
{
|
|||
|
dock = (Control)GD.Load<PackedScene>("addons/my_custom_dock/my_dock.tscn").Instance();
|
|||
|
AddControlToDock(DockSlot.LeftUl, dock);
|
|||
|
}
|
|||
|
|
|||
|
public override void _ExitTree()
|
|||
|
{
|
|||
|
// Clean-up of the plugin goes here.
|
|||
|
// Remove the dock.
|
|||
|
RemoveControlFromDocks(dock);
|
|||
|
// Erase the control from the memory.
|
|||
|
dock.Free();
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
Note that, while the dock will initially appear at its specified position,
|
|||
|
the user can freely change its position and save the resulting layout.
|
|||
|
|
|||
|
Checking the results
|
|||
|
^^^^^^^^^^^^^^^^^^^^
|
|||
|
|
|||
|
It's now time to check the results of your work. Open the **Project
|
|||
|
Settings** and click on the **Plugins** tab. Your plugin should be the only one
|
|||
|
on the list. If it is not showing, click on the **Update** button in the
|
|||
|
top-right corner.
|
|||
|
|
|||
|
.. image:: img/making_plugins-project_settings.png
|
|||
|
|
|||
|
You can see the plugin is inactive on the **Status** column; click on the status
|
|||
|
to select **Active**. The dock should become visible before you even close
|
|||
|
the settings window. You should now have a custom dock:
|
|||
|
|
|||
|
.. image:: img/making_plugins-custom_dock.png
|
|||
|
|
|||
|
Going beyond
|
|||
|
~~~~~~~~~~~~
|
|||
|
|
|||
|
Now that you've learned how to make basic plugins, you can extend the editor in
|
|||
|
several ways. Lots of functionality can be added to the editor with GDScript;
|
|||
|
it is a powerful way to create specialized editors without having to delve into
|
|||
|
C++ modules.
|
|||
|
|
|||
|
You can make your own plugins to help yourself and share them in the
|
|||
|
`Asset Library <https://godotengine.org/asset-library/>`_ so that people
|
|||
|
can benefit from your work.
|
|||
|
|
|||
|
.. _doc_making_plugins_autoload:
|
|||
|
|
|||
|
Registering autoloads/singletons in plugins
|
|||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|||
|
It is possible for editor plugins to automatically register
|
|||
|
:ref:`autoloads <doc_singletons_autoload>` when the plugin is enabled.
|
|||
|
This also includes unregistering the autoload when the plugin is disabled.
|
|||
|
|
|||
|
This makes setting up plugins faster for users, as they no longer have to manually
|
|||
|
add autoloads to their project settings if your editor plugin requires the use of
|
|||
|
an autoload.
|
|||
|
|
|||
|
Use the following code to register a singleton from an editor plugin:
|
|||
|
|
|||
|
::
|
|||
|
|
|||
|
tool
|
|||
|
extends EditorPlugin
|
|||
|
|
|||
|
# Replace this value with a PascalCase autoload name, as per the GDScript style guide.
|
|||
|
const AUTOLOAD_NAME = "SomeAutoload"
|
|||
|
|
|||
|
|
|||
|
func _enter_tree():
|
|||
|
# The autoload can be a scene or script file.
|
|||
|
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/my_addon/some_autoload.tscn")
|
|||
|
|
|||
|
|
|||
|
func _exit_tree():
|
|||
|
remove_autoload_singleton(AUTOLOAD_NAME)
|