godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91k stars 21.17k forks source link

Bake NavigationPolygon in NavigationRegion2D ignores other Tilemap layers #83967

Closed ThePat02 closed 1 year ago

ThePat02 commented 1 year ago

Godot version

v4.2-beta3

System information

Godot v4.2.beta3 - Windows 10.0.22621 - Vulkan (Forward+) - integrated AMD Radeon(TM) Graphics (Advanced Micro Devices, Inc.; 30.0.13044.14002) - AMD Ryzen 9 5900HX with Radeon Graphics (16 Threads)

Issue description

When baking a NavigationPolygon in a NavigationArea2D, only the collision data of the first layer of a TileMap is used. For example: Tiles placed on the second layer that have collision will not be taken into account when baking. If you change the order of the layers, the same bug appears and only the first layer will be baked into the NavigationPolygon!

Steps to reproduce

  1. Create a NavigationArea2D and add a new NavigationPolygon.
  2. Add a TileMap as a child and fill different layers with objects that have collision data.
  3. Bake the NavigationPolygon.

Minimal reproduction project

minimal_reproduction_navigationbaking.zip

smix8 commented 1 year ago

The 2D navigation mesh baking parses only the TileMap Layer0. This is not a bug but an intentional limitation.

The TileMap Layers system does not work for the navigation mesh system. You can not overlap navigation meshes from different TileMap Layers all in the same navigation map like you can do with visuals or collision. Overlapping navigation meshes causes the edge sync to implode breaking the navigation map and therefor the pathfinding.

So this is not a matter of the 2D navigation mesh baking supporting the TileMap, which it already does, but a matter of the TileMap figuring out its internals and features and how they can work correctly with the used engine features.

One solution could be that the TileMap adds options to define what Layers should be combined and how, making sure that there is only a single combined "layer" in the end prepared for the parser for the navigation mesh baking.

ThePat02 commented 1 year ago

How would you suggest solving this issue in a project? Havin a dedicated layer on your tilemap where you only put collision tiles?

smix8 commented 1 year ago

While this is not a provided feature of the TileMap users can work around the issue by using a NavigationMeshSourceGeometry2D object to parse the TileMap Layer0 with NavigationServer2D.parse_source_geometry_data().

The objects from the other TileMap Layers can be added on top manually to the source geometry for the baking. E.g. collision shapes from other TileMap Layers can be added as obstruction outlines to the source geometry while navigation polygons can be added as traversable outlines.

With the source geometry prepared the navigation mesh can be baked with NavigationServer2D.bake_from_source_geometry_async().

If adding this manually in script is too problematic another option might be to use a secondary TileMap as a "replacement layer" and a secondary source geometry. Parse Layer0 as well, then combine the data from the two source geometries into one for the baking. Or place the two TileMaps in a way that the parsing can read both into the same source geometry. The problem with many TileMap nodes will be that the parsing will run on the main thread because they are nodes in the SceneTree.

ThePat02 commented 1 year ago

Or place the two TileMaps in a way that the parsing can read both into the same source geometry. The problem with many TileMap nodes will be that the parsing will run on the main thread because they are nodes in the SceneTree.

I just use two TileMap nodes for now. This is a quick and dirty solution for now.

Pardok commented 11 months ago

Hey! I made a small tool called TileMapsChef (GitHub) following @smix8's suggestions. Maybe in the future I'll make it a bit more user-friendly (layer selection, proper UI). But if you just want to bake all colliders from all layers into your NavigationRegion2D, it will do the job.