@sent44 said: What is your "Question"?

This discussion was caught in the moderation queue, so my guess is that they planned to edit the post to include the question, but since it was caught in the queue, they couldn't edit until it was posted.

14 days later

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.

2 years later