mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-03 05:48:42 +03:00
Improve Inspector plugin tutorial with a better example
This commit is contained in:
BIN
tutorials/plugins/editor/img/inspector_plugin_example.png
Normal file
BIN
tutorials/plugins/editor/img/inspector_plugin_example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -4,53 +4,79 @@ Inspector plugins
|
||||
=================
|
||||
|
||||
The inspector dock supports custom plugins to create your own widgets for
|
||||
editing properties. This tutorial explains how to use the
|
||||
:ref:`class_EditorInspectorPlugin` and :ref:`class_EditorProperty` classes to
|
||||
write such plugins with the example of creating a custom value editor.
|
||||
editing properties. This can be beneficial when working with custom datatypes
|
||||
and resources, but may be used to change the default behavior for built-in
|
||||
types as well. It is possible to introduce custom controls for specific properties,
|
||||
entire objects, and even detached controls associated with particular datatypes.
|
||||
|
||||
This tutorial explains how to use the :ref:`class_EditorInspectorPlugin`
|
||||
and :ref:`class_EditorProperty` classes to create a custom control for each
|
||||
property of the integer type, replacing the default behavior with a button
|
||||
that generates random values between numbers 0 and 99.
|
||||
|
||||
.. figure:: img/inspector_plugin_example.png
|
||||
:align: center
|
||||
|
||||
The default behavior on the left, and the end result on the right.
|
||||
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
Just like :ref:`doc_making_plugins`, we start out by making a new plugin,
|
||||
getting a ``plugin.cfg`` file created, and start with our
|
||||
:ref:`class_EditorPlugin`. However, instead of using
|
||||
``add_custom_node`` or ``add_control_to_dock`` we'll use
|
||||
``add_inspector_plugin``.
|
||||
Follow the :ref:`doc_making_plugins` guide to setup the framework for your
|
||||
new plugin. Lets assume you've called your plugin folder ``my_inspector_plugin``.
|
||||
If so, you should end up with a new ``addons/my_inspector_plugin`` folder
|
||||
that contains two files: ``plugin.cfg`` and ``plugin.gd``.
|
||||
|
||||
As before, ``plugin.gd`` is a script extending :ref:`class_EditorPlugin` and you
|
||||
need to introduce new code for its ``_enter_tree`` and ``_exit_tree`` methods. To
|
||||
setup your inspector plugin you must load its script and then create and add
|
||||
the instance using ``add_inspector_plugin``. If the plugin is disabled you should
|
||||
remove the instance you have added using ``remove_inspector_plugin``.
|
||||
|
||||
.. note:: Take note, that here you are loading a script and not a packed scene.
|
||||
Therefore you should use ``new()`` instead of ``instance()``.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
# MyEditorPlugin.gd
|
||||
# plugin.gd
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
var plugin
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
# EditorInspectorPlugin is a resource, so we use `new()` instead of `instance()`.
|
||||
plugin = preload("res://addons/MyPlugin/MyInspectorPlugin.gd").new()
|
||||
plugin = preload("res://addons/my_inspector_plugin/MyInspectorPlugin.gd").new()
|
||||
add_inspector_plugin(plugin)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_inspector_plugin(plugin)
|
||||
|
||||
|
||||
EditorInspectorPlugin
|
||||
---------------------
|
||||
|
||||
To actually connect into the Inspector, we create a
|
||||
:ref:`class_EditorInspectorPlugin` class. This script provides the "hooks" to
|
||||
the inspector. Thanks to this class, the editor will call the functions within
|
||||
the EditorInspectorPlugin while it goes through the process of building the UI
|
||||
for the inspector. The script is used to check if we should enable ourselves for
|
||||
any :ref:`class_Object` that is currently in the inspector (including any
|
||||
:ref:`class_Resource` that is embedded!).
|
||||
To be able to interact with the inspector dock, your ``MyInspectorPlugin.gd`` script
|
||||
must extend the :ref:`class_EditorInspectorPlugin` class. This class provides
|
||||
several virtual methods that can be implemented to affect the way the inspector
|
||||
is handling properties.
|
||||
|
||||
Once enabled, EditorInspectorPlugin has methods that allow for adding
|
||||
:ref:`class_EditorProperty` nodes or just custom :ref:`class_Control` nodes to
|
||||
the beginning and end of the inspector for that :ref:`class_Object`, or for
|
||||
overriding or changing existing property editors.
|
||||
To have any effect at all the script must implement the ``can_handle()`` method. This
|
||||
function is called for each edited :ref:`class_Object` and must return ``true`` if
|
||||
this plugin should handle the object or its properties (including any :ref:`class_Resource`
|
||||
that is embedded!).
|
||||
|
||||
There are 4 other methods that can be implemented to add controls to the inspector at
|
||||
specific positions. The ``parse_begin()`` and ``parse_end()`` functions are called only once
|
||||
at the beginning and the end of parsing for each object, respectively. They can be used to
|
||||
add controls at the very top or very bottom of the inspector layout with ``add_custom_control``.
|
||||
|
||||
As the object is parsed the ``parse_category()`` and ``parse_property()`` functions are
|
||||
called. In addition to ``add_custom_control`` both ``add_property_editor`` and
|
||||
``add_property_editor_for_multiple_properties`` can be utilized. These methods are used
|
||||
specifically to add :ref:`class_EditorProperty`-based controls.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
@@ -58,71 +84,98 @@ overriding or changing existing property editors.
|
||||
# MyInspectorPlugin.gd
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
var RandomIntEditor = preload("res://addons/my_inspector_plugin/RandomIntEditor.gd")
|
||||
|
||||
|
||||
func can_handle(object):
|
||||
# Here you can specify which object types (classes) should be handled by
|
||||
# this plugin. For example if the plugin is specific to your player
|
||||
# class defined with `class_name MyPlayer`, you can do:
|
||||
# `return object is MyPlayer`
|
||||
# In this example we'll support all objects, so:
|
||||
# We will support all objects in this example.
|
||||
return true
|
||||
|
||||
|
||||
func parse_property(object, type, path, hint, hint_text, usage):
|
||||
# We will handle properties of type integer.
|
||||
if type == TYPE_INT:
|
||||
# Register *an instance* of the custom property editor that we'll define next.
|
||||
add_property_editor(path, MyIntEditor.new())
|
||||
# We return `true` to notify the inspector that we'll be handling
|
||||
# this integer property, so it doesn't need to parse other plugins
|
||||
# (including built-in ones) for an appropriate editor.
|
||||
# Create an instance of the custom property editor and register
|
||||
# it to a specific property path.
|
||||
add_property_editor(path, RandomIntEditor.new())
|
||||
# Inform the editor to remove the default property editor for
|
||||
# this property type.
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
EditorProperty
|
||||
--------------
|
||||
|
||||
Next, we define the actual :ref:`class_EditorProperty` custom value editor that
|
||||
we want instantiated to edit integers. This is a custom :ref:`class_Control` and
|
||||
we can add any kinds of additional nodes to make advanced widgets to embed in
|
||||
the inspector.
|
||||
The :ref:`class_EditorProperty` class is a special type of :ref:`class_Control` that
|
||||
can interact with edited objects inside of the inspector dock. By itself it doesn't
|
||||
display anything, but can house any other control nodes, including complex
|
||||
scenes.
|
||||
|
||||
There are three essential parts to the script extending :ref:`class_EditorProperty`:
|
||||
|
||||
1. There must be the ``_init`` method that sets up the node structure of the control.
|
||||
|
||||
2. The ``update_property()`` method should be implemented to handle changes to the
|
||||
data from the outside.
|
||||
|
||||
3. A signal must be emitted at some point to inform the inspector that the control has
|
||||
changed the property using ``emit_changed``.
|
||||
|
||||
You can display your custom widget in two ways. Use the default ``add_child`` method
|
||||
to display it to the right of the property name, and ``set_bottom_editor`` to position
|
||||
it below the name.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# MyIntEditor.gd
|
||||
# RandomIntEditor.gd
|
||||
extends EditorProperty
|
||||
class_name MyIntEditor
|
||||
|
||||
|
||||
# The main control for editing the property.
|
||||
var property_control = Button.new()
|
||||
# An internal value of the property.
|
||||
var current_value = 0
|
||||
# A guard against internal changes when the property is updated.
|
||||
var updating = false
|
||||
var spin = EditorSpinSlider.new()
|
||||
|
||||
|
||||
func _init():
|
||||
# We'll add an EditorSpinSlider control, which is the same that the
|
||||
# inspector already uses for integer and float edition.
|
||||
# If you want to put the editor below the property name, use:
|
||||
# `set_bottom_editor(spin)`
|
||||
# Otherwise to put it inline with the property name use:
|
||||
add_child(spin)
|
||||
# To remember focus when selected back:
|
||||
add_focusable(spin)
|
||||
# Setup the EditorSpinSlider
|
||||
spin.set_min(0)
|
||||
spin.set_max(1000)
|
||||
spin.connect("value_changed", self, "_spin_changed")
|
||||
# Add the control as a direct child of EditorProperty node.
|
||||
add_child(property_control)
|
||||
# Make sure the control is able to retain the focus.
|
||||
add_focusable(property_control)
|
||||
# Setup the initial state and connect to the signal to track changes.
|
||||
property_control.text = "Value: " + str(current_value)
|
||||
property_control.connect("pressed", self, "_on_button_pressed")
|
||||
|
||||
|
||||
func _spin_changed(value):
|
||||
func _on_button_pressed():
|
||||
# Ignore the signal if the property is currently being updated.
|
||||
if (updating):
|
||||
return
|
||||
emit_changed(get_edited_property(), value)
|
||||
|
||||
# Generate a new random integer between 0 and 99.
|
||||
current_value = randi() % 100
|
||||
property_control.text = "Value: " + str(current_value)
|
||||
emit_changed(get_edited_property(), current_value)
|
||||
|
||||
|
||||
func update_property():
|
||||
# Read the current value from the property.
|
||||
var new_value = get_edited_object()[get_edited_property()]
|
||||
if (new_value == current_value):
|
||||
return
|
||||
|
||||
# Update the control with the new value.
|
||||
updating = true
|
||||
spin.set_value(new_value)
|
||||
current_value = new_value
|
||||
property_control.text = "Value: " + str(current_value)
|
||||
updating = false
|
||||
|
||||
|
||||
Using the example code above you should be able to make a custom widget
|
||||
that replaces the default :ref:`class_SpinBox` control for integers with
|
||||
a :ref:`class_Button` that generates random values.
|
||||
|
||||
Reference in New Issue
Block a user