Bevy game engine development including ECS architecture, rendering, input handling, asset management, and game loop design. Use when building games with Bevy, working with entities/components/systems, or when the user mentions Bevy, game development in Rust, or 2D/3D games.
/plugin marketplace add laurigates/claude-plugins/plugin install bevy-plugin@lgates-claude-pluginsThis skill is limited to using the following tools:
Expert knowledge for developing games with Bevy, the data-driven game engine built in Rust with a focus on ergonomics, modularity, and performance.
Bevy Architecture
Rendering
ECS Fundamentals
use bevy::prelude::*;
// Components are plain data structs
#[derive(Component)]
struct Player;
#[derive(Component)]
struct Health(f32);
#[derive(Component)]
struct Velocity(Vec2);
// Spawn entities with components
fn spawn_player(mut commands: Commands) {
commands.spawn((
Player,
Health(100.0),
Velocity(Vec2::ZERO),
SpriteBundle {
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
},
));
}
// Systems query for components
fn move_player(
time: Res<Time>,
mut query: Query<(&Velocity, &mut Transform), With<Player>>,
) {
for (velocity, mut transform) in &mut query {
transform.translation += velocity.0.extend(0.0) * time.delta_seconds();
}
}
App Structure
use bevy::prelude::*;
fn main() {
App::new()
// Default plugins (window, rendering, input, etc.)
.add_plugins(DefaultPlugins)
// Custom plugins
.add_plugins(GamePlugin)
// Resources
.insert_resource(GameSettings::default())
// Startup systems (run once)
.add_systems(Startup, setup)
// Update systems (run every frame)
.add_systems(Update, (
player_movement,
collision_detection,
update_score,
))
.run();
}
// Organize with plugins
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, spawn_player)
.add_systems(Update, player_input);
}
}
Input Handling
fn player_input(
keyboard: Res<ButtonInput<KeyCode>>,
mut query: Query<&mut Velocity, With<Player>>,
) {
let mut direction = Vec2::ZERO;
if keyboard.pressed(KeyCode::KeyW) { direction.y += 1.0; }
if keyboard.pressed(KeyCode::KeyS) { direction.y -= 1.0; }
if keyboard.pressed(KeyCode::KeyA) { direction.x -= 1.0; }
if keyboard.pressed(KeyCode::KeyD) { direction.x += 1.0; }
for mut velocity in &mut query {
velocity.0 = direction.normalize_or_zero() * 200.0;
}
}
// Mouse input
fn mouse_click(
mouse: Res<ButtonInput<MouseButton>>,
windows: Query<&Window>,
) {
if mouse.just_pressed(MouseButton::Left) {
if let Some(position) = windows.single().cursor_position() {
println!("Clicked at: {:?}", position);
}
}
}
Asset Loading
#[derive(Resource)]
struct GameAssets {
player_sprite: Handle<Image>,
font: Handle<Font>,
sound: Handle<AudioSource>,
}
fn load_assets(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
commands.insert_resource(GameAssets {
player_sprite: asset_server.load("sprites/player.png"),
font: asset_server.load("fonts/game.ttf"),
sound: asset_server.load("sounds/jump.ogg"),
});
}
// Check if assets are loaded
fn check_assets_loaded(
asset_server: Res<AssetServer>,
assets: Res<GameAssets>,
mut next_state: ResMut<NextState<GameState>>,
) {
use bevy::asset::LoadState;
if asset_server.get_load_state(&assets.player_sprite) == Some(LoadState::Loaded) {
next_state.set(GameState::Playing);
}
}
Game States
#[derive(States, Debug, Clone, Eq, PartialEq, Hash, Default)]
enum GameState {
#[default]
Loading,
Menu,
Playing,
Paused,
GameOver,
}
fn setup_states(app: &mut App) {
app.init_state::<GameState>()
.add_systems(OnEnter(GameState::Menu), setup_menu)
.add_systems(OnExit(GameState::Menu), cleanup_menu)
.add_systems(Update, menu_input.run_if(in_state(GameState::Menu)))
.add_systems(Update, game_logic.run_if(in_state(GameState::Playing)));
}
fn pause_game(
keyboard: Res<ButtonInput<KeyCode>>,
state: Res<State<GameState>>,
mut next_state: ResMut<NextState<GameState>>,
) {
if keyboard.just_pressed(KeyCode::Escape) {
match state.get() {
GameState::Playing => next_state.set(GameState::Paused),
GameState::Paused => next_state.set(GameState::Playing),
_ => {}
}
}
}
Events
#[derive(Event)]
struct CollisionEvent {
entity_a: Entity,
entity_b: Entity,
}
#[derive(Event)]
struct ScoreEvent(u32);
fn detect_collisions(
mut collision_events: EventWriter<CollisionEvent>,
query: Query<(Entity, &Transform, &Collider)>,
) {
// Collision detection logic
for [(entity_a, transform_a, _), (entity_b, transform_b, _)] in query.iter_combinations() {
if colliding(transform_a, transform_b) {
collision_events.send(CollisionEvent { entity_a, entity_b });
}
}
}
fn handle_collisions(
mut collision_events: EventReader<CollisionEvent>,
mut score_events: EventWriter<ScoreEvent>,
) {
for event in collision_events.read() {
// Handle collision
score_events.send(ScoreEvent(10));
}
}
# Create new Bevy project
cargo new my_game
cd my_game
cargo add bevy
# Run with fast compile times (debug)
cargo run
# Run with optimizations
cargo run --release
# Enable dynamic linking for faster compiles (dev only)
cargo run --features bevy/dynamic_linking
# Common dev dependencies
cargo add bevy_egui # Debug UI
cargo add bevy_rapier2d # 2D physics
cargo add bevy_rapier3d # 3D physics
cargo add bevy_asset_loader # Asset loading helpers
cargo add leafwing-input-manager # Advanced input
my_game/
├── Cargo.toml
├── assets/
│ ├── sprites/
│ ├── fonts/
│ ├── sounds/
│ └── shaders/
└── src/
├── main.rs
├── lib.rs # Optional library crate
├── plugins/
│ ├── mod.rs
│ ├── player.rs
│ ├── enemy.rs
│ └── ui.rs
├── components/
│ └── mod.rs
├── resources/
│ └── mod.rs
├── systems/
│ └── mod.rs
└── events/
└── mod.rs
Performance
Query filters (With<T>, Without<T>) to narrow iterationQuery::iter() when you need specific entitiesChanged<T> and Added<T> filters for reactive systemsbevy_diagnostic and TracyCode Organization
Common Patterns
// Marker components
#[derive(Component)]
struct Enemy;
#[derive(Component)]
struct Bullet;
// Component bundles for common entity types
#[derive(Bundle)]
struct EnemyBundle {
enemy: Enemy,
health: Health,
sprite: SpriteBundle,
}
// System sets for ordering
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum GameSet {
Input,
Movement,
Collision,
Render,
}
For detailed ECS patterns, advanced queries, and system scheduling, see the bevy-ecs-patterns skill.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.