Spawner script works only for first instance of object

Godot Version – v4.2.1

Hi! For a bit of background, I am working on a 2D platformer which has a main character (CharactedBody2D node) and several objects the player interacts with, which come from one RigidBody2D node ‘spawned’ by a spawner script. The spawner instantiates the object, and I use a series of if statements to check the proximity of the player to the Area2D of the object so the player can pick it up (in reallity, the sprite of the object and the collision layer are momentarily hidden and the player can perform certain actions until they drop the object again).

extends Node2D

var balloon_scene = preload("res://scenes/Balloon.tscn")
var new_balloon_scene = balloon_scene.instantiate()

func spawn_balloon(balloon):
	balloon = new_balloon_scene
	balloon.set_collision_layer(1)
	balloon.set_collision_mask(1)
	balloon.set_position(self.position)
	print("\nspawn_balloon()")
	print("- BalloonSpawner is: %s" % self)
	print(" - Spawning balloon at position at: %s" % self.position)
	var balloon_sprite = balloon.find_child("Sprite2D")
	balloon_sprite.show()

func remove_balloon(balloon): 
	balloon.set_collision_layer(2)
	balloon.set_collision_mask(2)
	var balloon_sprite = balloon.find_child("Sprite2D")
	balloon_sprite.hide()

In the character code:
Custom signal

func tiny_guy_grabs_balloon():
	emit_signal("grabbed_balloon")
	emit_signal("position_with_balloon")
	

Where it checks whether the main character meets criteria to grab the object:

if Input.is_action_just_pressed("activate") and is_close_to_balloon:
		print("\nCharacter pressed activate")
		$Sprite2D.play("baloonfloat")
		print("\nPlaying animation of char with baloon")
		has_balloon = true
		print("grabbed_balloon at pos: %s" % self.position)
		tiny_guy_grabs_balloon()

The custom signal called in the spawner node:

func _on_tiny_guy_grabbed_balloon():
	get_tree().call_group("BalloonSpawners", "test_connection")
	print("Trying to call group here")
	can_drop_balloon = true
	print("_on_tiny_guy_grabbed_balloon() - can_drop_balloon=%s" % can_drop_balloon)
	print("_on_tiny_guy_grabbed_balloon() - balloon=%s" % self)

Now, onto the problem – when the player picks up and drops the object for the first instance it works fine. However, for all subsequent instances of the object the spawner hides the object at the right place, but never ‘respawns’ it (the second instance remains hidden and can_drop_balloon never becomes true). I have tried to put all instances in a group and emit a custom signal to the whole group, but it doesn’t seem to work.

Is this a problem with the underlying logic of how Godot operates or could it be a faulty signal not reaching all instances? Would appreciate any advice on this.

Thanks!

P.S. I’ve tried to include relevant snippets of code. I’m also new to both programming and Godot, so please keep that in mind!

Could you show the code snippets for the places in your code where spawn_balloon and remove_balloon are called? Presumably, there is something wrong with the logic that is calling these functions.

Sure thing!

In the _process() function I call both spawn_balloon and remove_balloon.

if first_time:
		new_balloon = new_balloon_scene
		get_parent().add_child(new_balloon)
		spawn_balloon(new_balloon)
		print("Created first balloon from spawner named: %s" % self)
		first_time = false

The first time the scene loads, the balloon is instantiated. Then I check what action is pressed and if the main character is close to the balloon (and the other way around).

Case 1

if Input.is_action_just_pressed("activate") and new_balloon.is_close_to_tiny_guy:
		remove_balloon(new_balloon)
		print("\tRemoved activated balloon %s" % new_balloon)

Case 2

if Input.is_action_just_pressed("deactivate"):
		print("- on deactivate pressed - BalloonSpawner is: %s" % self)
		if can_drop_balloon:
			spawn_balloon(new_balloon)
			print("Current new balloon position -  %s" % new_balloon.position)
			can_drop_balloon = false

Also the new_balloon.is_close_to_tiny_guy comes from the script of the the balloon itself, where I check with _on_area_2d_body_entered and _on_area_2d_body_exited the position of the character in relation to the balloon.

Thanks for the code snippets.

Notes

I am having trouble finding the exact cause of your issue from the code you have shown. However, I do think your system is somewhat convoluted (i.e. spaghetti code) so I’ll try and explain what I’m seeing and give you some suggestions on how to solve potential issues.
Correct me if I’m wrong.

Observation 1: Not making use of the _Ready method

In your recent reply you showcase an if-statement within _process which runs a single time via the use of a first_time variable.
This is not necessarily an issue - it’s what you would do in a fresh program to run something on startup. However, Godot already supplies you with a function that runs once. It’s called _ready and should contain any code that should only run once.
I would recommend moving the contents of your first_time if-statement into _ready

Observation 2: Using the spawner to directly control the state of a balloon

As far as I can tell, you’re using the spawner to control the visibility and position of a balloon. This works but it’s a bad practice - especially in larger projects.

Normally, you try to contain object-specific functionality inside its own class/script; a programming concept called encapsulation. You do this for a number of reasons:

  1. Avoids code duplication
  2. Simplifies class-to-class interactions
  3. Improves code readability
  4. …and more

In your case, this would mean moving all logic needed for balloons to your pre-existing Balloon script/class, such as:

  • Toggling the balloon’s visibility
  • Storing references to nodes inside the balloon(e.g. your Sprite2D)
  • Determing whether an object is in range of the balloon (e.g. your player)

The upside to this mode of implementation, is that your code now becomes much simpler.
The soft rule to remember is: if it’s called spawner it should only be spawning stuff; if it’s called player_movement it should only be performing movement-related stuff, and so on…

Example

I have never used GDScript so forgive any syntax errors here.
Balloon

extends Node2D

var sprite;

func _ready():
    sprite = get_node("Sprite2D")

// Move input logic from your spawner's _process function to here

func show():
    balloon.set_collision_layer(1)
    balloon.set_collision_mask(1)
    sprite.show()

func hide():
    balloon.set_collision_layer(2)
    balloon.set_collision_mask(2)
    sprite.hide()

Spawner

extends Node2D

var balloon_scene = preload("res://scenes/Balloon.tscn")

func _ready():
    spawn_balloon()

func spawn_balloon(balloon):
    balloon = balloon_scene.instantiate()
    balloon.set_position(self.position)
    balloon.show(t)

// Remove the remove_balloon function. You should be removing individual balloons via the balloon object.

Observation 3: Function and variable names that are too verbose

This is not too big of a deal for you at the moment, but as your projects grow you will need to create systems that are more generic. What does this mean? It means that a spawner may want to spawn all kinds of different objects - not just balloons. If the function were to still be called spawn_balloon, it would be misrepresenting its functionality. Similarly, tiny_guy_grabs_balloon may become confusing (at a later time or to others) as to what object the “tiny guy” is. It is already inferred that the “tiny guy” is the one grabbing the balloon because the object containing the function is the “tiny guy”. Therefore, name it grab_balloon because that is what it does.

Further questions

  • What is the purpose of can_drop_balloon?
    • What script is this variable in?
  • Why not just delete the balloon when picking it up?

If you got any corrections or additional questions, I’m happy to respond.

Note: The documentation of Godot is your friend. If you ever get stuck, remember to look up how something works.

1 Like

Another observation , you dont need to do this in code, as you can set these in the inspecter under the Area2D values.

Thank you for the detailed reply, I appreciate it (and the time it took to write all of it).

Re-spaghetti code, I agree with you – it has very quickly become quite convoluted, partly because I’m still grappling with how Godot works and how to make full use of its features. For example, I didn’t know code in the _ready() function only runs once. I did have _ready() in the script, but using it for other things.

To answer your questions:

  1. can_drop_balloon is a variable in the spawner node that is initialised to false. When the signal _on_tiny_guy_grabbed_balloon is emitted (by tiny_guy, upon pressing action “activate” and being close to the balloon) can_drop_balloon becomes true. Largely speaking, it’s a variable that limits the behaviour of the actions (e.g. without it, pressing ‘deactivate’ which is the action required to ‘drop’ the balloon
  2. tried deleting the balloon, but didn’t work as expected – might go back to testing this method out

Regarding encapsulation, the balloon_spawner.gd is kind of just that – a script that controls the behaviour of balloon instances. However, I see how that bit of code could work better in the balloon script rather than balloon_spawner.

Thanks for all the notes, I’ll keep all of this in mind going forward!

1 Like

Hi, thanks for pointing that out. The only reason I’m setting them in code is because I’m resetting them in another function call ( remove_balloon ) – thought that would be a clear way to see at which level the balloon is at different moments.

1 Like