godot icon indicating copy to clipboard operation
godot copied to clipboard

Add navigation layers to `AStar2D`, `AStar3D`, and `AStarGrid2D`

Open theashtronaut opened this issue 4 months ago • 4 comments

Adds navigation layers to AStar2D, AStar3D, and AStarGrid2D. By default, all points will be on layer one and all functions that now take a layer argument will default to layer 1, so existing implementations should not be impacted.

AStar2D::add_point and AStar3D::add_point now have an optional argument to specify the layer bitmask when adding the point. get_id_path, get_point_path for all support specifying a layer to use. Points that are not on that layer are ignored for pathfinding, as if they were disabled. This also means you can actually disable a point if you set it's layer to 0, but I kept the disabled functionality as a way to disable a point for all layers and preserve the point's layer data at the same time. get_closest_point for AStar2D and AStar3D will respect the provided layer, and only return the closest point with the matching layer. AStarGrid2D seems to constantly remake the points whenever you call update. I believe this will wipe out any custom layers you set as well, which may be an issue and should maybe be mentioned in the docs? I couldn't think of a way to preserve this data given the update function first clears all points. Open to suggestions on this!

Rational:

Instead of having to make duplicate AStar objects if some agents have slightly different path options, you can now use the same overall map data for 32 distinct path options. This greatly simplifies code for users. For example, in my project the map is the same once generated but some units can only travel on some tiles. Water tiles, for example, cannot be traversed by land units and vice versa. Air units can traverse all types. Previously I've had to use 3 separate AStar objects and switch which one I am using based on the unit. After adding layers, I can just specify which layers the points on are to represent land, sea, air during generation, and then for pathfinding specify which layers the agent uses and use one map instead of duplicating the entire thing.

Example:

Download: astar_test.zip

This project features two agents, a Red and Blue. The Red and Blue tiles can be traversed only by those agents respectively. The project also has use UI elements to view the layers of the point the mouse is over and showcase the get_closest_point function respecting the layer option. It pulls the layer from the currently selected character in the option box.

astar_navigation_layers.webm

By default all "open" tiles have been set to layer 2 and 3 (so the value will display 6). Gray "obstacle" tiles are set to layer 0. The red character is on layer 3 (value 4) and the blue character is on layer 2 (value 2). Towards the end, you can that I have my mouse over tile 6,7 (id 497) but the "closest point" in the UI is "496" as that tile is on a different layer from the red character.

Outstanding questions:

  1. I noticed some of the physics layer implementations have a distinct mask and mask_value function, where the former takes a bitmask and the later takes a layer as an int 1-32. Should I mirror that and have two functions, one to change the bitmask directly and one to specify the layer? Setting layer by direct int is a bit easier, but would not support multiple layers for "shared" points, so I'm not sure if that extra function would be considered a nice utility or just bloat.
  2. What should we do about AStarGrid2D::update wiping out any saved layer changes? Just add a documentation note about it? Change that function to remember all the layers for all points and somehow reapply them?

Previous questions, leaving here for community knowledge reasons

  • I need to add the compat functions, but I am not sure the proper procedure when a function already has one. I added one for get_id_path and get_point_path in a previous PR, and this one will be modifying those functions again. Is the correct approach to entirely remove those old compat methods and replace them with the new ones, or do I create separate compat functions and bind them again? I couldn't find a clear instruction on this. -> I was told they get additional ones, which I have now added.

Closes: https://github.com/godotengine/godot-proposals/issues/5188

theashtronaut avatar Oct 18 '24 22:10 theashtronaut