Introduction
Game development has traditionally been dominated by C++ engines like Unity and Unreal. However, Rust’s combination of performance and safety has attracted game developers looking for a modern alternative. Bevy is the leading Rust game engine, offering a data-driven architecture, excellent performance, and an active community.
With over 33,000 GitHub stars and growing adoption, Bevy has proven that Rust is ready for game development. Whether you’re building 2D platformers or 3D adventures, Bevy provides the tools you need.
What Is Bevy?
The Basic Concept
Bevy is a refreshingly simple, data-driven game engine built in Rust. It uses the Entity Component System (ECS) architecture, which separates data (components) from behavior (systems), enabling excellent performance through parallelism.
Key Terms
- ECS (Entity Component System): Architecture pattern where entities are ID-tagged objects, components are data, and systems are logic
- Entity: A unique identifier with attached components
- Component: Data (position, velocity, sprite)
- System: Logic that processes entities with specific components
- Bundle: Collection of components commonly added together
- Resource: Global singleton data
Why Bevy Matters in 2025-2026
| Feature | Bevy | Other Rust Engines |
|---|---|---|
| Architecture | Data-driven ECS | Various |
| 2D Support | Excellent | Good |
| 3D Support | Growing | Good |
| Community | Active (436+ contributors) | Smaller |
| Learning Curve | Moderate | Varies |
| Performance | Excellent | Excellent |
Architecture
ECS Pattern
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ World โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Entities (IDs) โ โ
โ โ Entity 1: [Position, Sprite] โ โ
โ โ Entity 2: [Position, Velocity] โ โ
โ โ Entity 3: [Position, Sprite, Velocity]โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Systems โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ movement_system: for each entity โ โ
โ โ with Position + Velocity: โ โ
โ โ position += velocity โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ render_system: for each entity โ โ
โ โ with Position + Sprite: โ โ
โ โ draw sprite at position โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Getting Started
Installation
# Install Rust if you haven't
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Create a new Bevy project
cargo new my_bevy_game
cd my_bevy_game
# Add Bevy to Cargo.toml
echo 'bevy = "0.13"' >> Cargo.toml
Minimal Bevy App
// src/main.rs
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
// Add a camera
commands.spawn(Camera2dBundle::default());
// Add a colored rectangle
commands.spawn(SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.5, 0.5, 1.0),
custom_size: Some(Vec2::new(50.0, 50.0)),
..default()
},
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
});
}
cargo run --release
Building a 2D Game
Step 1: Define Components
use bevy::prelude::*;
// Player component
#[derive(Component)]
struct Player {
speed: f32,
}
// Enemy tag component
#[derive(Component)]
struct Enemy;
// Projectile component
#[derive(Component)]
struct Projectile {
direction: Vec3,
speed: f32,
}
Step 2: Create Systems
// Movement system
fn player_movement(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut query: Query<&mut Transform, With<Player>>,
) {
let mut direction = Vec3::ZERO;
if keyboard_input.pressed(KeyCode::KeyW) || keyboard_input.pressed(KeyCode::ArrowUp) {
direction.y += 1.0;
}
if keyboard_input.pressed(KeyCode::KeyS) || keyboard_input.pressed(KeyCode::ArrowDown) {
direction.y -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyA) || keyboard_input.pressed(KeyCode::ArrowLeft) {
direction.x -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyD) || keyboard_input.pressed(KeyCode::ArrowRight) {
direction.x += 1.0;
}
for mut transform in query.iter_mut() {
transform.translation += direction.normalize() * 300.0 * 0.016;
}
}
// Projectile movement
fn projectile_movement(
mut commands: Commands,
mut query: Query<(Entity, &Transform, &Projectile), Without<Player>>,
time: Res<Time>,
) {
for (entity, transform, projectile) in query.iter_mut() {
transform.translation += projectile.direction * projectile.speed * time.delta_seconds();
// Despawn if off-screen
if transform.translation.length() > 1000.0 {
commands.entity(entity).despawn();
}
}
}
// Shooting system
fn shooting(
mut commands: Commands,
keyboard_input: Res<ButtonInput<KeyCode>>,
query: Query<&Transform, With<Player>>,
) {
if keyboard_input.just_pressed(KeyCode::Space) {
for player_transform in query.iter() {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::rgb(1.0, 0.0, 0.0),
custom_size: Some(Vec2::new(10.0, 10.0)),
..default()
},
transform: *player_transform,
..default()
},
Projectile {
direction: Vec3::Y,
speed: 500.0,
},
));
}
}
}
Step 3: Collision Detection
use bevy::sprite::collide_aabb::*;
fn projectile_enemy_collision(
mut commands: Commands,
projectile_query: Query<(Entity, &Transform, &Sprite, &Projectile)>,
enemy_query: Query<(Entity, &Transform, &Sprite), With<Enemy>>,
) {
for (proj_entity, proj_transform, proj_sprite, _) in projectile_query.iter() {
for (enemy_entity, enemy_transform, enemy_sprite) in enemy_query.iter() {
let collision = collide_aabb(
proj_transform,
proj_sprite.custom_size.unwrap(),
enemy_transform,
enemy_sprite.custom_size.unwrap(),
);
if collision.is_some() {
commands.entity(proj_entity).despawn();
commands.entity(enemy_entity).despawn();
}
}
}
}
Step 4: Main App with States
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, States)]
enum GameState {
Playing,
GameOver,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "My Bevy Game".into(),
resolution: (640., 480.).into(),
..default()
}),
..default()
}))
.init_state::<GameState>()
.add_systems(Startup, setup)
.add_systems(OnEnter(GameState::Playing), spawn_player)
.add_systems(Update, (
player_movement,
projectile_movement,
shooting,
projectile_enemy_collision,
).run_if(in_state(GameState::Playing)))
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
fn spawn_player(mut commands: Commands) {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.3, 0.5, 0.9),
custom_size: Some(Vec2::new(40.0, 40.0)),
..default()
},
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
},
Player { speed: 200.0 },
));
}
Best Practices
1. Use Resources for Global State
// Game score
#[derive(Resource)]
struct Score {
value: u32,
}
// Add in setup
app.insert_resource(Score { value: 0 });
// Use in system
fn add_score(mut score: ResMut<Score>) {
score.value += 10;
}
2. Bundle Related Components
#[derive(Bundle)]
struct PlayerBundle {
sprite: SpriteBundle,
player: Player,
health: Health,
collider: Collider,
}
commands.spawn(PlayerBundle {
sprite: SpriteBundle { /* ... */ },
player: Player { speed: 200.0 },
health: Health { current: 100, max: 100 },
collider: Collider::Rectangle(Vec2::new(32.0, 32.0)),
});
3. Query Efficiently
// โ
Good: Specific query
fn system(query: Query<(&Transform, &Velocity), With<RigidBody>>) {}
// โ ๏ธ Avoid: Too general
fn system(query: Query<&Transform>) {}
4. Use Events for Communication
// Define event
#[derive(Event)]
struct DamageEvent {
target: Entity,
amount: f32,
}
// Spawn event
fn damage_enemy(
mut commands: Commands,
mut damage_events: EventWriter<DamageEvent>,
query: Query<Entity, With<Enemy>>,
) {
for entity in query.iter() {
damage_events.send(DamageEvent {
target: entity,
amount: 10.0,
});
}
}
// Handle event
fn apply_damage(
mut query: Query<&mut Health>,
mut events: EventReader<DamageEvent>,
) {
for event in events.read() {
if let Ok(mut health) = query.get_mut(event.target) {
health.current -= event.amount;
}
}
}
External Resources
Official Documentation
Community
Learning Resources
Key Takeaways
- Bevy is a data-driven game engine using ECS architecture
- Entities are ID-tagged objects with attached components
- Systems contain logic that processes entities
- Resources hold global singleton data
- Best practices include bundling components, efficient queries, and events
- Community is active with growing ecosystem
Next Steps: Explore uv: Fast Python Package Manager in Rust to see Rust in the Python ecosystem.
Comments