Introduction: Pong
This instructable will serve as an introduction to Godot as well as a tutorial on how to make a simple Pong game.
Supplies
A computer or laptop, Godot Engine, and the assets linked below.
https://github.com/russs123/Pong_tut/blob/main/assets/Bg.png
https://github.com/russs123/Pong_tut/blob/main/assets/pixel_font.ttf
Step 1: Making Our Scene
After opening Godot:
- Select "Other Node"
- Next select Sprite2d and rename this node to "Main"
- This will act as our background and parent node
- On the right hand side under texture the Bg.png should be selected
- Finally, in the texture drop down under the CanvasItem section, the Filter should be set to Nearest in order to sharpen the background image.
Step 2: Player and CPU Nodes
For the player controlled paddle:
- Create a child node "Color Rect"
- Under "Layout" change "Anchors" to Center Left
- Under "Transform"
- Size: change x to 20 and y to 120
- Position: change y to -60
- Create a second child node "CollisionShape2D"
- Change Shape to Rectangle and drag the points around until it matches the Color Rect that was created before.
- Select the Player node and under Transform
- Position: change x to 50 and y to 324
For the CPU controlled paddle:
- Select the Player node and all child nodes inside of it, and hit Ctrl + D to duplicate
- Under Transform
- Position: change x to 1080 and y to 324
Step 3: Ball Node
After selecting the Main node
- Create a "CharacterBody2D" node and rename it "Ball"
- While selecting the Ball node, create a ColorRect node
- Under Layout change Anchors to Center
- Under Transform
- Size: Change x to 10 and y to 10
- Position: Change x to -5 and y to -5
- Under the Ball node, create a CollisionShape2D
- Change the Shape to Rectangle and make sure that the shape is mirroring the Ball ColorRect node
Step 4: Border Node
After selecting the Main node
- Create a StaticBody2D and rename it "Borders"
- Select the Borders node and create a CollisionShape2D and rename it Top
- Under "Collision" change Layer to 2 and make sure that 1 is deselected
- Then drag the corners until it matches the top box in the picture attached
- Select the Top node and duplicate it using Ctrl + D and move it down until it matched the bottom box in the picture attached
Step 5: Score Nodes
After selecting the Main node
- Create a "Area2D" child node and rename it "ScoreLeft"
- Adjust the corners until it matches the box on the left side of the image attached
- Next duplicate the ScoreLeft node and rename it "ScoreRight"
- Adjust the corners of ScoreRight until it matched the box on the right side of the image attached
Step 6: HUD Nodes
After selecting the Main node
- Create a "CanvasLayer" child node and rename it "HUD"
- Within the HUD node:
- Create a "Label" child node and rename it "Player Score"
- Within the "Player Score" child node:
- Set Text to 0
- Transform:
- Size: x to 50 and y to 53
- Position: x to 456 and y to 5
- Theme Overrides: Insert the font file attached under the materials section
- Next using Ctrl + D duplicate the Player Score child node and rename it "CPU Score"
- Within the CPU Score child node
- Transform:
- Position: x to 648 and y to 5
Your scene should now look like the image attached.
Step 7: BallTimer Node
After selecting the Main node
- Create a "Timer" child node and rename it BallTimer
- Next set "Wait Time" to 1 s and insure:
- Process Callback = Idle
- One Shot = On
- Autostart = On
Step 8: Main Script
- On the top of the Godot workspace, select the Script tool
- Select the main parent node and in the top left corner select the icon that looks like a scroll, when you hover over it, it should say "Attach or select an existing script to the selected node." From now on, I will refer to this as the new script button and it is used to add scripts to nodes.
- After clicking on the new script button, delete what is existing in the window and copy and paste what has been copied below.
extends Sprite2D
var score :=[0, 0]# 0:Player, 1: CPU
const PADDLE_SPEED : int = 500
func _on_ball_timer_timeout():
$Ball.new_ball()
func _on_score_left_body_shape_entered(body_rid, body, body_shape_index, local_shape_index):
score[1] += 1
$"HUD/CPU Score".text = str(score[1])
$BallTimer.start()
func _on_score_right_body_shape_entered(body_rid, body, body_shape_index, local_shape_index):
score[0] =+ 1
$"HUD/Player Score".text = str(score[0])
$BallTimer.start()
Although these scripts will not look very pretty due to how they transfer between documents, they should function just the same.
Step 9: Player Script
- Select the Player node and copy and paste the script attached
extends StaticBody2D
var win_height : int
var p_height : int
# Called when the node enters the scene tree for the first time.
func _ready():
win_height = get_viewport_rect().size.y
p_height = $ColorRect.get_size().y
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if Input.is_action_pressed("ui_up"):
position.y -= get_parent().PADDLE_SPEED * delta
elif Input.is_action_pressed("ui_down"):
position.y += get_parent().PADDLE_SPEED * delta
#limit paddle movement to window
position.y = clamp(position.y, p_height / 2, win_height - p_height / 2)
Step 10: CPU Script
- Select the CPU node and copy and paste the script attached below
extends StaticBody2D
var ball_pos : Vector2
var dist : int
var move_by : int
var win_height : int
var p_height : int
# Called when the node enters the scene tree for the first time.
func _ready():
win_height = get_viewport_rect().size.y
p_height = $ColorRect.get_size().y
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
#move paddle towards ball
ball_pos = $"../Ball".position
dist = position.y - ball_pos.y
if abs(dist) > get_parent().PADDLE_SPEED * delta:
move_by = get_parent().PADDLE_SPEED * delta * (dist / abs(dist))
else:
move_by = dist
position.y -= move_by
#limit paddle movement to window
position.y = clamp(position.y, p_height / 2, win_height - p_height / 2)
Step 11: Ball Script
- Select the Ball node and copy and paste the script attached below
extends CharacterBody2D
var win_size : Vector2
const START_SPEED : int = 500
const ACCEl : int = 50
var speed : int
var dir : Vector2
const MAX_Y_VECTOR : float = 0.6
# Called when the node enters the scenetree for the first time.
func _ready():
win_size = get_viewport_rect().size
func new_ball():
#randomize start position and direction
position.x = win_size.x / 2
position.y = randi_range(200, win_size.y -200)
speed = START_SPEED
dir = random_direction()
func _physics_process(delta):
var collision = move_and_collide(dir * speed * delta)
var collider
if collision:
collider = collision.get_collider()
#if ball hits paddle
if collider == $"../Player" or collider == $"../CPU":
speed += ACCEl
dir = dir.bounce(collision.get_normal())
#if it hits a wall
else:
dir = dir.bounce(collision.get_normal())
func random_direction():
var new_dir := Vector2()
new_dir.x = [1, -1].pick_random()
new_dir.y = randf_range(-1, 1)
return new_dir.normalized()
func new_direction(collider):
var ball_y = collider.position.y
var pad_y = collider.position.y
var dist = ball_y - pad_y
var new_dir :=Vector2()
#flip the horizontal direction
if dir.x > 0:
new_dir.x = -1
else:
new_dir.x = 1
new_dir.y = (dist / (collider.p_height / 2)) * MAX_Y_VECTOR
return new_dir.normalized()
Step 12: Testing
Press F5 and enjoy pong!

