Godot Tip: Move 2D pathfinding to separate thread

by Carl Furrow — on  ,  , 

As I was building out my latest game, I began to add dozens of enemies that needed to gradually move towards the player via a simple 2D pathfinding algorithm. When I play-tested the level, my framerate dropped quickly from 60fps down to 10-20fps. I knew exactly what it was, and it was something I was hoping to fix eventually: all the enemies on the level calculate a 2D path to the player every ~400ms or so. And all those calculations are happening in the main game thread, which is handling rendering, physics, etc. So each time an enemy needed to update their path to the player, the rest of the game had to pause for a fraction of a second. Obviously, this is terrible. Way too much happening in that single thread.

The solution: Add another thread!

Image from imgur

I needed to move the calculation of each enemy’s path to a thread, and better yet, if we could move all of the calculations to a single thread, and update all enemies’ paths that needed updating, that’d be ideal.

I found an example of what I wanted to do on Godot engine’s forum. User Skipperro posted a code sample of how one may move the pathfinding code to a thread:

As you can see, Skipperro makes a new thread (called pathfinder) to do the work for all enemies in a scene. The thread is responsible for

  1. checking if the thread is currently doing work. If so, do not attempt to do more work.
  2. finding all enemies that need a path:
    1. the enemy is not dead
    2. the enemy’s last update was > 1.0 second ago

Then, the code pushes details about the enemies that need updating onto an array, enemies_to_command

  1. the enemy node’s name
  2. the enemy node’s global position
  3. the player’s global position

Once the code has collected all the details about enemies needing updates, it pushes those details to the thread. The thread simply runs a function:

Then, _async_pathfinder, loops through the incoming data, and for each entry:

  1. it looks up the enemy node by name in the current scene
  2. calls nav.get_simple_path(start, nav.get_closest_point(end), true) to calculate the 2D path from start (the enemy) to end (the player)
  3. sends the path as an array of Vectors to the enemy node

When it’s done, it lets the Godot engine know the thread can be put to sleep and wait for further instructions by calling pathfinder.wait_to_finish().

I made some slight modifications to the script, mostly to fit my own use case.

  1. I based the update time per enemy to every 400ms
  2. I removed the first returned point in the calculated path, since the first point is the starting point of the enemy at the time of calculating the new path.

With that change, my frames per second was back up to 60fps (or better).

 Want to get updates in your inbox? Sign up to receive the newsletter!
Carl Furrow's photo Author

Carl Furrow


Addicted to learning and looking to master the art of solving problems through writing code, while occasionally yelling at computer screens.