UndoRedo in Godot – Complete Guide

Implementing undo and redo functionality is a critical feature in many applications, especially when it comes to game development and software that handles user inputs or complex transformations. Imagine working on a level editor where you can move objects around, resize them, or even add and delete them. An accidental move or deletion could be frustrating if you can’t easily revert it. The UndoRedo class in Godot 4 provides an elegant solution to this problem by offering a high-level interface to implement undo and redo operations effectively.

What is UndoRedo?

The UndoRedo class in Godot 4 is designed to handle undo and redo operations through a system of actions. Actions are comprised of methods and property changes that can be done and undone as needed. This class types offers a convenient way to track and reverse changes made during the game’s runtime, which is essential for game editors or any tool that benefits from non-linear workflow.

Why Should You Learn About UndoRedo?

Understanding and utilizing the UndoRedo class can elevate your development workflow within Godot:

Enhanced User Experience: By integrating undo and redo features, you give users the flexibility to experiment without consequences, knowing they can reverse actions.
Complex Changes Management: Handle complex sets of changes in an organized manner that can be rolled back and reapplied as needed.
Editor Tools Development: If you’re creating custom editor tools with Godot, undo and redo functionality will be expected and save users from many headaches.
Bug Minimization: In the development phase, being able to undo can help in tracking down when a particular bug or unwanted behavior was introduced.

By mastering the UndoRedo class, you’ll not only make your own development life easier but also ensure that your tools and game features are user-friendly and robust.

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

Starting with UndoRedo

To begin using the UndoRedo class in Godot 4, you first need to create an instance of it. This can be done simply by declaring a variable and assigning it to UndoRedo.

var undo_redo = UndoRedo.new()

Once you have an instance, you can create an action. An action in Godot is a collection of do/undo steps. Let’s say you have a sprite node and you want to change its position.

undo_redo.create_action("Move Sprite")
undo_redo.add_do_method(sprite, "set_position", Vector2(100, 100))
undo_redo.add_undo_method(sprite, "set_position", sprite.position)
undo_redo.commit_action()

This will move the sprite to position (100, 100) and allow you to undo this move later on.

Adding Property Changes to an Action

Now, let’s say you want to change a property of the sprite, like its scale. You can handle property changes by adding do/undo property steps to an action.

undo_redo.create_action("Change Sprite Scale")
undo_redo.add_do_property(sprite, "scale", Vector2(2, 2))
undo_redo.add_undo_property(sprite, "scale", sprite.scale)
undo_redo.commit_action()

This changes the scale of the sprite and registers the current scale for undo.

Grouping Multiple Changes into One Action

Sometimes you’ll want to group multiple changes into one action so that undoing will revert all those changes in one go. Here’s how you might move a sprite and change its color together.

undo_redo.create_action("Move and Recolor Sprite")
undo_redo.add_do_method(sprite, "set_position", Vector2(200, 200))
undo_redo.add_undo_method(sprite, "set_position", sprite.position)
undo_redo.add_do_property(sprite, "modulate", Color(1, 0, 0))
undo_redo.add_undo_property(sprite, "modulate", sprite.modulate)
undo_redo.commit_action()

This will allow the user to undo both the movement and recoloring of the sprite in one step.

Performing Undo and Redo Operations

After creating actions and committing them, you can easily perform undo and redo operations using the respective methods.

# To undo the last action
undo_redo.undo()

# To redo the previously undone action
undo_redo.redo()

Each call to undo() reverts the last committed action, and each call to redo() re-applies the last undone action. It’s important to ensure that the state of the game allows for these operations, as attempting to undo or redo without corresponding actions will have no effect.

With these examples, you have the basics to start implementing undo and redo functionality in your Godot 4 projects. Practice combining different actions and experiment with the UndoRedo class’s capabilities to get comfortable with its workflow.As you become more comfortable with the basic use of UndoRedo, it’s time to delve into some advanced examples and use cases. Each code snippet builds upon the last, demonstrating the flexibility and power of the UndoRedo class in Godot.

Advanced Usage of UndoRedo

Consider a situation where you need to track the creation and deletion of nodes. Here’s how you can manage this complex scenario:

# To track node addition
func add_node(parent, node, position):
    undo_redo.create_action("Add Node")
    undo_redo.add_do_method(parent, "add_child", node)
    undo_redo.add_do_method(node, "set_position", position)
    undo_redo.add_undo_method(node, "queue_free")
    undo_redo.commit_action()

# To track node deletion
func delete_node(node):
    undo_redo.create_action("Delete Node")
    undo_redo.add_do_method(node, "queue_free")
    undo_redo.add_undo_method(node.get_parent(), "add_child", node)
    undo_redo.commit_action()

In these snippets, note that when adding a node, you also need to specify the position by adding a do method for setting it. For deletion, you record the parent with add_child for the undo action to properly restore the hierarchy.

Now let’s talk about tracking complex changes involving multiple properties. We can modify a sprite node’s position, scale, and texture simultaneously and define the undo steps for each property change.

var texture = preload("res://sprite_texture.png")

undo_redo.create_action("Complex Sprite Transformation")
undo_redo.add_do_property(sprite, "position", Vector2(300, 300))
undo_redo.add_undo_property(sprite, "position", sprite.position)
undo_redo.add_do_property(sprite, "scale", Vector2(1.5, 1.5))
undo_redo.add_undo_property(sprite, "scale", sprite.scale)
undo_redo.add_do_property(sprite, "texture", texture)
undo_redo.add_undo_property(sprite, "texture", sprite.texture)
undo_redo.commit_action()

Each property change is addressed with a pair of add_do_property and add_undo_property calls, making sure the sprite can be reverted to its previous state entirely.

Merging actions is another useful feature that allows you to combine multiple actions into one, making them undoable in one step. Here’s how you can merge two separate actions.

# First, we create and commit our first action
undo_redo.create_action("First Action")
# ...
undo_redo.commit_action()

# Then we create our second action
undo_redo.create_action("Second Action")
# ...
# Instead of committing it separately, we merge with the previous action
undo_redo.commit_action(merge_mode:=UndoRedo.MERGE_ENDS)

In this example, both actions will be undone together when the user triggers the undo command next.

Lastly, consider handling the scenario of tracking selective properties based on a condition. We can have a conditional undo/redo process where you only want to revert a property if it meets a certain threshold.

undo_redo.create_action("Conditional Property Change")
if sprite.scale.length() > 1.0:
    undo_redo.add_do_property(sprite, "scale", Vector2(1, 1))
    undo_redo.add_undo_property(sprite, "scale", sprite.scale)
undo_redo.commit_action()

This means that if the sprite’s scale is larger than vector (1, 1), only then will the scale be set to (1, 1) and registered for undo.

These advanced examples showcase UndoRedo’s versatility in Godot 4. They provide a robust way to manage a multitude of changes in your games or tools. By practicing and incorporating these concepts, you’ll be able to ensure a user-friendly and intuitive experience in your game development projects with Godot.Dealing with multiple object properties and ensuring a clean and reversible action set can be quite complex. Here’s how you can modify several objects simultaneously and register all those modifications cohesively:

# Assume we have an array of sprite nodes
var sprites = [sprite1, sprite2, sprite3]

undo_redo.create_action("Move Multiple Sprites")
for sprite in sprites:
    undo_redo.add_do_property(sprite, "position", sprite.position + Vector2(50, 0))
    undo_redo.add_undo_property(sprite, "position", sprite.position)
undo_redo.commit_action()

In this scenario, all the sprites are moved horizontally by 50 pixels, and the undo action logs their original positions before the move, tracking each sprite’s changes independently within one action.

Suppose you realize that a previous action should not have been committed. In Godot, you can use UndoRedo to remove the last action from the stack:

# To remove the last action
undo_redo.remove_last_action()

However, use this with caution: once an action is removed, it cannot be recovered, making it critical to ensure this is really what you want to do.

Godot’s UndoRedo also allows you to set a breakpoint in the action stack, making it easier to manage compound actions during more complex changes:

undo_redo.create_action("Complex Transformations")
# Suppose we transform several nodes here...
# ...
# Set a breakpoint
undo_redo.set_action_stack_push_at_commit()
undo_redo.commit_action()

This breakpoint ensures that the created action is treated as a significant change that shouldn’t be easily undone, such as finalizing a particular state in the editor.

Now, if you find yourself working on an object that’s modified by various functions, you’ll want to make sure that all those changes can be undone with one click. Grouping actions together is the solution:

# Modify a node's properties in Function A
func modify_in_function_A():
    undo_redo.create_action("Modify Node")
    undo_redo.add_do_property(node, "position", Vector2(0, 100))
    undo_redo.add_undo_property(node, "position", node.position)
    undo_redo.commit_action(merge_mode:=UndoRedo.MERGE_ENDS)

# Modify the same node's properties in Function B
func modify_in_function_B():
    undo_redo.create_action("Modify Node")
    undo_redo.add_do_property(node, "scale", Vector2(1.5, 1.5))
    undo_redo.add_undo_property(node, "scale", node.scale)
    undo_redo.commit_action(merge_mode:=UndoRedo.MERGE_ENDS)

With both functions calling `commit_action(merge_mode:=UndoRedo.MERGE_ENDS)`, when the user invokes an undo operation, it will revert both sets of changes made by these functions.

Finally, let’s handle the scenario where performing or reverting an action requires some extra logic or clean-up that doesn’t involve just setting properties or calling methods. You can register callable objects for use in an undo/redo action like this:

func _after_move():
    # Perform cleanup or additional logic here

undo_redo.create_action("Move Node")
undo_redo.add_do_method(node, "set_position", Vector2(500, 300))
undo_redo.add_undo_method(node, "set_position", node.position)
# Add callable object to be called after the redo method
undo_redo.add_do_callable(funcref(self, "_after_move"))
undo_redo.commit_action()

Here, `_after_move` is a function that will be called after the redo operation is performed, allowing you to execute any necessary logic beyond the standard undo/redo mechanism.

Through these examples, it’s evident how flexible and powerful the UndoRedo class is in Godot 4. It offers extensive capabilities for managing the state of your game or editor efficiently, providing a solid foundation for handling user actions meticulously and offering an improved UX with robust undo and redo functions.

Continuing Your Game Development Journey

Now that you’ve started your journey into the world of undo and redo functionality with UndoRedo in Godot 4, the learning doesn’t have to stop here. Delving into game development further can open up a world of creativity and problem-solving that complements the skills you’ve just practiced. Your next step? Consider perusing our Godot Game Development Mini-Degree, where a plethora of courses awaits to deepen your knowledge and expertise.

Whether you’re just starting out or looking to refine your craft, our comprehensive Mini-Degree covers a broad range of topics. You’ll get to grips with both 2D and 3D game development, immerse yourself in GDScript, and grasp the intricacies of game mechanics across various genres. Our project-based curriculum not only teaches you theory but also allows you to apply what you learn in practical scenarios, all culminating with certification to showcase your accomplishments.

And if you’re eager to explore an even broader spectrum of courses, take a look at our complete collection of Godot tutorials and courses. At Zenva, we’re passionate about empowering you with the skills to create, innovate, and bring your game development aspirations to life, from beginner to professional levels.

Conclusion

Mastering the UndoRedo class in Godot 4 is just the beginning of crafting polished and user-friendly games and editor tools. By harnessing the power of this feature, you set a foundation for the type of sophisticated interaction that users expect from professional-grade software. Remember, every great game developer started with the basics and built their way up through practice, curiosity, and continuous learning.

Keep your momentum going by diving deeper into the Godot universe with our Godot Game Development Mini-Degree. Let us at Zenva guide you through your development journey, providing all the resources you need to become a game development wizard. Embrace the challenge, enjoy the creation process, and build the games you’ve always dreamed of. Your adventure into the vast realm of game development is just a click away!

FREE COURSES
Python Blog Image

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