Rotation of Multiple TileMapLayers in an isometric view in Godot 4.3

Godot 4.3

Hello all. I am trying to rotate this simple map that is composed of two tilemaplayers in a way that preserves their relationship to each other. I looked at a recent topic that was similar to this, but it only rotated a single tilemaplayer. I have a global script that has all the rotation logic and another script that is linked to the Node2D node that is housing the tilemaplayers. However, when I rotate it, the relationship is broken and the top layer rotates in a weird way with respect to the bottom layer. After four rotations, the map returns to its original position. I have attached pictures of the rotations and the scripts.

Screenshot (83)
Screenshot (84)
Screenshot (85)
Screenshot (86)

Global Code:
extends Node

Function to recursively search for a node by name

func find_node_recursive(base_node: Node, name: String) → Node:
for child in base_node.get_children():
if child.name == name:
return child
var result = find_node_recursive(child, name)
if result:
return result
return null

Function to get a specific Map node by its unique name

func get_region_map(region_name: String) → Node2D:
var node = find_node_recursive(get_tree().root, region_name)
if node and node is Node2D:
return node as Node2D
else:
print("Map node not found or invalid node type: ", region_name)
return null

Calculate the global map boundaries for all TileMapLayers

func get_map_boundaries(map: Node2D) → Rect2:
var min_pos = Vector2(INF, INF)
var max_pos = Vector2(-INF, -INF)

for layer in map.get_children():
	if layer is TileMapLayer:
		var used_cells = layer.get_used_cells()
		for cell in used_cells:
			# Adjust for layer's global position
			var cell_global_pos = cell + Vector2i(layer.position)
			min_pos.x = min(min_pos.x, cell_global_pos.x)
			min_pos.y = min(min_pos.y, cell_global_pos.y)
			max_pos.x = max(max_pos.x, cell_global_pos.x)
			max_pos.y = max(max_pos.y, cell_global_pos.y)

var size = max_pos - min_pos
return Rect2(min_pos, size)

Rotate all layers in the Map around a global center

func rotate_map(map: Node2D) → void:
# Get the global boundaries and calculate the global center of all layers
var boundaries = get_map_boundaries(map)
var global_center = boundaries.position + boundaries.size / 2.0 # Global center for all layers

# Prepare an array to hold new layers
var new_layers: Array = []

# Rotate each layer around the global center
for layer in map.get_children():
	if layer is TileMapLayer:
		# Duplicate the current layer for rotation
		var current_layer_copy = layer.duplicate()
		rotate_map_layer(current_layer_copy, global_center)  # Rotate it
		new_layers.append(current_layer_copy)

# Remove old layers and free them
for layer in map.get_children():
	if layer is TileMapLayer:
		map.remove_child(layer)
		layer.queue_free()

# Add rotated layers
for layer in new_layers:
	map.add_child(layer)

Rotate the cells in a TileMapLayer around the global center

func rotate_map_layer(tilemap_layer: TileMapLayer, global_center: Vector2) → void:
var new_cells = rotate_cell_data(tilemap_layer, global_center)
tilemap_layer.clear() # Clear the layer before setting new cells

for cell_data in new_cells:
	# Ensure new positions are set accurately
	tilemap_layer.set_cell(
		cell_data.coords, 
		cell_data.source_id, 
		cell_data.atlas_coords, 
		cell_data.alternative_tile
	)

# Recalculate global_center based on new boundaries (if needed)
var new_boundaries = get_map_boundaries(tilemap_layer)
global_center = new_boundaries.position + new_boundaries.size / 2.0

Rotate and collect all cell data for a TileMapLayer

func rotate_cell_data(tilemap_layer: TileMapLayer, global_center: Vector2) → Array:
var return_array: Array =
var used_cells = tilemap_layer.get_used_cells()

for cell in used_cells:
	var cell_global_pos = cell + Vector2i(tilemap_layer.position)  # Adjust for layer's global position
	var rotated_coords = rotate_vector_90_clockwise_around_center(cell_global_pos, global_center)
	var local_rotated_coords = (rotated_coords - tilemap_layer.position).round()  # Convert to local and round
	
	# Store the new cell data with recalculated positions
	var cell_data = CellData.new()
	cell_data.coords = local_rotated_coords
	cell_data.source_id = tilemap_layer.get_cell_source_id(cell)
	cell_data.atlas_coords = tilemap_layer.get_cell_atlas_coords(cell)
	cell_data.alternative_tile = tilemap_layer.get_cell_alternative_tile(cell)
	return_array.append(cell_data)

return return_array

Function to rotate a vector 90 degrees clockwise around the global center

func rotate_vector_90_clockwise_around_center(given_vector: Vector2, center: Vector2) → Vector2:
print(“Global Center:”, center) # Print the global center for debugging
# Calculate the vector relative to the center
var relative_vector = given_vector - center
# Rotate 90 degrees clockwise
var rotated_vector = Vector2(relative_vector.y, -relative_vector.x)
# Adjust back to global space
return rotated_vector + center

CellData class to hold cell information during rotation

class CellData:
var coords: Vector2i
var source_id: int
var atlas_coords: Vector2i
var alternative_tile: int

Node2D Code:
extends Node2D

@onready var PacificaCoast_Overworld = $“…”

Called when the node enters the scene tree for the first time.

func _ready() → void:
pass

Detect input to rotate the map

func _input(event: InputEvent) → void:
if event.is_action_pressed(“rotate_map”):
# Get the map node by name using the global script
var map_node = GlobalRotatemap.get_region_map(“PacificaCoast_Overworld_Map”)

	# Check if the map node exists
	if map_node:
		# Rotate the map using the global function
		GlobalRotatemap.rotate_map(map_node)
	else:
		print("Map node not found.")