EditorUndoRedoManager in Godot – Complete Guide

Managing scene changes efficiently is crucial in game development. One subtle yet powerful feature Godot 4 offers to developers is the EditorUndoRedoManager class. This class serves as the backbone of undo and redo functionalities within the editor, allowing creators to effortlessly revert or apply changes to their scenes. This capability not only saves time but can also save your project from unintended modifications or loss of progress. Dive into this tutorial to grasp how you can enhance your game development workflow by properly utilizing EditorUndoRedoManager for your projects in Godot 4.

What is EditorUndoRedoManager?

The EditorUndoRedoManager is an elegant and efficient system built into the Godot 4 engine. It’s specifically designed to handle the undo and redo history of scenes being edited in the Godot editor. By ensuring that every edit made is tracked and reversible, this tool becomes an indispensable part of the game developer’s toolkit.

What is it for?

The role of this impressive manager is multifaceted. It ordains a separate undo history for each scene, ensuring that each action carried out in the editor is associated with the correct scene context. Additionally, it automates the sorting of global history for actions not specific to scenes, like Project Settings edits or external resources modifications. It relieves developers from the complexities of tracking their actions manually.

Why Should I Learn It?

Mirroring the functionality of the famed UndoRedo class, EditorUndoRedoManager is tailored for use within the Godot editor. Mastering it means unlocking a new level of productivity and safety nets for your game development process. It can prevent time-consuming mistakes and encourage experimentation, knowing that any action can be reverted. Learning how to leverage this class not only enhances efficiency but also nurtures a more creative and daring approach to game design. Whether you’re a beginner or an experienced coder, understanding the workings of EditorUndoRedoManager is a step towards crafting robust and resilient game projects.

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

Initializing EditorUndoRedoManager in a Plugin

To begin using EditorUndoRedoManager, we must first initialize it within our custom Godot editor plugin. Here is how you set up a simple plugin with the EditorUndoRedoManager:

tool
extends EditorPlugin

var undo_redo = EditorUndoRedoManager.new()

func _enter_tree():
    add_custom_type("MyCustomNode", "Spatial", preload("res://my_custom_node.gd"), preload("res://icon.png"))
    undo_redo.clear_history()

func _exit_tree():
    remove_custom_type("MyCustomNode")

In this snippet, we create an instance of EditorUndoRedoManager and clear any existing history when the plugin enters the scene tree. This approach guarantees a clean slate for tracking changes.

Performing Actions with Undo/Redo Support

With the EditorUndoRedoManager initialized, we can now perform actions that support undo and redo functionalities. Below are examples of how to add a node to a scene with undo/redo support:

func add_node_with_undo_redo(node, parent):
    undo_redo.create_action("Add Node")
    undo_redo.add_do_method(parent, "add_child", node)
    undo_redo.add_undo_method(parent, "remove_child", node)
    undo_redo.commit_action()

To remove a node with undo/redo support, the process is similar:

func remove_node_with_undo_redo(node, parent):
    undo_redo.create_action("Remove Node")
    undo_redo.add_do_method(parent, "remove_child", node)
    undo_redo.add_undo_method(parent, "add_child", node)
    undo_redo.commit_action()

Notice how for each action we create, we specify the methods that should be called to undo and redo the action.

Modifying Properties with Undo/Redo

Editing properties also requires tracking the previous and new values to ensure they can be restored or reapplied:

func modify_property_with_undo_redo(node, property, new_value):
    var old_value = node.get(property)
    undo_redo.create_action("Modify Property")
    undo_redo.add_do_property(node, property, new_value)
    undo_redo.add_undo_property(node, property, old_value)
    undo_redo.commit_action()

With this setup, we can modify a property and have the peace of mind that we can revert back to its original state if necessary.

Advanced Actions in EditorUndoRedoManager

Sometimes, we need to perform a series of actions that involve multiple do and undo methods for complex changes. Take this complex move and rename action as an example:

func move_and_rename_with_undo_redo(node, new_parent, new_name):
    var old_parent = node.get_parent()
    var old_name = node.get_name()
    undo_redo.create_action("Move and Rename Node")
    undo_redo.add_do_method(node, "set_name", new_name)
    undo_redo.add_do_method(old_parent, "remove_child", node)
    undo_redo.add_do_method(new_parent, "add_child", node)
    undo_redo.add_undo_method(node, "set_name", old_name)
    undo_redo.add_undo_method(new_parent, "remove_child", node)
    undo_redo.add_undo_method(old_parent, "add_child", node)
    undo_redo.commit_action()

Editors can execute several operations in one go, and by committing them as a single action, they can be undone and redone as a unit. This maintains the logical grouping of related changes.While the previous examples cover basic uses of EditorUndoRedoManager, let’s now delve into some more nuanced scenarios. These will provide you with a broader understanding of the class’s flexibility, which is essential for robust plugin development.

Handling Multiple Node Properties

Imagine a scenario where you want to update various properties of a node simultaneously. Here’s how to handle that in a way that groups all property changes together:

func change_multiple_properties(node, new_properties):
    undo_redo.create_action("Change Multiple Properties")

    for property in new_properties.keys():
        var old_value = node.get(property)
        var new_value = new_properties[property]

        undo_redo.add_do_property(node, property, new_value)
        undo_redo.add_undo_property(node, property, old_value)

    undo_redo.commit_action()

This block of code allows you to modify multiple properties at once, with each being revertible to its original state.

Batch Adding and Removing Nodes

Next, let’s look at a batch operation where you want to add or remove multiple nodes within a single action:

func add_multiple_nodes(parent, nodes_to_add):
    undo_redo.create_action("Add Multiple Nodes")

    for node in nodes_to_add:
        undo_redo.add_do_method(parent, "add_child", node)
        undo_redo.add_undo_method(parent, "remove_child", node)

    undo_redo.commit_action()

func remove_multiple_nodes(parent, nodes_to_remove):
    undo_redo.create_action("Remove Multiple Nodes")

    for node in nodes_to_remove:
        undo_redo.add_do_method(parent, "remove_child", node)
        undo_redo.add_undo_method(parent, "add_child", node)

    undo_redo.commit_action()

These functions are convenient when dealing with scenarios like duplicating or clearing a selection of nodes in your scene, ensuring each node addition or removal can be undone as a group.

Integrating Resource Changes

It’s also possible to include actions that affect resources. Here’s how you might implement a change to a resource’s property, such as altering a material’s color:

func change_resource_property(resource, property, new_value):
    var old_value = resource.get(property)

    undo_redo.create_action("Change Resource Property")
    undo_redo.add_do_property(resource, property, new_value)
    undo_redo.add_undo_property(resource, property, old_value)
    undo_redo.commit_action()

Ensuring resource changes can be undone helps prevent unintended permanent modifications to your shared assets.

Sequencing Actions

Now, let’s say you want to execute a sequence of actions one after the other:

func perform_sequenced_actions():
    undo_redo.create_action("Perform Sequenced Actions")
    
    # Sequence of do actions
    undo_redo.add_do_method(self, "first_action")
    undo_redo.add_do_method(self, "second_action")
    undo_redo.add_do_method(self, "third_action")
    
    # Corresponding sequence of undo actions
    undo_redo.add_undo_method(self, "undo_third_action")
    undo_redo.add_undo_method(self, "undo_second_action")
    undo_redo.add_undo_method(self, "undo_first_action")

    undo_redo.commit_action()

This code ensures that when you undo the action, the corresponding undo methods will be called in reverse order, rolling back the changes properly.

Using the Singleton for Global Access

If you need to use the EditorUndoRedoManager from different parts of your plugin, you can access it as a singleton after initializing it in the plugin script:

func _enter_tree():
    add_undo_redo_singleton()

func _exit_tree():
    remove_undo_redo_singleton()

func add_undo_redo_singleton():
    # Make it globally available
    GlobalUndoRedo = undo_redo

func remove_undo_redo_singleton():
    GlobalUndoRedo = null

Now, `GlobalUndoRedo` can be used anywhere in your plugin to access the `EditorUndoRedoManager` instance.

Understanding and utilizing these more complex interaction patterns with EditorUndoRedoManager will help you build a more user-friendly and professional experience within the Godot 4 editor. The undo-redo system becomes a powerful tool in your development arsenal to create plugins that handle complex scenarios with grace and efficiency.In this section, we’ll explore additional, advanced use cases of `EditorUndoRedoManager` and provide multiple code examples. These will help to further clarify how you can utilize this tool to provide comprehensive undo/redo functionality in your Godot 4 editor plugins and customized workflows.

Grouping Actions into a Single Undo Step

Sometimes, you may want to group a set of actions together so that they all can be undone with a single step. Use the ‘merge_actions’ method to combine multiple actions into one.

func grouped_actions():
    var node = get_node("SomeNode")
    
    undo_redo.create_action("Change Node Properties Grouped")
    undo_redo.add_do_method(node, "set_position", Vector3(10, 0, 0))
    undo_redo.add_undo_method(node, "set_position", node.position)
    undo_redo.add_do_method(node, "set_rotation_degrees", Vector3(0, 45, 0))
    undo_redo.add_undo_method(node, "set_rotation_degrees", node.rotation_degrees)
    
    undo_redo.commit_action()
    undo_redo.merge_actions(4, 4, 2)

This code ensures that changing the position and rotation of a node counts as one single action when undoing or redoing.

Evenly Distributing Property Changes Across Several Nodes

A common operation is to change a property across several nodes at once. Here’s how to evenly distribute a property change, like scaling, to a group of nodes.

func distribute_scale(nodes, scale_value):
    undo_redo.create_action("Distribute Scale")
    
    for node in nodes:
        var old_scale = node.scale
        undo_redo.add_do_method(node, "set_scale", scale_value)
        undo_redo.add_undo_method(node, "set_scale", old_scale)

    undo_redo.commit_action()

Each node’s scale will change, but it can all be undone in one action, maintaining a consistent state across the nodes.

Complex Node Transfers Between Scenes

When moving a node from one scene to another, you may need to maintain not only its properties but also its underlying script and connections.

func complex_node_transfer(node, new_scene_root):
    var old_parent = node.get_parent()
    
    undo_redo.create_action("Complex Node Transfer")

    # Transfer the node
    undo_redo.add_do_method(new_scene_root, "add_child", node)
    undo_redo.add_undo_method(old_parent, "add_child", node)
    
    # Restore previous parent
    undo_redo.add_do_method(old_parent, "remove_child", node)
    undo_redo.add_undo_method(new_scene_root, "remove_child", node)

    undo_redo.commit_action()

This operation makes sure that the node’s place in the hierarchy is preserved and undoable when transferring between scenes.

Undoing Scene Instance Creation

Instance creation is a frequent task in scene management. Let’s manage the creation of an instance with undo/redo:

func instance_scene_with_undo_redo(scene_resource, parent):
    var instance = scene_resource.instance()
    
    undo_redo.create_action("Instance Scene")
    undo_redo.add_do_method(parent, "add_child", instance)
    undo_redo.add_undo_method(parent,"remove_child", instance)
    undo_redo.commit_action()

If an instanced node needs to be removed, a single undo step will revert the scene back to its prior state.

Connecting and Disconnecting Signals

Another frequent action that may need undo/redo is connecting signals between nodes. The process to handle this can also be handled elegantly:

func connect_signal_with_undo_redo(emitter, signal_name, target, method_name):
    undo_redo.create_action("Connect Signal")

    undo_redo.add_do_method(emitter, "connect", signal_name, target, method_name)
    undo_redo.add_undo_method(emitter, "disconnect", signal_name, target, method_name)

    undo_redo.commit_action()

Likewise, disconnecting signals can be undone if you set up the actions inversely.

By thoroughly incorporating undo and redo capabilities using the EditorUndoRedoManager class, you will significantly enhance the robustness of your editor tools. Applying these patterns will allow developers to reliably roll back errors or changes, streamlining the development process and maintaining the focus on the creative aspects of game design.

Furthering Your Game Development Journey

The world of game development is vast and continuously evolving, and there’s always more to learn. If you’ve enjoyed diving into the intricacies of Godot 4 with EditorUndoRedoManager or are simply looking for a structured way to level up your game creation skills, our Godot Game Development Mini-Degree is an excellent next step. It provides a deep dive into building cross-platform games using this dynamic engine.

Our courses cover a wide spectrum of game development aspects, suitable for those just starting out or developers looking to enhance their skill set. We focus on practical skills that can be directly applied to personal projects or professional aspirations. And for those wishing to explore an array of Godot topics, be sure to check out our full range of Godot courses.

Join us at Zenva to turn your creative ideas into reality with engaging, hands-on learning experiences. Empower yourself with knowledge, create captivating games, and progress with confidence in your game development career.

Conclusion

As we’ve explored, mastering the EditorUndoRedoManager in Godot 4 equips you with a powerful tool to enhance your game development workflow. Encapsulating complex undo and redo operations in your editor plugins can significantly improve the efficiency and safety of your game design process. But the journey doesn’t end here. Every tool and technique you learn is a stepping stone to becoming a more proficient and inventive game developer.

Unlock the full potential of your game development skills with our Godot Game Development Mini-Degree. Continue your learning adventure with us at Zenva, where your progress is our priority, and every lesson brings you one step closer to realizing your game development dreams. Let’s craft the future of gaming together!

FREE COURSES
Python Blog Image

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