DEV Community

Cover image for Create a 2D Pong Game with Rust and Bevy: A Step-by-Step Guide
Trish
Trish

Posted on

Create a 2D Pong Game with Rust and Bevy: A Step-by-Step Guide

In this blog, we will create a 2D Pong game using Rust and the Bevy engine. Pong is a classic arcade game and a great starting project for learning game development. The Bevy game engine provides a modern ECS-based architecture that's fun to work with.

By the end of this guide, you'll have your own Pong game, and you can find the complete source code here. Connect with me on Twitter for questions or discussions!


Prerequisites

  • Rust Installed: If you don’t have Rust installed, get it here.
  • Basic Rust Knowledge: Familiarity with Rust basics is helpful.
  • Cargo: The Rust package manager, included with Rust.

Step 1: Set Up a New Bevy Project

Start by creating a new Rust project:

cargo new pong-rs --bin  
cd pong-rs  
Enter fullscreen mode Exit fullscreen mode

Add Bevy and Bevy Rapier to your Cargo.toml:

[dependencies]  
bevy = "0.11"  
bevy_rapier2d = "0.23"  
rand = "0.8"  
Enter fullscreen mode Exit fullscreen mode

Then, run:

cargo build  
Enter fullscreen mode Exit fullscreen mode

Step 2: Initialize the Bevy App

In your main.rs, set up the Bevy application. We’ll configure the window and add core plugins:

use bevy::prelude::*;  
use bevy_rapier2d::prelude::*;  

fn main() {  
    App::new()  
        .add_plugins(DefaultPlugins)  
        .add_plugins(RapierPhysicsPlugin::<NoUserData>::default())  
        .run();  
}  
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure the Game Window

Add a window configuration so our game has a fixed size and resolution. Replace the add_plugins(DefaultPlugins) line with:

.add_plugins(DefaultPlugins.set(WindowPlugin {  
    primary_window: Some(Window {  
        resolution: WindowResolution::new(1280., 720.),  
        resizable: false,  
        ..Default::default()  
    }),  
    ..Default::default()  
}))  
Enter fullscreen mode Exit fullscreen mode

Now, when you run the game, a window with the specified size will open!


Step 4: Add the Game Components

Define the components for paddles, players, and the ball. Components are essential for the ECS (Entity Component System) architecture in Bevy.

Paddle Component:

#[derive(Component)]  
struct Paddle {  
    move_up: KeyCode,  
    move_down: KeyCode,  
}  
Enter fullscreen mode Exit fullscreen mode

Player Enum:

#[derive(Component, Clone, Copy, PartialEq, Eq, Hash)]  
enum Player {  
    Player1,  
    Player2,  
}  
Enter fullscreen mode Exit fullscreen mode

Ball Component:

#[derive(Component)]  
struct Ball;  
Enter fullscreen mode Exit fullscreen mode

Step 5: Spawn Entities

Create the paddles, ball, and camera.

Add the Camera:

fn spawn_camera(mut commands: Commands) {  
    commands.spawn(Camera2dBundle::default());  
}  
Enter fullscreen mode Exit fullscreen mode

Add the Paddles:

fn spawn_players(mut commands: Commands) {  
    commands.spawn((  
        SpriteBundle {  
            transform: Transform::from_translation(Vec3::new(-620., 0., 0.)),  
            sprite: Sprite {  
                color: Color::RED,  
                custom_size: Some(Vec2::new(10., 150.)),  
                ..Default::default()  
            },  
            ..Default::default()  
        },  
        Paddle {  
            move_up: KeyCode::KeyW,  
            move_down: KeyCode::KeyS,  
        },  
        Player::Player1,  
    ));  

    commands.spawn((  
        SpriteBundle {  
            transform: Transform::from_translation(Vec3::new(620., 0., 0.)),  
            sprite: Sprite {  
                color: Color::GREEN,  
                custom_size: Some(Vec2::new(10., 150.)),  
                ..Default::default()  
            },  
            ..Default::default()  
        },  
        Paddle {  
            move_up: KeyCode::ArrowUp,  
            move_down: KeyCode::ArrowDown,  
        },  
        Player::Player2,  
    ));  
}  
Enter fullscreen mode Exit fullscreen mode

Step 6: Implement Paddle Movement

Create a system to move paddles based on keyboard input:

fn move_paddle(  
    mut paddles: Query<(&mut Transform, &Paddle)>,  
    input: Res<Input<KeyCode>>,  
    time: Res<Time>,  
) {  
    for (mut transform, paddle) in paddles.iter_mut() {  
        if input.pressed(paddle.move_up) {  
            transform.translation.y += 300. * time.delta_seconds();  
        }  
        if input.pressed(paddle.move_down) {  
            transform.translation.y -= 300. * time.delta_seconds();  
        }  
    }  
}  
Enter fullscreen mode Exit fullscreen mode

Step 7: Add Physics with Bevy Rapier

Use Bevy Rapier to add physics to the ball and paddles.

Ball Initialization:

fn spawn_ball(mut commands: Commands) {  
    commands.spawn((  
        SpriteBundle {  
            transform: Transform::from_translation(Vec3::new(0., 0., 1.)),  
            sprite: Sprite {  
                color: Color::WHITE,  
                custom_size: Some(Vec2::new(50., 50.)),  
                ..Default::default()  
            },  
            ..Default::default()  
        },  
        Ball,  
        RigidBody::Dynamic,  
        Collider::ball(25.),  
        Velocity::linear(Vec2::new(300., 300.)),  
    ));  
}  
Enter fullscreen mode Exit fullscreen mode

Step 8: Detect Ball Collisions

Use sensors and events to detect when the ball collides with paddles or goals. This system adds interactivity.

Ball Collision System:

fn ball_collision(  
    mut balls: Query<(&mut Velocity, &CollidingEntities)>,  
) {  
    for (mut velocity, collisions) in balls.iter_mut() {  
        for _ in collisions.iter() {  
            velocity.linvel.x *= -1.;  
        }  
    }  
}  
Enter fullscreen mode Exit fullscreen mode

Step 9: Add Scoring

Track the score of each player and display it using a UI:

fn spawn_score(mut commands: Commands) {  
    commands.spawn(TextBundle {  
        text: Text::from_section(  
            "0 - 0",  
            TextStyle {  
                font_size: 50.0,  
                color: Color::WHITE,  
                ..Default::default()  
            },  
        ),  
        ..Default::default()  
    });  
}  
Enter fullscreen mode Exit fullscreen mode

Step 10: Polish and Play

Tie it all together by adding systems to manage ball resets, scoring, and paddle movement. Finally, run the game:

cargo run  
Enter fullscreen mode Exit fullscreen mode

Conclusion

You've successfully built a 2D Pong game using Rust and Bevy! Explore further by adding sound effects, enhancing graphics, or implementing AI opponents.

Check out the complete source code on GitHub. Don’t forget to connect with me on Twitter for more Rust and game development discussions!

Happy coding! 🎮


Top comments (0)