Relevant official examples: ecs_guide, startup_system, system_param.

Systems are pieces of functionality to be run by Bevy. They are typically implemented using regular Rust functions. This is how you implement all your game logic.

These functions can only take special parameter types, to specify what data you need access to. If you use unsupported parameter types in your function, you will get confusing compiler errors!

Some of the possibilities are:

See here for a full list!

fn debug_start(
    // access resource
    start: Res<StartingLevel>
) {
    eprintln!("Starting on level {:?}", *start);

System parameters can be grouped into tuples (which can be nested). This is useful for organization.

fn complex_system(
    (a, mut b): (
    (q0, q1, q2): (
        Query<(/* … */)>,
        Query<(/* … */)>,
        Query<(/* … */)>,
) {
    // …

Your function can have a maximum of 16 total parameters. If you need more, group them into tuples to work around the limit. Tuples can contain up to 16 members, but can be nested indefinitely.

There is also a different kind of system: exclusive systems. They have full direct access to the ECS World, so you can access any data you want and do anything, but cannot run in parallel. For most use cases, you should use regular parallel systems.

fn reload_game(world: &mut World) {
    // ... access whatever we want from the World


In order for your systems to actually be run by Bevy, you need to configure them via the app builder:

fn main() {
        // run these only once at launch
        .add_systems(Startup, (setup_camera, debug_start))
        // run these every frame update
        .add_systems(Update, (move_player, enemies_ai))
        // ...

Be careful: writing a new system fn and forgetting to add it to your app is a common mistake! If you run your project and your new code doesn't seem to be running, make sure you added the system!

The above is enough for simple projects.

Systems are contained in schedules. Update is the schedule where you typically add any systems you want to run every frame. Startup is where you typically add systems that should run only once on app startup. There are also other possibilities.

As your project grows more complex, you might want to make use of some of the powerful tools that Bevy offers for managing when/how your systems run, such as: explicit ordering, run conditions, system sets, states.

One-Shot Systems

Sometimes you don't want Bevy to run your system for you. In that case, don't add it to a schedule.

If you are a writing a system that you want to call yourself whenever you want (such as on a button press), you can do that using one-shot systems.