- Edited
I'm making this high-level guide to help save other newbies some headache for procedurally generating maps with huge amounts of tiny tiles (1,000,000+).
Essentially I am working on a 2D dungeon platformer with procedurally generated, high-fidelity dungeon rooms. There are plenty of great tutorials for this already so I will save my breath on this. And while TileMaps work great, huge performance issues arise while generating maps with huge amounts of tiles - the initial generation step takes forever but also low-FPS occurs when playing the map. So you might be wondering, why not just load/unload the TileMap in chunks as you go? The issue with this approach is that I'm using tiny tiles (4x4 px) in large amounts to give the map a high-fidelity look; and even in relatively small chunks, I was still having performance problems.
My solution: use a combination of TileMap + MultiMesh Instances.
The thought process is that most tile objects do not need to have individual collision/navigation layers, so we can greatly reduce the amount of GPU draw() calls by having these display tiles share resources and be rendered as a single-object. MultiMeshes allow us to render large amounts of meshes (individual tiles in this case) very efficiently.
So, in implementation, I still use TileMaps for the floor tiles with collision layers, but use tile-specific MultiMeshes for everything else. My code looks like this:
Init the MultiMesh-tile map:
@onready var multimesh_tiles = {
$WaterMultiMesh: [],
$GrassMultiMesh: [],
$JungleMultiMesh: [],
}
Where I translate noise to tiles:
#Other Biomes
else:
#plains
if moist < 0.9 and temp < 0.6:
~~set_cell_tile(pos, random_tile(biome_data["plains"]))~~
self.multimesh_tiles[$GrassMultiMesh].append(pos)
#jungle
elif moist >= 0.4 and temp > 0.6:
~~set_cell_tile(pos, random_tile(biome_data["jungle"]))~~
self.multimesh_tiles[$JungleMultiMesh].append(pos)
Where I set the transforms of each individual MultiMesh instance:
func set_multimesh_instance_transforms():
for mm in self.multimesh_tiles:
mm.multimesh.instance_count = len(self.multimesh_tiles[mm])
for i in mm.multimesh.instance_count:
mm.multimesh.set_instance_transform_2d(
i, Transform2D(0.0, map_to_local(self.multimesh_tiles[mm][i]))
)
https://docs.godotengine.org/en/stable/tutorials/performance/using_multimesh.html#multimesh-example
https://docs.godotengine.org/en/stable/classes/class_multimesh.html#class-multimesh-method-set-instance-transform-2d