Reputation: 563
I am doing a GODOT tutorial. I am using GODOT 3.5.1 It seems that the Func make_path() does not build array as it should. I get the error Invalid get index '0' (on base: 'PoolVector2Array')Please see codes below. Any help will be greatly appreciated!
extends "res://Characters/NPCs/PlayerDetection.gd"
onready var navigation = get_tree().get_root().find_node("Navigation2D", true, false)
onready var destination = navigation.get_node("Destination")
var motion
var possible_destinations
var path
export var minimum_arrival_distance = 5
export var walk_speed = 0.5
func _ready():
randomize()
possible_destinations = destination.get_children()
make_path()
print(possible_destinations)
func _physics_process(delta):
navigate()
func navigate():
var distance_to_destination = position.distance_to(path[0])
if distance_to_destination > minimum_arrival_distance:
move()
else:
update_path()
func move():
look_at(path[0])
motion = (path[0] - position).normalized() * (MAX_SPEED * walk_speed)
move_and_slide(motion)
func update_path():
if path.size() == 1 and $Timer.is_stopped():
$Timer.start()
else:
path.remove(0)
func make_path():
var new_destination = possible_destinations[randi() % possible_destinations.size() - 1]
path = navigation.get_simple_path(position, new_destination.position)
func _on_Timer_timeout():
make_path()
Upvotes: 0
Views: 149
Reputation: 40295
I've seen version of the same question before, for example: Invalid get index '0' (on base: 'PoolVector2Array').
The reason you are getting an empty PoolVector2Array
from get_simple_path
is because navigation is not being to find a path. That could be because:
There isn't and shouldn't be a path, which you should handle in the code. Which I'll cover in this answer below.
The navigation is not setup properly.
You can use the "Visible Navigation" option you find in the Debug menu in the editor to help you check if the navigation polygons are correct in runtime (you don't want gaps where there shouldn't be, and you don't want overlapping polygons).
A notable case was a problem with TileMap
s: Navigation2D always return empty path.
Or it has not been updated if you are doing it dynamically, see: Navigation2D get_simple_path returns PoolVector2Array size: 0.
The destination position isn't correct.
The issue about the wrong position applies to the call to get_simple_path
. Chances are that your local spaces match, they might even match the global space (if everything is located at the origin, with no rotation or scaling), but that also means that moving them would break the code.
Well, get_simple_path
will work on the local coordinates of the Navigation2D
, so we should do this:
navigation.get_simple_path(
navigation.to_local(global_position),
navigation.to_local(new_destination.global_position)
)
And similarly the result will be in local coordinates of the Navigation2D
. We might convert the result to global coordinates right away (with global_transform.xform
), but we will not. Instead we will be converting points as we get them.
As you have discovered, you cannot rely on the result of get_simple_path
being not empty.
I'm going to have you discard your navigation code, and we work it again.
First we need to know our speed. Which from the tutorial code it is:
var speed := MAX_SPEED * walk_speed
Second we need to know the delta time. Which you have in _physics_process
, but if you really don't want to put the code directly inside of _physics_process
, you can get it like this:
var delta := get_physics_process_delta_time()
Now we can compute what is the distance the character should move this physics frame:
var total_distance_to_move := speed * delta
Now, the conditions to move are:
Let us write that:
if total_distance_to_move > 0.0 and not path.empty()
pass
Ultimately, we need the velocity vector that we will pass to move_and_slide
. And I remind you that velocity is speed
with a direction. So we need the direction to the next point.
To compute the direction, we need both the current position of the character, and the position of the next point. Both in global space. The position of the character is global_position
… But Navigation2D
gives us the path in its local space, so we need to convert it to global coordinates. We will convert just the one point like this:
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
We with that we get the direction:
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
We can do velocity = direction_to_point * speed
and call move_and_slide
with that… But don't. The problems are that it would overshoot and we need to handle the case where we get to the destination point and we have to take the next one from the path.
So we need to get the distance to the point too:
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
And now, the distance that we should move is the minimum between total_distance_to_move
and distance_to_point
, that way we never advance more than distance_to_point
… So we don't overshoot. Let us write that:
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
Ah, but we need a velocity, and what we have a distance. What do we do? As you know, velocity is a speed with a direction. Well, a speed is a distance over time. Let us do that!
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
And we call move_and_slide
:
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
move_and_slide(velocity)
Next we handle the case were we arrived at the point. Which is when the distance_to_move
is equal to distance_to_point
. There are a few ways to write this… The following works for me:
if total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
move_and_slide(velocity)
if distance_to_point <= distance_to_move:
path.pop_front()
Here pop_front()
will remove the first element of path
, so the next time we can continue to take path[0]
.
Now, wait a minute! We might not have advanced the whole total_distance_to_move
! We should continue advancing to the next point… Well, we can update the total_distance_to_move
by removing what we advanced:
total_distance_to_move -= distance_to_move
And turn the whole thing into a loop:
while total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
move_and_slide(velocity)
if distance_to_point <= distance_to_move:
path.pop_front()
total_distance_to_move -= distance_to_move
We might do that before the if statement if you prefer:
while total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
move_and_slide(velocity)
total_distance_to_move -= distance_to_move
if distance_to_point <= distance_to_move:
path.pop_front()
You also want to use look_at
, sure we can add that:
while total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
move_and_slide(velocity)
look_at(point)
total_distance_to_move -= distance_to_move
if distance_to_point <= distance_to_move:
path.pop_front()
And you have a custom threshold for reaching a target called minimum_arrival_distance
, we can add that too:
while total_distance_to_move > 0.0 and not path.empty()
var point:Vector2 = navigation.to_global(path[0])
var direction_to_point := global_position.direction_to(point)
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(total_distance_to_move, distance_to_point)
var velocity := direction_to_point * (distance_to_move / delta)
move_and_slide(velocity)
look_at(point)
total_distance_to_move -= distance_to_move
if distance_to_point - distance_to_move <= minimum_arrival_distance:
path.pop_front()
Finally, it seems you want to start a timer to create a new path when you reached the destination. Sure, we can do that after the loop:
if path.empty() and $Timer.is_stopped():
$Timer.start()
And you would use this code instead of navigate
, move
and update_path
.
Upvotes: 0