bevy-ecs-systems
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.
How do I install this agent skill?
npx skills add https://github.com/chrisgliddon/bevy-skills --skill bevy-ecs-systemsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill is a technical reference for the Bevy 0.20 game engine ECS systems. It contains documentation and Rust code templates without any malicious code, external dependencies, or security risks.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
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.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/chrisgliddon/bevy-skills/bevy-ecs-systems">View bevy-ecs-systems on skillZs</a>