Navigation - experimental

It seem to be fairly simple and I manage to put this into game but not sure about some of the features.

So basically NavigationRegion - is used for bake navigation mesh ( for some reason mine is above surface ), when I try change agent radius or height it complains about it .

Navigationagent is added to NPC which will go around.

What parameter can I change to make navigation mesh more precise?

Should I attach all environment objects or floor is fine for bake?

Should all points of target be global positions?( such I would like to create path where the Skleton enemies be guarding the territory, changing guards of treasure.

What kind of nodes and approach be best for it( all low poly , KayKit assets)

Finding some inspiration in https://www.youtube.com/watch?v=2W4JP48oZ8U

For now code is just basic from docs

extends CharacterBody3D

var movement_speed: float = 2.0
@export var movement_target_position: Vector3 

@onready var navigation_agent: NavigationAgent3D = $Node3D/NavigationAgent3D


func _ready():
	# These values need to be adjusted for the actor's speed
	# and the navigation layout.
	navigation_agent.path_desired_distance = 0.5
	navigation_agent.target_desired_distance = 0.5

	# Make sure to not await during _ready.
	actor_setup.call_deferred()

func actor_setup():
	# Wait for the first physics frame so the NavigationServer can sync.
	await get_tree().physics_frame

	# Now that the navigation map is no longer empty, set the movement target.
	set_movement_target(movement_target_position)

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 current_agent_position: Vector3 = global_position
	var next_path_position: Vector3 = navigation_agent.get_next_path_position()

	velocity = current_agent_position.direction_to(next_path_position) * movement_speed
	move_and_slide()

Some ideas how to set it up ?

  • skeleton would get around map perimeter on floor level without need of specific target locations
  • check if he is stuck to avoid obstacles

What about tree’s should I include them in bake or is there different way to exclude part of mesh from being used in navigation mesh?

For even this simple project it still baking Navigation Mesh above the obstacles.

So Project got a bit upgraded to see how it will perform with multiple Agent’s

current code

extends CharacterBody3D

@onready var navigation_agent_3d: NavigationAgent3D = $NavigationAgent3D

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("ui_accept"):
		var random_position = Vector3.ZERO
		random_position.x = randf_range(-15.0, 15.0)
		random_position.z = randf_range(-5.0, 35.0)
		navigation_agent_3d.set_target_position(random_position)
		
func _physics_process(delta: float) -> void:
	var destination = navigation_agent_3d.get_next_path_position()
	var local_destination = destination - global_position
	var direction = local_destination.normalized()
	
	velocity = direction * 5.0
	move_and_slide()

This obviously will cause stuck when path is blocked by other Agents Bodies.

some progress :slight_smile:

added path3d and pathfollow3d nodes to make it go to set points.

extends CharacterBody3D

@onready var navigation_agent_3d: NavigationAgent3D = $NavigationAgent3D
@onready var path_follow_3d: PathFollow3D = get_tree().get_first_node_in_group("path_follow")

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("ui_accept"):
		var random_position = Vector3.ZERO
		random_position.x = randf_range(-15.0, 15.0)
		random_position.z = randf_range(-5.0, 35.0)
		navigation_agent_3d.set_target_position(path_follow_3d.global_position)
		
func _physics_process(delta: float) -> void:
	var destination = navigation_agent_3d.get_next_path_position()
	var local_destination = destination - global_position
	var direction = local_destination.normalized()
	
	velocity = direction * 5.0
	move_and_slide()

script to change a that a bit for now

extends NavigationRegion3D
@onready var path_3d: Path3D = $Path3D
@onready var path_follow_3d: PathFollow3D = $Path3D/PathFollow3D

func _ready() -> void:
	print(path_follow_3d.progress_ratio)


func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("ui_accept"):
		path_follow_3d.progress_ratio += 0.1
		if path_follow_3d.progress_ratio >= 0.9:
			path_follow_3d.progress_ratio = 0.0
		print(path_follow_3d.global_position)

extends CharacterBody3D

@onready var navigation_agent_3d: NavigationAgent3D = $NavigationAgent3D
@onready var path_follow3d := get_tree().get_first_node_in_group("path_follow")
@onready var mesh_instance_3d: MeshInstance3D = $MeshInstance3D

var waypoints: Array[Dictionary] = [
	{"ratio": 0.01, "wait": 1.0},
	{"ratio": 0.25, "wait": 2.0},
	{"ratio": 0.35, "wait": 0.5},
	{"ratio": 0.5,  "wait": 3.0},
	{"ratio": 0.7,  "wait": 1.5},
	{"ratio": 0.8,  "wait": 2.5},
	{"ratio": 0.9,  "wait": 1.0},
	{"ratio": 1.0,  "wait": 4.0},
]

var ratio_step := 0
var is_waiting := false

func make_move() -> void:
	var index := int(pingpong(ratio_step, waypoints.size() - 1))
	var point := waypoints[index]

	await get_tree().create_timer(point["wait"]).timeout

	var target_pos: Vector3 = path_follow3d.get_random_position(point["ratio"])
	navigation_agent_3d.set_target_position(target_pos)
	ratio_step += 1

func _physics_process(delta: float) -> void:
	var destination = navigation_agent_3d.get_next_path_position()
	var local_destination = destination - global_position
	var direction = local_destination.normalized()

	velocity = direction * 5.0
	move_and_slide()

	if velocity.is_zero_approx():
		mesh_instance_3d.get_surface_override_material(0).albedo_color = Color.RED
		if is_waiting == false:
			is_waiting = true
			await make_move()
			is_waiting = false
	else:
		mesh_instance_3d.get_surface_override_material(0).albedo_color = Color.GREEN

Instead of action the function is called now automatically when timer is out.

To determine if NPC is arrived to waypoint I have used velocity, ratio and ratio_step ( for sake of choosing next waypoint and be able use pingpong)

Maybe could use some other system instead of Dictionary, but I found it could be useful for “guarding”, wonder how aggro(player detected) could be solved then - the player should be detected only if is visible( not behind the wall) and then distance threshold, if player manage to escape from chase the npc returns back to way pointing routine.

So far the script doing this

There was quite a saga on Reddit about how Godot was generating poor navigation meshes. IIRC, there was a lot of good research and the OP had a PR at one point. https://www.reddit.com/r/godot/comments/1oos02c/why_does_navigationregion3d_create_bad_and_low/
https://www.reddit.com/r/godot/comments/1opodp6/navigationregion3d_and_its_consequences_has_been/

1 Like

Nice read, but I don’t think this will apply to my scenario.

For this basic use it’s fine, now figuring how to make this NPC “see a player” { sensible could be ShapeCast3D to detect him overall as it doesn’t have validation is something like wall is between player and npc.

The raycast be great, but question is shall I keep checking player position from NPC at all the time?

In both scenarios see downside’s , Shapecast for looping through colliders, passing player position and checking for distance could be cool but this need to be process function I guess or maybe separate collision mask/layer for check radius maybe ?

Hmm, if you have some ideas let me know :slight_smile:

The enemy can have an area 3D with a sphere shape. If the player is not inside the area, no need to check the raycast. If the player is inside the area, check the raycast.

1 Like

I had this sort of implementation with reference to player, but I like Area3D this would do signal based logic.

With an area you can also make the collision shape a box (or a custom shape, like a cone or a “frustrum”) attached to the enemy’s head to simulate his cone of vision, so it doesn’t trigger if the player is out of his field of vision.

1 Like

Currently trying to resolve how to make NPC jump or go around the boulders.

Nav Mesh Baked them like this :

You can put obstacles or change the slope of the navmesh.

1 Like

I have placed Floor, Rocks, Houses on collision layer 4, slope adjusted but it still bake a strongly ,

Just find out AABB can be used and (h) make significant difference what and how it’s baked.

1 Like