Time to handle scoring. The first step is to create goals. For this we are going to use an Area node, attached to it is a CollisionShape2D that defines the region. We create one of these on each side of the field. Not sure why I’m saying we, I did it.
Now, it’s time to learn a little bit about signals in Godot. Signals are Godot’s version of an observer pattern, they allow you to have a trigger on one node be reacted to from another. According to Godot’s documentation this is to help limit coupling and keeps your code more flexible. In our use case here, for example, we can send a signal from the goals to the game controller, allowing it to react to the ball entering the zone.
We’ll start with the player goal. In the editor we’ll go to the node tab to expose the available signals.
Double clicking on body_entered brings up this screen, where we hope to connect to the script on the game scene Node.
Not really a need to change the receiver method name, so I click Connect.
And now the editor has added a new function to our GameController script where we’ll process the signal.
For testing purposes we’ll start with something simple. When the ball enters the area, we’ll delete it. Also, I’m going to take this chance to fix some of last week’s tech debt, and make it so only one ball exists at a time.
With that we can now delete the ball, as well as prevent another ball from being fired while a ball is active.
Next, time to add a score. For this we are going to have our GameController handle and track the score for both the player and the com. When the PlayerGoal is entered we need to make sure to increment the Com score (and vice-versa for the other goal).
Next we need a way to display the score. I spent a bit of time to download a free pixel font, and spent a bit more time googling and learning how to import that font into Godot, but I finally got my two labels made, and I’m pretty happy with the look of them. Next I just need to change the label text for each to match the current score value associated with it.
With this it’s just a matter of pressing go and seeing if it works.
And, almost immediately I ran into a problem.
Before even launching my first ball the score is already 3 to 2. It took me a bit to figure out the problem which is that my paddles are technically partially inside my scoring area, so they are triggering the scores to go up before the initialization even finishes…
The easy solution would be to simply move the paddles slightly so that they are not inside the score zone. The better solution was to learn a little bit about collision layers and masks in Godot.
By setting up separate collisions layers for the paddles and balls, and making the score area only care about the ball layer with a mask, it should solve my issue.
In project settings I made three layers, named Paddles, Ball and Goals.
In the inspector I can set the collision layers and masks for different object. For my Paddles I set them on layer 1, and have them mask on 1 and 2.
For the ball, it lives on layer 2, and has a mask on all three layers.
And then the goals live on layer 3, and only listen to layer 2.
And that fixed my issue. Now, when I start up the game the score is 0 to 0. As much as I would have appreciated a bit of a handicap in my favor, better to fix the issue and have everything behave as I want it to.
And with that, we can finally play Pong!
After each ball pressing spacebar will fire another ball from the player side. Of course, you can’t win or lose yet, as you play the scores will just increase with no real end to the game. But, that’s a problem for another day. Here is the final code for my GameController script after today:
extends Node2D var ballscene = load("res://Ball.tscn") onready var comPaddle = get_node("Com Paddle") onready var playerPadd = get_node("Player Paddle") var current_ball var playerScore = 0 var comScore = 0 func _init(): playerScore = 0 comScore = 0 func _process(_delta): $PlayerScoreLabel.text = str(playerScore) $ComScoreLabel.text = str(comScore) if Input.is_action_just_pressed("launch"): if current_ball != null: return current_ball = ballscene.instance() current_ball.position.x = playerPadd.position.x + 20 current_ball.position.y = playerPadd.position.y add_child(current_ball) comPaddle.ball = current_ball func _delete_ball(): if current_ball != null: current_ball.queue_free() current_ball = null comPaddle.ball = null func _on_PlayerGoal_body_entered(body): _delete_ball() comScore += 1 func _on_ComGoal_body_entered(body): _delete_ball() playerScore += 1
Tomorrow I plan to add victory/defeat, and maybe a start screen.