My instantiated enemies don't do anything I tell them to. (godot)

33 views Asked by At

So, I'm making a top down shooter, and I want to infinitely instantiate enemies to chase the player. When I instantiate the enemy, it just appears on the screen and does none of the code i put in the enemy script. here is the main lvl script: the enemy is it's child.

extends Node2D

var enemy_scene = preload("res://level/enemies/zombie.tscn")

func _ready():
    pass 

func _on_timer_timeout():
    spawn_enemy()

func spawn_enemy():
    var enemy_instance = enemy_scene.instantiate() as CharacterBody2D
    enemy_instance.position = get_enemy_position()
    enemy_instance.visible = true
    add_child(enemy_instance)

func get_enemy_position():
    var spawn_x = randf_range($player.position.x + 100.0, $player.position.x - 100.0)
    var spawn_y = randf_range($player.position.y + 100.0, $player.position.y - 100.0)
    return Vector2(spawn_x, spawn_y)

there is a 3 second timer which instantiates a enemy, but it doesn't do ANYTHING i've tried coding it to do. I can use the variable "enemy instance" to do basic stuff, like setting visability to true, but if I want to do something every frame, thats a problem.

I've tried to watch youtube toturials, which for some reason, they did the same thing as me, and the instantiations still worked. I even asked GPT, who was of no use.

1

There are 1 answers

4
Theraot On

Create a Zombie Class

It is either a good idea or it is necessary to give a script to the root of the zombie.tscn class. So let us start there...

Create an script, I'll call it zombie.gd, which you attach to the root of the zombie.tscn scene.

That zombie.gd must extend CharacterBody2D since that is the type of node the root of the zombie.tscn scene is:

zombie.gd

extends CharacterBody2D

You can also give it a class name so we can use that as a type globally:

zombie.gd

class_name Zombie
extends CharacterBody2D

Make the Zombies move on their own

In that script you can define _physics_process which will run every physics frame, and _process which will rune every graphics frame (The delta argument will have the time in seconds since the last frame of its kind):

zombie.gd

class_name Zombie
extends CharacterBody2D


func _process(delta: float) -> void:
    print(delta)


func _physics_process(delta: float) -> void:
    print(delta)

So you can use these to have your Zombie do something every frame.

Notably, being a CharacterBody2D, you probably want to call move_and_slide on _physics_process.


I'll also assume you want them to chase the player character. This is what comes to mind:

You can add an Area2D as a child to the Zombie with a collision shape that defines the area within which the Zombie will detect the player character.

For that detection you can connect the body_entered signal to the root of the zombie.tscn scene, adding a new method in the the zombie.gd script that looks somthing like this:

zombie.gd

func _on_body_entered(body:Node) -> void:
    pass

So you can get a reference to the body it detected:

zombie.gd

var maybe_player_character:Node2D

func _on_body_entered(body:Node) -> void:
    maybe_player_character = body as Node2D

You might want to confirm it is the player character. We might do this by giving also a script with a class name to the player character scene (similar to how we gave Zombie to the zombie.tscn scene. For example if that class is PlayerCharacter we can do this:

zombie.gd

var player_character:PlayerCharacter

func _on_body_entered(body:Node) -> void:
    var maybe_player_character := body as PlayerCharacter
    if is_instance_valid(maybe_player_character):
        player_character = maybe_player_character

I'm going to defer you to How do I detect collisions in Godot? for details


Then using global_position of the Zombie and the player character you can pick the direction to move, and export a variable for the speed (you set it in the Inspector dock):

zombie.gd

@export var speed:float


func _physics_process(delta: float) -> void:
    if not is_instance_valid(player_character):
        return

    var direction := global_positition.direction_to(
        player_character.global_positition
    )

    velocity = direction * speed
    move_and_slide()

The behavior of the zombies could be more complex, of course, but with this you have zombies that do something.


Presumably you also want to handle the case of the zombies colliding with the player character. Again please refer to How do I detect collisions in Godot?. And presumably you also want some kind of damage/health/lives... While that is beyond the scope of this question, here is a hint: you can add a health property to the PlayerCharacter class.


A Director telling the Zombies what to do

You seem to want your spawner script to not only spawn the enemies, but also to control them. I'll continue to call it spawner for the sake of this answer, but know what you would be making is a director.

Let us start by using the Zombie class in your spawner:

spawner

var enemy_instance := enemy_scene.instantiate() as Zombie

So if you define additional properties or methods in your zombie.gd, you can access them from your spawner. For example:

zombie.gd

class_name Zombie
extends CharacterBody2D


var another_property:int

spawner

var enemy_instance := enemy_scene.instantiate() as Zombie
enemy_instance.another_property = 42

Or:

zombie.gd

class_name Zombie
extends CharacterBody2D


func my_method() -> void:
    print("my_method called")

spawner

var enemy_instance := enemy_scene.instantiate() as Zombie
enemy_instance.my_method()

To be clear, you could call any method, including move_and_slide, not just the ones you defined.


You can add paramters to the method, for example:

zombie.gd

class_name Zombie
extends CharacterBody2D


func my_method(something:String) -> void:
    print(something)

spawner

var enemy_instance := enemy_scene.instantiate() as Zombie
enemy_instance.my_method("Director says hello")

This can be a way to give information to the zombies, such as the position of the player, or tell them to stop chasing, etc.


If you need to call something on the zombies after you created them, you are going to need to keep a reference to them. One way would be to keep a list of the instances you created (which you also need to maintain to remove any no longer valid instances, such as zombies the player destroyed)...

But since you are adding the instances as children:

add_child(enemy_instance)

We could use the list of children to do this instead.

So if you want to call something on all of them, you could iterate over all the children, for example:

spawner

func _process(_delta:float) -> void:
    for child in get_children():
        var zombie := child as Zombie
        if not is_instance_valid(zombie):
            continue

        zombie.my_method("Director says hello")

Another option could be to put the root of the zombie.tscn scene on a node group (You find this in the Groups tabs of the Node dock), for example, let us say they are in the "zombie" group, so you can do this:

spawner

func _process(_delta:float) -> void:
    get_tree().call_group(&"zombie", &"my_method", "Director says hello")

Node communication is an iceberg. There are other approaches for telling your nodes to do something, notably using signals. But without more context of what are you trying to do, the above ones are the ones I'm willing to suggest.