I'm trying to make a bird that follows a Position2D node attached to the character.(The Position2D node is in group called birdpos) When I run the game as soon as the bird is on screen (screendetector) it goes to the Position2D node. However once it reaches its destination it gives me the error "Invalid get index '1' (on base: 'Array')." (im also getting jittering when it reaches position) Im sure this is an easy fix, here is my code
extends KinematicBody2D
export(int) var SPEED: int = 100
var velocity: Vector2 = Vector2.ZERO
var path: Array = []
var levelNavigation: Navigation2D = null
var birdpos = null
onready var line2d = $Line2D #shows pathing
func _ready():
var tree = get_tree()
$flyingsprite1/AnimationPlayer.play("flying")
if tree.has_group("LevelNavigation"):
levelNavigation = tree.get_nodes_in_group("LevelNavigation")[0]
func move():
velocity = move_and_slide(velocity)
func _on_screenchecker_area_entered(area):
$Timer.start()
print("ligma")
yield(get_tree(), "idle_frame")
var tree = get_tree()
if tree.has_group("LevelNavigation"): #navigation node
levelNavigation = tree.get_nodes_in_group("LevelNavigation")[0]
if tree.has_group("birdpos"): #Position2D that is attached to player
birdpos = tree.get_nodes_in_group("birdpos")[0]
func _on_screenchecker_area_exited(area):
print("liga")
$Timer.stop()
var birdpos = null
var levelNavigation: Navigation2D = null
func _on_Timer_timeout():
line2d.global_position = Vector2.ZERO
if birdpos and levelNavigation:
generate_path()
func _physics_process(delta):
if Global.player.facing_right == true:
$flyingsprite1.scale.x = -1
else:
$flyingsprite1.scale.x = 1
if birdpos and levelNavigation:
navigate()
move()
func generate_path():
if levelNavigation != null and birdpos != null:
path = levelNavigation.get_simple_path(global_position, birdpos.global_position, false)
line2d.points = path
func navigate():
if path.size() > 0:
velocity = global_position.direction_to(path[1]) * SPEED
if global_position == path[0]:
path.pop_front()
edit: Updated Code
extends KinematicBody2D
export(int) var SPEED: int = 200
var velocity: Vector2 = Vector2.ZERO
var path: Array = []
var levelNavigation: Navigation2D = null
var birdpos = null
onready var line2d = $Line2D
func _ready():
# speed is distance over time
var tree = get_tree()
$flyingsprite1/AnimationPlayer.play("flying")
#if tree.has_group("Player"):
#player = tree.get_nodes_in_group("Player")[0]
func _on_screenchecker_area_exited(area):
$Timer.stop()
var birdpos = null
var levelNavigation: Navigation2D = null
func _on_Timer_timeout():
line2d.global_position = Vector2.ZERO
if birdpos and levelNavigation:
generate_path()
func _physics_process(delta):
if path.size() == 0:
return
var levelNavigation: Navigation2D = null
var birdpos = null
var next := global_position.move_toward(path[0], SPEED * delta)
var displacement := next - global_position
# And divide by it delta to get velocity:
move_and_slide(displacement/delta)
if Global.player.facing_right == true:
$flyingsprite1.scale.x = -1
else:
$flyingsprite1.scale.x = 1
if birdpos and levelNavigation:
navigate()
move()
func _input(event):
if Input.is_key_pressed(KEY_Q):
var tree = get_tree()
func _on_screenchecker_area_entered(area):
$Timer.start()
yield(get_tree(), "idle_frame")
var tree = get_tree()
if tree.has_group("LevelNavigation"):
levelNavigation = tree.get_nodes_in_group("LevelNavigation")[0]
if tree.has_group("birdpos"):
birdpos = tree.get_nodes_in_group("birdpos")[0]
func generate_path():
if levelNavigation != null and birdpos != null:
if is_instance_valid(birdpos):
path = levelNavigation.get_simple_path(global_position, birdpos.global_position, false)
line2d.points = path
func navigate():
if path.size() > 1:
velocity = global_position.direction_to(path[1]) * SPEED
if path.size() == 0:
path.pop_front()
func move():
if path.size() == 0:
return
velocity = move_and_slide(velocity)
In this code:
if path.size() > 0:
velocity = global_position.direction_to(path[1]) * SPEED
if global_position == path[0]:
path.pop_front()
You check if the path
has more than 0 points with path.size() > 0
. That is, you are checking if the path
has at least 1 point.
But to access path[1]
, the path must have at least 2 points.
Thus, if the path
has exactly 1 point, it will pass the check path.size() > 0
and fail when reading path[1]
.
I don't know when the path would have exactly one point. It is not documented how this could happen, and it could be a problem with the navigation, it could even be a bug in Godot. But, as far as I can tell it is happening for you.
Presumably you want to reach path[0]
instead of path[1]
since that is what you are checking against to remove points.
If you do in fact want path[1]
, then check if the path has at least 2 points with path.size() > 1
(or path.size() >= 2
if you prefer).
I'm assuming here that path[0]
is the target.
I believe it is three things:
You cannot trust vector equality
Vector equality boils down to equality of the components. Which is float equality. And thus Vector equality has all the problems of float equality.
So, to compare to your current target use is_equal_approx
. For example global_position.is_equal_approx(path[0])
.
You don't want to move if you reached the target
This is easy enough: If there are no more points in path
, don't move. That is, you can add this at the start of move
:
if path.size() == 0:
return
If you will have the code in _physics_process
instead of move
, remember to check there.
You don't want to overshoot
So, move_and_slide
will move the object as much as it should given the time between physics frames (delta
). But that might be more than necessary to reach the target. As consequence, it is very likely to overshoot the target. As a result, the next physics frame it will have to move in the opposite direction, and overshoot again, and so on… Jitter!
I'll give you three solutions:
Don't use move_and_slide
(with the caveat that you would be forgoing physics collisions):
# you can use move_toward
global_position = global_position.move_toward(path[0], SPEED * delta)
Let us keep move_and_slide
, but compute the displacement we want.
# you can use move_toward
var next := global_position.move_toward(path[0], SPEED * delta)
# Then compute the displacement
var displacement := next - global_position
# And divide by it delta to get velocity:
move_and_slide(displacement/delta)
Again, using move_and_slide
, but this time we figure out the maximum speed to not overshoot:
# speed is distance over time
var max_speed := global_position.distance_to(path[0])/delta
# And we clamp!
move_and_slide(velocity.clamped(max_speed))
For the versions of the code that use delta
you can either put the code in _physics_process
, or pass delta
to move
as a parameter. Also don't forget the check for path.size()
mentioned in the prior point.
Addendum If you use path[0]
as target, but it was equal to the current position, you would get about no velocity, and have to waste a physics frame. Consider this rewrite:
if path.size() > 0 and global_position.is_equal_approx(path[0]):
path.pop_front()
if path.size() > 0:
velocity = global_position.direction_to(path[0]) * SPEED