TileMapLayers are very laggy with a lot of large tiles

Godot Version

v4.5.stable.custom_build

Context

I am populating multiple TileMapLayers with tiles at runtime, which are specified in a JSON. The TileSetSources are also created at runtime to allow people to swap out or add tiles outside of the Godot editor.
Each cell also has a “height” value associated with it, which just offsets the texture upwards by some amount.

This height system is the part I did as a custom build. (The performance impact is essentially zero because I’m just changing the draw position of every cell slightly, without any calculations)

Sort of how it works, in case anyone's interested

I added the function set_cell_height to the TileMapLayer class:

// tile_map_layer.cpp

void TileMapLayer::set_cell_height(const Vector2i &p_coords, const int p_height) {
	HashMap<Vector2i, CellData>::Iterator E = tile_map_layer_data.find(p_coords);
	ERR_FAIL_COND_MSG(!E, "TileMapLayer has no cell at coords " + stringify_variants(p_coords));

	if (E->value.height == p_height) {
		return;
	}

	E->value.height = p_height;

	// Make the given cell dirty.
	if (!E->value.dirty_list_element.in_list()) {
		dirty.cell_list.add(&(E->value.dirty_list_element));
	}
	_queue_internal_update();

	used_rect_cache_dirty = true;
}

And hijack the struct “CellData” to store the height

// tile_map_layer.h

struct CellData {
	Vector2i coords;
	TileMapCell cell;
	int height; // this is new

	// ...
};

Then I subtract the height from the draw position in rendering_update

// tile_map_layer.cpp

void TileMapLayer::_rendering_update(bool p_force_cleanup) {
	// ...
	if (!forced_cleanup) {
		// ...
		for (SelfList<RenderingQuadrant> *quadrant_list_element = dirty_rendering_quadrant_list.first(); quadrant_list_element;) {
			// ...
			if (has_a_tile) {
				// ...
				for (SelfList<CellData> *cell_data_quadrant_list_element = rendering_quadrant->cells.first(); cell_data_quadrant_list_element; cell_data_quadrant_list_element = cell_data_quadrant_list_element->next()) {
					// ...
					// instead of just const Vector2 local_tile_pos = tile_set->map_to_local(cell_data.coords)
					const Vector2 local_tile_pos = tile_set->map_to_local(cell_data.coords) + Vector2(0, height_enabled * -cell_data.height);
}

As you can see, it’s not that complicated and definitely not that laggy

Edit: this is another solution to a question I posted before: Tilemap with 3rd "height" dimension

Question

As you can see in the image, I need to draw cliffs below all the offset tiles to get rid of any gaps. (The cliffs are on a separate TileMapLayer) Otherwise it would look like this:


So I’ve got these huge cliff tiles under all the tiles:

They need to be this big for every tile (1024 pixels tall) because I want to allow very big heights. And I can’t make 1023 smaller variants because that would be too expensive to generate at runtime.

The problem is, with a lot (a few hundred) of these visible, the game becomes really laggy.

These are the two solutions I can think of:

  • Hide cliff tiles that are completely hidden by any other tiles
  • Somehow optimise TileMapLayers for large tiles

But I don’t know how to do either. Is there a way to do this, or is there maybe another option?

The way I would solve that problem, based on what I see, is make a 2.5D game. Have the environment be 3D and the players, monsters, etc be 2D. It would seem to simplify your map generation problems. But I don’t know if that would cause you problems other places.

If I understand correctly, what you are suggesting is having an orthographic camera at an angle so it looks like the isometric setup I have right now.
But then the textures would be squashed by the perspective and their height would need to be adjusted. Also, it would be additional effort to make tiles that don’t fit inside the perfect diamond shape of a tile.
I don’t think it would be worth going through all this, to be honest

I might just be misunderstanding your answer, but this is how I see it :smiley:

Yeah that was my general thought. I get it might not be the solution for you. Just the first idea that came to me. :slight_smile:

This doesn’t have to be the case. If you have a fixed camera angle and you set the UVs correctly on your tile polygons, the warping will be eliminated in the math and the diamond textures should look onscreen exactly as they do in the source texture.

Sorry sorry !! I know this isn’t helpful but that looks nice!

Thank you :smiley: I actually didn’t do the art, only the “implementation”

Oh, that is true…

I’m going to try out the idea of @dragonforge-dev and reply back whether it works. It feels very wrong to do it this way (moving to 3D), but I can’t think of any game-breakers right now so it’s definitely worth a try

Even if you didn’t do the art, the height thing you did and the art still looks nice!