Your First Game

Let's build a simple game step by step to understand Jugar's core concepts.

Game Concept

We'll create a simple "Collect the Coins" game:

  • Player moves with WASD or arrow keys
  • Coins spawn randomly
  • Collect coins to increase score

Step 1: Setup

use jugar::prelude::*;

// Define game components
#[derive(Clone, Copy)]
struct Player;

#[derive(Clone, Copy)]
struct Coin;

#[derive(Clone, Copy)]
struct Score(u32);

fn main() {
    let config = JugarConfig {
        width: 800,
        height: 600,
        title: "Coin Collector".to_string(),
        ..Default::default()
    };

    let mut engine = JugarEngine::new(config);

    // Initialize game state
    setup(&mut engine);

    engine.run(game_loop);
}

Step 2: Setup Function

#![allow(unused)]
fn main() {
fn setup(engine: &mut JugarEngine) {
    let world = engine.world_mut();

    // Spawn player at center
    let player = world.spawn();
    world.add_component(player, Player);
    world.add_component(player, Position::new(400.0, 300.0));
    world.add_component(player, Velocity::new(0.0, 0.0));

    // Spawn initial coins
    for _ in 0..5 {
        spawn_coin(world);
    }

    // Add score resource
    world.add_resource(Score(0));
}

fn spawn_coin(world: &mut World) {
    let x = fastrand::f32() * 800.0;
    let y = fastrand::f32() * 600.0;

    let coin = world.spawn();
    world.add_component(coin, Coin);
    world.add_component(coin, Position::new(x, y));
}
}

Step 3: Game Loop

#![allow(unused)]
fn main() {
fn game_loop(ctx: &mut GameContext) -> LoopControl {
    // Handle input
    handle_input(ctx);

    // Update physics
    update_movement(ctx);

    // Check collisions
    check_coin_collection(ctx);

    LoopControl::Continue
}

fn handle_input(ctx: &mut GameContext) {
    let input = ctx.input();
    let world = ctx.world_mut();

    let speed = 200.0; // pixels per second
    let mut vel = Vec2::ZERO;

    if input.key_held(KeyCode::W) || input.key_held(KeyCode::ArrowUp) {
        vel.y -= speed;
    }
    if input.key_held(KeyCode::S) || input.key_held(KeyCode::ArrowDown) {
        vel.y += speed;
    }
    if input.key_held(KeyCode::A) || input.key_held(KeyCode::ArrowLeft) {
        vel.x -= speed;
    }
    if input.key_held(KeyCode::D) || input.key_held(KeyCode::ArrowRight) {
        vel.x += speed;
    }

    // Update player velocity
    for (_, (_, velocity)) in world.query::<(&Player, &mut Velocity)>() {
        *velocity = Velocity(vel);
    }
}

fn update_movement(ctx: &mut GameContext) {
    let dt = ctx.delta_time();
    let world = ctx.world_mut();

    for (_, (pos, vel)) in world.query::<(&mut Position, &Velocity)>() {
        pos.x += vel.x * dt;
        pos.y += vel.y * dt;
    }
}

fn check_coin_collection(ctx: &mut GameContext) {
    let world = ctx.world_mut();
    let collect_radius = 20.0;

    // Get player position
    let player_pos = world.query::<(&Player, &Position)>()
        .iter()
        .next()
        .map(|(_, (_, pos))| *pos);

    let Some(player_pos) = player_pos else { return };

    // Find coins to collect
    let mut collected = Vec::new();
    for (entity, (_, coin_pos)) in world.query::<(&Coin, &Position)>() {
        let dx = player_pos.x - coin_pos.x;
        let dy = player_pos.y - coin_pos.y;
        let dist = (dx * dx + dy * dy).sqrt();

        if dist < collect_radius {
            collected.push(entity);
        }
    }

    // Remove collected coins and update score
    for entity in collected {
        world.despawn(entity);
        if let Some(score) = world.get_resource_mut::<Score>() {
            score.0 += 1;
        }
        // Spawn a new coin
        spawn_coin(world);
    }
}
}

Step 4: Build and Run

# Native
cargo run

# WASM
cargo build --target wasm32-unknown-unknown --release

Testing Your Game

Add tests using Probar:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;
    use jugar_probar::Assertion;

    #[test]
    fn test_coin_collection_increases_score() {
        let mut engine = JugarEngine::new(JugarConfig::default());
        setup(&mut engine);

        let initial_score = engine.world().get_resource::<Score>().unwrap().0;

        // Simulate collecting a coin
        // ... test logic

        let assertion = Assertion::equals(&initial_score, &0);
        assert!(assertion.passed);
    }
}
}

Next Steps