Pong was one the earliest arcade video games ever created. Being one of the earliest, it is a simple game. All it takes to recreate it is just a ball, two paddles and some walls for the ball to bounce on. As such, it seems only fitting that this 20 games challenge begins with recreating this milestone of software and hardware engineering.

The decision to tackle this challenge using the Godot game engine - an engine I had never used before - was a chance to learn something new and refreshing in the game engine space. Because it’s my first attempt at building anything in Godot, I skipped reading about best practices and how to tackle challenges the “Correct” way, and instead opted to poke around and find out.

What is pong

At its core, Pong is mostly a computer physics simulation - the video game. Its two paddles and ball are the only moving parts of the entire screen, and both of those components have very well defined properties of how they move. The ball will move across the screen and bounce from the top and bottom walls, while the paddles will move up and down based on player inputs and deflect the ball depending on where the ball hits the paddle. In essence, the game can be broken down into three different interactions, and all of those involve the ball.

The physics of the Ball and the Paddles are trivial to implement with modern game engines, where they already handle all the bouncing, the material properties of the bounce, the movement of the paddles and everything, requiring very little input from the developer themselves. However, in the spirit of the challenge, and taking my first steps into the Godot game engine, I decided to forgo the built-in physics engine to, instead, handle the collisions myself. This does not mean that I didn’t use the provided physics body components, but that the movement and interaction of these components was something I coded up myself.

The Paddles

Kicking the journey off are the paddles. Their basic construction is a sprite and a collider, all of it responding to keyboard inputs. Flexing my drawing skills in 4x4 pixel sprites, I put together two sprites, on for the end of the paddle and one for the middle, and stitched them together in-engine, gave each paddle its own distinct colour and it’s off to the races with the look.

For the functionality, Godot includes 4 different 2D physics colliders: an Area and either a Kinematic, a Rigid or Static Body. In our case, a static body wouldn’t do much good, since it, by design, does not move and is meant to be used for walls. A Rigid or Kinematic body sound like good ideas, however my design required the paddles to reflect the ball in a specific angle depending on where the ball hits the paddle, more on that later. As such, I was left with using an Area, which, in fact, did not actually collide with anything, but rather raises events when a collider enters or exits the area, which I handle in code by adding a listener to the _on_Paddle_body_entered event (again, more on that later).

The movement of the paddles I handle in the Godot _physics_process() subroutine, as it was suggested to handle movement of bodies in here in the documentation for how to handle keybinds. There’s not much to say about it, so here’s the entire code that handles paddles moving up and down:

export var paddleSide = ''; # This is set as either `left` or `right` in the editor
export var upEvent = '%s_paddle_up'
export var downEvent = '%s_paddle_down'

export var maxSpeed = 100;
export var maxY = 260;
export var minY = -260;

func _physics_process(delta):
  var speed = 0;
  if Input.is_action_pressed(upEvent % paddleSide):
    speed = delta * -maxSpeed;
  elif Input.is_action_pressed(downEvent % paddleSide):
    speed = delta * maxSpeed;
 
  global_position.y = clamp(global_position.y + speed, minY, maxY)

The đź…±all

Next on the chopping block is the ball, arguably the star of the show. The sprite for this is a simple 4x4px cross, but the sprite isn’t what makes it the most important component. Its all the interactions that the ball has with the entire rest of the game. It interacts with pretty much anything that is on the screen, and, since I needed special interactions for it, I chose to have the ball be a Kinematic body. This means that the body itself does not move, but is moved through code by settings its velocity. However, it handles collisions no problem, as its velocity is set via the function move_and_collide. To set up the ball and its movement through space, I used set its initial speed and and current speed, picked a random direction for the ball to start heading in and then waited for the player to hit space to start the ball moving

# Initialize variables
var initialDirection = Vector2.ZERO
var velocity = Vector2.ZERO;

export var initialSpeed = 500
# Set initial speed to 0 to have the game be startable with the spacebar
var speed = 0;

# Pick random starting direction
func _ready():
  randomize()
  var initialDir = randi() % 2;
  initialDirection = Vector2.RIGHT if initialDir == 1 else Vector2.LEFT;

The interactions which doesn’t require its own section is the ball to the walls interaction. Since Kinematic bodies will move in the direction you tell them to until hitting a wall, in which case they will just slide along the wall without bouncing, I have to handle the interaction with code, so here it is, the entire code that is responsible for the ball bouncing from walls

func _physics_process(delta):
  var collider = move_and_collide(velocity * delta)
  if collider:
    velocity=velocity.bounce(collider.normal)

When the Ball hits the Paddle

The next step to figure out was the ball hitting a paddle. As mentioned before, the paddles are Physics Areas. As such, they do not have collision detection by default, and they only detect when a collider enters or exits the area. So, to accomplish the required bouncing of the ball, I implemented a listener to the _on_body_entered event emitted by the Area. This listener would check that the body entered was, in fact, the ball (so it wouldn’t trigger when the paddle overlapped the walls), and get its position relative to the paddle and set the return angle based on this position:

func _on_Paddle_body_entered(body: PhysicsBody2D):
  if body.get_script() == ball:
    var bodyPosition = body.global_position;
    var directionVector = global_position.direction_to(bodyPosition); 
    # This roughly translates to a return angle between -90 to 90 degrees

    directionVector.y /= 2; 
    # Direction vector y component is halved so that the return angle would be clamped to 45 degrees

    emit_signal("ball_bounce", directionVector.normalized())

As can be seen above, there’s also a signal ball_bounce that gets emitted from the paddle. Here is its counterpart in the ball script:

export var speedIncrease = 0.1;

func _on_Paddle_ball_bounce(angle: Vector2):
  velocity = angle*speed;
  speed += speed * speedIncrease;

As can be seen, all it does is change the current velocity of the ball based on the angle vector. However, there is one more component here that I haven’t mentioned yet: the ball increases its speed exponentially as the bouncing continues, but only after the second bounce, to have the ball move somewhat predictably at the beginning of the game. After the second bounce, the speed is increased by 10%. The effect is the game speeding up and getting more challenging the longer it goes on until someone scores a goal

GOOOAAAAL!!!! âš˝

Last but not least, it’s the end-zones that need to be set up. Those are, again, Areas that detect when a body enters them, but now the ball does not bounce, but instead, the game should count a point to the player who scored the goal and reset the balls speed and move it to the middle of the screen, and, after a delay, send it towards the player who didn’t score. That’s all done in the resetBall function below. The Timer is set to be 3 seconds long to give both players plenty of time to rest and re-center.

func resetBall(direction: Vector2):
  speed = intialSpeed
  global_position = direction * 5
  velocity = Vector2.ZERO
  $Timer.start()
  yield($Timer, "timeout")
  velocity = speed * direction;

UI & Sound

Now that all of that is set and done, the game needs a UI. This is achieved by following the tutorial on how to create UIs from the Godot engine documentation, but really, all it is, is a few text nodes with the whole UI having a little bit of code that handles game starting and score updating

signal start_game

var gameBegun = false

func _process(delta):
  if !gameBegun && Input.is_action_pressed('ui_select'):
    $Message.text = '';
    gameBegun = true
    emit_signal('start_game')

func update_score_left(score: int):
  $ScoreLeft.text = str(score);

func update_score_right(score: int):
  $ScoreRight.text = str(score);

As for sounds, I thought it would be a good touch to have just simple pops when a wall is hit and when a paddle is hit. This is achieved by sprinkling some calls to audio streams in the scene:

# Added to the logic that controls the way the ball
# bounces off of walls
$WallHit.play()

# Added the _on_body_entered signal for the paddles
$Hit.play()

Closing Thoughts

Sometimes, going back to the basics is a great way to learn something new. Even more often, sleeping on the knowledge acquired opens up new possibilities of growth. In this case, with the power of hindsight, I could have handled the collisions with the paddles in the ball physics update script, and maybe not relied on vectors for bounce angles, but rather calculated the angle properly, or I could have cleaned up the way I name my variables so they would be in snake_case instead of camelCase to match the rest of the engine.

Oh well, onwards and upwards. Next game: Breakout, as suggested by the 20 games challenge.

Clong the game can be found on itch.io

The source code for the game is on github

Updated:

Comments