gabe
gabe

Reputation: 89

Godot Engine Multiple Objects Instanced

I have an issue instantiating objects.

On the one hand, I have the following script for one projectile in one scene:

extends KinematicBody2D

var speed = 200
var life_time = 2
var life_spawn = 0

func _physics_process(delta):
    var collision = move_and_collide(Vector2.UP * delta * speed)
    life_spawn += delta
    if life_spawn > life_time:
        queue_free()
    pass

and on the other hand I have the player in other scene with the following script:

extends KinematicBody2D

func _physics_process(delta):
    if Input.is_action_pressed("ui_accept"):
        createLaser()
    pass

func createLaser():
    var laser = preload("res://scenes/space_ship/laser/Laser.tscn")
    var left_fired_laser = laser.instance()
    var right_fired_laser = laser.instance()
    var left_cannon = get_node("Cannons/left_cannon").get_global_position()
    var right_cannon = get_node("Cannons/right_cannon").get_global_position()
    left_fired_laser.position = Vector2(left_cannon.x, left_cannon.y)
    get_parent().call_deferred("add_child", left_fired_laser)
    right_fired_laser.position = Vector2(right_cannon.x, right_cannon.y)
    get_parent().call_deferred("add_child", right_fired_laser)
    pass

The problem is that the object is instantiated a lot of times. Even if I put a yield() function. If I put that function the object waits to be instantiated but it is instantiated a lot of times anyway.

Upvotes: 2

Views: 3063

Answers (1)

Theraot
Theraot

Reputation: 40170

How many and how often do you want to instance?

You say:

The problem is that the object is instantiated a lot of times.

But how many times is a lot?

I'll answer the cases that come to mind. Also, some of these can be combined.

If this answer does not cover what you want… You will need to be more specific. Baring that, hopefully you can figure it out considering these tools:

  • Picking between is_action_pressed vs is_action_just_pressed.
  • The use of Timers (the actual object or makeshift with delta).
  • Signals (including but not limited to the timeout signals of actual Timers).

Speaking of signals, all the approaches below require no special knowledge of the instance (except that it is some Node2D). You might add custom signals to the instances and connect to them to know when you can create more instances.

Another thing you can do is hold references to the instances to interrogate them (you may for example use is_instance_valid). However, I present below an approach that uses a tree_exited signal, which I consider more general.

You could also take advantage of AnimationPlayer with a call method track. For example if you need to add the instances on a particular frame of the animation.


Once per input

This code instances every physics frame as long as the input is pressed:

func _physics_process(delta:float) -> void:
    if Input.is_action_pressed("ui_accept"):
        createLaser()

If you only want to instance the first physic frame when the input was pressed, use is_action_just_pressed:

func _physics_process(delta:float) -> void:
    if Input.is_action_just_pressed("ui_accept"):
        createLaser()

Once every so often

We can use the same makeshift timer strategy you use for bullet life time:

var instance_period:float = 10.0
var instance_elapsed:float = 0.0

func _physics_process(delta:float) -> void:
    instance_elapsed += delta
    if Input.is_action_pressed("ui_accept") and instance_elapsed > instance_period:
        createLaser()
        instance_elapsed = 0.0

Only one time ever

If you only want one, we can hold a boolean variable to know if we already instanced:

var did_instance:bool = false

func _physics_process(delta:float) -> void:
    if Input.is_action_pressed("ui_accept") and not did_instance:
        createLaser()
        did_instance = true

Only a fixed number of times ever

You can use an integer countdown:

export var yet_to_instance:int = 10

func _physics_process(delta:float) -> void:
    if Input.is_action_pressed("ui_accept") and yet_to_instance > 0:
        createLaser()
        yet_to_instance -= 1

I made it an export variable so you can edit it from the Inspector panel.

*This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Also you can consider "Only one time ever" an special case when the number is 1.


Only a recharging number of times

This is a particular way to combines the makeshift timer with the idea of a fixed number of times to instance:

var recharge_period:float = 10.0
var recharge_elapsed:float = 0.0

export var max_to_instance:int = 10
onready var yet_to_instance:int = max_to_instance

func _physics_process(delta:float) -> void:
    if Input.is_action_pressed("ui_accept") and yet_to_instance > 0:
        createLaser()
        yet_to_instance -= 1
        # recharge_elapsed = 0.0

    recharge_elapsed += delta
    if recharge_elapsed > recharge_period:
        if yet_to_instance < max_to_instance:
            yet_to_instance += 1

        recharge_elapsed = 0.0

This way the number of instances you can create increases overtime up to a maximum. If you can un-comment # instance_elapsed = 0.0 if you want to prevent that number to increase when there is input. You can think of it as autoreloading.

This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Or alternatively with "Once every so often" (i.e. makeshift time) to limit the instance rate.


At most an amount alive

We are going to connect to the tree_exited signal to update our count:

const laser = preload("res://scenes/space_ship/laser/Laser.tscn")

export var max_to_instance:int = 20

func _physics_process(delta:float) -> void:
    if Input.is_action_pressed("ui_accept") and max_to_instance > 0:
        createLaser()

func createLaser() -> void:
    createLaserFrom($Cannons/left_cannon)
    createLaserFrom($Cannons/right_cannon)

func createLaserFrom(cannon:Node2D) -> void:
    var fired_laser = laser.instance()

    max_to_instance -= 1
    fire_laser.connect("tree_exited", self, "laser_exited")

    get_parent().add_child(fired_laser)
    fired_laser.global_position = cannon.global_position

func laser_exited() -> void:
    max_to_instance += 1

This approach combines well with "Once per input" (i.e. use is_action_just_pressed). Or alternatively with "Once every so often" (i.e. makeshift time) to limit the instance rate.

Upvotes: 1

Related Questions