0

I'm making a game that's top down and has enemies. They don't shoot just walk at you. Well there supposed too. I just can't seem to get to enemies to be able to locate the player even if the player is the parent node. This is the code here:

extends KinematicBody2D
var health: int = 100
var motion = Vector2()
var speed = 100

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()

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)

but all I get from this is an error saying: Invalid get index 'position' (on base: 'null instance'). I don't understand what's going here. So far I've had to make the Player the parent but I also need to make it the main scenes parent so I can instance it so there are multiple of them. That's literally impossible in Godot standards. another question is I'm trying to make the enemies spawn around a moving camera but all they do is go to one corner. My code is as showed in a camera 2d :https://youtu.be/klBvssJE5Qg

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)

Last problem I'm facing is that I made it so the zombies had 100 health and would die at 0. Whenever a bunch spawn(ed) (I tried fixing it but ended up making it worse) and I killed 2 at the same time it would just crash. The codes here.

   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()

If you read all of this your a legend. If you helped me out with all the questions I would've given you boba tea. To bad I don't know where you live and can't send it to you. Please someone answer :3 UwU

I have looked up many many ways to do it. I only use stack overflow if it's my last hope. Please help!!!

1 Answers1

0

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:

  1. A first body enters.
  2. The execution for the first body reaches yield.
  3. After the timeout the execution for the first body resumes and reaches queue_free.
  4. A second body enters, the same frame that queue_free was called.
  5. The execution for the second reaches yield.
  6. Godot frees the Node (because you had called queue_free).
  7. 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:

  1. A first body enters.
  2. The execution for the first body reaches yield.
  3. A second body enters.
  4. The execution for the second reaches yield.
  5. After the timeout the execution for the first body resumes and reaches queue_free.
  6. Godot frees the Node (because you had called queue_free).
  7. 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 Nodes 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.

Theraot
  • 31,890
  • 5
  • 57
  • 86