#[cfg(feature = "bevy_reflect")]1use bevy_ecs::reflect::ReflectComponent;2use bevy_ecs::{3component::Component,4entity::Entity,5event::EventReader,6system::{Commands, Query},7};8#[cfg(feature = "bevy_reflect")]9use bevy_reflect::prelude::*;1011use crate::state::{StateTransitionEvent, States};1213/// Entities marked with this component will be removed14/// when the world's state of the matching type no longer matches the supplied value.15///16/// If you need to disable this behavior, add the attribute `#[states(scoped_entities = false)]` when deriving [`States`].17///18/// ```19/// use bevy_state::prelude::*;20/// use bevy_ecs::prelude::*;21/// use bevy_ecs::system::ScheduleSystem;22///23/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]24/// enum GameState {25/// #[default]26/// MainMenu,27/// SettingsMenu,28/// InGame,29/// }30///31/// # #[derive(Component)]32/// # struct Player;33///34/// fn spawn_player(mut commands: Commands) {35/// commands.spawn((36/// DespawnOnExit(GameState::InGame),37/// Player38/// ));39/// }40///41/// # struct AppMock;42/// # impl AppMock {43/// # fn init_state<S>(&mut self) {}44/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}45/// # }46/// # struct Update;47/// # let mut app = AppMock;48///49/// app.init_state::<GameState>();50/// app.add_systems(OnEnter(GameState::InGame), spawn_player);51/// ```52#[derive(Component, Clone)]53#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]54pub struct DespawnOnExit<S: States>(pub S);5556impl<S> Default for DespawnOnExit<S>57where58S: States + Default,59{60fn default() -> Self {61Self(S::default())62}63}6465/// Despawns entities marked with [`DespawnOnExit<S>`] when their state no66/// longer matches the world state.67pub fn despawn_entities_on_exit_state<S: States>(68mut commands: Commands,69mut transitions: EventReader<StateTransitionEvent<S>>,70query: Query<(Entity, &DespawnOnExit<S>)>,71) {72// We use the latest event, because state machine internals generate at most 173// transition event (per type) each frame. No event means no change happened74// and we skip iterating all entities.75let Some(transition) = transitions.read().last() else {76return;77};78if transition.entered == transition.exited {79return;80}81let Some(exited) = &transition.exited else {82return;83};84for (entity, binding) in &query {85if binding.0 == *exited {86commands.entity(entity).despawn();87}88}89}9091/// Entities marked with this component will be despawned92/// upon entering the given state.93///94/// ```95/// use bevy_state::prelude::*;96/// use bevy_ecs::{prelude::*, system::ScheduleSystem};97///98/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]99/// enum GameState {100/// #[default]101/// MainMenu,102/// SettingsMenu,103/// InGame,104/// }105///106/// # #[derive(Component)]107/// # struct Player;108///109/// fn spawn_player(mut commands: Commands) {110/// commands.spawn((111/// DespawnOnEnter(GameState::MainMenu),112/// Player113/// ));114/// }115///116/// # struct AppMock;117/// # impl AppMock {118/// # fn init_state<S>(&mut self) {}119/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}120/// # }121/// # struct Update;122/// # let mut app = AppMock;123///124/// app.init_state::<GameState>();125/// app.add_systems(OnEnter(GameState::InGame), spawn_player);126/// ```127#[derive(Component, Clone)]128#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]129pub struct DespawnOnEnter<S: States>(pub S);130131/// Despawns entities marked with [`DespawnOnEnter<S>`] when their state132/// matches the world state.133pub fn despawn_entities_on_enter_state<S: States>(134mut commands: Commands,135mut transitions: EventReader<StateTransitionEvent<S>>,136query: Query<(Entity, &DespawnOnEnter<S>)>,137) {138// We use the latest event, because state machine internals generate at most 1139// transition event (per type) each frame. No event means no change happened140// and we skip iterating all entities.141let Some(transition) = transitions.read().last() else {142return;143};144if transition.entered == transition.exited {145return;146}147let Some(entered) = &transition.entered else {148return;149};150for (entity, binding) in &query {151if binding.0 == *entered {152commands.entity(entity).despawn();153}154}155}156157158