New ball instance isn't detected by either of my goals (2D Pong-like game)

Godot Version

4.3

Question

Hello! I have a problem with my project: a new instance of my ball scene isn’t detected by either of my goals (Area2D), even though it acts fine with everything else. I have configured all of the collision layers and masks properly, but there’s clearly something else wrong.

The way my game works is that whenever something enters either of the Area2Ds (I have a body_entered signal coming from both of them), it checks if the entered body is part of the global “Ball” group I made. If not, it gets added, an “increase points for appropriate player” fires depending on the goal scored and the object gets freed using queue_free() method. A snippet from my ball script below:

signal LeftGoalScored
signal RightGoalScored

func _on_left_goal_body_entered(body): #Signal coming from the left goal
	var condition = body.is_in_group("Ball")
	if not condition:
		body.add_to_group("Ball")
		LeftGoalScored.emit()
		queue_free()
	else:
		LeftGoalScored.emit()
		queue_free()


func _on_right_goal_body_entered(body): #Same thing, but for the right
	var condition = body.is_in_group("Ball")
	if not condition:
		body.add_to_group("Ball")
		RightGoalScored.emit()
		queue_free()
	else:
		RightGoalScored.emit()
		queue_free()


The execution continues on top of the scene tree, where I have a script attached to a Node2D. The script below:

extends Node2D


@onready var ball_scene = preload("res://Scenes/Ball/ball.tscn")


@onready var P1_Score_node = $P1_Score
@onready var P2_Score_node = $P2_Score


var P1_Score
var P2_Score

func _ready():
	P1_Score = 0
	P2_Score = 0


func AddNewBall():
	var new_ball_instance = ball_scene.instantiate()
	new_ball_instance.set_collision_layer_value(2, true)
	new_ball_instance.set_collision_layer_value(1, false)
	new_ball_instance.set_collision_mask_value(2, true)
	new_ball_instance.name = "NewBall"
	call_deferred("add_child", new_ball_instance)


func _on_ball_left_goal_scored():
	P2_Score += 1
	P2_Score_node.text = "%s" % P2_Score
	AddNewBall()


func _on_ball_right_goal_scored():
	P1_Score += 1
	P1_Score_node.text = "%s" % P1_Score
	AddNewBall()

The new ball isn’t detected by either of the goals. I checked using print-statements.

Also I think it’s worth mentioning here that I put the StaticBody2Ds (ceiling and floor) specifically to a different collision layer, because they were interfering with my goals (corners were touching). The collision layers go as follows:

  1. Ceiling = Layer 1, Mask 1
  2. Floor = same as above
  3. Ball = Layer 2, Mask 1 and 2
  4. Player1 = Layer 1, Mask 1 and 2
  5. Player2 = same as above
  6. Left goal = Layer 2, Mask 2
  7. Right goal = same as above

Scene tree (editor view)

Scene tree (remote view)

Scene tree (after the first goal)

Thank you for your answers in advance!

queue_free() is applied to self, what ever this script is attached to is being deleted. You want to delete the ball which would be body.queue_free().

Small question, why check is_in_group at all?

Hi!

This script is attached to my ball, which is a scene on it’s own. And about your question, i guess the check doesn’t do anything. I removed it now. I’ll try your suggestion about the body.queue_free() now.

Edit:
Thanks for the suggestion! I applied it, but nothing changes.

If this script is part of the Ball then you have to connect these signals when you make a new ball. I’d recommend moving this goal checking script to the goals instead for simplicity.

You could connect the signals like so, but again this can be avoided if the script is on the goals instead.

func AddNewBall():
	var new_ball_instance = ball_scene.instantiate()
	new_ball_instance.set_collision_layer_value(2, true)
	new_ball_instance.set_collision_layer_value(1, false)
	new_ball_instance.set_collision_mask_value(2, true)
	new_ball_instance.name = "NewBall"
	call_deferred("add_child", new_ball_instance)

	# Connecting signals dynamically
	$LeftGoal.body_entered.connect(new_ball_instance._on_left_goal_body_entered)
	$RightGoal.body_entered.connect(new_ball_instance._on_right_goal_body_entered)
	new_ball_instance.LeftGoalScored.connect(_on_ball_left_goal_scored)
	new_ball_instance.RightGoalScored.connect(_on_ball_right_goal_scored)

Do you mean that i should make a specific script for each of the goals or move the goal checking system to the top of my scene tree (the code that you are referencing in your last reply) ?

Thanks!

Either one will work better than the Ball, I suppose the top of the scene would be best since it already has functions for scoring a goal, you would only need to add body.queue_free() to the left/right_goal_scored connections.

My new code goes as follows:

extends Node2D


@onready var ball_scene = preload("res://Scenes/Ball/ball.tscn")


@onready var P1_Score_node = $P1_Score
@onready var P2_Score_node = $P2_Score


var P1_Score
var P2_Score


signal LeftGoalScored
signal RightGoalScored


func _ready():
	P1_Score = 0
	P2_Score = 0


func AddNewBall():
	var new_ball_instance = ball_scene.instantiate()
	new_ball_instance.set_collision_layer_value(2, true)
	new_ball_instance.set_collision_layer_value(1, false)
	new_ball_instance.set_collision_mask_value(2, true)
	new_ball_instance.name = "NewBall"
	call_deferred("add_child", new_ball_instance)
	
	$LeftGoal.body_entered.connect(new_ball_instance._on_left_goal_body_entered)
	$RightGoal.body_entered.connect(new_ball_instance._on_right_goal_body_entered)
	new_ball_instance.LeftGoalScored.connect(_on_ball_left_goal_scored)
	new_ball_instance.RightGoalScored.connect(_on_ball_right_goal_scored)

func _on_left_goal_body_entered(body):
	LeftGoalScored.emit()
	body.queue_free()


func _on_right_goal_body_entered(body):
	RightGoalScored.emit()
	body.queue_free()


func _on_ball_left_goal_scored():  
	P2_Score += 1
	P2_Score_node.text = "%s" % P2_Score
	AddNewBall()


func _on_ball_right_goal_scored():
	P1_Score += 1
	P1_Score_node.text = "%s" % P1_Score
	AddNewBall()

I removed everything from the ball script like the signals, checks etc… However, i fear i might have broken something. Ball gets freed from the queue, but scores dont update nor the new ball doesnt spawn.

I kinda understand what your code does, but did i do something wrong? I’ll try to figure it out in the meantime.

Thanks!

I recommended either moving the script, or connecting signals, not both. You can remove my previous code sample.


Since the goal scored functions are on the same script you do not need the GoalScored signals, only connecting the area’s body_entered signal through the editor to the top script.

func _on_left_goal_body_entered(body):
	_on_ball_left_goal_scored()
	body.queue_free()


func _on_right_goal_body_entered(body):
	_on_ball_right_goal_scored()
	body.queue_free()


func _on_ball_left_goal_scored():  
	P2_Score += 1
	P2_Score_node.text = "%s" % P2_Score
	AddNewBall()


func _on_ball_right_goal_scored():
	P1_Score += 1
	P1_Score_node.text = "%s" % P1_Score
	AddNewBall()

No way, it actually works :sob:

Edit: i’m so darn happy. Can you shortly explain why this fixed it and what can i do in the future to avoid such mistakes?

The difference in setup is that outside connections are not part of a scene, you had to connect the Ball to various nodes in the editor, but when the ball is destroyed so are those connections. When you add a new Ball it is exactly and only as it is defined in it’s .tscn scene file, no external connections.

So to circumvent removing and re-attaching connections you can only connect to the nodes you know will not be destroyed, the Goals and the root’s script. Signals do not know or care where you connect them so a Goal → Root connection is just as valid and functional as a Goal → Ball connection, but the latter is destroyed with the Ball. Maybe you think a collision base signal must be connected to the colliding objects, but that is not so, the connected function will have the other colliding body through the body argument.

1 Like