bevy_state/
state_scoped.rs

1#[cfg(feature = "bevy_reflect")]
2use bevy_ecs::reflect::ReflectComponent;
3use bevy_ecs::{
4    component::Component,
5    entity::Entity,
6    event::EventReader,
7    system::{Commands, Query},
8};
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::prelude::*;
11
12use crate::state::{StateTransitionEvent, States};
13
14/// Entities marked with this component will be removed
15/// when the world's state of the matching type no longer matches the supplied value.
16///
17/// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`].
18/// It's also possible to enable it when adding the state to an app with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities).
19///
20/// ```
21/// use bevy_state::prelude::*;
22/// use bevy_ecs::prelude::*;
23/// use bevy_ecs::system::ScheduleSystem;
24///
25/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
26/// #[states(scoped_entities)]
27/// enum GameState {
28///     #[default]
29///     MainMenu,
30///     SettingsMenu,
31///     InGame,
32/// }
33///
34/// # #[derive(Component)]
35/// # struct Player;
36///
37/// fn spawn_player(mut commands: Commands) {
38///     commands.spawn((
39///         StateScoped(GameState::InGame),
40///         Player
41///     ));
42/// }
43///
44/// # struct AppMock;
45/// # impl AppMock {
46/// #     fn init_state<S>(&mut self) {}
47/// #     fn enable_state_scoped_entities<S>(&mut self) {}
48/// #     fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
49/// # }
50/// # struct Update;
51/// # let mut app = AppMock;
52///
53/// app.init_state::<GameState>();
54/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
55/// ```
56#[derive(Component, Clone)]
57#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
58pub struct StateScoped<S: States>(pub S);
59
60impl<S> Default for StateScoped<S>
61where
62    S: States + Default,
63{
64    fn default() -> Self {
65        Self(S::default())
66    }
67}
68
69/// Removes entities marked with [`StateScoped<S>`]
70/// when their state no longer matches the world state.
71pub fn clear_state_scoped_entities<S: States>(
72    mut commands: Commands,
73    mut transitions: EventReader<StateTransitionEvent<S>>,
74    query: Query<(Entity, &StateScoped<S>)>,
75) {
76    // We use the latest event, because state machine internals generate at most 1
77    // transition event (per type) each frame. No event means no change happened
78    // and we skip iterating all entities.
79    let Some(transition) = transitions.read().last() else {
80        return;
81    };
82    if transition.entered == transition.exited {
83        return;
84    }
85    let Some(exited) = &transition.exited else {
86        return;
87    };
88    for (entity, binding) in &query {
89        if binding.0 == *exited {
90            commands.entity(entity).despawn();
91        }
92    }
93}