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
bevy_state— main implementation.bevy_app— schedule labels (StateTransition).bevy_ecs— run conditions.
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 returnsbool.
Common patterns
- Loading screen —
OnEnter(AppState::Loading)spawns the loading UI and starts asset loading; a system polls the asset server and callsnext.set(AppState::InGame)when ready. - Menu/game pause —
AppState::Menu,AppState::InGame,AppState::Pausedare top-level. Per-feature sub-states (combat, dialog, inventory) attach asSubStates. - Multiple state types — fine. They tick independently.
Common pitfalls
- Forgetting
init_state— systems within_state(...)will never run because the resource doesn't exist. - Setting
NextStatefrom insideOnExit/OnEnter— works, but the next transition won't happen until the next frame. Don't expect immediate cascading transitions. - Forgetting to handle the default state —
OnEnter(default)does run on app startup, so default-state setup belongs there.
See also
bevy_state— implementation.bevy_app— schedule labels.examples/state/.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.