+1 vote


I'm trying to create a basic enemy AI that has two states: IDLE and WANDER.
I'm using Navmesh 3D demo from Godot's asset store.

If stop_distance is set to 1.0, the AI will pathfind to a single location, then once (target_position - transform.origin).length() evaluates to value of about 5.7 the AI stops pathfinding never reaching the IDLE state again (tested with print() statements).

Setting the stop_distance to above 5.7 correctly switches between IDLE and WANDER states and AI proceeds to pathfind.

Any idea why this might be happening?

extends KinematicBody

var move_speed = 3.0

var state = STATE.IDLE

onready var start_position = get_global_transform().origin
onready var target_position = get_global_transform().origin

var nav = null
var path = []

var stop_distance = 1.0

func _ready():
    nav = get_parent() # Navigation node is parent of enemy node in the scene tree during run-time

func _process(delta):
    match state:
            var next_waypoint = Vector3(rand_range(5, 20), translation.y, rand_range(5, 20))
            target_position = start_position + next_waypoint
            var p = nav.get_simple_path(start_position, target_position, true)
            if not p.empty():
                path = Array(p)
                state = STATE.WANDER
            var to_walk = delta * move_speed
            var to_watch = Vector3.UP
            while to_walk > 0 and path.size() >= 2:
                var pfrom = path[path.size() - 1]
                var pto = path[path.size() - 2]
                to_watch = (pto - pfrom).normalized()
                var d = pfrom.distance_to(pto)
                if d <= to_walk:
                    path.remove(path.size() - 1)
                    to_walk -= d
                    path[path.size() - 1] = pfrom.linear_interpolate(pto, to_walk / d)
                    to_walk = 0
                var atpos = path[path.size() - 1]
                var atdir = to_watch
                atdir.y = 0
                var t = Transform()
                t.origin = atpos
                t = t.looking_at(atpos + atdir, Vector3.UP)
                if path.size() < 2:
                    path = []
            if (target_position - transform.origin).length() < stop_distance:
                start_position = get_global_transform().origin
                state = STATE.IDLE

Optional: Why divide to_walk by d for the rate argument in pfrom.linear_interpolate(pto, to_walk / d)?
***Answer***: *this ensures the enemy AI lerps to destination at constant speed until the end, otherwise it will begin to slow down as it approaches the destination.*

Optional 2: Why does the Navmesh3D demo project inverts the path array(after converting it to Array)? Unfortunately it's not documented or explained in their code.
Answer: removing last element from array is more efficient rather than from the beginning.

in Engine by (20 points)
edited by

1 Answer

0 votes
Best answer

The issue was that target position wasn't on the navigation mesh due to global vs local coordinates, but above it. Enemy AI would arrive at X and Z position but Y would be above the navigation mesh, which is why the length() was 5.7 units away from final position.

We need to make sure to calculate position and path in local space using to_local(), then convert the results back to global with to_global(), and use get_closest_point() of Navigation node to get proper target position on the navigation mesh. This will result is correct length().

by (20 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.