bevy-ecs-systems
Skill29 starsChanged 11 days agoMITView on GitHub
Use when deriving `SystemParam`, grouping with `SystemSet`, gating execution with `.run_if(on_message::<M>)` / `in_state(...)` / `resource_exists::<R>`, combining conditions with `.and_then()` / `.or_else()`, ordering with `.before`/`.after`/`.chain()`/`.chain_weak()`, driving states with `NextState::set_if_different`, or removing systems with `remove_systems_in_set` in Bevy 0.20.
In this repository34 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- bevy-a11ySkill
- bevy-animationSkill
- bevy-assetsSkill
- bevy-audioSkill
- bevy-camerasSkill
- bevy-captureSkill
- bevy-cargo-featuresSkill
- bevy-core-conceptsSkill
- bevy-custom-assetsSkill
- bevy-diagnostics-profilingSkill
- bevy-ecs-componentsSkill
- bevy-ecs-queriesSkill
- bevy-fluentSkill
- bevy-input-actionsSkill
- bevy-migration-0-17-to-0-18Skill
- bevy-migration-0-18-to-0-19Skill
- bevy-migration-0-19-to-0-20Skill
- bevy-pbr-materialsSkill
- bevy-physicsSkill
- bevy-portingSkill
- bevy-renderingSkill
- bevy-save-loadSkill
- bevySkill
- bevy-testingSkill
- bevy-uiSkill
- bevy-vfxSkill
- bevy-voxel-dataSkill
- bevy-voxel-pipelineSkill
- bevy-voxel-runtimeSkill
- bevy-wasm-webgpuSkill
- similarity-rsSkill
The file
Files
Bevy 0.20 — ECS Systems (params, sets, run conditions)
When to use this skill
- Bundling related state into one parameter via
#[derive(SystemParam)]. - Grouping systems into a
SystemSetso callers can order against the whole group. - Gating systems with run conditions (
run_if,on_message,in_state,resource_exists,any_with_component). - Hooking into state transitions with
OnEnter(S)/OnExit(S)/OnTransition { exited, entered }. - Hot-removing systems at runtime (tearing down a debug overlay) with
remove_systems_in_set. - Choosing strict
.chain()versus weak.chain_weak()ordering (0.20).
Canonical pattern
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
#[derive(SystemSet, Hash, PartialEq, Eq, Clone, Debug)]
enum GameLoop { Input, Simulate, Render }
#[derive(States, Default, Hash, PartialEq, Eq, Clone, Debug)]
enum AppState { #[default] Loading, Playing }
#[derive(Resource, Default)]
struct Score(u32);
#[derive(Message)]
struct GoalScored { team: u8 }
// Composite param: pass one argument, get three.
// 'w = world borrow; 's = system-local state borrow.
#[derive(SystemParam)]
struct GameCtx<'w, 's> {
time: Res<'w, Time>,
score: ResMut<'w, Score>,
goals: MessageReader<'w, 's, GoalScored>,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<Score>()
.add_message::<GoalScored>()
.init_state::<AppState>()
.configure_sets(Update, (GameLoop::Input, GameLoop::Simulate, GameLoop::Render).chain())
.add_systems(OnEnter(AppState::Playing), spawn_level)
.add_systems(OnExit(AppState::Playing), despawn_level)
.add_systems(Update, read_input.in_set(GameLoop::Input))
.add_systems(
Update,
tally_goals
.in_set(GameLoop::Simulate)
.run_if(on_message::<GoalScored>),
)
.add_systems(Update, (draw_hud, start_game).in_set(GameLoop::Render))
.run();
}
#[derive(Component)]
struct LevelEntity;
fn spawn_level(mut commands: Commands) {
commands.spawn((LevelEntity, Name::new("level")));
}
fn despawn_level(mut commands: Commands, query: Query<Entity, With<LevelEntity>>) {
for e in &query { commands.entity(e).despawn(); }
}
fn read_input(mut writer: MessageWriter<GoalScored>) {
writer.write(GoalScored { team: 0 });
}
fn tally_goals(mut ctx: GameCtx) {
let _ = ctx.time.delta_secs();
for goal in ctx.goals.read() {
ctx.score.0 += 1;
info!("team {} scored, total {}", goal.team, ctx.score.0);
}
}
fn draw_hud(score: Res<Score>) { let _ = score.0; }
// 0.20: `set_if_different` (was `set_if_neq`) skips OnExit/OnEnter when
// the target equals the current state.
fn start_game(mut next: ResMut<NextState<AppState>>) {
next.set_if_different(AppState::Playing);
}Run condition cheat sheet
| Built-in | Triggers when |
on_message::<M> | The Messages<M> buffer has unread items. |
resource_exists::<R> | Resource R is in the World. |
resource_changed::<R> | R was mutated this tick. |
in_state(MyState::X) | Current State<MyState> equals X. |
state_changed::<MyState> | The state changed this tick. |
any_with_component::<C> | At least one entity has C. |
Combine with .and_then() / .or_else() (short-circuit) or .and_eager() / .or_eager() (always evaluate both): run_if(in_state(GameState::Playing).and_then(resource_exists::<NetSession>)). Stale LLM output uses .and() / .or(); those methods do not exist.
Topics
| Topic | Reference |
#[derive(SystemParam)], 'w/'s lifetimes, Local<T>, &mut World as a param, Commands deferred apply | references/system-params.md |
#[derive(SystemSet)] requirements, .in_set, .configure_sets, .chain() | references/system-sets.md |
| Every built-in condition, combinators, custom conditions, cost rule | references/run-conditions.md |
OnEnter(S) / OnExit(S) / OnTransition, NextState<S>, set_if_different, DespawnOnExit | references/state-schedules.md |
.before, .after, .chain(), .chain_weak(), .ambiguous_with, shuffle_seed, debugging ambiguity | references/ordering.md |
remove_systems_in_set, ScheduleCleanupPolicy, 3-arg vs 4-arg receivers, side-effect limits | references/runtime-removal.md |
Gotchas (0.18 → 0.20)
&mut Worldis aSystemParam(0.20). It may appear anywhere in the parameter list, beside params that do not borrow the World (Local<T>,&mut SystemState,&mut QueryState).ExclusiveSystemParam,ExclusiveFunctionSystemandSystem::is_exclusive()are removed; exclusivity is tracked at runtime viaSystemAccess::is_exclusive().SystemParamvalidation happens inget_param(0.19).SystemParam::validate_paramis gone;get_paramreturnsResult<Item, SystemParamValidationError>.SystemState::get/get_mutnow returnResult, so add.unwrap()or?. Frequently-skipped systems (Single) now spawn a task before failing validation; add arun_ifif that shows in profiles.- Generic
ResMut<R>needsR: Resource<Mutability = Mutable>(0.19) because resources can be immutable.Res<R>is unchanged. Command::Outis an associated type (0.19).fn cmd() -> impl Command<Result>→impl Command;impl Command for Foomust declaretype Out = ....NextState::set_if_neq→set_if_different(0.20).PendingIfNeq→PendingIfDifferent,set_state_if_neq→set_state_if_different.set()always runsOnExit/OnEnter, and since 0.19DespawnOnEnter/DespawnOnExitalso fire on a same-stateset().OnTransition { exited, entered }, not{ from, to }..chain_weak(),.before_weak(),.after_weak()(0.20) order only systems whose tracked data access conflicts. Bevy's ownPostUpdateUI sets and render sets use them; dependencies through channels or interior mutability need an explicit.after(system).- No
SimpleExecutorsince 0.18. Unordered conflicting systems run in arbitrary order and are not reported unlessambiguity_detectionis raised. See references/ordering.md. MessageReader/MessageWriter, notEventReader/EventWriter.#[derive(Message)],app.add_message::<M>()(0.17+).remove_systems_in_setreturnsResult<usize, ScheduleError>.Schedules::remove_systems_in_settakes 4 args (label, set, world, policy);Schedule::remove_systems_in_settakes 3.System::type_id→System::system_type(0.19).DefaultErrorHandler→FallbackErrorHandler(0.19); it now also receives panics (0.20).WindowPluginexit systems run inLast(0.19), inside theExitSystemsset; systems afterUpdatethat rely on a window no longer panic on the final frame.
See also
bevy-core-concepts— which schedule (Startup,Update,FixedUpdate) to add a system to.bevy-ecs-queries— query is one of manySystemParams;Changed<T>/Added<T>filters.bevy-ecs-components— defining theComponenttypes your systems act on; observers.bevy-migration-0-18-to-0-19— param validation,Command::Out, executor renames.bevy-migration-0-19-to-0-20— exclusive unification,chain_weak,set_if_different.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

