Game Dev Basics
9 examples to get you started with Rust game development - 7 basic and 2 intermediate.
Search across all documentation pages
9 examples to get you started with Rust game development - 7 basic and 2 intermediate.
cargo new my_game && cd my_game
cargo add bevyTooling: Examples use Bevy on Rust 1.97.0 (edition 2024). Pin Bevy version in
Cargo.toml- APIs evolve per release.
Window + default plugins bootstrap rendering and input.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
}DefaultPlugins bundles window, render, input, and asset plugins.Startup systems run once; Update runs every frame.Commands queue spawns/despawns applied later in schedule.Related: Bevy Engine
Entities are IDs with bundles of components.
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
Sprite::from_image(asset_server.load("player.png")),
Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
));
}AssetServer loads asynchronously - handle LoadState for placeholders.Transform position uses right-handed coords; 2D often uses XY plane.Related: Assets & Scenes
Read input resource in an Update system.
fn player_move(
keyboard: Res<ButtonInput<KeyCode>>,
mut query: Query<&mut Transform, With<Player>>,
) {
let mut dir = Vec2::ZERO;
if keyboard.pressed(KeyCode::KeyW) { dir.y += 1.0; }
if keyboard.pressed(KeyCode::KeyS) { dir.y -= 1.0; }
if keyboard.pressed(KeyCode::KeyA) { dir.x -= 1.0; }
if keyboard.pressed(KeyCode::KeyD) { dir.x += 1.0; }
for mut transform in &mut query {
transform.translation += (dir.normalize_or_zero() * 200.0).extend(0.0) * 0.016;
}
}ButtonInput replaces older Input API in recent Bevy versions.Player) narrow queries.Related: Input & Events
Zero-sized types tag entities for queries.
#[derive(Component)]
struct Player;With<Player> filters.Component for registration in app type registry.Frame and fixed timestep from Time.
fn debug_fps(time: Res<Time>) {
if time.elapsed_secs().fract() < 0.016 {
println!("delta: {:.4}", time.delta_secs());
}
}delta_secs varies with FPS - use for rendering interpolation.Time<Fixed> provides fixed dt for physics systems in FixedUpdate schedule.Related: Game Loops & Timing
Fire and listen without direct system coupling.
#[derive(Event)]
struct DamageEvent {
amount: f32,
}
fn apply_damage(mut reader: EventReader<DamageEvent>) {
for e in reader.read() {
println!("damage {}", e.amount);
}
}Related: Input & Events
Game flow with state-scoped systems.
#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
#[default]
Menu,
Playing,
}OnEnter(GameState::Playing) spawns level entities.OnExit cleans up to avoid leaked entities.Separate physics tick from render frame rate.
// Register system in FixedUpdate with Time<Fixed> for stable integration
// Example: velocity integration uses fixed_delta_seconds()Related: Game Loops & Timing
Lower-level rendering when Bevy materials are not enough.
// wgpu: device.create_shader_module, render pass set_pipeline, draw indexedRelated: Graphics with wgpu
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 16, 2026