Before looking at anything else, you say you have this error:
Invalid get index 'position' (on base: 'null instance')
This is not hard to understand. You have a null
and you are trying to get position
of it.
And looking at the code, that got to be here:
func _physics_process(delta):
var Player = get_parent().get_node("Player")
position += (Player.position - position)/50
look_at(Global.player.global_position)
move_and_collide(motion)
More precisely, here:
position += (Player.position - position)/50
So Player
is null
. So this get_parent().get_node("Player")
gave you null
.
Please notice that get_parent().get_node("Player")
is looking for a sibling. It literally wants to get a child called "Player" from the parent. A child from the parent. A sibling.
I'll also point out that get_parent().get_node("Player")
is equivalent to get_node("../Player")
which is equivalent to $"../Player"
. If you need to put the Player
somewhere else, update the path accordingly.
Let us break this code down:
extends Node2D
onready var camera_x = $Player/playercamera.global_position.x
onready var camera_y = $Player/playercamera.global_position.y
var normal_zombie = preload("res://scenes/Enemy.tscn")
var player = null
func _on_Timer_timeout():
var enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
while enemy_position.x < 510 and enemy_position.x > -510 and enemy_position.y < 310 and enemy_position.y > -310:
enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
var normal_zombie_instance = normal_zombie.instance()
normal_zombie_instance.position = enemy_position
add_child(normal_zombie_instance)
First of all, here you are taking a copy of these values when the Node
is ready:
onready var camera_x = $Player/playercamera.global_position.x
onready var camera_y = $Player/playercamera.global_position.y
And second I don't see you update them or use them.
I would, instead have a reference to the camera. Which assuming these lines are correct would be like this:
onready var camera = $Player/playercamera
Which means that the Player
is a child of this Node
, and playercamera
is a child of Player
. If you need to put the camera somewhere else, you can update the path accordingly.
Then (I assuming the indentation problem was when posting the code here), you have this:
func _on_Timer_timeout():
var enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
while enemy_position.x < 510 and enemy_position.x > -510 and enemy_position.y < 310 and enemy_position.y > -310:
enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
var normal_zombie_instance = normal_zombie.instance()
normal_zombie_instance.position = enemy_position
add_child(normal_zombie_instance)
You are using position
, which as I said in the other answer is relative to the parent. And the parent is not not the camera. Quite the opposite, the camera is a child of the Player
, which is a child of this Node
.
Instead set the global_position
(or the global_transform.origin
if you prefer) to camera.to_global(enemy_position)
(where camera
is the reference to the camera).
In the other answer mentioned to use camera.to_global
if the camera was not the parent but you had a reference to it. You are not forced to make it a parent.
Note: The global_position
is not relative to the parent, but to the world.
And about the crash (also assuming the indentation problem was posting the code here), you have this:
func _on_Area2D_body_entered(body):
yield(get_tree().create_timer(0.1), 'timeout')
if "Bullet" in body.name:
health -= 20
if health <= 1:
queue_free()
I want you to imagine the following scenario:
- A first body enters.
- The execution for the first body reaches
yield
.
- After the timeout the execution for the first body resumes and reaches
queue_free
.
- A second body enters, the same frame that
queue_free
was called.
- The execution for the second reaches
yield
.
- Godot frees the
Node
(because you had called queue_free
).
- After the timeout… Eh? Godot cannot resume the execution for the second body because the
Node
was freed.
That is not the only way it can happen. It is just the easier to explain because it is the most sequential scenario. It could also be something like this:
- A first body enters.
- The execution for the first body reaches
yield
.
- A second body enters.
- The execution for the second reaches
yield
.
- After the timeout the execution for the first body resumes and reaches
queue_free
.
- Godot frees the
Node
(because you had called queue_free
).
- After the timeout… Eh? Godot cannot resume the execution for the second body because the
Node
was freed.
I bring this up because the way I described it earlier might suggest to you that you can avoid this problem simply by using is_queued_for_deletion
but that is not the case.
Even if you were to fix that for this method, anything else that was pending in yield
when the Node
was freed would be an issue. Thus, you have two solutions:
- Don't call
queue_free
unless you know nothing is pending in yield
.
- Don't use
yield
.
There are gotchas and caveats. I'll point you to Are Yields a Bad Code Practice in GDScript?.
I'll side on not using yield
in this case. So my fix would be like this:
func _on_Area2D_body_entered(body):
get_tree().create_timer(0.1).connect("timeout", self, take_damage, [20])
func take_damage(amount) -> void:
health -= amount
if health <= 1:
queue_free()
In principle it is the same idea. You are creating a timer, and when the timer emits the "timeout" signal it will execute some code. The difference is that we didn't use yield
, instead we connected to the signal, and when a Node
is freed Godot disconnect any signals it might have connected.
I have said a couple times that you can update a path according to where the Node
s are relative to each other. You could also export a NodePath
which I also mentioned in the other answer and set it via the inspector. For the camera example, it could be like this:
export var camera_path:NodePath
onready var camera = get_node(camera_path)
With the caveat that, as I mentioned earlier, the value of the onready
variable is a copy taken when the Node
is ready. So, changing the camera_path
in runtime won't update the camera
, you could handles that using setget
if you need to.
I am going to, once more, point you to Nodes and scene instances, and see the documentation on NodePath
too.