Make Pong Game in Godot

pong game made in godot

Prerequisite: This tutorial is targeted to users who are familiar with basics of Godot engine. If you are in doubt, you can take a look at this basic Godot tutorial before continuing the Pong game tutorial.

Pong is one of the earliest arcade video games, developed by Atari and released in 1972. It’s a two-dimensional sports game that simulates table tennis. The game features two paddles and a ball. Each player controls a paddle in a vertical motion across the left or right side of the screen. The goal is to use the paddle to hit the ball back and forth, aiming to score points by making the ball miss the opponent’s paddle.

Pong’s simple yet engaging gameplay made it a massive hit, leading to the creation of numerous similar games and contributing significantly to the popularity of video games in the early 1970s. Its success laid the foundation for the gaming industry we know today.

In this post, we will create pong in Godot engine 4.3.

Breakdown

  1. We have to create a ball that follows Newtonian physics. And give it some initial velocity at the start of the game.
  2. We have to create two paddles (or bats). Both of them are same except both are controlled by different players.
  3. A level that has instances of paddles and a ball object. Overall game simulation occurs in the level.

What we will do in Godot engine

  1. A paddle is made as a CharacterBody2D node which will go upward or downward via a script.
  2. Ball is made as a RigidBody2D, so Newtonian physics will be inherited by it as RigidBody2D implements 2D Newtonian physics.
  3. Level is made as a scene. In Level scene:
    • Ball is instanced and initially given a random impulse and after that, we must control it using the two pedals.
    • Two instances of Pedal are created. Both of them are assigned separate player, so they listen to different input events.
    • Goals are made behind each player pedal, if ball goes into goal-1, player-2 gets point else player-1 gets point.
    • Their scores are written in the score GUI labels.
  4. Table tennis-like spin in the ball is also present due to nature of RigidBody2D.

GitHub Project Link + Assets

You can use all the assets from the GitHub project. Remember that all those assets that are not mine are given credit in a.txt file under any folder.

Creating a Ball

Ball is a scene with a RigidBody2D root node. Its gravity_scale is set to 0.0 so it is not effected by the gravity and thus should float freely in space.

Below the root node, add a sprite with ball texture & a collision shape of a circle. Remember to make ball scene in a separate /Ball/ folder, so it will be neat and modular.

The ball scene overall looks like this:

rigid body 2d ball godot scene

After you have create ball scene, attach a script Ball.gd to the root node.

Make sure to connect the body_entered signal on root node (RigidBody2D) to a function in script. Overall script code should be this:

Ball.gd:

extends RigidBody2D


func _ready():
	apply_central_impulse(Vector2(-200, 0))


func _on_body_entered(body):
	if linear_velocity.length() < 232:
		apply_central_impulse(linear_velocity.normalized() * 10)
	
	if linear_velocity.normalized().y > 0.95:
		apply_central_impulse(Vector2(-50, 0))
	

Above code just applies impulse on the ball to give it an initial push towards left. Also, in _process(), we need to make sure that the speed of ball must never decrease too much, so I gave it a small boost if its speed falls below 232.

Also, if the ball is continually moving up and down (vertically), and is stuck, we will just add a horizontal boost to it so it starts moving slightly horizontal and can then reach the pedals.

Creating a Pedal

Pedal/Racket is a simple scene that has CharacterBody2D as its root and looks like this:

pong game godot pedal

Polygon2D is just a node to draw 2D polygon shapes. You can use a rectangle texture. Add a collision shape to the scene as well.

Add script to this scene:

Pedal.gd:

extends CharacterBody2D


@export var speed: float = 2048
@export var player: int = 0 # 0 or 1
@export var custom_color: Color = Color.WHITE


@onready var initial_x_position = position.x

func _ready():
	modulate = custom_color


func _physics_process(delta: float) -> void:
	velocity -= velocity * delta * 4.0
	if player == 0:
		if Input.is_action_pressed("ui_up"):
			velocity.y -= speed * delta
		if Input.is_action_pressed("ui_down"):
			velocity.y += speed * delta
	if player == 1:
		if Input.is_action_pressed("ui_home"):
			velocity.y -= speed * delta
		if Input.is_action_pressed("ui_end"):
			velocity.y += speed * delta
	velocity = round(velocity * 30.0) / 30.0
	move_and_slide()
	
	position.x = initial_x_position

In above code, we have two options for player variable, either it is 0 or 1. If player is 0, it only listens to arrow up or arrow down keys for moving up and down. And if it is 1, it listens to home and end keys.

initial_x_position variable hold the pedal’s initial position. The reason to ad this is that the ball when colliding with pedal pushes the pedal from its original position. Since its x-axis position must remain at same place and only y-axis movement is allowed, I stored its initial position so when rigid-body collides with it and tries to push it, its original position can be overridden and it stays at same position.

Apart from that, the code is just assigning values to y-component of velocity. The following snippet of code is used to make the velocity a bit jagged/pixelated so it feels like older games:

velocity = round(velocity * 30.0) / 30.0

Whenever we spawn a pedal, we just have to assign its player as 0 or 1 and it will work and be able to push the moving rigid-bodies.

Making the Pong Level

Create Level as a scene under a folder named Level.

Replicating Pedals

We have to create two instances of pedals.

making pong game level in godot engine

Spawning the Ball

Ball can be added directly to the scene tree, but we want to spawn new ball every time old one is deleted after having a goal. So, for now, we just have to add a logic to spawn a ball on game start. And spawn ball every time existing ball is found to be null.

Add a script to the level as Level.gd:

extends Node2D

@export var ball: PackedScene

var current_ball: Node2D


func _ready():
	# instantiate ball in middle of screen
	var ball_instance = ball.instantiate()
	add_child(ball_instance)

	ball_instance.global_position = Vector2(
		get_viewport().size.x / 4,
		get_viewport().size.y / 4
	)

	current_ball = ball_instance



func _process(delta):
	if current_ball == null:
		var ball_instance = ball.instantiate()
		add_child(ball_instance)
		
		ball_instance.global_position = Vector2(
			get_viewport().size.x / 4,
			get_viewport().size.y / 4
		)
		
		current_ball = ball_instance

Make sure to assign the ball scene in the inspector; as @export keyword in this script has added a slot for assigning a ball scene in editor’s inspector.

in above code, it spawns a ball in the middle of the screen (by getting viewport width/height and dividing it to get 1/4 of its values).

Adding the Goals & Side Barriers

We need to prevent the ball from crossing the screen in vertical y-axis direction. And we need to make the horizontal ends of screen pass-able but passing through them must be registered as a goal for a player.

So we must add StaticBody2D on up and down ends of screen. And Area2D nodes of left and right ends of screen. This is how the setup looks like:

godot pong tutorial level construction

Notice the nodes named Goal1 and Goal2. They have signals connected for body_entered. Whenever a body (i.e. our ball) enters Goal2, right-side player gets a point, and when ball hits the Goal1 area, left-side player gets a point. In code, thus we have to create 2 more variables for storing the scores and we must implement the two signals that we connected earlier:

Level.gd:


var left_score = 0 # Player 1 score
var right_score = 0 # Player 2 score


# _proces(delta) and _ready() are here...


func _on_goal_1_body_exited(body):
	left_score += 1
	current_ball.queue_free()
	current_ball = null


func _on_goal_2_body_exited(body):
	right_score += 1
	current_ball.queue_free()
	current_ball = null

Adding Score Label

In our scene, we want to visualize the scores of the player. So we must add some nodes to represent the labels text.

I added a CanvasLayer to separate GUI from gameplay. And then added a Control node below it, and two Label nodes below that control node. Setup looks like this (with GUI labels added):

score labels in godot game

In above setup, I applied a pixel-art font on the labels to make them look good. Font name is 04b03 Font. It is shipped with the same GitHub repo I linked.

And in Level.gd script, replace the two functions so UI labels are updated with the score values whenever the score changes:

func _on_goal_1_body_exited(body):
	left_score += 1
	$CanvasLayer/Control/Label1.text = "Left: " + str(left_score)
	current_ball.queue_free()
	current_ball = null


func _on_goal_2_body_exited(body):
	right_score += 1
	$CanvasLayer/Control/Label2.text = "Right: " + str(right_score)
	current_ball.queue_free()
	current_ball = null

Thank you for reading <3

Leave a Reply

Your email address will not be published. Required fields are marked *