Rust with Raylib

Setup

  • Install Rust :

  • File System :

    • The code needs to be inside the src  file, as it's the default target.

     .
    ├──  cargo.toml
    ├──  cargo.lock  // created when running `cargo run`
    ├──  target      // created when running `cargo run`
    └──  src
        └──  main.rs
    
  • cargo.toml :

    [package]
    name = "moving_ball"
    version = "0.1.0"
    edition = "2024"
    
    [dependencies]
    raylib = "5.5.1"
    
  • Dependencies :

    • Requires glfw , cmake , and curl .

    • When running cargo run , all dependencies are downloaded.

    • "35 packages downloaded".

    • The

  • Execute :

    • cargo run

Moving Ball

  • cargo.toml :

[package]
name = "moving_ball"
version = "0.1.0"
edition = "2024"

[dependencies]
raylib = "5.5.1"
  • main.rs :

/* 
cargo run
*/

use raylib::prelude::*;

fn main() {
    const SCREEN_WIDTH: i32 = 800;
    const SCREEN_HEIGHT: i32 = 450;

    let (mut rl, thread) = raylib::init()
        .size(SCREEN_WIDTH, SCREEN_HEIGHT)
        .title("Raylib in Rust | 1_moving_ball")
        .build();

    rl.set_target_fps(60);

    // Initialize ball
    let mut ball_position = Vector2 {
        x: SCREEN_WIDTH  as f32 / 2.0,
        y: SCREEN_HEIGHT as f32 / 2.0,
    };

    while !rl.window_should_close() {
        // Update ball position
        if rl.is_key_down(KeyboardKey::KEY_RIGHT) {
            ball_position.x += 2.0;
        }
        if rl.is_key_down(KeyboardKey::KEY_LEFT) {
            ball_position.x -= 2.0;
        }
        if rl.is_key_down(KeyboardKey::KEY_UP) {
            ball_position.y -= 2.0;
        }
        if rl.is_key_down(KeyboardKey::KEY_DOWN) {
            ball_position.y += 2.0;
        }

        // Draw
        let mut d = rl.begin_drawing(&thread);
        d.clear_background(Color::RAYWHITE);

        d.draw_text("move the ball with arrow keys", 10, 10, 20, Color::DARKGRAY);

        d.draw_circle_v(ball_position, 50.0, Color::MAROON);
    }
}

Collect The Coin

  • cargo.toml :

[package]
name = "collect_the_coins"
version = "0.1.0"
edition = "2024"

[dependencies]
raylib = "5.5.1"
rand = "0.8"
  • main.rs :

/* 
cargo run
*/

use raylib::prelude::*;
use rand::Rng;

const MAX_COINS:  usize = 20;
const PLAYER_SPEED: f32 = 4.0;

struct Player {
    position: Vector2,
    radius:   f32,
    score:    i32,
}

struct Coin {
    position: Vector2,
    radius:   f32,
    active:   bool,
}

fn main() {
    const SCREEN_WIDTH: i32  = 800;
    const SCREEN_HEIGHT: i32 = 600;

    let (mut rl, thread) = raylib::init()
        .size(SCREEN_WIDTH, SCREEN_HEIGHT)
        .title("Raylib in Rust | 2_collect_the_coins")
        .build();

    rl.set_target_fps(60);

    // Initialize player
    let mut player = Player {
        position: Vector2::new(400.0, 300.0),
        radius: 20.0,
        score: 0,
    };

    // Initialize coins
    let mut rng = rand::thread_rng();
    let mut coins: Vec<Coin> = (0..MAX_COINS)
        .map(|_| Coin {
            position: Vector2::new(
                rng.gen_range(20.0..(SCREEN_WIDTH as f32 - 20.0)),
                rng.gen_range(20.0..(SCREEN_HEIGHT as f32 - 20.0)),
            ),
            radius: 8.0,
            active: true,
        })
        .collect();

    while !rl.window_should_close() {
        // Update player
        if rl.is_key_down(KeyboardKey::KEY_W) {
            player.position.y -= PLAYER_SPEED;
        }
        if rl.is_key_down(KeyboardKey::KEY_S) {
            player.position.y += PLAYER_SPEED;
        }
        if rl.is_key_down(KeyboardKey::KEY_A) {
            player.position.x -= PLAYER_SPEED;
        }
        if rl.is_key_down(KeyboardKey::KEY_D) {
            player.position.x += PLAYER_SPEED;
        }

        // Check collisions
        for coin in coins.iter_mut() {
            if coin.active {
                let dist = player.position.distance_to(coin.position);
                if dist < player.radius + coin.radius {
                    coin.active = false;
                    player.score += 1;
                }
            }
        }

        // Draw
        let mut d = rl.begin_drawing(&thread);
        d.clear_background(Color::DARKGREEN);

        d.draw_circle_v(player.position, player.radius, Color::BLUE);
        for coin in coins.iter() {
            if coin.active {
                d.draw_circle_v(coin.position, coin.radius, Color::GOLD);
            }
        }
        d.draw_text(&format!("Score: {}", player.score), 10, 10, 20, Color::WHITE);
    }
}

Space Invaders

  • cargo.toml :

[package]
name = "space_invaders"
version = "0.1.0"
edition = "2024"

[dependencies]
raylib = "5.5.1"
rand = "0.8"
  • main.rs :

/* 
cargo run
*/

use raylib::prelude::*;
use rand::Rng;

const MAX_BULLETS: usize = 64;
const BULLET_SPEED:  f32 = 6.0;

#[derive(Clone, Copy)]
struct Player {
    position: Vector2,
    radius: f32,
}

#[derive(Clone, Copy)]
struct Bullet {
    position: Vector2,
    velocity: Vector2,
    radius: f32,
    active: bool,
}

#[derive(Clone, Copy)]
struct Enemy {
    position: Vector2,
    radius: f32,
    active: bool,
    color: Color,
}

fn spawn_bullet(bullets: &mut [Bullet; MAX_BULLETS], pos: Vector2) {
    for bullet in bullets.iter_mut() {
        if !bullet.active {
            bullet.active = true;
            bullet.position = pos;
            bullet.velocity = Vector2::new(0.0, -BULLET_SPEED);
            bullet.radius = 5.0;
            return;
        }
    }
}

fn spawn_inactive_enemies(enemies: &mut [Enemy]) {
    let mut rng = rand::thread_rng();
    for enemy in enemies.iter_mut() {
        if !enemy.active {
            enemy.position = Vector2::new(rng.gen_range(50..751) as f32, rng.gen_range(50..301) as f32);
            enemy.radius = 15.0;
            enemy.active = true;
            enemy.color = Color::new(
                rng.gen_range(100..255),
                rng.gen_range(100..255),
                rng.gen_range(100..255),
                255,
            );
        }
    }
}

fn main() {
    const SCREEN_WIDTH:  i32 = 800;
    const SCREEN_HEIGHT: i32 = 600;

    let (mut rl, thread) = raylib::init()
        .size(SCREEN_WIDTH, SCREEN_HEIGHT)
        .title("Raylib in Rust | 3_space_invaders")
        .build();

    rl.set_target_fps(60);

    let mut player = Player {
        position: Vector2::new(400.0, 500.0),
        radius: 20.0,
    };

    let mut bullets: [Bullet; MAX_BULLETS] = [Bullet {
        position: Vector2::zero(),
        velocity: Vector2::zero(),
        radius: 0.0,
        active: false,
    }; MAX_BULLETS];

    let enemies_count = 20;
    let mut enemies: Vec<Enemy> = vec![
        Enemy {
            position: Vector2::zero(),
            radius: 0.0,
            active: false,
            color: Color::BLACK,
        };
        enemies_count
    ];

    let mut enemies_destroyed_count = 0;
    spawn_inactive_enemies(&mut enemies);

    while !rl.window_should_close() {
        // Player movement
        if rl.is_key_down(KeyboardKey::KEY_A) { player.position.x -= 4.0; }
        if rl.is_key_down(KeyboardKey::KEY_D) { player.position.x += 4.0; }
        if rl.is_key_down(KeyboardKey::KEY_W) { player.position.y -= 4.0; }
        if rl.is_key_down(KeyboardKey::KEY_S) { player.position.y += 4.0; }
        if rl.is_key_pressed(KeyboardKey::KEY_SPACE) {
            spawn_bullet(&mut bullets, player.position);
        }

        // Update bullets
        for bullet in bullets.iter_mut() {
            if bullet.active {
                bullet.position.x += bullet.velocity.x;
                bullet.position.y += bullet.velocity.y;
                if bullet.position.y < 0.0 { bullet.active = false; }
            }
        }

        // Update enemies
        let mut active_enemies = 0;
        for enemy in enemies.iter_mut() {
            if enemy.active {
                enemy.position.y += 0.5;
                active_enemies += 1;
            }
        }

        // Respawn inactive enemies
        if active_enemies < 5 {
            spawn_inactive_enemies(&mut enemies);
        }

        // Collision detection
        for bullet in bullets.iter_mut() {
            if !bullet.active { continue; }
            for enemy in enemies.iter_mut() {
                if !enemy.active { continue; }
                if Vector2::distance_to(&bullet.position, enemy.position) < (bullet.radius + enemy.radius) {
                    bullet.active = false;
                    enemy.active = false;
                    enemies_destroyed_count += 1;
                }
            }
        }

        let mut d = rl.begin_drawing(&thread);
        d.clear_background(Color::BLACK);

        d.draw_circle_v(player.position, player.radius, Color::BLUE);

        for bullet in bullets.iter() {
            if bullet.active {
                d.draw_circle_v(bullet.position, bullet.radius, Color::YELLOW);
            }
        }

        for enemy in enemies.iter() {
            if enemy.active {
                d.draw_circle_v(enemy.position, enemy.radius, enemy.color);
            }
        }

        d.draw_text("Space Invaders: WASD move | SPACE shoot", 10, 10, 20, Color::WHITE);
        d.draw_text(
            &format!("Enemies destroyed: {}", enemies_destroyed_count),
            10,
            40,
            20,
            Color::YELLOW,
        );
    }
}