For quite a while, I've been wondering how one might go about making an undo/redo list for something like an editor, and last night I had a revelation about it!
When I do buy AppGameKit 2 here in the nearish future, one of the first things I want to do is start coding a tile map editor for my game that I'll want to make. One of the features I'll want the most is the ability to have every action I do recorded (much like the history list in GIMP/Photoshop), and to be able to undo/redo whatever my last action was!
I can't take the credit for this method, as I know tiresius was using it in his level editor for the Marble Maze Construction Set, but I figured out how to implement it in my own way at 3am last night.
The best part is that it can be easily expanded to record actions of any kind. I'm sure there's a better method overall, but this is the program I came up with.
left click to "place a tile", right click to "move" it, press Z for undo and R for redo!
rem this is all a test!
SetVirtualResolution(1024, 768)
SetWindowSize(1024, 768, 0)
SetWindowTitle("A Simple Undo/Redo and History List!")
rem define custom type for storing history list
type actionHistoryType
actionType as integer
actionName as string
endtype
global historyList as actionHistoryType[]
rem create arrays for unbdo/redo list using integers
global undoActionList as integer[]
global redoActionList as integer[]
rem create some text objects to show length of undo/redo lists
CreateText(1, "")
CreateText(2, "")
SetTextSize(1, 25)
SetTextSize(2, 25)
SetTextColor(1, 255, 255, 255, 255)
SetTextColor(2, 255, 255, 255, 255)
SetTextAlignment(1, 0)
SetTextAlignment(2, 0)
SetTextPosition(1, 0, 768-100)
SetTextPosition(2, 0, 768-75)
do
rem get the length of the history list
arrayLength = historyList.length
undoTextLength = undoActionList.length
redoTextLength = redoActionList.length
SetTextString(1, "UNDO LIST LENGTH: " + Str(undoTextLength))
SetTextString(2, "REDO LIST LENGTH: " + Str(redoTextLength))
rem print info out
if arrayLength >= 0
for i = 0 to arrayLength
print ( historyList[i].actionName )
next i
elseif arrayLength < 0
print("Nothing in history...")
endif
rem if history list is greater than 15 elements delete first one
if arrayLength > 14 then historyList.remove(0)
rem just some test controls
if GetRawMouseLeftPressed() = 1
CheckNewAction()
AddToHistory("[ACTION] - Tile Placed", 1)
AddToUndoList(1)
endif
if GetRawMouseRightPressed() = 1
CheckNewAction()
AddToHistory("[ACTION] - Tile Moved", 2)
AddToUndoList(2)
endif
rem undo the last action, whatever that may be!
if GetRawKeyPressed(90) = 1 and undoActionList.length >= 0
UndoLastAction()
endif
if GetRawKeyPressed(82) = 1 and redoActionList.length >= 0
RedoLastAction()
endif
rem refresh screen
sync()
loop
rem check if a new action was performed in middle or end of undo list
function CheckNewAction()
rem delete all redo elements if a new action is performed
if undoActionList.length <> 0
redoActionList.length = -1
endif
endfunction
rem add action to history function
function AddToHistory(s as string, i as integer)
rem set a local history variable
local history as actionHistoryType
rem add this action to the history list
history.actionName = s
history.actionType = i
historyList.insert(history)
endfunction
rem add action to undo list
function AddToUndoList(i as integer)
if undoActionList.length > 14 then undoActionList.remove(0)
undoActionList.insert(i)
endfunction
rem add action to redo list
function AddToRedoList()
rem check to see if maximum redo is reached
if redoActionList.length > 14 then redoActionList.remove(0)
rem grab the last index of the undo list
lastElement = undoActionList.length
rem grab the action that was performed in the undo
lastUndoAction = undoActionList[lastElement]
rem add to redo list
redoActionList.insert(lastUndoAction)
endfunction
rem undo last action function
function UndoLastAction()
rem find the last element in the undo list
lastElement = undoActionList.length
rem find the last action type
lastActionType = undoActionList[lastElement]
rem determine what action to take
select lastActionType
case 1:
rem undo tile placement and record in history list
AddToHistory("[ACTION] - UNDO Tile Placed", 1)
rem copy undo action to redo list!
AddToRedoList()
rem remove last array element
undoActionList.remove()
endcase
case 2:
rem undo move tile and record to history list
AddToHistory("[ACTION] - UNDO Tile Moved", 2)
rem copy undo action to redo list!
AddToRedoList()
rem remove last array element
undoActionList.remove()
endcase
endselect
endfunction
rem redo last action function
function RedoLastAction()
rem find last element that was undone
lastElement = redoActionList.length
rem find last action type
lastActionType = redoActionList[lastElement]
rem determine what action to take
select lastActionType
case 1:
rem redo tile placement
AddToHistory("[ACTION] - REDO Tile Placed", 1)
rem copy redo action to undo list!
AddToUndoList(1)
rem remove last array element
redoActionList.remove()
endcase
case 2:
rem redo tile move
AddToHistory("[ACTION] - REDO Tile Moved", 2)
rem copy redo action to undo list!
AddToUndoList(2)
rem remove last array element
redoActionList.remove()
endcase
endselect
endfunction
Now, it's not perfect. For example, if you undo an action and then perform an action in the middle of undo list, the redo list will still contain all of the original actions and won't know what to do. What should happen is that if the undo list has a length shorter than that of the redo list and a new action takes place, the redo list should be deleted, and that would solve that problem. I'm too lazy to figure that out though
EDIT: I thought of a way to fix the aforementioned problem! It seems to work very well so far, give it a shot!
Anyway, enjoy! Hope this helps somebody
Meh game development blaugh!