Skip to main content

Overview

Space Pong uses two main GDScript files: ball.gd for ball physics and game start logic, and player.gd for paddle movement control. Both scripts extend CharacterBody2D to leverage Godot’s physics engine.

Script Files

ball.gd

22 lines - Physics, collision, and game start

player.gd

20 lines - Input handling and movement

Ball Script

File: res://scenes/ball.gd (22 lines) The ball script handles game initialization, physics simulation, and collision response.

Complete Source Code

extends CharacterBody2D

var started = false
var start_speed = 500
var incremental_speed = 1.05
var angle = [-250, 250]

func _physics_process(delta):
	if Input.is_action_pressed("Start") and started == false: 
		start_game()  
		
	if started:
		var collision = move_and_collide(velocity * delta)
		if collision != null:
			velocity = velocity.bounce(collision.get_normal()) * incremental_speed
			print(velocity)

func start_game():
	started = true
	velocity.y = -start_speed
	velocity.x = angle.pick_random()

Variables

1

started (bool)

Tracks whether the game has begun. Initialized to false, set to true when spacebar is pressed.
var started = false
2

start_speed (int)

Initial vertical speed of the ball when the game starts. Set to 500 pixels/second.
var start_speed = 500
3

incremental_speed (float)

Speed multiplier applied after each collision. Set to 1.05 (5% increase per bounce).
var incremental_speed = 1.05
This creates progressively faster gameplay as the ball bounces more.
4

angle (Array)

Array of possible horizontal velocities for random ball direction at game start.
var angle = [-250, 250]
The ball will move either left (-250) or right (+250) pixels per second.

Physics Process

The _physics_process(delta) function runs every physics frame (~60 FPS):
func _physics_process(delta):
	if Input.is_action_pressed("Start") and started == false: 
		start_game()  
		
	if started:
		var collision = move_and_collide(velocity * delta)
		if collision != null:
			velocity = velocity.bounce(collision.get_normal()) * incremental_speed
			print(velocity)

Game Start Detection

if Input.is_action_pressed("Start") and started == false:
	start_game()
This checks two conditions:
  1. Input.is_action_pressed(“Start”): Is the spacebar (custom “Start” action) pressed?
  2. started == false: Has the game not yet started?
If both are true, start_game() is called.
The game can only start once per session. After started becomes true, pressing spacebar has no effect.

Movement and Collision

if started:
	var collision = move_and_collide(velocity * delta)
	if collision != null:
		velocity = velocity.bounce(collision.get_normal()) * incremental_speed
		print(velocity)
1

Move the Ball

move_and_collide(velocity * delta) moves the ball by its velocity scaled by delta time.
  • Returns null if no collision occurred
  • Returns a KinematicCollision2D object if collision detected
2

Handle Collision

When collision occurs:
velocity = velocity.bounce(collision.get_normal()) * incremental_speed
  • collision.get_normal() returns the surface normal vector
  • velocity.bounce() reflects the velocity vector across the normal
  • Multiply by incremental_speed (1.05) to increase speed
3

Debug Output

print(velocity)
Prints the new velocity vector to console after each collision.

Start Game Function

func start_game():
	started = true
	velocity.y = -start_speed
	velocity.x = angle.pick_random()
This function initializes the ball’s movement:
started = true  # Enable physics simulation
The pick_random() method selects either -250 (left) or +250 (right) from the angle array, ensuring varied gameplay.

Ball Physics Summary

PropertyInitial ValueAfter 1st BounceAfter 2nd Bounce
Vertical Speed500 px/s525 px/s551.25 px/s
Horizontal Speed±250 px/s±262.5 px/s±275.63 px/s
Speed Multiplier1.0x1.05x1.1025x
The ball accelerates by 5% with each collision, creating increasing difficulty.

Player Script

File: res://scripts/player.gd (20 lines) The player script handles paddle movement using arrow keys or touch input.

Complete Source Code

extends CharacterBody2D

@export var speed = 100
var ball

func _ready(): 
	ball = get_parent().get_node("Ball")

func _physics_process(delta): 
	velocity = Vector2.ZERO  # Resetar a velocidade a cada frame
	
	if Input.is_action_pressed("ui_left") and ball.started == true:
		velocity.x -= speed
	if Input.is_action_pressed("ui_right") and ball.started == true:
		velocity.x += speed
	
	move_and_collide(velocity * delta)
	
	pass

Variables

1

speed (@export int)

Exported variable for paddle movement speed. Default is 100 px/s, but overridden to 730 px/s in the scene file.
@export var speed = 100
The @export decorator makes this variable editable in the Godot editor. The scene file sets speed = 730.
2

ball (Node reference)

Reference to the Ball node, used to check if the game has started.
var ball
Populated in _ready() function.

Ready Function

func _ready(): 
	ball = get_parent().get_node("Ball")
Called once when the node enters the scene tree:
1

Get Parent

get_parent() returns the “Game” Node2D that contains the Player instance.
2

Find Ball Node

get_node("Ball") finds the sibling Ball node in the scene tree.
3

Store Reference

The ball reference is stored to check ball.started during movement.
This creates a dependency on the scene structure. The Ball node must be named exactly “Ball” and be a sibling to Player under the Game node.

Physics Process

func _physics_process(delta): 
	velocity = Vector2.ZERO  # Resetar a velocidade a cada frame
	
	if Input.is_action_pressed("ui_left") and ball.started == true:
		velocity.x -= speed
	if Input.is_action_pressed("ui_right") and ball.started == true:
		velocity.x += speed
	
	move_and_collide(velocity * delta)

Velocity Reset

velocity = Vector2.ZERO  # Comment in Portuguese: "Reset velocity each frame"
The velocity is reset to (0, 0) every frame. This prevents momentum accumulation and ensures the paddle only moves when input is pressed.

Input Handling

if Input.is_action_pressed("ui_left") and ball.started == true:
	velocity.x -= speed
Both input checks require two conditions:
  1. Arrow key pressed: Built-in ui_left or ui_right actions
  2. Game started: ball.started == true prevents movement before game begins
The paddle cannot move until the player presses spacebar to start the game. This prevents cheating by positioning the paddle before the ball launches.

Movement Application

move_and_collide(velocity * delta)
Moves the paddle by the calculated velocity scaled by delta time. This function:
  • Handles collision detection with walls
  • Stops movement when hitting boundaries
  • Returns collision data (unused in this script)

Player Movement Summary

InputVelocitySpeed (px/s)Direction
None(0, 0)0Stationary
Left Arrow(-730, 0)730Left
Right Arrow(+730, 0)730Right
Both Arrows(0, 0)0Cancelled out
If both arrow keys are pressed simultaneously, the velocity becomes (0, 0) because the left and right movements cancel each other.

Input Actions Reference

Both scripts use Godot’s input action system:
ActionKey BindingUsed InPurpose
StartSpacebar (keycode 32)ball.gdStart the game
ui_leftLeft Arrow (built-in)player.gdMove paddle left
ui_rightRight Arrow (built-in)player.gdMove paddle right
ui_left and ui_right are built-in Godot actions that automatically support keyboard, gamepad, and touch input.

Script Interaction Flow

1

Game Initialization

Both scripts start with started = false and movement disabled.
2

Player Presses Spacebar

ball.gd detects “Start” action and calls start_game().
3

Ball Launches

Ball velocity set to (±250, -500) and started becomes true.
4

Player Movement Enabled

player.gd checks ball.started == true and allows arrow key movement.
5

Gameplay Loop

Ball moves and bounces, player tracks ball position, speed increases with each collision.

Code Quality Notes

Strengths

  • Simple and readable: Both scripts are under 25 lines
  • Clear variable names: started, start_speed, incremental_speed
  • Good use of Godot features: @export, move_and_collide(), velocity.bounce()

Potential Improvements

Remove Debug Print

The print(velocity) statement in ball.gd:16 should be removed for production builds.

Remove Empty Pass

The pass statement at player.gd:19 serves no purpose and can be removed.

Use Signals

Instead of get_parent().get_node("Ball"), use signals to communicate game start.

Add Speed Cap

The ball speed can grow infinitely. Consider adding a maximum speed limit.

Performance Characteristics

  • Physics FPS: Both scripts run at 60 FPS (Godot default)
  • Collision Detection: move_and_collide() uses Godot’s optimized physics engine
  • Memory Usage: Minimal - no dynamic allocations during gameplay
  • CPU Impact: Very low - simple vector math and conditional checks

Next Steps

Scenes

See how these scripts attach to scene nodes

Project Structure

Understand the overall project organization

Build docs developers (and LLMs) love