1
\$\begingroup\$

If I have a tilemap that is 4x8, positioned at 0,0 in the editor as follows:

4x8 tilemap positioned at 0,0

What code would I use such that it ends up "centered" when my game runs?

I'm hoping for an end result that would look like this:

Demonstration of 4x8 tilemap centered by moving the tilemap to -24x-24

As you can see, the tilemap has been moved left 24 pixels and up 24 pixels. This example is manually done, but I'd like to be able to do it using the Godot APIs so that tilemaps of different dimensions can be centered.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

My current answer makes the following assumptions about your TileSet:

  • The Tile Shape is "Isometric".
  • The Tile Layout is "Diamond Right" or "Diamond Down".
  • The Tile Offset Axis is "Horizontal Offset" (default).
  • You aren't going to move any layer using move_layer().

Different settings may require changing some formulas below to consider different layout orientations or topologies.

Find the TileMap corners

Before computing the centre of mass of the TileMap, we need to know its current extent:

var used_rect: Rect2 = get_used_rect() 

This describes where the top-leftmost cell is (via used_rect.position) and the size of the current map (via used_rect.size), in your case, 8x4. Now, we can work out the position of all corners in map space and convert them into TileMap local space:

var map_corners: Array = [ rect.position, rect.position + rect.size * Vector2.RIGHT, rect.position + rect.size * Vector2.DOWN, rect.position + rect.size ] var local_corners: Array = map_corners.map( func(v): return map_to_local(v)) 

However, we are counting an extra cell along each layout axis (I'm using a custom script for drawing. The Godot icon is at the (0,0) position for reference, and the blue dot is the top-leftmost corner of the layout):

Before

We can fix this by subtracting one from each direction. After we get the used rectangle, and before computing the map_corners array, we do:

used_rect.size -= Vector2.ONE 

Now, everything is fine:

After

Find the TileMap centroid

The centre of the TileMap is the average of the corner positions in local space. The following line uses reduce() to accumulate the sum of all corner coordinates, and finally divides it by their number to compute the mean position:

var local_centroid: Vector2 = local_corners.reduce( func(accum, corner): return accum + corner, Vector2.ZERO) / len(local_corners) 

This is indeed the centroid of the TileMap in local coordinates (red dot):

enter image description here

Move the TileMap

This turned out to be very easy. At first, I started thinking about inverse transforms and stuff... This line works perfectly and is independent of any Node hierarchy involved:

global_position = -centroid 

enter image description here

Put it all together

Here's the final class and the function to centre the TileMap:

extends TileMap func _ready() -> void: recentre() func recentre() -> void: var used_rect: Rect2 = get_used_rect() used_rect.size -= Vector2.ONE var map_corners: Array = [ rect.position, rect.position + rect.size * Vector2.RIGHT, rect.position + rect.size * Vector2.DOWN, rect.position + rect.size ] var local_corners: Array = map_corners.map( func(v): return map_to_local(v)) var local_centroid: Vector2 = local_corners.reduce( func(accum, corner): return accum + corner, Vector2.ZERO) / len(local_corners) global_position = -centroid 
\$\endgroup\$
2
  • \$\begingroup\$ Is there any significant change if I'm using diamond down? \$\endgroup\$ Commented May 8, 2024 at 13:48
  • \$\begingroup\$ @AlexanderTrauzzi Hey, I tested my solution for the "Diamond Down" layout and my solution works without requiring any changes: it only differs in tile orientation, but the underlying topology is the same. I updated my answer accordingly :) \$\endgroup\$ Commented May 11, 2024 at 8:48

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.