2D Side Scrolling Pathfinding Using TileMapLayer In Procedurally Generated Worlds

Help Programming Navigation 2d game godot-4 tilemap gdscript

Godot Version

Godot v4.3

What I want to make

Hi, I have been trying to make a pathfinding system in 2D, that is compatible with TileMapLayers. This strictly has to be for a side scrolling style 2D game, rather than a top down. My map is procedurally generated on top of that, which makes it even harder (It is a cave-based game). Sorry if making something like this is completely obvious for you, but I just started godot development a few days ago, and this is the part that I have been stuck on for 2-3 days. (I do have experience with coding and game development in general, but this is a huge roadbump for me for whatever reason)

What I tried using

I tried using Navigation Layers, (These don’t work, because only the cave’s walls are filled with grids) AStar2Ds, (These aren’t optimal because the map can be manipulated by the players by mining + building, and the points take too long to generate) NavigationPolygons (These don’t work because I can’t form them in the map’s shape), AI and watching every single tutorial i could find, but none helped.

Question

If you have any idea how this could be made, then please share your knowledge with me, the help would be much appriciated. Again, the requirements for this pathfinding system:

  • 2D Side scrolling
  • Map is a TileMapLayer, procedurally generated, and can be manipulated (cave-based)
  • The walkable places are the inverse of filled gridpoints in the TileMapLayer
  • Must be proper AI (the path to the player is generated in advance, which is followed by the CharacterBody2D point by point)
PS.: My main language is not english, so I’m sorry if something isn’t clear, feel free to ask whatever.

Thank you for your help in advance.

If your game just

one time generated level before next level

no additional generation when player on map

NOT an isometric tile map

TileMapLayer is OK for this purpose. BUT if its will be made as generated chunks that join your map - this will lead to noticeable annoying lag spikes. Procedurally generated map with chunks are terrible thing when tried with TileMapLayer because of expensive method set_tile() and becomes even worse if it adds tiles with navigation

Well the map is completely using a TileMapLayer, which can be manipulated by the player with the set_cell() function. But the map generation is using set_cells_terrain_connect() function, along with an array of tile positions which are generated using a FastNoiseLite with a random seed. This process is pretty fast for small to mid scale maps, but when it gets bigger things do get a little slower. So now that we know the TileMapLayer isn’t really good, how else do you think I could go on about generating a world in the same style, but much more efficently?

Here is the current map generation script for anyone wondering:

var WALL_CAP = 0.3

# Call in _ready()
func generate_world():
	# Noise generator
	var terrain_noise = FastNoiseLite.new()
	terrain_noise.fractal_octaves = 2.85
	terrain_noise.frequency = 0.05
	terrain_noise.seed = randi()

	# Generate map and save noise value
	var cells_tile = []

	for x in MAP_SIZE.x:
		for y in MAP_SIZE.y:
			var terrain_value = terrain_noise.get_noise_2d(x, y)

			# Fill tiles according to noise data
			if terrain_value < WALL_CAP:
				cells_tile.append(Vector2(x, y))

	# Apply terrain to tilemap
	tilemap.set_cells_terrain_connect(cells_tile, 0, 0)

I may say that you may avoid to use it if you familiar with classes like Geometry2D (for tile positions and tile checks), Image class related funcs to get terrain rendered texture runtime. VisibleOnScreenNotifier2D to get signals when new chunk has to be generated/freed draw_colored_polygon()/draw_texture() to draw chunks and how to use uv map(may skip if map not isometric) - when I used it I struggled a lot with RenderingServer uncleaned texture memory data since new texture rendered/freed every time when new chunk enters/leaves screen. I used RS to draw 64x64 tiles chunks (tile 128x64 px) in one single texture for one chunk. I said what I used, mb you got inspired or even invent your own way to deal with it.

There no easy way if you want to avoid TileMapLayer.

So I might have to stay with the TileMapLayers, since they’re much easier for me to understand and i have already read the whole documentation for it. Which made me think even deeper: how could I use them efficently, to avoid lagspikes and implement AI navigation too. This did make me figure out another way of generating: load the first chunk in the main thread, then load the others in the background so that the loading is much faster. For the navigation, i came up with an idea for that too.

How the generation system would work

These chunks would have a given size and a given max amount, to act like borders for the world. The first chunk would load in the _ready() function and when this generation is done, generate the rest of the chunks in the background, to let the player play in the meantime. I’m not sure if this would cause lagspikes tho. (Having to render every cell at once, since i don’t know is godot has occlusion culling)

How the pathfinding system would work

Along with the map generation, another array of cell positions would be generated: the walkable areas. This is the inverse of the used cells in the TileMapLayer. The reason I think this would be better than AStar2D is because when i tried generating points with A* a huge lagspike would occur, which could get annoying after a while. Since the walkable areas would be an array of Vector2s, it would be much faster. Getting a path between the player and the entity will be basically just copying the AStar2D algorithm: The algorithm calculates costs for each neighboring cell to prioritize the accuracy of finding the right path, combining the actual cost from the start (G-cost) and the estimated cost to the target (H-cost) into an overall priority score (F-cost). It records the path as it moves, and once the target is reached, it backtracks to reconstruct the shortest route for the AI to follow. If the map changes, the algorithm updates the grid dynamically, ensuring efficient and accurate pathfinding in a modifiable environment.

If you have any other ideas however this would be even better, share with me please.

I’ll update when im done or I’m stuck.

PS.: I will provide the main functions to make others jobs easier.