Open-Source Wikis

/

Bevy

/

Features

/

States

bevyengine/bevy

States

Typed state machines that gate which systems run. Use them to model "we're in a menu," "we're loading," "we're paused," etc.

Pieces

The basic shape

use bevy::prelude::*;

#[derive(States, Default, Debug, Hash, Eq, PartialEq, Clone)]
enum AppState {
    #[default]
    Menu,
    InGame,
    Paused,
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<AppState>()
        .add_systems(OnEnter(AppState::InGame), spawn_world)
        .add_systems(OnExit(AppState::InGame), despawn_world)
        .add_systems(Update, gameplay.run_if(in_state(AppState::InGame)))
        .run();
}

init_state registers the state with the app and inserts State<AppState> and NextState<AppState> resources. OnEnter and OnExit are schedule labels that run their systems exactly when a transition happens.

Transitions

Switch states by mutating NextState:

fn pause(input: Res<ButtonInput<KeyCode>>, mut next: ResMut<NextState<AppState>>) {
    if input.just_pressed(KeyCode::KeyP) {
        next.set(AppState::Paused);
    }
}

The StateTransition schedule (between PreUpdate and Update) detects the change and runs:

graph LR
    OnExit[OnExit<from>] --> OnTransition[OnTransition<from, to>]
    OnTransition --> OnEnter[OnEnter<to>]
    OnEnter --> Apply[State<S> := to]

Sub-states

A SubStates is a state that only exists while a parent state is active:

#[derive(SubStates, Default, Debug, Hash, Eq, PartialEq, Clone)]
#[source(AppState = AppState::InGame)]
enum CombatState {
    #[default]
    OutOfCombat,
    InCombat,
}

CombatState is only valid when AppState::InGame. Transitioning out of InGame despawns the sub-state automatically.

Computed states

A ComputedStates derives from one or more inputs:

#[derive(Default, Debug, Hash, Eq, PartialEq, Clone)]
struct InGameAndUnpaused;

impl ComputedStates for InGameAndUnpaused {
    type SourceStates = AppState;
    fn compute(sources: AppState) -> Option<Self> {
        if matches!(sources, AppState::InGame) { Some(Self) } else { None }
    }
}

Useful for "compose the meaning of multiple states into one." Avoids duplicating run conditions across systems.

Run conditions

in_state(AppState::InGame) is the canonical condition. There are several other state-related conditions:

  • state_changed::<S>() — fires once when state changes.
  • state_exists::<S>() — checks whether the resource is present.
  • Custom: write a closure that reads Res<State<S>> and returns bool.

Common patterns

  • Loading screenOnEnter(AppState::Loading) spawns the loading UI and starts asset loading; a system polls the asset server and calls next.set(AppState::InGame) when ready.
  • Menu/game pauseAppState::Menu, AppState::InGame, AppState::Paused are top-level. Per-feature sub-states (combat, dialog, inventory) attach as SubStates.
  • Multiple state types — fine. They tick independently.

Common pitfalls

  • Forgetting init_state — systems with in_state(...) will never run because the resource doesn't exist.
  • Setting NextState from inside OnExit/OnEnter — works, but the next transition won't happen until the next frame. Don't expect immediate cascading transitions.
  • Forgetting to handle the default stateOnEnter(default) does run on app startup, so default-state setup belongs there.

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

States – Bevy wiki | Factory