godot: Navigation2D#get_simple_path doesn't behave properly when using per-tile navigation polygons
Godot version:
3.1.stable.official
OS/device including version:
Windows 10 (10.0.17134 Build 17134), but happens on other systems too.
Issue description:
When adding NavigationPolygonInstance
s that entirely cover tiles of a tileset, then covering a Tilemap
with said tiles, using Navigation2D
and its get_simple_path
method produces unwanted behavior (unoptimal paths that include more points than needed):
https://i.imgur.com/yn4hegV.gif
https://streamable.com/47nzc
The actual result should instead be the same as what happens when covering the whole navigable area with a single NavigationPolygonInstance
:
https://streamable.com/exk7u
Steps to reproduce:
Note that these steps can be reproduced either with the 3.1’s new tileset editor, or using the old-fashioned way (scene converted to tileset).
- Create a tileset resource with one single tile (to keep things simple).
- Add a
NavigationPolygonInstance
covering exactly that whole tile. - Create a scene with a
Navigation2D
node that includes aTileMap
node referencing that tileset. Cover the tilemap with the tile. - Add a sprite node that will serve as the moving object and place it somewhere on the map.
- Use
get_simple_path
(withoptimize
totrue
) to compute a path between the sprite and a position far away (ideally, diagonally). - The resulted path will contain unwanted steps, even though there is no obstacle and the whole area is supposed to be navigable.
Minimal reproduction project:
Notes:
- This was initially posted as a question here and someone agreed it looked like a bug (even remade his own minimal projects to find out it behaved slightly differently in 3.0.6).
- If you take KidsCanCode’s recent YouTube tutorial (that you can download from here) and simply fill the “holes” on the tilemap with the given tile, it will occur as well (that’s actually what I did for the streamable.com links above). I talked a bit with KidsCanCode on Discord and he thought that seemed suspicious.
- I’m still very much a Godot beginner, so sorry if I missed anything obvious.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 12
- Comments: 47 (26 by maintainers)
Hello! I’m using 3.5 alpha and the problem is still here.
Sorry i use google translate
There are fewer problems in calculating the path around holes. But there are still problems with finding direct or shortest paths. I think the problem is that the algorithm uses astar on a graph of triangles and stops when it finds the first match on the minimum number of transitions.
It is necessary that when finding the first path, the algorithm remembers the result as the minimum of those found, and continues searching in other branches until they exceed its length. If the length is exceeded, discard the search branch. If a shorter length is found, update the saved result.
I found that the algorithm uses costs derived from the distances from the input point of the polygon to the edge of the next polygon. This is a very rough estimate and can often be wrong. (You can see it on the picture)
I really love Godot and want to help the development. I’m sorry I didn’t understand the build system. I hope my advice is helpful.
PS: I figured out how to run the build it. I found the code where the path is calculated (nav_map.cpp:82). Yes, the approximate path length used to discard “long” paths is incorrectly calculated here. I’ll try to fix it, hope I can)
@clayjohn I don’t think usability tag is the right one - you can set up the navigation in editor just fine. Issue is about method
$Navigation2D.get_simple_path()
(when usingTileMap
as source of your navigation poly) returning silly routes:Note that every tile is 100% covered with
NavigationPolygon
without overlaping - i checked polys in .tscn file, my first thought was some float creeping to the neighboring cell, but no. Also note that edges of paths are exactly on edges of tiles.Fun thing is, it doesnt work as one would expect even in Godot 3.0.6 - but it works sligtly better (bends are not so sharp):
Sorry if this isn’t right, but I was following the GDC tutorial on using simple_path and I was getting some odd behaviour, too. After some digging I found this post, but also an old pathfinding demo on github that hadn’t been updated for three years… However I managed to get the demo working, and compared and the pathing behaviour was much more as expected. I still can’t figure out why though, allthough they both do use different methods.
Here’s a gif of the results of the gdc tutorial pathfinding:
https://media.giphy.com/media/JnusvvxCd1w9w9kWfA/giphy.gif
Here’s a gif of the modified older demo:
https://media.giphy.com/media/JszpjWCBSdHyPjirOy/giphy.gif
As you can hopefully see, in the gdc version, the characted moves first up or down then left or right whereas the old modified version, shows the path as being the same as the gdc one, but moves diagonally as you’d expect.
Here is a copy of the the codes from the modified older demo:
From the main ‘game’ node:
from the player script:
here is a link to the unmodified github repo: https://github.com/FEDE0D/godot-pathfinding2d-demo
and the GDC code is available in the video tutorial: https://www.youtube.com/watch?v=0fPOt0Jw52s
Godot 3.1.1, Windows 7 64-bit
Happens for me too. If I make the optimize argument false, it looks like it uses simplified, Manhattan distances. Though looking at source code, I find only good old square root, hmm…
Another idea that maybe it optimizes away tiles and uses different-sized navmeshes instead (is fine), which are then just followed center to center instead without smoothing (not fine).
Related: https://github.com/godotengine/godot/issues/17885 https://github.com/godotengine/godot/issues/19011 https://github.com/godotengine/godot/pull/28640
I wanted to use the algorithm described by the user jb-dev on the page Navigation Meshes and Pathfinding on gamedev.net I took the base picture from one question on stackoverflow
It simulates two stretched threads, by the length of which we can calculate the cost of transitions to each next polygon. But to implement it, I will have to do a flat sweep for each sequence of polygons. This is necessary because Godot has only one implementation of pathfinding - for 3D space. Pathfinding in 2D just wraps it and provides the same interfase but with
Vector2
insteadVector3
. The algorithm will produce the correct result, but it is not optimal. Each polygon needs to be rotated in 3D space. To do this, needed to allocate memory and perform trigonometric operations.In this case, it is necessary to keep in memory two threads of the path (left-red and right-green) with theirs turning points. I’m sorry, but it looks like I can’t do it.
This needs to be re-tested in the latest 3.x branch (what will be Godot 3.5) due to the navigation server backport that was just merged. It would also be nice to get testing done in master and 3.4 for completeness.
Still valid in 3.2.4 beta3
I think (I don’t really understand that pathfinding code, it’s more guessing) the problem is Navigation2d::_navpoly_link as it seems to only link edges and that in combination with the simple path and the mecanism of finding the path through the polygons leads to the weird paths.
To demonstrate that this problem is not related to the tilemap itself, I updated my example (below). Maybe someone with some deeper understanding of the pathfinding can come and fix this. I assume it could be done with just fixing the _navpoly_link method to extend the linking to vertices and not only edges. I feel uncomfortable and lost in writing a solution.
Three notes on my new example:
New Example Project: bug_#28235.zip
The point is however, that in the GDC version the path is calculated as in our demo, what you see with this “odd path”, but since you recalculate it every physics tick, you get the “better” path. Keep in mind, that the pathfinding is an expensive algorithm (running linear), so I expect, you found yourself with lowered FPS. The path you saw is as wrong as the other.
so #22047 does not fixed this
I can confirm that this error persists in a freshly build version of master branch. My tests so far (I used the provided minimal example as starting point): Project at the bottom
With a fully covered Tile you get really weird paths (3 segments)
@nettocavalcanti Path without overlapping takes ~3sec to be generated and are not much better
(5 segments):

Overlapping is not better: (96 segments)
A Navigation Polygon with ~30 Verticies is not performance friendly:
Updated test project: tests.zip
I hope this helps a bit. I still struggle to get into that pathfinding code.
EDIT: Path optimisation made not better nor worse
Well, I think this is the same problem @HaSa1002 .
I removed all links to the desired tile and overlaped the junction between the two tiles. But the
get_simple_path()
method returns an empty array.I tried your solution, but still not working:
I think this is some kind of bug when the engine tries to link a NavPoly to another.
@giviz In one jam game I used this workaround, which smoothes the path after A* (Theta*-ish):
@git2013vb It uses distances for weights. Yes, some games require custom weights (so e.g. swamp is less preferred than road), but a lot of games don’t.
If it is crucial don’t use tile-based navigation and prefer to create a polygon which is then added as instance. (See my weird looking screenshot above)
So I looked into the algorithmn and found various possible reasons for the misbehaviour:
One word on 1 and 2: I don’t know if it would break something (I assume it does) and is even possible, but what is about batching the navigation polygons together first and see then what happens?
My somewhat unrelated question, why we use a map there to store the polygons? Do they need to be sorted? And how are they sorted? As I implied with point 3, I don’t really get how it works and it blows my mind regulary when I try.
@ka0s420 The path is as wrong as the other, because the debug navigation shows exactly the same wrong path as in the calc and run method. The reason is the path seams “better”, because you recalculate the path every time and the first points is a bit special and differently set
@ka0s420 if i get it, you are finding new path every physics tick - not by finding one at the beginning and then following it. Nice.