Supporting Undo/Redo in building game

I am making a building game and I need users in my game to undo and redo the items they insert onto their plot. I do not know how to do this. I was thinking to make a table of everything inserted. But I am still not sure and I don’t know how to insert things in tables.

Tables would be my go to in regards to something like this, here’s a dev hub article Tables | Documentation - Roblox Creator Hub

Some useful things to note

table.insert(table_name, item) --inserting items
#table_name --total number of items in the table

What you can do

local playerObjects = {playerName ={}}
function revert(player)
   if playerObjects[player.Name] ~= nil then
       playerObjects[player.Name][#playerObjects[player.Name]]:Destroy()
   end
end
1 Like

How can I support redo?

The best way to do this would be using stacks

2 Likes

I just stumbled across this topic attempting to do the exact same thing. I know it’s over 4 years old, however, I have a solution that works really well.

Using the limited information @cjjdawg provided, I have created something that supports undo and redo using Stacks.

(Information on Stacks can be found here)

First, you need to create a ModuleScript. Inside of there, you need to paste the following code:

local Stack = {}
Stack.__index = Stack

function Stack.new()
	local self = setmetatable({}, Stack)
	
	self.UndoStack = {}
	self.RedoStack = {}
	
	return self
end

-- Check if the stack is empty
function Stack:IsEmpty(stack: {})
	return stack == 0
end

-- Create a new value in the stack
function Stack:Push(value)
	table.insert(self.UndoStack, value)
end

-- Remove the newest value in the stack
function Stack:Undo()
	if self:IsEmpty(self.UndoStack) then
		return nil
	end
	
	local object = table.remove(self.UndoStack, #self.UndoStack)
	table.insert(self.RedoStack, object)
	
	return object
end

-- Add the last value in the redo stack
function Stack:Redo()
	if self:IsEmpty(self.RedoStack) then
		return nil
	end
	
	local object = table.remove(self.RedoStack, #self.RedoStack)
	table.insert(self.UndoStack, object)
	
	return object
end

return Stack

The link I provided to the documentation only uses 1 table, which is great for undoing things, however, in order to redo things there needs to be a place to store all of the values we might want to redo, which is where the second table comes in.

I have 2 tables, self.UndoStack and self.RedoStack. The self.UndoStack table contains all of the pushed values that are initially pushed to the stack, using Stack:Push(). The self.RedoStack table contains all of the undone values.

Stack.new() - Creates a new Stack object
Stack:Push() - Inserts the given value into self.UndoStack
Stack:IsEmpty() - Checks if the given stack is empty. Returns either true or false
Stack:Undo() - Removes the last value from self.UndoStack and inserts it into self.RedoStack
Stack:Redo() - Removes the last value from self.RedoStack and inserts it back into self.UndoStack

If this is being ran on the server and is per-player (Like in my case) you can use the following process:

Server Script

local Stack = require(modules.Stacks)
local stacks = {}

-- Get or create a stack for the player (Ensures only 1 stack is used)
local function GetStack(player: Player)
	local stack = stacks[player.Name]
	
	if stack == nil then
		stack = Stack.new()
		stacks[player.Name] = stack
	end
	
	return stacks[player.Name]
end

-- Push the placed object to the stack on placement
remotes.Build.OnServerEvent:Connect(function(player, object)
    local stack = GetStack(player)
    stack:Push(object)
end)

-- Get the last object the player placed and parent it in ReplicatedStorage
remotes.Undo.OnServerEvent:Connect(function(player)
	local stack = GetStack(player)
	local object = stack:Undo()
	if object ~= nil then object.Parent = ReplicatedStorage.OldItems end
end)

-- Get the last object that was undone and parent it back to the plot
remotes.Redo.OnServerEvent:Connect(function(player)
	local stack = GetStack(player)
	local object = stack:Redo()
	if object ~= nil then object.Parent = Workspace.Plot.PlotObjects end
end)

This post is meant for anyone who is just now finding this topic. Hopefully it will help you!