EditorProperty in Godot – Complete Guide

Creating customized tools that streamline your workflow in game development can be a significant asset, and the Godot engine provides an incredibly flexible tool to do just that: the EditorProperty class. As we dive into the functionalities and applications of this class, we encourage both budding and seasoned developers to explore this powerful feature of Godot 4, which allows for personalized property editing within the EditorInspector. The idea of extending the editor might seem daunting at first, but with some patience and creativity, it can greatly enhance your game development process.

As we delve into this tutorial, we’ll uncover what the EditorProperty class is, explore its various features and applications, and understand why adding this skill to your toolkit can make a profound difference in the way you create games.

What is the EditorProperty Class?

The EditorProperty class in Godot 4 serves as a custom control within the EditorInspector, enabling developers to craft unique and tailored property editors. This class extends the Container node, making it part of the Godot UI node system, and therefore, integrates seamlessly with the editor’s existing infrastructure.

What is it for?

When working with complex scripts or dealing with unique data types, the default property editors in Godot may not suffice for an efficient development workflow. By creating a custom EditorProperty, developers can concoct specialized interfaces for their game’s specific needs, whether it’s for tuning a procedural terrain generator or adjusting the AI behavior settings.

Why Should I Learn It?

Learning to utilize the EditorProperty class can significantly enhance your Godot capabilities, tailoring the editor to your project’s specific requirements. It allows for a more intuitive way to manipulate data in your project, potentially speeding up development time and making complex tasks more manageable. Such customization also brings an aspect of reusability, as you can employ these bespoke property editors in your future projects or even share them within the Godot community.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Getting Started with EditorProperty

To kick start your journey with EditorProperty, we’ll begin by setting up a basic custom editor. This initial example sets the groundwork for more complex implementations later on. First, create a new script that extends `EditorProperty`.

extends EditorProperty
func _init():
    pass

Then, you need to tell Godot what kind of property our EditorProperty will work with. We do this by overriding the `_get_edited_property()` method.

func _get_edited_property() -> String:
    return "path.to.your.property"

Note that the string returned by `_get_edited_property()` should match the property path you want to customize in the inspector.

Building a Simple EditorProperty

Let’s say we want to customize how a Vector2 property is edited. We want to make an EditorProperty that allows you to edit the Vector2 with sliders.

First, we need to add the necessary UI elements (HSeparator, VSlider, etc.) to our EditorProperty. We also need to override the `_update_property()` function to ensure the sliders reflect the current property value.

extends EditorProperty
var x_slider
var y_slider

func _init():
    x_slider = HSlider.new()
    y_slider = HSlider.new()
    add_child(x_slider)
    add_child(y_slider)
    x_slider.connect("value_changed", self, "_value_changed", [x_slider])
    y_slider.connect("value_changed", self, "_value_changed", [y_slider])

func _get_edited_property() -> String:
    return "path.to.your.property"

func _update_property():
    var value = get_edited_object()[_get_edited_property()]
    x_slider.value = value.x
    y_slider.value = value.y

func _value_changed(new_value, slider):
    var vector = get_edited_object()[_get_edited_property()]
    if slider == x_slider:
        vector.x = new_value
    elif slider == y_slider:
        vector.y = new_value
    set_edited_object_undo_redo(get_edited_object(), _get_edited_property(), vector)

This snippet sets up two sliders to adjust the X and Y values of a Vector2 property respectively.

Handling Undo and Redo

One of Godot’s strengths is its robust undo/redo system, and our custom EditorProperty should integrate with it. To ensure that changes made through the EditorProperty are captured in the undo/redo stack, we utilize the `set_edited_object_undo_redo()` method.

Whenever the value of a property is changed through the EditorProperty, we call this method to register the change:

func _value_changed(new_value, slider):
    var vector = get_edited_object()[_get_edited_property()]
    if slider == x_slider:
        vector.x = new_value
    elif slider == y_slider:
        vector.y = new_value
    
    set_edited_object_undo_redo(get_edited_object(), _get_edited_property(), vector)

Implementing Update Callbacks

Godot’s EditorProperty class provides several callback functions that we can use to update our custom editor properly. One such callback is `_update_property()`, which is called whenever the property that EditorProperty is responsible for is changed from somewhere else in the editor.

Our Vector2 sliders should adjust their values accordingly when this happens:

func _update_property():
    var value = get_edited_object()[_get_edited_property()]
    x_slider.value = value.x
    y_slider.value = value.y

With these foundations, you’re now equipped to create simple custom property editors in Godot. In the following sections, we’ll expand on these basics to construct more sophisticated controls tailored to specific use cases, ultimately enhancing our workflows and productivity in the Godot editor.

Connecting EditorProperty Signals

To fully integrate your custom `EditorProperty` with the Godot editor, you must handle signals properly. Your class emits signals when the property’s value changes, which is essential for real-time updates and ensuring that the scene gets saved with the new property values.

One important signal is `property_changed`, which should be emitted whenever the property’s value gets updated:

func _value_changed(new_value, slider):
    var vector = get_edited_object()[_get_edited_property()]
    if slider == x_slider:
        vector.x = new_value
    elif slider == y_slider:
        vector.y = new_value

    set_edited_object_undo_redo(get_edited_object(), _get_edited_property(), vector)
    emit_signal("property_changed", _get_edited_property(), vector)

By emitting `property_changed`, you notify the Godot editor that the property has been altered, so it can update other editor parts and mark the scene as unsaved.

Validating Property Input

When creating a custom `EditorProperty`, validating input is crucial to maintaining consistency and preventing errors. You can override the `_validate_input()` method to check whether the new input is acceptable:

func _validate_input(new_value, slider):
    if new_value  100:
        emit_signal("property_changed", _get_edited_property(), get_edited_object()[_get_edited_property()])
        return false
    return true

This example assumes you only want values from 0 to 100. The function emits the `property_changed` signal with the old value if the input fails validation, effectively discarding the invalid change.

Enhancing UI Elements

Besides basic UI controls, you can also add more complex elements. If you want to include a color picker for a `Color` property, here’s how you’d set it up:

var color_picker = ColorPickerButton.new()

func _init():
    add_child(color_picker)
    color_picker.connect("color_changed", self, "_color_changed")

func _color_changed(new_color):
    set_edited_object_undo_redo(get_edited_object(), _get_edited_property(), new_color)
    emit_signal("property_changed", _get_edited_property(), new_color)

This code snippet adds a `ColorPickerButton` and connects its signal to a method within your `EditorProperty`. The method then handles the new color data.

Custom Rendering within EditorProperty

Your `EditorProperty` is not limited to UI widgets; you can also perform custom rendering within its GUI elements. To do this, you would typically override the `_draw()` method:

func _draw():
    # Assuming _draw() is called within a custom EditorProperty that deals with colors
    var rect = Rect2(Vector2(10, 10), Vector2(100, 20))
    var current_color = get_edited_object()[_get_edited_property()]
    draw_rect(rect, current_color)

In this case, a colored rectangle is rendered to represent the current value of a `Color` property. It’s crucial to call `update()` whenever you want to update the custom drawing.

Responding to the Inspector

Responding to the inspector’s refresh is also important. The `_refresh()` callback can be overridden to respond when Godot calls for a refresh of the `EditorInspector`:

func _refresh():
    # Perform tasks that are necessary to update the look or feel of the custom EditorProperty
    update()

This is generally where you’d want to update your custom rendering or interface elements to reflect any changes in the property value or settings.

With these code examples, you have a solid foundation for creating feature-rich, custom property editors within the Godot 4 engine. Custom EditorProperties not only enrich your game development experience but also pave the way for creating more user-friendly and precise tools tailored for your project’s unique needs.Custom `EditorProperty` widgets can be highly interactive and dynamic. Suppose you are creating a custom EditorProperty for a `Range` type property and wish to provide a graphical representation of that range. Here’s how you might add a drawing of the range within the EditorProperty:

func _draw():
    var range = get_edited_object()[_get_edited_property()]
    var start_pos = Vector2(10, 25)
    var end_pos = Vector2(190, 25)
    var range_start_pos = start_pos.linear_interpolate(end_pos, (range.min_value - 10) / (190 - 10))
    var range_end_pos = start_pos.linear_interpolate(end_pos, (range.max_value - 10) / (190 - 10))
    
    # Draw range line
    draw_line(start_pos, end_pos, Color(0.9, 0.9, 0.9))
    # Draw active range
    draw_line(range_start_pos, range_end_pos, Color(0.2, 0.6, 0.2), 2)

This snippet creates a visual representation of a `Range` with colored lines representing the active portion of the range.

Handling user interaction is another important aspect of custom EditorProperties. For example, adding the capability for a user to reset a property to its default value by right-clicking it:

func _input(event):
    if event is InputEventMouseButton and event.button_index == BUTTON_RIGHT and event.pressed:
        reset_to_default_value()

func reset_to_default_value():
    var default_value = ... # Determine the default value for the property
    set_edited_object_undo_redo(get_edited_object(), _get_edited_property(), default_value)
    emit_signal("property_changed", _get_edited_property(), default_value)
    update()

In this example code, the `_input()` method detects a right-click and calls a method to reset the property’s value.

Now, let’s look at implementing an EditorProperty for a property with multiple sub-values. Suppose you have a custom resource with several fields that need individual EditorProperty widgets. You would need to create separate EditorProperty instances for each sub-value. Here’s an example of how that could be structured:

func create_editor_for_sub_value(sub_value):
    match sub_value:
        "sub_value_1":
            var editor = SubValue1Editor.new()
        "sub_value_2":
            var editor = SubValue2Editor.new()
        _:
            editor = null
    add_child(editor)
    return editor

User feedback is vital for a comfortable user experience. One way to provide feedback is through tooltips. Here’s an example of setting a tooltip for your EditorProperty:

func _init():
    set_tooltip("This is a custom EditorProperty for a Vector3. Drag the sliders to change values.")

Finally, consider the case where you’d like to add a button to your EditorProperty that, when clicked, opens a helper window or performs some action. Here’s how you might accomplish that:

var my_button = Button.new()

func _init():
    my_button.text = "Open Helper"
    my_button.connect("pressed", self, "_on_my_button_pressed")
    add_child(my_button)

func _on_my_button_pressed():
    # Code to open a helper window or perform an action

By integrating dynamic elements such as buttons and sliders, combining them with custom drawn elements, and ensuring every interaction is saved in the undo/redo stack, you can create a powerful and user-friendly EditorProperty in Godot. Each custom EditorProperty enriches the development experience, enabling more intuitive and quick editing of complex custom types and structures in your games.

Continuing Your Game Development Journey

The road to mastering game development is an ongoing adventure, and each step you take builds upon your existing knowledge and skills. Having explored the EditorProperty class in Godot 4, you’ve equipped yourself with the tools to create more personalized and efficient game development workflows. But why stop there?

We at Zenva encourage you to keep pushing the boundaries of your creativity and technical expertise. For those of you who are eager to dive deeper into game creation with Godot, our Godot Game Development Mini-Degree provides an expansive learning path that covers a spectrum of essential topics. From mastering 2D and 3D game mechanics to programming in GDScript and beyond, our mini-degree is designed to level up your skills, whether you’re just starting out or looking to add advanced techniques to your developer toolkit.

For a broader selection of topics and to further expand your prowess, be sure to explore our full range of Godot courses. Each course is crafted to help you build practical, project-based knowledge that translates into real-world applications. Embark on this journey with Zenva, and transform your passion for game development into reality. Together, let’s create, innovate, and bring your game ideas to life!

Conclusion

Pushing the boundaries of game development requires a blend of creativity, passion, and technical know-how. By mastering tools like the EditorProperty class in Godot 4, you unlock new possibilities for customization and efficiency, making your development process as unique as the games you dream up. Remember, what you’ve learned here is just the tip of the iceberg, and there’s a whole world of advanced Godot techniques waiting for you.

Embrace the challenge and continue your growth with Zenva. Whether you’re refining your skills or starting a new development venture, our Godot Game Development Mini-Degree is the companion you need to take that next big leap. Join us and be part of a community that’s turning game development dreams into everyday reality. Let’s create not just games, but experiences that resonate and inspire. Happy coding!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.