TileMap Transforms: combining flipping and rotatining cell/tile isn't working as expected

Godot Version

4.4

Question

My game requires of me to first apply rotations, and then flips to a tile. While my solution works for most of them, there are cases where the flipping is incorrect or it simply doesn’t work. What works: Rotations by themselves. Flips and rotations combined give incorrect results

What I tried is this:

# Rotation methods
enum TileTransform {
		ROTATE_0 = 0,
		ROTATE_90 = TileSetAtlasSource.TRANSFORM_TRANSPOSE | TileSetAtlasSource.TRANSFORM_FLIP_H,
		ROTATE_180 = TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V,
		ROTATE_270 = TileSetAtlasSource.TRANSFORM_TRANSPOSE | TileSetAtlasSource.TRANSFORM_FLIP_V,
	}

func get_tile_transform(properties: String) -> int:
	var rotation = int(properties[0]) % 4  # 0-3
	var flip = int(properties[1])          # 0-3
	var transform = 0

	match rotation:
		0: transform = 0
		1: transform = TileTransform.ROTATE_90  # ROTATE_90
		2: transform = TileTransform.ROTATE_180     # ROTATE_180
		3: transform = TileTransform.ROTATE_270  # ROTATE_270

	# 2. Apply additional flips
	if flip & 1:  # Horizontal flip (codes 1 or 3)
		transform |= TileSetAtlasSource.TRANSFORM_FLIP_H
	if flip & 2:  # Vertical flip (codes 2 or 3)
		transform |= TileSetAtlasSource.TRANSFORM_FLIP_V

	return transform

And this is what used to work perfect, before I switched from using sprite2d’s as tiles to a tilemap:

var rotation_keys = [0, 90, 180, 270]

# Applies tile properties (rotation, flip, etc.) based on tile properties string
func apply_tile_properties(sprite, properties: String):
	var xScale = 1
	var yScale = 1
	var collision = int(properties[2])
	
	var rotation_code = int(properties[0])
	if rotation_code in range(0, rotation_keys.size()):
		sprite.rotation_degrees = rotation_keys[rotation_code]  # Apply rotation based on the index

	var flip = int(properties[1])  # Second digit for flipping
	
	if flip == 1 or flip == 3:
		xScale = -1  # Flip horizontally
	if flip == 2 or flip == 3:
		yScale = -1  # Flip vertically

	# Apply the scale transformation to flip the sprite
	sprite.scale = Vector2(xScale, yScale)
	
	# apply collision shapes
	apply_collision_shape(sprite, collision)

What am I doing wrong?

The solution to this was using a custom transform table, ignoring the godot’s incorrect order.

const TRANSFORM_FLIP_H = TileSetAtlasSource.TRANSFORM_FLIP_H  # 1
const TRANSFORM_FLIP_V = TileSetAtlasSource.TRANSFORM_FLIP_V  # 2
const TRANSFORM_TRANSPOSE = TileSetAtlasSource.TRANSFORM_TRANSPOSE

const TRANSFORM_TABLE = [
	# Rotation 0°
	[
		0,                          # No flip
		TRANSFORM_FLIP_H,            # H flip
		TRANSFORM_FLIP_V,            # V flip
		TRANSFORM_FLIP_H | TRANSFORM_FLIP_V  # Both flips
	],
	# Rotation 90°
	[
		TRANSFORM_TRANSPOSE | TRANSFORM_FLIP_H,  # 5 (90°)
		TRANSFORM_TRANSPOSE | TRANSFORM_FLIP_H | TRANSFORM_FLIP_V,  # 7 (90° + H flip)
		TRANSFORM_TRANSPOSE,                      # 4 (90° + V flip → 270°)
		TRANSFORM_TRANSPOSE | TRANSFORM_FLIP_V    # 6 (90° + both flips)
	],
	# Rotation 180°
	[
		TRANSFORM_FLIP_H | TRANSFORM_FLIP_V,  # 3 (180°)
		TRANSFORM_FLIP_V,                     # 2 (180° + H flip)
		TRANSFORM_FLIP_H,                     # 1 (180° + V flip)
		0                                      # 0 (180° + both flips)
	],
	# Rotation 270°
	[
		TRANSFORM_TRANSPOSE | TRANSFORM_FLIP_V,  # 6 (270°)
		TRANSFORM_TRANSPOSE,                      # 4 (270° + H flip → 90°)
		TRANSFORM_TRANSPOSE | TRANSFORM_FLIP_H | TRANSFORM_FLIP_V,  # 7 (270° + V flip)
		TRANSFORM_TRANSPOSE | TRANSFORM_FLIP_H    # 5 (270° + both flips)
	]
]

func get_tile_transform(properties: String) -> int:
	var rotation = int(properties[0]) % 4
	var flip = int(properties[1]) % 4
	return TRANSFORM_TABLE[rotation][flip]

Use case when drawing a tile:

var transform = get_tile_transform(tile_data["properties"])
		
		# Set cell with transform
		tilemap.set_cell(
			cell_pos,
			source_id,
			Vector2i(0, 0), 
			transform  # Apply the transform
		)

I think this should be addressed in future updates honestly. Add simple methods like “rotate_90” and take the order of flipping/rotating operations into account.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.