Bevy ECS Patterns
Advanced patterns and techniques for Bevy's Entity Component System, focusing on performance, maintainability, and complex game architectures.
When to Use This Skill
| Use this skill when... |
Use bevy-game-engine instead when... |
| Optimizing ECS query performance |
Setting up a new Bevy project from scratch |
| Implementing complex entity relationships or hierarchies |
Learning basic ECS concepts (components, systems, resources) |
| Designing system scheduling and ordering |
Handling input, assets, or game states |
Using change detection (Changed<T>, Added<T>) |
Writing basic game logic or app structure |
Working with ParamSet, parallel iteration, or batch operations |
Configuring rendering, UI, or audio |
| Debugging archetype fragmentation or storage strategies |
Adding plugins or common Bevy dependencies |
Core Expertise
Advanced Queries
- Complex query filters and combinations
- Query state and caching
- Entity relationships and hierarchies
- Parallel iteration strategies
System Scheduling
- System ordering and dependencies
- Run conditions and gating
- System sets and scheduling
- Fixed timestep systems
Change Detection
Changed<T> and Added<T> filters
Ref<T> for change tracking
- Efficient reactive systems
Advanced Query Patterns
Query Filters
use bevy::prelude::*;
// Multiple filters
fn targeting_system(
query: Query<
(&Transform, &mut Target),
(With<Enemy>, Without<Dead>, Changed<Health>)
>,
) {
for (transform, mut target) in &query {
// Only enemies that are alive and recently damaged
}
}
// Optional components
fn flexible_system(
query: Query<(&Transform, Option<&Velocity>, Option<&Acceleration>)>,
) {
for (transform, velocity, acceleration) in &query {
if let Some(vel) = velocity {
// Has velocity
}
}
}
// Or filters
fn pickup_system(
query: Query<Entity, Or<(With<HealthPickup>, With<AmmoPickup>)>>,
) {
for entity in &query {
// Process any pickup type
}
}
Multiple Queries with Conflicts
// Use ParamSet when queries have conflicting access
fn combat_system(
mut param_set: ParamSet<(
Query<&mut Health, With<Player>>,
Query<&mut Health, With<Enemy>>,
)>,
) {
// Access one query at a time
for mut health in param_set.p0().iter_mut() {
health.0 += 1.0; // Heal player
}
for mut health in param_set.p1().iter_mut() {
health.0 -= 10.0; // Damage enemies
}
}
// Query transmutation for flexible access
fn dynamic_query(
all_entities: Query<(Entity, &Transform)>,
players: Query<&Player>,
) {
for (entity, transform) in &all_entities {
if players.get(entity).is_ok() {
// This is a player
}
}
}
Entity Relationships
// Parent-child hierarchies
#[derive(Component)]
struct Inventory;
#[derive(Component)]
struct InventorySlot(usize);
fn spawn_inventory(mut commands: Commands) {
commands.spawn((Inventory, SpatialBundle::default()))
.with_children(|parent| {
for i in 0..10 {
parent.spawn((InventorySlot(i), SpriteBundle::default()));
}
});
}
// Query parent from child
fn child_system(
children: Query<(&InventorySlot, &Parent)>,
parents: Query<&Inventory>,
) {
for (slot, parent) in &children {
if let Ok(inventory) = parents.get(parent.get()) {
// Access parent inventory
}
}
}
// Query children from parent
fn parent_system(
parents: Query<&Children, With<Inventory>>,
slots: Query<&InventorySlot>,
) {
for children in &parents {
for &child in children.iter() {
if let Ok(slot) = slots.get(child) {
// Access child slot
}
}
}
}
Agentic Optimizations
| Context |
Command |
| Quick compile check |
cargo check 2>&1 | head -30 |
| Fast test run |
cargo test --lib -- --test-threads=1 -q |
| Check ECS usage errors |
cargo clippy -- -W clippy::all 2>&1 | head -50 |
| List components in project |
grep -rn "derive(Component)" src/ --include="*.rs" |
| List systems in project |
grep -rn "fn.*Query<" src/ --include="*.rs" |
| Find system ordering |
grep -rn "\.add_systems|\.chain()|\.after(|\.before(" src/ --include="*.rs" |
| Check for ParamSet usage |
grep -rn "ParamSet" src/ --include="*.rs" |
For system scheduling, change detection, performance patterns, custom entity commands, and design best practices, see REFERENCE.md.
1---
2created: 2025-12-16
3modified: 2026-05-09
4reviewed: 2025-12-16
5name: bevy-ecs-patterns
6description: "Advanced Bevy ECS: complex queries, system scheduling, change detection, and performance tuning. Use when optimizing Bevy architecture or implementing complex game systems."
7user-invocable: false
8allowed-tools: Glob, Grep, Read, Bash(cargo *), Edit, Write, TodoWrite, WebFetch, WebSearch
9---
10
11# Bevy ECS Patterns
12
13Advanced patterns and techniques for Bevy's Entity Component System, focusing on performance, maintainability, and complex game architectures.
14
15## When to Use This Skill
16
17| Use this skill when... | Use bevy-game-engine instead when... |
18|------------------------|--------------------------------------|
19| Optimizing ECS query performance | Setting up a new Bevy project from scratch |
20| Implementing complex entity relationships or hierarchies | Learning basic ECS concepts (components, systems, resources) |
21| Designing system scheduling and ordering | Handling input, assets, or game states |
22| Using change detection (`Changed<T>`, `Added<T>`) | Writing basic game logic or app structure |
23| Working with `ParamSet`, parallel iteration, or batch operations | Configuring rendering, UI, or audio |
24| Debugging archetype fragmentation or storage strategies | Adding plugins or common Bevy dependencies |
25
26## Core Expertise
27
28**Advanced Queries**
29- Complex query filters and combinations
30- Query state and caching
31- Entity relationships and hierarchies
32- Parallel iteration strategies
33
34**System Scheduling**
35- System ordering and dependencies
36- Run conditions and gating
37- System sets and scheduling
38- Fixed timestep systems
39
40**Change Detection**
41- `Changed<T>` and `Added<T>` filters
42- `Ref<T>` for change tracking
43- Efficient reactive systems
44
45## Advanced Query Patterns
46
47**Query Filters**
48```rust
49use bevy::prelude::*;
50
51// Multiple filters
52fn targeting_system(
53 query: Query<
54 (&Transform, &mut Target),
55 (With<Enemy>, Without<Dead>, Changed<Health>)
56 >,
57) {
58 for (transform, mut target) in &query {
59 // Only enemies that are alive and recently damaged
60 }
61}
62
63// Optional components
64fn flexible_system(
65 query: Query<(&Transform, Option<&Velocity>, Option<&Acceleration>)>,
66) {
67 for (transform, velocity, acceleration) in &query {
68 if let Some(vel) = velocity {
69 // Has velocity
70 }
71 }
72}
73
74// Or filters
75fn pickup_system(
76 query: Query<Entity, Or<(With<HealthPickup>, With<AmmoPickup>)>>,
77) {
78 for entity in &query {
79 // Process any pickup type
80 }
81}
82```
83
84**Multiple Queries with Conflicts**
85```rust
86// Use ParamSet when queries have conflicting access
87fn combat_system(
88 mut param_set: ParamSet<(
89 Query<&mut Health, With<Player>>,
90 Query<&mut Health, With<Enemy>>,
91 )>,
92) {
93 // Access one query at a time
94 for mut health in param_set.p0().iter_mut() {
95 health.0 += 1.0; // Heal player
96 }
97
98 for mut health in param_set.p1().iter_mut() {
99 health.0 -= 10.0; // Damage enemies
100 }
101}
102
103// Query transmutation for flexible access
104fn dynamic_query(
105 all_entities: Query<(Entity, &Transform)>,
106 players: Query<&Player>,
107) {
108 for (entity, transform) in &all_entities {
109 if players.get(entity).is_ok() {
110 // This is a player
111 }
112 }
113}
114```
115
116**Entity Relationships**
117```rust
118// Parent-child hierarchies
119#[derive(Component)]
120struct Inventory;
121
122#[derive(Component)]
123struct InventorySlot(usize);
124
125fn spawn_inventory(mut commands: Commands) {
126 commands.spawn((Inventory, SpatialBundle::default()))
127 .with_children(|parent| {
128 for i in 0..10 {
129 parent.spawn((InventorySlot(i), SpriteBundle::default()));
130 }
131 });
132}
133
134// Query parent from child
135fn child_system(
136 children: Query<(&InventorySlot, &Parent)>,
137 parents: Query<&Inventory>,
138) {
139 for (slot, parent) in &children {
140 if let Ok(inventory) = parents.get(parent.get()) {
141 // Access parent inventory
142 }
143 }
144}
145
146// Query children from parent
147fn parent_system(
148 parents: Query<&Children, With<Inventory>>,
149 slots: Query<&InventorySlot>,
150) {
151 for children in &parents {
152 for &child in children.iter() {
153 if let Ok(slot) = slots.get(child) {
154 // Access child slot
155 }
156 }
157 }
158}
159```
160
161## Agentic Optimizations
162
163| Context | Command |
164|---------|---------|
165| Quick compile check | `cargo check 2>&1 \| head -30` |
166| Fast test run | `cargo test --lib -- --test-threads=1 -q` |
167| Check ECS usage errors | `cargo clippy -- -W clippy::all 2>&1 \| head -50` |
168| List components in project | `grep -rn "derive(Component)" src/ --include="*.rs"` |
169| List systems in project | `grep -rn "fn.*Query<" src/ --include="*.rs"` |
170| Find system ordering | `grep -rn "\.add_systems\|\.chain()\|\.after(\|\.before(" src/ --include="*.rs"` |
171| Check for ParamSet usage | `grep -rn "ParamSet" src/ --include="*.rs"` |
172
173For system scheduling, change detection, performance patterns, custom entity commands, and design best practices, see [REFERENCE.md](REFERENCE.md).