EditorUndoRedoManager in Godot – Complete Guide

Understanding the EditorUndoRedoManager in Godot 4 is a voyage into the heart of developing robust and user-friendly game editors. Whether you’re a seasoned developer or just starting out, mastering how to manage edit histories can vastly improve your workflow. Not only does it add a safety net by allowing you to undo and redo actions, but it also organizes your changes neatly, ensuring each element of your scene can be edited with precision.

Let’s embark on a journey to explore the intricacies of Godot’s EditorUndoRedoManager, understand its purpose, and discover why it could become an indispensable tool in your game development toolkit.

What is the EditorUndoRedoManager?

The EditorUndoRedoManager is Godot’s native feature that operates behind the scenes to manage the undo and redo actions for scenes being edited within the Godot editor. It’s a specialized class designed to handle the modifications you make so you can easily revert or reapply them as needed.

What is it Used For?

When you move a sprite, tweak a texture, or adjust a script, imagine the EditorUndoRedoManager as your diligent assistant, carefully logging each step. This manager is not just keeping track of your recent changes but associating them with the relevant scene or even providing a global history for project-wide modifications.

Why Should I Learn It?

Being familiar with the EditorUndoRedoManager is crucial for any developer working on plugins or tools within the Godot environment. It ensures a seamless integration with the Godot editor’s existing systems and provides a more professional experience for both the developers and the users of the plugins. Having control over your undo and redo stacks can significantly improve the robustness of your editing tools and could save you from losing important changes or corrupting your scenes.

With an understanding of the EditorUndoRedoManager, you can:

– Customize the undo/redo logic to the specific needs of your scenes
– Create complex editing tools with the assurance of a reliable undo/redo system
– Ensure that your plugins behave in a way that is familiar and intuitive to Godot users

Stay tuned as we delve into practical examples to truly harness the capabilities of the EditorUndoRedoManager.

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

Creating Undo/Redo Actions

Before we add any functionality to our editor, let’s start with the basics of creating undo/redo actions. In Godot, each action is a step that the user can undo or redo. Here’s a simple example of how to create an action that changes the position of a node.

var undo_redo = EditorUndoRedoManager.new()

func move_node(node, new_position):
    undo_redo.create_action("Move Node")
    undo_redo.add_do_method(node, "set_position", new_position)
    undo_redo.add_undo_method(node, "set_position", node.position)
    undo_redo.commit_action()

This snippet creates an action called “Move Node”. The `add_do_method` and `add_undo_method` define what to do when the action is done and undone, respectively. Finally, we call `commit_action` to record this action in the undo/redo stack.

Adding Undo/Redo for Property Changes

Now let’s implement undo/redo for property changes of an object. Here’s how to set up an undo/redo action when changing the `modulate` property of a Sprite node:

func change_sprite_modulate(sprite, new_color):
    var old_color = sprite.modulate
    undo_redo.create_action("Change Sprite Color")
    undo_redo.add_do_property(sprite, "modulate", new_color)
    undo_redo.add_undo_property(sprite, "modulate", old_color)
    undo_redo.commit_action()

When dealing with property changes, we use `add_do_property` and `add_undo_property`. This ensures that the property changes are recorded correctly.

Complex Undo/Redo Actions

Moving onto more complex sequences, imagine we want to implement a feature in our editor that duplicates a node. We need to ensure that both the creation and deletion of the node can be undone and redone:

func duplicate_node(original_node):
    var duplicated_node = original_node.duplicate()
    undo_redo.create_action("Duplicate Node")
    undo_redo.add_do_method(self, "add_child", duplicated_node)
    undo_redo.add_undo_method(duplicated_node, "queue_free")
    undo_redo.commit_action()

This action adds the duplicated node as a child and uses `queue_free` to delete it when the action is undone.

Grouping Actions

Sometimes, multiple actions need to be grouped together. For example, if we are implementing a custom transform tool, moving and rotating might be considered as one action:

func transform_node(node, new_position, new_rotation):
    undo_redo.create_action("Transform Node")
    undo_redo.add_do_method(node, "set_position", new_position)
    undo_redo.add_do_method(node, "set_rotation_degrees", new_rotation)
    undo_redo.add_undo_method(node, "set_position", node.position)
    undo_redo.add_undo_method(node, "set_rotation_degrees", node.rotation_degrees)
    undo_redo.commit_action()

This way, when the user triggers an undo, both the movement and rotation revert together.

Stay tuned as we continue to build upon these examples in the next parts, showing you how to integrate the undo/redo system deeply into your Godot projects. We’ll cover more advanced topics and provide additional practical examples to enhance your editor scripts.Let’s continue to expand our mastery of the EditorUndoRedoManager by exploring more practical usage scenarios. We’ll see how to deal with compound actions, resource modifications, and scripting nuances that can help your editor tools feel native to the Godot environment.

Undo/Redo with Multi-Node Selections

When dealing with multiselection, such as moving several nodes at once, it’s important that all associated actions are treated as one. Below is how to handle this case:

func move_multiple_nodes(nodes, new_positions):
    undo_redo.create_action("Move Multiple Nodes")
    for i in range(nodes.size()):
        undo_redo.add_do_method(nodes[i], "set_position", new_positions[i])
        undo_redo.add_undo_method(nodes[i], "set_position", nodes[i].position)
    undo_redo.commit_action()

This loop adds actions for each node individually, but since they are under the same `create_action`, they will be undone and redone together.

Handling Resource Changes

Altering resources, like textures or scripts, can also be captured by the undo/redo manager. Here’s an example of changing a texture resource on a sprite:

func change_texture(sprite, new_texture):
    var old_texture = sprite.texture
    undo_redo.create_action("Change Sprite Texture")
    undo_redo.add_do_property(sprite, "texture", new_texture)
    undo_redo.add_undo_property(sprite, "texture", old_texture)
    undo_redo.commit_action()

This is particularly useful for editor tools that change shared resources, ensuring that all nodes using the resource reflect the change.

Renaming Nodes

Node renaming is a common operation that deserves undo/redo support. Here’s how you could implement it:

func rename_node(node, new_name):
    var old_name = node.name
    undo_redo.create_action("Rename Node")
    undo_redo.add_do_property(node, "name", new_name)
    undo_redo.add_undo_property(node, "name", old_name)
    undo_redo.commit_action()

This lets users freely rename nodes, comfortable in the knowledge they can revert changes if necessary.

Complex Property Editing

Sometimes we need to deal with properties that are not as straightforward, such as editing a value in an array property.

func edit_array_property(object, property, index, new_value):
    var old_value = object[property][index]
    undo_redo.create_action("Edit Array Property")
    undo_redo.add_do_method(self, "set_array_property_value", object, property, index, new_value)
    undo_redo.add_undo_method(self, "set_array_property_value", object, property, index, old_value)
    undo_redo.commit_action()

func set_array_property_value(obj, prop, idx, value):
    var arr = obj[prop].duplicate()
    arr[idx] = value
    obj[prop] = arr

Wrapping the array editing in methods is necessary because direct index modification on the property isn’t captured by the undo/redo system.

Scripting Undo for Custom Signals

For custom editor tools emitting signals, you might also want to create undo actions when those signals lead to changes:

signal node_moved(node)

func _on_node_moved(node):
    var move_action = undo_redo.create_action("Node Moved")
    undo_redo.add_do_method(self, "_do_move_node", node, Vector2(10, 10))
    undo_redo.add_undo_method(self, "_undo_move_node", node, node.position)
    undo_redo.commit_action()

func _do_move_node(node, new_pos):
    node.position = new_pos
    emit_signal("node_moved", node)

func _undo_move_node(node, old_pos):
    node.position = old_pos

Preserving Script’s Undo/Redo Across Sessions

Lastly, it’s worth noting that the undo/redo stack is not saved with the project. If you need to maintain an undo history across editing sessions, you’ll have to implement serialization for your actions, which can get complex.

The provided code snippets exemplify the versatility of the EditorUndoRedoManager. With the groundwork laid out here, you can continue to develop more sophisticated functionalities that fit your specific editing needs, ensuring both robustness and a friendly user experience within the Godot editor.Let’s dive into further complexities that we might encounter when implementing undo/redo capabilities within the Godot Editor.

Undo/Redo for Custom Inspector Plugins

When creating custom inspector plugins, you can leverage the EditorUndoRedoManager to handle changes made through the inspector.

func _on_property_changed(object, property, new_value, old_value):
    undo_redo.create_action("Change Property via Inspector")
    undo_redo.add_do_property(object, property, new_value)
    undo_redo.add_undo_property(object, property, old_value)
    undo_redo.commit_action()

This function could be connected to a signal that is emitted when an inspector property is changed, ensuring that edits made by the user can be reversed.

Grouping Property Changes as One Action

Continuing with inspector-style changes, it might be desirable to accumulate several changes and then commit them as a single undo/redo action:

func _on_multiple_properties_changed(object, property_changes):
    undo_redo.create_action("Change Multiple Properties")
    for change in property_changes:
        undo_redo.add_do_property(object, change.property, change.new_value)
        undo_redo.add_undo_property(object, change.property, change.old_value)
    undo_redo.commit_action()

The hypothetical `property_changes` variable is assumed to be an array of objects with `property`, `new_value`, and `old_value` member variables.

Undo/Redo for Node Deletion and Creation

Creation and deletion of nodes are critical actions to manage, especially in custom tool development within Godot.

func delete_node(node):
    var parent = node.get_parent()
    var idx = parent.get_index(node)
    undo_redo.create_action("Delete Node")
    undo_redo.add_do_method(node, "queue_free")
    undo_redo.add_undo_method(parent, "add_child", node, idx)
    undo_redo.commit_action()

This snippet ensures that when a node is deleted, it can be brought back with all its properties intact, including its position in the parent’s hierarchy.

To handle node creation, the operation needs to be reversible. It generally involves scripting both the creation and the setup of the node in the `do` method and ensuring that the created node can be properly removed if the action is undone.

func create_node(type, parent, name):
    var node = Node.new()
    undo_redo.create_action("Create Node")
    undo_redo.add_do_method(self, "_do_create_node", type, parent, name)
    undo_redo.add_undo_method(parent, "remove_child", node)
    undo_redo.commit_action()

func _do_create_node(type, parent, name):
    var node = Node.new()
    node.name = name
    parent.add_child(node)
    return node

Combining Actions from Different Contexts

A robust plugin may involve doing and undoing actions that span different contexts. For example, a node’s transform may be reset, and a message sent to a server:

func reset_node_and_notify(node):
    undo_redo.create_action("Reset Node and Notify")
    undo_redo.add_do_method(node, "set_transform", Transform2D.IDENTITY)
    undo_redo.add_do_method(self, "notify_server_of_reset", node)
    undo_redo.add_undo_method(node, "set_transform", node.transform)
    undo_redo.add_undo_method(self, "notify_server_of_undo_reset", node)
    undo_redo.commit_action()

Please note that the `notify_server_of_reset` and its undo counterpart are custom methods you would need to define based on your networking logic.

Managing Undo/Redo When Editing Graph-Based Structures

Editing graph-based structures, like nodes in a visual shader or a scene tree, can also be handled by the EditorUndoRedoManager:

func add_graph_node(graph, type, position):
    var node = GraphNode.new(type)
    undo_redo.create_action("Add Graph Node")
    undo_redo.add_do_method(graph, "add_node", node, position)
    undo_redo.add_undo_method(graph, "remove_node", node)
    undo_redo.commit_action()

For complex interactions within a graph, where nodes can have complex connections, you’ll also need to manage these relationships:

func connect_graph_nodes(graph, from_node, to_node, signal, method):
    undo_redo.create_action("Connect Graph Nodes")
    undo_redo.add_do_method(graph, "connect_node", from_node, signal, to_node, method)
    undo_redo.add_undo_method(graph, "disconnect_node", from_node, signal, to_node, method)
    undo_redo.commit_action()

These are a few additional ways the EditorUndoRedoManager can be utilized to make sure your editor enhancements are as user-friendly and professional as those built into Godot itself. The examples provided are intended to inspire your own implementations and highlight the flexible nature of Godot’s undo/redo system for custom editing tools.

Continuing Your Godot Education

Embarking on your game development journey with Godot 4 is an exciting adventure, filled with endless possibilities for creativity and technical growth. If you’ve enjoyed delving into the intricacies of the EditorUndoRedoManager and are eager to deepen your understanding of Godot, our Godot Game Development Mini-Degree is a fantastic next step.

This comprehensive program offers a curated learning path through the essentials and intricacies of game development with Godot 4. Covering a wide array of topics from mastering 2D and 3D assets, to programming in GDScript, and implementing various game mechanics, the Mini-Degree has something for both newcomers and seasoned game developers.

For those who desire to explore even broader horizons within the Godot ecosystem, we invite you to browse our full suite of Godot courses. Designed to be flexible and accommodating of your learning pace, our courses are your gateway to transforming from a beginner to a professional game developer. By joining us at Zenva, you can learn coding, create impressive games, and earn certificates to showcase your skills. So why wait? Continue to shape your future in game development with Zenva today!

Conclusion

Embarking on a learning journey with Godot 4 and the EditorUndoRedoManager showcases just a glimpse of the power at your fingertips. Whether forging an epic RPG, engineering a quirky platformer, or crafting a visually stunning visual novel, Godot grants you the canvas to bring your ideas to life. And remember, the knowledge you’ve gained today is just the beginning. With our Godot Game Development Mini-Degree, you’re not just learning to code; you’re stepping into a realm where your imagination is the only limit.

So continue to build, iterate, and learn. Game development is as much about the journey as it is the destination. With Zenva’s bite-sized lessons, practical projects, and the surrounding community of fellow developers, you’re well on your way to shaping the games of tomorrow. Start creating with confidence, and let Godot and Zenva be your trusted companions on this exhilarating adventure.

FREE COURSES
Python Blog Image

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