state_scoped/
state_scoped.rs

1//! Shows how to spawn entities that are automatically despawned either when
2//! entering or exiting specific game states.
3//!
4//! This pattern is useful for managing menus, levels, or other state-specific
5//! content that should only exist during certain states.
6
7use bevy::prelude::*;
8
9fn main() {
10    App::new()
11        .add_plugins(DefaultPlugins)
12        .init_state::<GameState>()
13        .add_systems(Startup, setup_camera)
14        .add_systems(OnEnter(GameState::A), on_a_enter)
15        .add_systems(OnEnter(GameState::B), on_b_enter)
16        .add_systems(OnExit(GameState::A), on_a_exit)
17        .add_systems(OnExit(GameState::B), on_b_exit)
18        .add_systems(Update, toggle)
19        .insert_resource(TickTock(Timer::from_seconds(1.0, TimerMode::Repeating)))
20        .run();
21}
22
23#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
24enum GameState {
25    #[default]
26    A,
27    B,
28}
29
30#[derive(Resource)]
31struct TickTock(Timer);
32
33fn on_a_enter(mut commands: Commands) {
34    info!("on_a_enter");
35    commands.spawn((
36        DespawnOnExit(GameState::A),
37        Text::new("Game is in state 'A'"),
38        TextFont {
39            font_size: 33.0,
40            ..default()
41        },
42        TextColor(Color::srgb(0.5, 0.5, 1.0)),
43        Node {
44            position_type: PositionType::Absolute,
45            top: px(0),
46            left: px(0),
47            ..default()
48        },
49    ));
50}
51
52fn on_a_exit(mut commands: Commands) {
53    info!("on_a_exit");
54    commands.spawn((
55        DespawnOnEnter(GameState::A),
56        Text::new("Game state 'A' will be back in 1 second"),
57        TextFont {
58            font_size: 33.0,
59            ..default()
60        },
61        TextColor(Color::srgb(0.5, 0.5, 1.0)),
62        Node {
63            position_type: PositionType::Absolute,
64            top: px(0),
65            left: px(500),
66            ..default()
67        },
68    ));
69}
70
71fn on_b_enter(mut commands: Commands) {
72    info!("on_b_enter");
73    commands.spawn((
74        DespawnOnExit(GameState::B),
75        Text::new("Game is in state 'B'"),
76        TextFont {
77            font_size: 33.0,
78            ..default()
79        },
80        TextColor(Color::srgb(0.5, 0.5, 1.0)),
81        Node {
82            position_type: PositionType::Absolute,
83            top: px(50),
84            left: px(0),
85            ..default()
86        },
87    ));
88}
89
90fn on_b_exit(mut commands: Commands) {
91    info!("on_b_exit");
92    commands.spawn((
93        DespawnOnEnter(GameState::B),
94        Text::new("Game state 'B' will be back in 1 second"),
95        TextFont {
96            font_size: 33.0,
97            ..default()
98        },
99        TextColor(Color::srgb(0.5, 0.5, 1.0)),
100        Node {
101            position_type: PositionType::Absolute,
102            top: px(50),
103            left: px(500),
104            ..default()
105        },
106    ));
107}
108
109fn setup_camera(mut commands: Commands) {
110    commands.spawn(Camera3d::default());
111}
112
113fn toggle(
114    time: Res<Time>,
115    mut timer: ResMut<TickTock>,
116    state: Res<State<GameState>>,
117    mut next_state: ResMut<NextState<GameState>>,
118) {
119    if !timer.0.tick(time.delta()).is_finished() {
120        return;
121    }
122    *next_state = match state.get() {
123        GameState::A => NextState::Pending(GameState::B),
124        GameState::B => NextState::Pending(GameState::A),
125    }
126}