Enemies path to position off NavRegion and not to set target

Godot Version

4.2.1 w/ Jolt Physics

Problem and context

Hello! I have a prototype 3D scene where I am trying to get enemies to path to a car and the enemies are attempting to path to a point off the navmesh (-9.201, -0.181171, -85.30832). I have set the target location to be the $Vehicle in the scene (or so I thought) but the agents are attempting to get to a different coordinate.

I am not sure why this is the case and at a loss of how to debug.

My goal is to have the enemies/agents always path to the car no matter its location on the oval track.

What additional information can I provide or find to help debug?

Any help or tips would be greatly appreciated, thanks!


[Enemies attempting to path off nav mesh]


[Nav Region settings]
https://forum.godotengine.org/uploads/default/original/2X/4/4d93e02650a30a5bd907be2d970c762a20790d1b.jpeg
[Nav Agent settings]
https://forum.godotengine.org/uploads/default/original/2X/c/c1bafd493713112538b12430fb4c7bb935f849b6.jpeg

Code Snippets

zombie.gd

extends CharacterBody3D

@export_group("Zombie Stats")
@export var movement_speed: float = 4.0
@export var Health = 1

@onready var collision_shape = $CollisionShape3D
@onready var nav_agent = $NavigationAgent3D
@onready var movement_target = %Vehicle

func _physics_process(delta):
	var current_location = global_transform.origin
	var next_location = nav_agent.get_next_path_position()
	var new_velocity = (next_location - current_location).normalized() * movement_speed
	
	velocity = velocity.move_toward(new_velocity, .25)
	move_and_slide()

func update_target_location(target_location):
	nav_agent.target_position = target_location

world.gd

extends Node3D

@onready var car = $Vehicle

func _physics_process(delta):
	get_tree().call_group("Enemies", "update_target_location", car.global_transform.origin)
	print(car.global_transform.origin)

Please try with official code examples from the documentation to verify that both your physics setup and your navigation setup are working on their own before combining them and before you add your own customization on top.

That velocity calculation alone looks fishy and is certainly not part of the documentation examples because move_and_slide() already uses physics delta internally.

2 Likes

Thank you for the reply @smix8!

Is 3D navigation overview — Godot Engine (stable) documentation in English and the included code block what you recommend I should be using instead?

You can find the code example for a CharacterBody3D here Using NavigationAgents — Godot Engine (latest) documentation in English

1 Like

Thanks @smix8!

I implemented the example code and I don’t have something hooked up correctly because the agents/enemies/zombies do not move when the scene is started.

The code doesn’t say I need to @onready var movement_target so I added that but it doesn’t make a difference either way, which adds to my confusion because I don’t understand how the example code knows what the movement_target is :confounded:

I rebaked the NavMesh just in case that was the problem and still same behavior of the agents not moving.

My scene tree is unchanged from the screenshot in the OP, so if needed that can be used for reference.

Appreciate any tips you can provide. I am reading the documentation and not understanding what I am doing differently yet.

zombie.gd

extends CharacterBody3D

@export_group("Zombie Stats")
@export var movement_speed: float = 4.0
@export var Health = 1

@onready var movement_target = %Vehicle
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")

func _ready() -> void:
	navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))

func set_movement_target(movement_target: Vector3):
	navigation_agent.set_target_position(movement_target)

func _physics_process(delta):
	if navigation_agent.is_navigation_finished():
		return

	var next_path_position: Vector3 = navigation_agent.get_next_path_position()
	var new_velocity: Vector3 = global_position.direction_to(next_path_position) * movement_speed
	if navigation_agent.avoidance_enabled:
		navigation_agent.velocity = new_velocity
	else:
		_on_velocity_computed(new_velocity)

func _on_velocity_computed(safe_velocity: Vector3):
	velocity = safe_velocity
	move_and_slide()

Instead of using a NavigationAgent node you could just query a pure navigation path for debug with the NavigationServer3D.map_get_path() function. That would rule out any agent setup issues that might interfere. If this function returns a path it is something with the nodes. If this function does not return a path there is no navigation mesh on the navigation map for whatever reason.

so use
@onready var navigation_agent: NavigationAgent3D = NavigationServer3D.map_get_path()
instead of
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
to debug what the issue is?

You can find a code example on the navigation path documentation page here Using NavigationPaths — Godot Engine (latest) documentation in English

Thank you! I think I got it setup so that I am querying the pure nav path and it is coming back as not finding the path and that there are nav mesh issues.

To be clear I attached this script to a new Node3D that is in the main/world scene, my understanding from the documentation is that would be okay but if it needs to be different let me know!

So my question is… what now? My nav mesh is clearly there and I am using default values for it (cell size, height, etc.) so I am unclear what I need to do differently to fix the nav mesh so that it “appears” correctly.

Also, in my original code (that had the issues of the agents pathing to “not the movement_target”) the agents were at least pathing using the same nav mesh setup, so that is doubly confusing for me.

Any ideas @smix8 ? Thanks so much for your help!

extends Node3D

func _ready():
	var default_3d_map_rid: RID = get_world_3d().get_navigation_map()
	var start_position: Vector3 = Vector3(0.0, 0.0, 0.0)
	var target_position: Vector3 = Vector3(5.0, 0.0, 3.0)
	var path: PackedVector3Array = NavigationServer3D.map_get_path(
	default_3d_map_rid,
	start_position,
	target_position,
	true
)

	if path.size() > 0:
		print("Path found, issue might be with the nodes.")
	else:
		print("No path found, there might be no navigation mesh on the navigation map.")

this should really pretty straightforward, you have navigationregion on where the zombie can walk or find path in, then the target is the player location, can be its global posiiton. then throw the get_next_path_position() in _physics_process for the zombie to move

i ever coded navigation on 2d but not 3d, but in the nutshell it’s like:
you first need to determine the navigation agent’s target position

nav_agent.target_position=the_player.global_position

then, set the agent’s velocity by lerping the direction from the navigation agent’s get_next_path_position() minus its global position

var direction = nav_agent.get_next_path_position()-global_position
direction=direction.normalized()
velocity=lerp(velocity,direction*randf_range(100,150),delta*SPEED)

yes all of these are inside the func _physics_process(delta) function block

1 Like

Thank you so much @zdrmlpzdrmlp! Before I try this, just to be clear, you are suggestion to make these changes to the original code in the OP?

i just tell what’s the basics of navigation agent, once you done the baking of the navigation region 's mesh, it’s usually just let the agent to find the path for you.
I read your code in the original post. you have send the update target location of the car every physics process, now it’s probably just the way it moves is not right, hence it failed to move as expected

try change the movement code similar to the snippet i shown, instead of move_toward, try lerping

1 Like

Thanks for helping me out again @zdrmlpzdrmlp but I am still experiencing issues and hoping you could help.

I have implemented your suggestions (I believe) and I am still experiencing the behavior where the zombies path to a single point that is not the Vehicle (aka the player). Also, the zombies are moving very fast now and not sure if I have some leftover logic messing with that or not.

zombie_pathing_to_point

Here is my zombie.gd in it’s entirety

extends CharacterBody3D

@export_group("Zombie Stats")
@export var movement_speed: float = 4.0
@export var Health = 1

@onready var vehicle = $"../Vehicle"
@onready var nav_agent: NavigationAgent3D = get_node("NavigationAgent3D")
@onready var sfx_zombie_run_over = $"../sfx_zombie_run_over"
@onready var zombies_killed_label = get_node("hud/Score/Zombies_Killed/zombies_killed_label")

signal zombie_killed

#Official method that doesn't work
#func _ready() -> void:
	#navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))

#Official method that doesn't work
#func set_movement_target(movement_target: Vector3):
	#navigation_agent.set_target_position(movement_target)

func _physics_process(delta):
	nav_agent.target_position = vehicle.global_position
	
	var current_location = global_transform.origin
	var next_location = nav_agent.get_next_path_position()
	var new_velocity = (next_location - current_location).normalized() * movement_speed
	var direction = nav_agent.get_next_path_position()-global_position
	
	direction = direction.normalized()
	velocity = lerp(velocity,direction*randf_range(100,150),delta*movement_speed)
	
	velocity = velocity.move_toward(new_velocity, .25)
	move_and_slide()
	
func update_target_location(target_location):
	nav_agent.target_position = target_location

#Official method that doesn't work
	#if navigation_agent.is_navigation_finished():
		#return
#
	#var next_path_position: Vector3 = navigation_agent.get_next_path_position()
	#var new_velocity: Vector3 = global_position.direction_to(next_path_position) * movement_speed
	#if navigation_agent.avoidance_enabled:
		#navigation_agent.velocity = new_velocity
	#else:
		#_on_velocity_computed(new_velocity)
#func _on_velocity_computed(safe_velocity: Vector3):
	#velocity = safe_velocity
	#move_and_slide()

## Bullet damage logic
func Hit_Successful(Damage, _Direction: Vector3 = Vector3.ZERO, _Position: Vector3 = Vector3.ZERO):
	Health -= Damage
	if Health <= 0:
		zombie_killed.emit()
		queue_free()
		print("zombie shot")
		
## Collision damage logic
func _on_body_entered() -> void:
	zombie_killed.emit()
	$"../sfx_zombie_run_over".play()
	queue_free()
	print("zombie ran over")
	
# This function will be called from the Main scene.
func initialize(start_position, player_position):
	# We position the mob by placing it at start_position
	# and rotate it towards player_position, so it looks at the player.
	look_at_from_position(start_position, player_position, Vector3.UP)

Any help would be greatly appreciated! Cheers!

i think dont need this line anymore if you have the lerp already

these 2 too

Ok awesome, thank you! I removed those 3 lines but am still experiencing the same behavior. Super speedy zombies that are pathing to a single point.

How could I debug this further if you think the code looks solid?

Cheers!

if you move your vehicle, did the single point also moved?
if that happen, then your vehicle/any node base position might be shifted

The path position doesn’t move if I move the vehicle, the path position always stays the same.

can you try it on a flat surface (not donut disk)? see actually where the target is targeting?

I baked the navmesh on the floor instead of the test track (now hidden) and got the same results, they all path to the same point.

your code for the physics process looks like this now?

func _physics_process(delta):
	nav_agent.target_position = vehicle.global_position
	
	var current_location = global_transform.origin
	var direction = nav_agent.get_next_path_position()-global_position
	
	direction = direction.normalized()
	velocity = lerp(velocity,direction*randf_range(100,150),delta*movement_speed)
	move_and_slide()