Skip to content

Instantly share code, notes, and snippets.

@naranyala
Created June 22, 2025 01:21
Show Gist options
  • Save naranyala/c40117f3afd6dc6b1b6cf540ea2a6958 to your computer and use it in GitHub Desktop.
Save naranyala/c40117f3afd6dc6b1b6cf540ea2a6958 to your computer and use it in GitHub Desktop.
classic breakout game with odin+raylib
package main
import "core:math/rand"
import "core:fmt"
import rl "vendor:raylib"
Ball :: struct {
pos: rl.Vector2,
velocity: rl.Vector2,
radius: f32,
}
Block :: struct {
rect: rl.Rectangle,
color: rl.Color,
active: bool,
}
Paddle :: struct {
rect: rl.Rectangle,
speed: f32,
}
main :: proc() {
SCREEN_WIDTH :: 800
SCREEN_HEIGHT :: 600
rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Breakout Game")
defer rl.CloseWindow()
rl.SetTargetFPS(60)
// Initialize ball
ball := Ball{
pos = rl.Vector2{SCREEN_WIDTH / 2, SCREEN_HEIGHT - 100},
velocity = rl.Vector2{250, -250},
radius = 10,
}
// Initialize paddle
paddle := Paddle{
rect = rl.Rectangle{
x = SCREEN_WIDTH / 2 - 60,
y = SCREEN_HEIGHT - 40,
width = 120,
height = 15,
},
speed = 400,
}
// Initialize blocks
blocks := make([dynamic]Block)
defer delete(blocks)
// Create grid of blocks
block_width: f32 = 70
block_height: f32 = 25
for row in 0..<6 {
for col in 0..<10 {
block := Block{
rect = rl.Rectangle{
x = f32(col) * (block_width + 5) + 25,
y = f32(row) * (block_height + 3) + 70,
width = block_width,
height = block_height,
},
color = rl.Color{
u8(255 - i32(row) * 25),
u8(50 + i32(row) * 25),
u8(100 + i32(col) * 10),
255,
},
active = true,
}
append(&blocks, block)
}
}
game_over := false
ball_lost := false
for !rl.WindowShouldClose() {
dt := rl.GetFrameTime()
// Reset game on R press
if rl.IsKeyPressed(rl.KeyboardKey.R) {
ball.pos = rl.Vector2{SCREEN_WIDTH / 2, SCREEN_HEIGHT - 100}
ball.velocity = rl.Vector2{250, -250}
paddle.rect.x = SCREEN_WIDTH / 2 - 60
game_over = false
ball_lost = false
for &block in blocks {
block.active = true
}
}
if !game_over && !ball_lost {
// Move paddle
if rl.IsKeyDown(rl.KeyboardKey.LEFT) || rl.IsKeyDown(rl.KeyboardKey.A) {
paddle.rect.x -= paddle.speed * dt
if paddle.rect.x < 0 do paddle.rect.x = 0
}
if rl.IsKeyDown(rl.KeyboardKey.RIGHT) || rl.IsKeyDown(rl.KeyboardKey.D) {
paddle.rect.x += paddle.speed * dt
if paddle.rect.x + paddle.rect.width > SCREEN_WIDTH {
paddle.rect.x = SCREEN_WIDTH - paddle.rect.width
}
}
// Move ball
ball.pos.x += ball.velocity.x * dt
ball.pos.y += ball.velocity.y * dt
// Bounce off side walls
if ball.pos.x - ball.radius <= 0 || ball.pos.x + ball.radius >= SCREEN_WIDTH {
ball.velocity.x = -ball.velocity.x
ball.pos.x = ball.radius if ball.pos.x < ball.radius else SCREEN_WIDTH - ball.radius
}
// Bounce off top wall
if ball.pos.y - ball.radius <= 0 {
ball.velocity.y = -ball.velocity.y
ball.pos.y = ball.radius
}
// Check if ball fell off bottom
if ball.pos.y > SCREEN_HEIGHT + 50 {
ball_lost = true
}
// Check collision with paddle
if rl.CheckCollisionCircleRec(ball.pos, ball.radius, paddle.rect) && ball.velocity.y > 0 {
ball.velocity.y = -ball.velocity.y
// Add spin based on where ball hits paddle
paddle_center := paddle.rect.x + paddle.rect.width / 2
hit_pos := (ball.pos.x - paddle_center) / (paddle.rect.width / 2)
ball.velocity.x += hit_pos * 200 // Add horizontal velocity based on hit position
// Ensure ball doesn't get stuck in paddle
ball.pos.y = paddle.rect.y - ball.radius - 1
}
// Check collision with blocks
for &block in blocks {
if !block.active do continue
if rl.CheckCollisionCircleRec(ball.pos, ball.radius, block.rect) {
block.active = false
// Calculate bounce direction
center_x := block.rect.x + block.rect.width / 2
center_y := block.rect.y + block.rect.height / 2
dx := ball.pos.x - center_x
dy := ball.pos.y - center_y
if abs(dx / block.rect.width) > abs(dy / block.rect.height) {
// Hit from left or right
ball.velocity.x = -ball.velocity.x
} else {
// Hit from top or bottom
ball.velocity.y = -ball.velocity.y
}
break // Only hit one block per frame
}
}
// Check win condition
active_blocks := 0
for block in blocks {
if block.active do active_blocks += 1
}
if active_blocks == 0 {
game_over = true
}
}
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.Color{15, 15, 25, 255})
// Draw blocks
for block in blocks {
if block.active {
rl.DrawRectangleRec(block.rect, block.color)
rl.DrawRectangleLinesEx(block.rect, 1, rl.WHITE)
}
}
// Draw paddle
rl.DrawRectangleRec(paddle.rect, rl.BLUE)
rl.DrawRectangleLinesEx(paddle.rect, 2, rl.LIGHTGRAY)
// Draw ball
if !ball_lost {
rl.DrawCircleV(ball.pos, ball.radius, rl.WHITE)
rl.DrawCircleLinesV(ball.pos, ball.radius, rl.GRAY)
}
// Draw single line UI at top
// Count remaining blocks first
active_blocks := 0
for block in blocks {
if block.active do active_blocks += 1
}
ui_text := fmt.ctprintf("LEFT/RIGHT: Move Paddle | R: Reset | Blocks: %d", active_blocks)
rl.DrawText(ui_text, 10, 10, 16, rl.WHITE)
// Game state messages
if game_over {
rl.DrawText("YOU WIN!", SCREEN_WIDTH/2 - 100, SCREEN_HEIGHT/2, 40, rl.GREEN)
rl.DrawText("Press R to play again", SCREEN_WIDTH/2 - 120, SCREEN_HEIGHT/2 + 50, 20, rl.WHITE)
} else if ball_lost {
rl.DrawText("BALL LOST!", SCREEN_WIDTH/2 - 100, SCREEN_HEIGHT/2, 40, rl.RED)
rl.DrawText("Press R to try again", SCREEN_WIDTH/2 - 120, SCREEN_HEIGHT/2 + 50, 20, rl.WHITE)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment