bevy/examples/ecs
mgi388 7a1fcb7fe7
Rename StateScoped to DespawnOnExitState and add DespawnOnEnterState (#18818)
# Objective

- Alternative to and builds on top of #16284.
- Fixes #15849.

## Solution

- Rename component `StateScoped` to `DespawnOnExitState`.
- Rename system `clear_state_scoped_entities` to
`despawn_entities_on_exit_state`.
- Add `DespawnOnEnterState` and `despawn_entities_on_enter_state` which
is the `OnEnter` equivalent.

> [!NOTE]
> Compared to #16284, the main change is that I did the rename in such a
way as to keep the terms `OnExit` and `OnEnter` together. In my own
game, I was adding `VisibleOnEnterState` and `HiddenOnExitState` and
when naming those, I kept the `OnExit` and `OnEnter` together. When I
checked #16284 it stood out to me that the naming was a bit awkward.
Putting the `State` in the middle and breaking up `OnEnter` and `OnExit`
also breaks searching for those terms.

## Open questions

1. Should we split `enable_state_scoped_entities` into two functions,
one for the `OnEnter` and one for the `OnExit`? I personally have zero
need thus far for the `OnEnter` version, so I'd be interested in not
having this enabled unless I ask for it.
2. If yes to 1., should we follow my lead in my `Visibility` state
components (see below) and name these
`app.enable_despawn_entities_on_enter_state()` and
`app.enable_despawn_entities_on_exit_state()`, which IMO says what it
does on the tin?

## Testing

Ran all changed examples.

## Side note: `VisibleOnEnterState` and `HiddenOnExitState`

For reference to anyone else and to help with the open questions, I'm
including the code I wrote for controlling entity visibility when a
state is entered/exited.

<details>
<summary>visibility.rs</summary>

```rust
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_render::prelude::*;
use bevy_state::{prelude::*, state::StateTransitionSteps};
use tracing::*;

pub trait AppExtStates {
    fn enable_visible_entities_on_enter_state<S: States>(&mut self) -> &mut Self;

    fn enable_hidden_entities_on_exit_state<S: States>(&mut self) -> &mut Self;
}

impl AppExtStates for App {
    fn enable_visible_entities_on_enter_state<S: States>(&mut self) -> &mut Self {
        self.main_mut()
            .enable_visible_entities_on_enter_state::<S>();
        self
    }

    fn enable_hidden_entities_on_exit_state<S: States>(&mut self) -> &mut Self {
        self.main_mut().enable_hidden_entities_on_exit_state::<S>();
        self
    }
}

impl AppExtStates for SubApp {
    fn enable_visible_entities_on_enter_state<S: States>(&mut self) -> &mut Self {
        if !self
            .world()
            .contains_resource::<Events<StateTransitionEvent<S>>>()
        {
            let name = core::any::type_name::<S>();
            warn!("Visible entities on enter state are enabled for state `{}`, but the state isn't installed in the app!", name);
        }
        // We work with [`StateTransition`] in set
        // [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`],
        // because [`OnExit`] only runs for one specific variant of the state.
        self.add_systems(
            StateTransition,
            update_to_visible_on_enter_state::<S>.in_set(StateTransitionSteps::ExitSchedules),
        )
    }

    fn enable_hidden_entities_on_exit_state<S: States>(&mut self) -> &mut Self {
        if !self
            .world()
            .contains_resource::<Events<StateTransitionEvent<S>>>()
        {
            let name = core::any::type_name::<S>();
            warn!("Hidden entities on exit state are enabled for state `{}`, but the state isn't installed in the app!", name);
        }
        // We work with [`StateTransition`] in set
        // [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`],
        // because [`OnExit`] only runs for one specific variant of the state.
        self.add_systems(
            StateTransition,
            update_to_hidden_on_exit_state::<S>.in_set(StateTransitionSteps::ExitSchedules),
        )
    }
}

#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Debug)]
pub struct VisibleOnEnterState<S: States>(pub S);

#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Debug)]
pub struct HiddenOnExitState<S: States>(pub S);

/// Makes entities marked with [`VisibleOnEnterState<S>`] visible when the state
/// `S` is entered.
pub fn update_to_visible_on_enter_state<S: States>(
    mut transitions: EventReader<StateTransitionEvent<S>>,
    mut query: Query<(&VisibleOnEnterState<S>, &mut Visibility)>,
) {
    // We use the latest event, because state machine internals generate at most
    // 1 transition event (per type) each frame. No event means no change
    // happened and we skip iterating all entities.
    let Some(transition) = transitions.read().last() else {
        return;
    };
    if transition.entered == transition.exited {
        return;
    }
    let Some(entered) = &transition.entered else {
        return;
    };
    for (binding, mut visibility) in query.iter_mut() {
        if binding.0 == *entered {
            visibility.set_if_neq(Visibility::Visible);
        }
    }
}

/// Makes entities marked with [`HiddenOnExitState<S>`] invisible when the state
/// `S` is exited.
pub fn update_to_hidden_on_exit_state<S: States>(
    mut transitions: EventReader<StateTransitionEvent<S>>,
    mut query: Query<(&HiddenOnExitState<S>, &mut Visibility)>,
) {
    // We use the latest event, because state machine internals generate at most
    // 1 transition event (per type) each frame. No event means no change
    // happened and we skip iterating all entities.
    let Some(transition) = transitions.read().last() else {
        return;
    };
    if transition.entered == transition.exited {
        return;
    }
    let Some(exited) = &transition.exited else {
        return;
    };
    for (binding, mut visibility) in query.iter_mut() {
        if binding.0 == *exited {
            visibility.set_if_neq(Visibility::Hidden);
        }
    }
}
```

</details>

---------

Co-authored-by: Benjamin Brienen <Benjamin.Brienen@outlook.com>
Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
2025-05-06 00:37:04 +00:00
..
change_detection.rs Rename track_change_detection flag to track_location (#17075) 2025-01-01 18:43:47 +00:00
component_hooks.rs Preserve spawned RelationshipTarget order and other improvements (#17858) 2025-03-05 22:18:57 +00:00
custom_query_param.rs Prefer Display over Debug (#16112) 2024-12-27 00:40:06 +00:00
custom_schedule.rs Simpler lint fixes: makes ci lints work but disables a lint for now (#15376) 2024-09-24 11:42:59 +00:00
dynamic.rs Remove deprecated component_reads_and_writes (#16348) 2025-03-04 08:22:29 +00:00
ecs_guide.rs Renamed EventWriter::send methods to write. (#17977) 2025-02-23 21:18:52 +00:00
entity_disabling.rs Make Query::single (and friends) return a Result (#18082) 2025-03-02 19:51:56 +00:00
error_handling.rs Use Display instead of Debug in the default error handler (#18629) 2025-03-31 18:28:19 +00:00
event.rs Renamed EventWriter::send methods to write. (#17977) 2025-02-23 21:18:52 +00:00
fallible_params.rs Replace ValidationOutcome with Result (#18541) 2025-03-26 03:36:16 +00:00
fixed_timestep.rs aligning public apis of Time,Timer and Stopwatch (#15962) 2024-10-16 21:09:32 +00:00
generic_system.rs Relationships (non-fragmenting, one-to-many) (#17398) 2025-01-18 22:20:30 +00:00
hierarchy.rs Add an example teaching users about custom relationships (#17443) 2025-01-20 23:17:38 +00:00
immutable_components.rs Rename bevy_platform_support to bevy_platform (#18813) 2025-04-11 23:13:28 +00:00
iter_combinations.rs aligning public apis of Time,Timer and Stopwatch (#15962) 2024-10-16 21:09:32 +00:00
nondeterministic_system_order.rs
observer_propagation.rs Improve derive(Event) and simplify macro code (#18083) 2025-03-07 02:01:23 +00:00
observers.rs Rename bevy_platform_support to bevy_platform (#18813) 2025-04-11 23:13:28 +00:00
one_shot_systems.rs Merge Style properties into Node. Use ComputedNode for computed properties. (#15975) 2024-10-18 22:25:33 +00:00
parallel_query.rs Make Query::single (and friends) return a Result (#18082) 2025-03-02 19:51:56 +00:00
relationships.rs Change with_related to work with a Bundle and added with_relationships method (#18699) 2025-04-09 02:34:49 +00:00
removal_detection.rs Rename trigger.entity() to trigger.target() (#16716) 2024-12-08 21:55:09 +00:00
run_conditions.rs aligning public apis of Time,Timer and Stopwatch (#15962) 2024-10-16 21:09:32 +00:00
send_and_receive_events.rs Renamed EventWriter::send methods to write. (#17977) 2025-02-23 21:18:52 +00:00
startup_system.rs
state_scoped.rs Rename StateScoped to DespawnOnExitState and add DespawnOnEnterState (#18818) 2025-05-06 00:37:04 +00:00
system_closure.rs Prefer Display over Debug (#16112) 2024-12-27 00:40:06 +00:00
system_param.rs
system_piping.rs Fix a few typos (#17292) 2025-01-10 22:48:30 +00:00
system_stepping.rs Remove stepping from default features (#12847) 2024-04-03 19:16:02 +00:00