What is the best/most efficient way to do 2D navigation? With a region and having the tilemap with obstacles as a child? Or with tilemap navigation layers? (In the case of tilemap navigation layers I'm not even sure how to go about it since my tilemap is layered, with objects like trees having ground tiles under them).
Also, another question: how do you avoid enemies getting stuck on corners?
#Navigation2D - avoidance with several nav maps
96 messages · Page 1 of 1 (latest)
(I'm also considering the option of making my own system, if it's really that bad currently with Godot's, but I'm not sure if I have to or if it's too overkill)
avoiding the TileMap is usually the answer.
to not have enemies stuck on collision you need a margin between navmesh surface and collision shape. The TileMap can not create or bake such a margin, you need e.g. a NavigationRegion2D for that.
well, even keeping the radius at a 10 on the navigation polygon, enemies still get quite stuck on corners of the navigation polygon
it's not that they stop entirely, but they slow down a lot to the point that it's pretty much the same as getting stuck in terms of behavior
that depends on what else is going on, getting stuck is usually a physics problem, as long as you have valid navmesh layouts you can not get stuck at corners because the paths can only ever be on the navmesh. It is usually the physics velocity drag or the path desired distance skipping of the NavigationAgent2D that allows agents to leave the navmesh path and that is what gets them stuck.
gonna record it to show what I mean, give me a second
some random assets to test things. and the tilemap is put as a child of the navigation region. with an agents radius of 10px. you can see how much the enemy slows down on corners. in some situations it almost entirely stops though here it only slowed down for a little bit
a radius of 10px is already a pretty big issue if I have smaller objects with collision, such as those rocks or trees, since having 2 close together would basically make it impossible for the enemy to go through there despite fitting
well a navmesh is the surface for an agents center, if an agent can not stand there with its center there should be no navmesh, everything else is a physics shape problem. That also looks like the typical physics slowdown that you get when the shapes collide.
but I dont see the navmesh or collision in that video, but the path looks fine.
here's how the region looks
navmesh looks fine, might be even too much margin, but that depends on what collision shape you are using for those rocks / trees / houses.
alright, figured it out
yeah there's too much margin for smaller objects sadly
a radius of 10px is too much for those
buuut
I think I figured it out after debugging more stuff visually
had to lower these
thats what was going on
path desired distance allows corner skipping if set too high yes
because it is the distance at which the agent will go to the next path point
and if that is a corner it might leave navmesh
the enemies were hugging the collision because they were allowed to stick a little further away from their path, thats pretty much it
still sucks that I have to have a radius this big to avoid wall/corner hugging
not sure if anything can be done
you might just need to look out that your agents do not get stuck on navmesh edges if the desired distance is too small. That can happen should you update the path often or if you agent moves too quick. the agent might overshoot path points and backtrack.
managed to lower the radius to 6 px instead of 10 and it's still good enough. gonna guess that it's sadly collision shape dependent, aka bigger enemies will still have issues?
you might have to end up what many games do and have different collision shapes for your actors movement collision and your "hitbox" collisions.
so basically way smaller collision shapes despite enemy size in order to avoid issues like these
many games have a smaller and simple circle / sphere collision shape for world geometry collision so the agent has more options to manoeuvre without getting stuck easily.
and a dedicated collision shape just for hit detection or collision with other actors.
it'd be much better if there was a way for me to make bigger enemies walk further away from the obstacles, but I don't see how that'd be doable with this system
well that is the navmesh bake radius, larger agents shrink the navmesh and that solves the issue as far as pathfinding is concerned, it does not account for all the other possible shenanigans with movement like physics velocities and shapes because agents are just points in a navigation system.
what I meant is that, for example, if an enemy would be twice the size of this one...obviously it'd get stuck on corners again
unless i gave it a really small collision shape
which might make it look awkward since it could fit through spaces it shouldn't
for each distinct agent size it requires its own navigation map with a navmesh baked for those agent properties.
and wouldnt it be a lot more laggy if I had several navigation regions?
(I'd probs not need more than 2-3 but just curious)
not nessarily because they each do their own thing, the only thing is that it requires extra memory for the navmesh graph but there is not really much of another way to have multiple different agent sizes as a navmesh is a tailor made surface for a specific agent type. Think of it like a traffic map, pedestrians, bikes, cars, trucks, while maybe using the same "geometry" still all need their own navigation map because they can not necessarily share the same or it is not efficient to do so.
most games that have different agent sizes usually have 2-3 different navigation maps
games like Horizon Zero Dawn used 7 navigation maps for example just to give you an idea how common that is
I see. yeah, that's perfect then. 2-3 is plenty for me
how would I set each agent to a different nav region though?
with the nav layers?
that requires using the NavigationServer2D directly
you can still setup NavigationRegion2D nodes but you need a script and switch them to a different map, same for the agents.
by default they all use the default navigation map of the Viewport World2D
are you sure? it seems to work just fine with 2 nav regions on different nav layers (and the agents assigned to the respective layer)
assigned the bigger enemy and the nav region with the bigger radius to layer 2 and they seem to work
or is this not what you meant?
navmesh surfaces can not overlap, that will give you edge merge errors and logic bugs, and the only way to separate them is by moving them to different maps. if it still works in your case that is more by luck. the moment edges overlap things might explode already.
so they should be assigned to different navigation maps which can only be created by code?
with something like this? then assigning the new animation map to the nav region and agents
yes, create the map and set it active, then set all the NavigationRegion2D.set_navigation_map() to that map, same for the agents.
other than that, everything else is fine? such as having those two navigation regions set up that way like this?
yeah you can do with the NavigationRegion2D nodes what you want when you set them to a different map once, they will update the navmesh on their assigned map, it is just the editor debug that will rendered that convoluted mess of overlapping navmesh but on the NavigationServer2D they will be strictly separated.
👍 got it
that's about it then, since I'm guessing there's no better way to do it in the editor itself
thanks!
@winter gyro sorry to come back to this and ping but, if I do use your solution (have now moved to different maps instead of navigation layers), how do I make it so that enemies avoid each other again??
you mean with the avoidance? that is a little more difficult because currently the avoidance is based per map. So you would need to use scripts to create agents just for the avoidance only and assign them all to the same map just for the avoidance simulation. so you end up having 2 agents per real agent, one for pathfinding, one for avoidance.
the navigation documentation has a agent script example that shows how to create a server agent and avoidance callback. basically you create the agent, set the map, set the agent radius, and register a callable for the safe velocity callback, then each physics process you set the velocity of the agent and you get the ssafe velocity on the callback and use that to move your agent.
oh boy
it sounds more difficult to desc it than what it actually is
it is just that there is no user interface for it
I think I might stick to just adding a slight randomized offset for the target ngl
and are you positively sure it's an issue to use navigation layers for the regions? cause it seemed to limit the larger enemies to the larger radius and smaller enemies to the smaller radius
navigation_layers are just a bitmask filter for what polygons an agent can use in a path query
they cant help you with avoiding merge conflicts of navmehs polygons on the navigation map
var agent: RID = NavigationServer2D.agent_create()
NavigationServer2D.agent_set_map(avoidance_map_rid)
NavigationServer2D.agent_set_radius(agent_radius)
NavigationServer2D.agent_set_avoidance_enabled(agent, true)
NavigationServer2D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done"))
func _physics_process(delta):
var new_velocity: Vector2 = ... # calc velocity like with NavigationAgent2D parent
NavigationServer2D.agent_set_velocity(agent, new_velocity)
func _avoidance_done(safe_velocity: Vector2):
# move agent node like with NavigationAgent2D parent```
that is how you create and use a server agent for avoidance
what mostly confuses me is how it works. because I still need to have the agent stuff that I have right now alongside the avoidance. would I just send the velocity I currently calculate to that agent instead for the safe velocity and everything else stays the same?
that is because the NavigationAgent2D has both combined
in theory you do not need it for the pathfinding, you could just NavigationServer.query_path() and use that path array to move your agent along.
but since you cant give the NavigationAgent2D a different map for pathfinding and a different map for avoidance you need to split it in that case, no other way if you want to use a different map. It is a little complicated because it was never designed with such a use in mind.
and yes, if you have a NavigationAgent2D in use with avoidance right now you would basically send the same velocity
because internally when you set NavigationAgent2D.set_velocity() it does the same for you, just calls NavigationServer2D.agent_set_velocity()
I'm sorry, I tried and couldn't get it to work, but that's because I still don't get it. am I supposed to use the current navigation agents I had for pathfinding and the avoidance agent made through the server for avoidance? I tried to do that but the enemies don't move properly at all. I tried setting everything just in case. didn't know which map to use or if I should make a new one (or how it'd be related to the 2 regions I had for the different enemy sizes) so I reused the big one. then the callback function. which is written identically to how I had it for the navigation agents beforehand. lastly, I just commented the code that set the velocity to the navigation agent and instead set it for the avoidance one. (sorry if this is slightly unclear, have a state machine structure there, but it's definitely not at fault here). oh and I also disabled the avoidance on the navigation agents cause I thought it'd interfere with this
Navigation2D - avoidance with several nav maps
(and here's what I mean by "couldnt get it to work")
Checking everything, it seems like the velocity is correct before it calculates the safe velocity. It does enter that safe velocity function as well. And I made a new map for avoidance only, just in case, but nothing changed. Should any of the regions be assigned the avoidance map?
hey, no regions have nothing to do with it, the entire navmesh system has nothing to do with it, avoidance is its own simulation but that simulation is tied to a navigation map, so you can create a new empty map that is just for the avoidance of all the agents. The only thing that matters for avoidance is, map, agent position, radius, velocity (and other avoidance related properties, not listing them all but you already have them in your script) and the callback so you actually get the calculated velocity result.
Then I dont really get why it didnt work right...
it seems to be a consistent issue outside of my state machine code, as I tried setting up 2 character bodies with their navigation split between the navigation agent and avoidance agent, same thing happens: safe velocity's y is always 0
I am not sure why the safe velocity would be 0 just on one axis only. What Godot version are you using?
oh wait maybe cause in the older versions the NavigationServer2D used the NavigationServer3D behind the scene and the actual velocity was a Vector3. Not sure if that was converted on the server or on the node level.
can you try the avodance callback function with a Vector2 and with a Vector3 parameter? what does it print or are there errors?
because recently the NavigationServer2D was moved to its own server so it is a little confusing atm.
just looked at the source code and it stills sends a Vector3 with the callback that gets only converted at the NavigationAgent2D node level, so that is likely the reason.
sorry, I really didnt see the reply. I'm using 4.3. if any changes were made to the navigation between 4.3 and 4.4, let me know
I see. Gonna try with a vector3 in a bit
yuuuup. thats it. x and z have values, y doesn't