Restructuring
@@ -1,32 +0,0 @@
|
||||
.. _doc_consoles:
|
||||
|
||||
Console Support in Godot
|
||||
========================
|
||||
|
||||
Official Support
|
||||
----------------
|
||||
|
||||
Godot does not officially support consoles (save for XBox One via UWP), and
|
||||
this situation will most likely never change.
|
||||
|
||||
The reasons for this are:
|
||||
|
||||
* To develop for consoles, one must be licensed as a company. Godot, as an open source project, does not have such legal figure.
|
||||
* Console SDKs are secret, and protected by non-disclosure agreements. Even if we could get access to them, we could not publish the code as open-source.
|
||||
* Consoles require specialized hardware to develop for, so regular individuals can't create games for them anyway.
|
||||
|
||||
This, however, does not mean you can't port your games to console. It's
|
||||
quite the contrary.
|
||||
|
||||
Third-Party Support
|
||||
--------------------
|
||||
|
||||
Console ports of Godot are offered via third party companies (which have
|
||||
ported Godot on their own) and offer porting and publishing services of
|
||||
your games to consoles.
|
||||
|
||||
Following is the list of providers:
|
||||
|
||||
* .. _a Lone Wolf Technology: http://www.lonewolftechnology.com/ offers
|
||||
Switch, PS4 and XBox One porting and publishing of Godot games.
|
||||
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 143 B |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,420 +0,0 @@
|
||||
.. _doc_import_plugins:
|
||||
|
||||
Import plugins
|
||||
==============
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
An import plugin is a special type of editor tool that allows custom resources
|
||||
to be imported by Godot and be treated as first-class resources. The editor
|
||||
itself comes bundled with a lot of import plugins to handle the common resources
|
||||
like PNG images, Collada and glTF models, OGG Vorbis sounds, and many more.
|
||||
|
||||
This tutorial will show you how to create a simple import plugin to load a
|
||||
custom text file as a material resource. This text file will contain three
|
||||
numeric values separated by comma, which represents the three channels of a
|
||||
color, and the resulting color will be used as the albedo (main color) of the
|
||||
imported material.
|
||||
|
||||
.. note:: This tutorial assumes you already know how to make generic plugins. If
|
||||
in doubt, refer to the :ref:`doc_making_plugins` page. This also
|
||||
assumes you are acquainted with Godot's import system.
|
||||
|
||||
The sample file to import contains only a line representing the pure blue color
|
||||
(zero red, zero green, and full blue):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
0,0,255
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
First we need a generic plugin that will handle the initialization and
|
||||
destruction of our import plugin. Let's add the ``plugin.cfg`` file first:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[plugin]
|
||||
|
||||
name="Silly Material Importer"
|
||||
description="Imports a 3D Material from an external text file."
|
||||
author="Yours Truly"
|
||||
version="1.0"
|
||||
script="material_import.gd"
|
||||
|
||||
Then we need the ``material_import.gd`` file to add and remove the import plugin
|
||||
when needed:
|
||||
|
||||
::
|
||||
|
||||
# material_import.gd
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
var import_plugin
|
||||
|
||||
func _enter_tree():
|
||||
import_plugin = preload("import_plugin.gd").new()
|
||||
add_import_plugin(import_plugin)
|
||||
|
||||
func _exit_tree():
|
||||
remove_import_plugin(import_plugin)
|
||||
import_plugin = null
|
||||
|
||||
When this plugin is activated, it will create a new instance of the import
|
||||
plugin (which we'll soon make) and add it to the editor using the
|
||||
:ref:`add_import_plugin<class_EditorPlugin_add_import_plugin>` method. We store
|
||||
a reference to it in a class member ``import_plugin`` so we can refer to it
|
||||
later when removing it. The
|
||||
:ref:`remove_import_plugin<class_EditorPlugin_remove_import_plugin>` method is
|
||||
called when the plugin is deactivated to clean up the memory and let the editor
|
||||
know the import plugin isn't available anymore.
|
||||
|
||||
Note that the import plugin is a reference type so it doesn't need to be
|
||||
explicitly released from the memory with the ``free()`` function. It will be
|
||||
released automatically by the engine when it goes out of scope.
|
||||
|
||||
The EditorImportPlugin class
|
||||
----------------------------
|
||||
|
||||
The main character of the show is the
|
||||
:ref:`EditorImportPlugin class<class_EditorImportPlugin>`. It is responsible to
|
||||
implement the methods that are called by Godot when it needs to know how to deal
|
||||
with files.
|
||||
|
||||
Let's begin to code our plugin, one method at time:
|
||||
|
||||
::
|
||||
|
||||
# import_plugin.gd
|
||||
tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
func get_importer_name():
|
||||
return "demos.sillymaterial"
|
||||
|
||||
The first method is the
|
||||
:ref:`get_importer_name<class_EditorImportPlugin_get_importer_name>`. This is a
|
||||
unique name to your plugin that is used by Godot to know which import was used
|
||||
in a certain file. When the files needs to be reimported, the editor will know
|
||||
which plugin to call.
|
||||
|
||||
::
|
||||
|
||||
func get_visible_name():
|
||||
return "Silly Material"
|
||||
|
||||
The :ref:`get_visible_name<class_EditorImportPlugin_get_visible_name>` method is
|
||||
responsible to inform the name of the type it imports and will be shown to the
|
||||
user in the Import dock.
|
||||
|
||||
You should choose this name as a continuation to "Import as". Eg. *"Import as
|
||||
Silly Material"*. Yes, this one is a bit silly, but you certainly can come up
|
||||
with a descriptive name for your plugin.
|
||||
|
||||
::
|
||||
|
||||
func get_recognized_extensions():
|
||||
return ["mtxt"]
|
||||
|
||||
Godot's import system detects file types by their extension. In the
|
||||
:ref:`get_recognized_extensions<class_EditorImportPlugin_get_recognized_extensions>`
|
||||
method you return an array of strings to represent each extension that this
|
||||
plugin can understand. If an extension is recognized by more than one plugin,
|
||||
the user can select which one to use when importing the files.
|
||||
|
||||
.. tip:: Common extensions like ``.json`` and ``.txt`` might be used by many
|
||||
plugins. Also, there could be files in the project that are just data
|
||||
for the game and should not be imported. You have to be careful when
|
||||
importing to validate the data. Never expect the file to be well-formed.
|
||||
|
||||
::
|
||||
|
||||
func get_save_extension():
|
||||
return "material"
|
||||
|
||||
The imported files are saved in the ``.import`` folder at the project's root.
|
||||
Their extension should match the type of resource you are importing, but since
|
||||
Godot can't tell what you'll use (because there might be multiple valid
|
||||
extensions for the same resource), you need to inform what will be the used in
|
||||
the import.
|
||||
|
||||
Since we're importing a Material, we'll use the special extension for such
|
||||
resource types. If you are importing a scene, you can use ``scn``. Generic
|
||||
resources can use the ``res`` extension. However, this is not enforced in any
|
||||
way by the engine.
|
||||
|
||||
::
|
||||
|
||||
func get_resource_type():
|
||||
return "SpatialMaterial"
|
||||
|
||||
The imported resource has a specific type, so the editor can know which property
|
||||
slot it belongs to. This allows drag and drop from the FileSystem dock to a
|
||||
property in the Inspector.
|
||||
|
||||
In our case it's a :ref:`class_SpatialMaterial`, which can be applied to 3D
|
||||
objects.
|
||||
|
||||
.. note:: If you need to import different types from the same extension, you
|
||||
have to create multiple import plugins. You can abstract the import
|
||||
code on another file to avoid duplication in this regard.
|
||||
|
||||
Options and presets
|
||||
-------------------
|
||||
|
||||
Your plugin can provide different options to allow the user to control how the
|
||||
resource will be imported. If a set of selected options is common, you can also
|
||||
create different presets to make it easier for the user. The following image
|
||||
shows how the options will appear in the editor:
|
||||
|
||||
.. image:: img/import_plugin_options.png
|
||||
|
||||
Since there might be many presets and they are identified with a number, it's a
|
||||
good practice to use an enum so you can refer to them using names.
|
||||
|
||||
::
|
||||
|
||||
tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
enum Presets { PRESET_DEFAULT }
|
||||
|
||||
...
|
||||
|
||||
Now that the enum is defined, let's keep looking at the methods of an import
|
||||
plugin:
|
||||
|
||||
::
|
||||
|
||||
func get_preset_count():
|
||||
return Presets.size()
|
||||
|
||||
The :ref:`get_preset_count<class_EditorImportPlugin_get_preset_count>` method
|
||||
returns the amount of presets that this plugins defines. We only have one preset
|
||||
now, but we can make this method future-proof by returning the size of our
|
||||
``Presets`` enumeration.
|
||||
|
||||
::
|
||||
|
||||
func get_preset_name(preset):
|
||||
match preset:
|
||||
PRESET_DEFAULT: return "Default"
|
||||
_ : return "Unknown"
|
||||
|
||||
|
||||
Here we have the
|
||||
:ref:`get_preset_name<class_EditorImportPlugin_get_preset_name>` method, which
|
||||
gives names to the presets as they will be presented to the user, so be sure to
|
||||
use short and clear names.
|
||||
|
||||
We can use the ``match`` statement here to make the code more structured. This
|
||||
way it's easy to add new presets in the future. We use the catch all pattern to
|
||||
return something too. Although Godot won't ask for presets beyond the preset
|
||||
count you defined, it's always better to be on the safe side.
|
||||
|
||||
If you have only one preset you could simply return its name directly, but if
|
||||
you do this you have to be careful when you add more presets.
|
||||
|
||||
::
|
||||
|
||||
func get_import_options(preset):
|
||||
match preset:
|
||||
PRESET_DEFAULT:
|
||||
return [{
|
||||
"name": "use_red_anyway",
|
||||
"default_value": false
|
||||
}]
|
||||
_: return []
|
||||
|
||||
This is the method which defines the available options.
|
||||
:ref:`get_import_options<class_EditorImportPlugin_get_import_options>` returns
|
||||
an array of dictionaries, and each dictionary contains a few keys that are
|
||||
checked to customize the option as its shown to the user. The following table
|
||||
shows the possible keys:
|
||||
|
||||
+-------------------+------------+---------------------------------------------------------------------------------------------------------+
|
||||
| Key | Type | Description |
|
||||
+===================+============+=========================================================================================================+
|
||||
| ``name`` | String | The name of the option. When showed, underscores become spaces and first letters are capitalized. |
|
||||
+-------------------+------------+---------------------------------------------------------------------------------------------------------+
|
||||
| ``default_value`` | Any | The default value of the option for this preset. |
|
||||
+-------------------+------------+---------------------------------------------------------------------------------------------------------+
|
||||
| ``property_hint`` | Enum value | One of the :ref:`PropertyHint<enum_@GlobalScope_PropertyHint>` values to use as hint. |
|
||||
+-------------------+------------+---------------------------------------------------------------------------------------------------------+
|
||||
| ``hint_string`` | String | The hint text of the property. The same as you'd add in the ``export`` statement in GDScript. |
|
||||
+-------------------+------------+---------------------------------------------------------------------------------------------------------+
|
||||
| ``usage`` | Enum value | One of the :ref:`PropertyUsageFlags<enum_@GlobalScope_PropertyUsageFlags>` values to define the usage. |
|
||||
+-------------------+------------+---------------------------------------------------------------------------------------------------------+
|
||||
|
||||
The ``name`` and ``default_value`` keys are **mandatory**, the rest are optional.
|
||||
|
||||
Note that the ``get_import_options`` method receives the preset number, so you
|
||||
can configure the options for each different preset (especially the default
|
||||
value). In this example we use the ``match`` statement, but if you have lots of
|
||||
options and the presets only change the value you may want to create the array
|
||||
of options first and then just change it based on the preset.
|
||||
|
||||
.. warning:: The ``get_import_options`` method is called even if you don't
|
||||
define presets (by making ``get_preset_count`` return zero). You
|
||||
have to return an array even it's empty, otherwise you can get
|
||||
errors.
|
||||
|
||||
::
|
||||
|
||||
func get_option_visibility(option, options):
|
||||
return true
|
||||
|
||||
For the
|
||||
:ref:`get_option_visibility<class_EditorImportPlugin_get_option_visibility>`
|
||||
method, we simply return ``true`` because all of our options (i.e. the single
|
||||
one we defined) are visible all the time.
|
||||
|
||||
If you need to make certain option visible only if another is set with a certain
|
||||
value, you can add the logic in this method.
|
||||
|
||||
The ``import`` method
|
||||
---------------------
|
||||
|
||||
The heavy part of the process, responsible for the converting the files into
|
||||
resources, is covered by the :ref:`import<class_EditorImportPlugin_import>`
|
||||
method. Our sample code is a bit long, so let's split in a few parts:
|
||||
|
||||
::
|
||||
|
||||
func import(source_file, save_path, options, r_platform_variants, r_gen_files):
|
||||
var file = File.new()
|
||||
var err = file.open(source_file, File.READ)
|
||||
if (err != OK):
|
||||
return err
|
||||
|
||||
var line = file.get_line()
|
||||
|
||||
file.close()
|
||||
|
||||
The first part of our import method opens and reads the source file. We use the
|
||||
:ref:`File<class_File>` class to do that, passing the ``source_file``
|
||||
parameter which is provided by the editor.
|
||||
|
||||
If there's an error when opening the file, we return it to let the editor know
|
||||
that the import wasn't successful.
|
||||
|
||||
::
|
||||
|
||||
var channels = line.split(",")
|
||||
if channels.size() != 3:
|
||||
return ERR_PARSE_ERROR
|
||||
|
||||
var color
|
||||
if options.use_red_anyway:
|
||||
color = Color8(255, 0, 0)
|
||||
else:
|
||||
color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))
|
||||
|
||||
This code takes the line of the file it read before and splits it in pieces
|
||||
that are separated by a comma. If there are more or less than the three values,
|
||||
it considers the file invalid and reports an error.
|
||||
|
||||
Then it creates a new :ref:`Color<class_Color>` variable and sets its values
|
||||
according to the input file. If the ``use_red_anyway`` option is enabled, then
|
||||
it sets the color as a pure red instead.
|
||||
|
||||
::
|
||||
|
||||
var material = SpatialMaterial.new()
|
||||
material.albedo_color = color
|
||||
|
||||
This part makes a new :ref:`SpatialMaterial<class_SpatialMaterial>` that is the
|
||||
imported resource. We create a new instance of it and then set its albedo color
|
||||
as the value we got before.
|
||||
|
||||
::
|
||||
|
||||
return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], material)
|
||||
|
||||
This is the last part and quite an important one, because here we save the made
|
||||
resource to the disk. The path of the saved file is generated and informed by
|
||||
the editor via the ``save_path`` parameter. Note that this comes **without** the
|
||||
extension, so we add it using :ref:`string formatting<doc_gdscript_printf>`. For
|
||||
this we call the ``get_save_extension`` method that we defined earlier, so we
|
||||
can be sure that they won't get out of sync.
|
||||
|
||||
We also return the result from the
|
||||
:ref:`ResourceSaver.save<class_ResourceSaver_save>` method, so if there's an
|
||||
error in this step, the editor will know about it.
|
||||
|
||||
Platform variants and generated files
|
||||
-------------------------------------
|
||||
|
||||
You may have noticed that our plugin ignored two arguments of the ``import``
|
||||
method. Those are *return arguments* (hence the ``r`` at the beginning of their
|
||||
name), which means that the editor will read from them after calling your import
|
||||
method. Both of them are arrays that you can fill with information.
|
||||
|
||||
The ``r_platform_variants`` argument is used if you need to import the resource
|
||||
differently depending on the target platform. While it's called *platform*
|
||||
variants, it is based on the presence of :ref:`feature tags<doc_feature_tags>`,
|
||||
so even the same platform can have multiple variants depending on the setup.
|
||||
|
||||
To import a platform variant, you need to save it with the feature tag before
|
||||
the extension, and then push the tag to the ``r_platform_variants`` array so the
|
||||
editor can know that you did.
|
||||
|
||||
For an example, let's say we save a different material for mobile platform. We
|
||||
would need to do something like the following:
|
||||
|
||||
::
|
||||
|
||||
r_platform_variants.push_back("mobile")
|
||||
return ResourceSaver.save("%s.%s.%s" % [save_path, "mobile", get_save_extension()], mobile_material)
|
||||
|
||||
The ``r_gen_files`` argument is meant for extra files that are generated during
|
||||
your import process and need to be kept. The editor will look at it to
|
||||
understand the dependencies and make sure the extra file is not inadvertently
|
||||
deleted.
|
||||
|
||||
This is also an array and should be filled with full paths of the files you
|
||||
save. As an example, let's create another material for the next pass and save it
|
||||
in a different file:
|
||||
|
||||
::
|
||||
|
||||
var next_pass = SpatialMaterial.new()
|
||||
next_pass.albedo_color = color.inverted()
|
||||
var next_pass_path = "%s.next_pass.%s" % [save_path, get_save_extension()]
|
||||
|
||||
err = ResourceSaver.save(next_pass_path, next_pass)
|
||||
if err != OK:
|
||||
return err
|
||||
r_gen_files.push_back(next_pass_path)
|
||||
|
||||
Trying the plugin
|
||||
-----------------
|
||||
|
||||
This has been very theoretical, but now that the import plugin is done, let's
|
||||
test it. Make sure you created the sample file (with the contents described in
|
||||
the introduction section) and save it as ``test.mtxt``. Then activate the plugin
|
||||
in the Project Settings.
|
||||
|
||||
If everything goes well, the import plugin is added to the editor and the file
|
||||
system is scanned, making the custom resource appear on the FileSystem dock. If
|
||||
you select it and focus the Import dock, you can see the only option to select
|
||||
there.
|
||||
|
||||
Create a MeshInstance node in the scene and for it's Mesh property set up a new
|
||||
SphereMesh. Unfold the Material section in the Inspector and then drag the file
|
||||
from the FileSystem dock to the material property. The object will update in the
|
||||
viewport with the blue color of the imported material.
|
||||
|
||||
.. image:: img/import_plugin_trying.png
|
||||
|
||||
Go to Import dock, enable the "Use Red Anyway" option, and click on "Reimport".
|
||||
This will update the imported material and should automatically update the view
|
||||
showing the red color instead.
|
||||
|
||||
And that's it! Your first import plugin is done! Now get creative and make
|
||||
plugins for your own beloved formats. This can be quite useful to write your
|
||||
data in a custom format and then use it in Godot as if they were native
|
||||
resources. This shows how the import system is powerful and extendable.
|
||||
@@ -1,9 +0,0 @@
|
||||
Editor plugins
|
||||
==============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: toc-devel-plugins
|
||||
|
||||
making_plugins
|
||||
import_plugins
|
||||
@@ -1,249 +0,0 @@
|
||||
.. _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 not as powerful, there's still a lot of things you can
|
||||
do with them. Note that a plugin is not different from any scene you already
|
||||
can make, except that it is made via script to add 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 base to develop and test the plugins.
|
||||
|
||||
The first thing you need to do is to create a new plugin that the editor can
|
||||
understand as such. For that you need two files: ``plugin.cfg`` for the
|
||||
configuration and a custom GDScript with the functionality.
|
||||
|
||||
Plugins have a standard path like ``addons/plugin_name`` inside the project
|
||||
folder. So create the folder ``my_custom_node`` inside ``addons``. So you'll
|
||||
have a directory structure like this:
|
||||
|
||||
.. image:: img/making_plugins-my_custom_mode_folder.png
|
||||
|
||||
To make the ``plugin.cfg`` file, open your favorite text editor with a blank
|
||||
file. Godot is not able (yet) to open text files besides scripts, so this must
|
||||
be done in an external editor. Add the following structure to your
|
||||
``plugin.cfg``::
|
||||
|
||||
[plugin]
|
||||
|
||||
name="My Custom Node"
|
||||
description="A custom node made to extend the Godot Engine."
|
||||
author="Your Name Here"
|
||||
version="1.0"
|
||||
script="custom_node.gd"
|
||||
|
||||
This is a simple ``ini`` file with metadata about your plugin. You need to set
|
||||
up the name and description so users can understand what it does. Add your
|
||||
own name so you can be properly credited. A version number is useful so users can see if
|
||||
they have an outdated version (if you are unsure on how to come up with
|
||||
the version number, check `SemVer <http://semver.org/>`_). And finally a main
|
||||
script file to load when your plugin is active.
|
||||
|
||||
The script file
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Open the script editor (F3) and create a new GDScript file called
|
||||
``custom_node.gd`` inside the ``my_custom_node`` folder. This script is special
|
||||
and it has two requirements: it must be a ``tool`` script and it has to
|
||||
inherit from :ref:`class_EditorPlugin`.
|
||||
|
||||
It's important to deal with initialization and clean-up of resources. So a good
|
||||
practice is to use the virtual function
|
||||
:ref:`_enter_tree() <class_Node__enter_tree>` to initialize your plugin and
|
||||
:ref:`_exit_tree() <class_Node__exit_tree>` to clean it up. You can delete the
|
||||
default GDScript template from your file and replace it with the following
|
||||
structure:
|
||||
|
||||
.. _doc_making_plugins_template_code:
|
||||
.. code-block:: python
|
||||
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
# Initialization of the plugin goes here
|
||||
pass
|
||||
|
||||
func _exit_tree():
|
||||
# Clean-up of the plugin goes here
|
||||
pass
|
||||
|
||||
This is a good template to use when devising new plugins.
|
||||
|
||||
A custom node
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes you want a certain behavior in many nodes. Maybe 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 between many
|
||||
projects. A good solution to this is to make a plugin that adds a node with a
|
||||
custom behavior.
|
||||
|
||||
To create a new node type, you can avail of the function
|
||||
:ref:`add_custom_type() <class_EditorPlugin_add_custom_type>` from the
|
||||
:ref:`class_EditorPlugin` class. This function can add new types to the editor,
|
||||
be it nodes or resources. But before you can create the type you need a script
|
||||
that will act as the logic for the type. While such script doesn't need to have
|
||||
the ``tool`` keyword, it is interesting to use it so the user can see it acting
|
||||
on the editor.
|
||||
|
||||
For this tutorial, we'll create a simple button that prints a message when
|
||||
clicked. And for that we'll need a simple script that extends from
|
||||
:ref:`class_Button`. It could also extend
|
||||
:ref:`class_BaseButton` if you prefer::
|
||||
|
||||
tool
|
||||
extends Button
|
||||
|
||||
func _enter_tree():
|
||||
connect("pressed", self, "clicked")
|
||||
|
||||
func clicked():
|
||||
print("You clicked me!")
|
||||
|
||||
That's it for our basic button. You can save this as ``button.gd`` inside the
|
||||
plugin folder. You'll also need a 16x16 icon to show in the scene tree. If you
|
||||
don't have one, you can grab the default one from the engine:
|
||||
|
||||
.. 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::
|
||||
|
||||
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("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")
|
||||
|
||||
With that done, the plugin should already be available in the plugin list at
|
||||
Project Settings. So activate it and try to add a new node to see the result:
|
||||
|
||||
.. image:: img/making_plugins-custom_node_create.png
|
||||
|
||||
When you add the node, you can see that it already have 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 a text in the console:
|
||||
|
||||
.. image:: img/making_plugins-custom_node_console.png
|
||||
|
||||
|
||||
A custom dock
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Maybe 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 how to create them is not far beyond your knowledge.
|
||||
|
||||
The way to start this plugin is similar to the custom node. So create a new
|
||||
``plugin.cfg`` file in the ``addons/my_custom_dock`` folder. And then with
|
||||
your favorite text editor add the following content to it::
|
||||
|
||||
[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"
|
||||
|
||||
Then create the script ``custom_dock.gd`` in the same folder. Fill 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
|
||||
such dock. This is nothing more than a standard Godot scene. So you can create
|
||||
a new scene in the editor and start creating it.
|
||||
|
||||
For an editor dock, it is mandatory that the root of the scene is a
|
||||
:ref:`Control <class_Control>` or one of its child classes. For this tutorial,
|
||||
you can make a single button. The name of the root node will also be the name
|
||||
that appears on the dock tab, so be sure to put a descriptive but short one.
|
||||
Don't forget to add a text to your button.
|
||||
|
||||
.. image:: img/making_plugins-my_custom_dock_scene.png
|
||||
|
||||
Save this scene as ``my_dock.tscn``.
|
||||
|
||||
Now you need to grab that scene you just created and add it as a dock in the
|
||||
editor. For this you can rely on the function
|
||||
:ref:`add_control_to_dock() <class_EditorPlugin_add_control_to_dock>` from the
|
||||
:ref:`EditorPlugin <class_EditorPlugin>` class.
|
||||
|
||||
The code is very straightforward, you just need to select a dock position to
|
||||
add it and have a control to add (which is the scene you just created). It is
|
||||
also very important that you remember to **remove the dock** when the plugin is
|
||||
deactivated. The code can be like this::
|
||||
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
var dock # A class member to hold the dock during the plugin lifecycle
|
||||
|
||||
func _enter_tree():
|
||||
# Initialization of the plugin goes here
|
||||
# First 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 scene from the docks:
|
||||
remove_control_from_docks( dock ) # Remove the dock
|
||||
dock.free() # Erase the control from the memory
|
||||
|
||||
While the dock position is chosen when adding it, the user is free to move it
|
||||
and save the layout with the dock in any position.
|
||||
|
||||
Checking the results
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Now it is the moment to check the results of your work. Open the *Project
|
||||
Settings* and click on the *Plugins* tab. Your plugin should be the only on
|
||||
the list. If it is not showing, click on the *Update* button at the top right
|
||||
corner.
|
||||
|
||||
.. image:: img/making_plugins-project_settings.png
|
||||
|
||||
At the *Status* column, you can see that the plugin is inactive. So you just
|
||||
need to click on the status to select *Active*. The dock should be immediately
|
||||
visible, even before you close the settings window. And now, lo and behold, you
|
||||
have a custom dock! In just a bit of coding and a simple scene.
|
||||
|
||||
.. image:: img/making_plugins-custom_dock.png
|
||||
|
||||
Going beyond
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Now that you learned how to make basic plugins, you can extend the editor in
|
||||
many nice ways. Many functions can be added to editor on the fly with GDScript,
|
||||
it is a powerful way to create special editors without having to delve into C++
|
||||
modules.
|
||||
|
||||
You can make your own plugins to help you and also share them in Godot's Asset
|
||||
Library so many people can benefit of your work.
|
||||