Ah yes, I had accidentally hit enter too early, and couldn't edit the question. My question was about the UndoRedo class, which I was trying to use. After I gave up on it in frustration, and implemented something similar. I had forgotten that I had tried to post a question about it.
After I implemented Ste's In-Game Level Editor Tutorial (which can be found at https://www.youtube.com/playlist?list=PL2a2FUOhjDzv35DHi_sacCq8FS122Q2Gw ), I wanted to try to implement UndoRedo. What I ultimately implemented was this:
#An extended version of Editor_Object from Ste's In-Game Editor Tutorial.
extends Node2D
var can_place = true
var is_panning = true
var do_save = false
onready var level = get_node("/root/Main/Level")
onready var editor = get_node("/root/Main/cam_container")
onready var editor_cam = editor.get_node("Camera")
onready var popup : FileDialog = get_node("/root/Main/ItemSelect/FileDialog")
onready var tile_map : TileMap = level.get_node("TileMap")
var cam_spd = 10
var current_item
#Undo Redo Stuff
var doList = []
var doPointer = -1
func _ready():
editor_cam.current = true
pass # Replace with function body.
# warning-ignore-all:unused_argument
func _process(delta):
global_position = get_global_mouse_position()
if Input.is_action_just_pressed("undo"):
Undo()
if Input.is_action_just_pressed("redo"):
Redo()
if !Global.place_tile:
if !Global.filesystem_shown:
if current_item != null and can_place and Input.is_action_just_pressed("mb_left"):
Action_PlaceInstance(get_global_mouse_position(), current_item)
else:
if !Global.filesystem_shown:
if can_place:
var mouse_pos = tile_map.world_to_map(get_global_mouse_position())
if Input.is_action_pressed("mb_left"):
Action_PlaceTile(mouse_pos, Global.current_tile)
if Input.is_action_pressed("mb_right"):
Action_PlaceTile(mouse_pos, -1)
move_editor()
if Input.is_action_pressed("save"):
Global.filesystem_shown = true
do_save = true
popup.mode = 4
popup.show()
if Input.is_action_pressed("load"):
Global.filesystem_shown = true
do_save = true
popup.mode = 0
popup.show()
is_panning = Input.is_action_pressed("mb_middle")
func move_editor():
if !Global.filesystem_shown:
var xMove = int(Input.is_action_pressed("d")) - int(Input.is_action_pressed("a"))
var yMove = int(Input.is_action_pressed("s")) - int(Input.is_action_pressed("w"))
editor_cam.global_position.x += xMove * cam_spd
editor_cam.global_position.y += yMove * cam_spd
func _unhandled_input(event):
if !Global.filesystem_shown:
if event is InputEventMouseButton:
if event.is_pressed():
if event.button_index == BUTTON_WHEEL_UP:
editor_cam.zoom -= Vector2(0.1,0.1)
if event.button_index == BUTTON_WHEEL_DOWN:
editor_cam.zoom += Vector2(0.1,0.1)
if event is InputEventMouseMotion:
if is_panning:
editor.global_position -= event.relative * editor_cam.zoom
func save_level():
var toSave : PackedScene = PackedScene.new()
tile_map.owner = level
toSave.pack(level)
ResourceSaver.save(popup.current_path + ".tscn", toSave)
func load_level():
var toLoad : PackedScene = PackedScene.new()
toLoad = ResourceLoader.load(popup.current_path)
var this_level = toLoad.instance()
get_parent().remove_child(level)
level.queue_free()
get_parent().add_child(this_level)
tile_map = get_parent().get_node("Level/TileMap")
level = this_level
func _on_FileDialog_confirmed():
if popup.mode == 4:
save_level()
elif popup.mode == 0:
load_level()
func _on_FileDialog_hide():
Global.filesystem_shown = false
do_save = false
func Undo():
if !doList.empty() and doPointer >= 0:
var actionDict = doList[doPointer]
match actionDict["action"]:
"PlaceTile":
place_tile(actionDict["undoVector"], actionDict["undoIndex"])
"PlaceInstance":
erase_object(actionDict["undoId"])
doPointer -= 1
func Redo():
if !doList.empty() and doPointer < doList.size() -1:
var actionDict = doList[doPointer]
match actionDict["action"]:
"PlaceTile":
place_tile(actionDict["redoVector"], actionDict["redoIndex"])
"PlaceInstance":
var new_item = place_object(actionDict["redoVector"], actionDict["redoObject"])
actionDict["undoId"] = new_item
doPointer += 1
func NewEndOfDoList():
if (doPointer +1) != doList.size():
doList.resize(doPointer +1)
func Action_PlaceTile(pos, tileIndex):
var whatsAtTheTileNow = tile_map.get_cell(pos.x, pos.y)
if whatsAtTheTileNow != tileIndex:
NewEndOfDoList()
#create dictionary for doList
var placeTileDict = {
"action" : "PlaceTile",
"redoIndex" : tileIndex,
"redoVector" : pos,
"undoIndex" : whatsAtTheTileNow,
"undoVector" : pos,
}
doList.append(placeTileDict)
doPointer += 1
place_tile(pos, tileIndex)
func place_tile(pos, tile_index):
tile_map.set_cell(pos.x, pos.y, tile_index)
tile_map.update_bitmask_region(pos + Vector2(-2,-2), pos + Vector2(2, 2))
func Action_PlaceInstance(pos, object):
NewEndOfDoList()
#create dictionary for doList
var new_item = place_object(pos, object)
var placeInstanceDict = {
"action" : "PlaceInstance",
"redoObject" : object,
"redoVector" : pos,
"undoId" : new_item,
}
doList.append(placeInstanceDict)
doPointer += 1
func Action_RemoveInstance():
#do it like Action_PlaceInstance but in reverse.
#don't forget to update _process!
#maybe also move the now quite large "if !Global.place_tile:" section
#into it's own function? call it handle_mouse_input() or something
#like that.
pass
func place_object(pos, object):
var new_item = object.instance()
level.add_child(new_item)
new_item.owner = level
new_item.global_position = pos
return new_item
func erase_object(id):
id.queue_free()
Now of course you're wondering why I couldn't do that with UndoRedo. I could easily add and remove tiles, because tile ids are just integer values, but I couldn't figure out how to undo object placement using UndoRedo that didn't also permanently undo them without being able to redo them. As you can see above, add_do_reference and add_undo_reference return void but my place_object function returns the id of the instance.
Anyway, I think the problem is a lack of tutorial for UndoRedo in the official documentation. I'm not the only one who is dissatisfied with UndoRedo, but that may be because the intended implementation is not clear from the current documentation by itself. I know the Godot editor itself uses UndoRedo, but I couldn't tell you how.