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
Add Bevy and Bevy Rapier to your Cargo.toml
:
[dependencies]
bevy = "0.11"
bevy_rapier2d = "0.23"
rand = "0.8"
Then, run:
cargo build
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();
}
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()
}))
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,
}
Player Enum:
#[derive(Component, Clone, Copy, PartialEq, Eq, Hash)]
enum Player {
Player1,
Player2,
}
Ball Component:
#[derive(Component)]
struct Ball;
Step 5: Spawn Entities
Create the paddles, ball, and camera.
Add the Camera:
fn spawn_camera(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
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,
));
}
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();
}
}
}
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.)),
));
}
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.;
}
}
}
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()
});
}
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
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)