Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_state/src/state_scoped.rs
9395 views
1
#[cfg(feature = "bevy_reflect")]
2
use bevy_ecs::reflect::ReflectComponent;
3
use bevy_ecs::{
4
component::Component,
5
entity::Entity,
6
entity_disabling::Disabled,
7
message::MessageReader,
8
query::Allow,
9
system::{Commands, Query},
10
};
11
#[cfg(feature = "bevy_reflect")]
12
use bevy_reflect::prelude::*;
13
14
use crate::state::{StateTransitionEvent, States};
15
16
/// Entities marked with this component will be removed
17
/// when the world's state of the matching type no longer matches the supplied value.
18
///
19
/// If you need to disable this behavior, add the attribute `#[states(scoped_entities = false)]` when deriving [`States`].
20
///
21
/// ```
22
/// use bevy_state::prelude::*;
23
/// use bevy_ecs::prelude::*;
24
/// use bevy_ecs::system::ScheduleSystem;
25
///
26
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
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
/// DespawnOnExit(GameState::InGame),
40
/// Player
41
/// ));
42
/// }
43
///
44
/// # struct AppMock;
45
/// # impl AppMock {
46
/// # fn init_state<S>(&mut self) {}
47
/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
48
/// # }
49
/// # struct Update;
50
/// # let mut app = AppMock;
51
///
52
/// app.init_state::<GameState>();
53
/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
54
/// ```
55
#[derive(Component, Clone)]
56
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
57
pub struct DespawnOnExit<S: States>(pub S);
58
59
impl<S> Default for DespawnOnExit<S>
60
where
61
S: States + Default,
62
{
63
fn default() -> Self {
64
Self(S::default())
65
}
66
}
67
68
/// Despawns entities marked with [`DespawnOnExit<S>`] when their state no
69
/// longer matches the world state.
70
///
71
/// If the entity has already been despawned no warning will be emitted.
72
pub fn despawn_entities_on_exit_state<S: States>(
73
mut commands: Commands,
74
mut transitions: MessageReader<StateTransitionEvent<S>>,
75
query: Query<(Entity, &DespawnOnExit<S>), Allow<Disabled>>,
76
) {
77
// We use the latest event, because state machine internals generate at most 1
78
// transition event (per type) each frame. No event means no change happened
79
// and we skip iterating all entities.
80
let Some(transition) = transitions.read().last() else {
81
return;
82
};
83
if transition.entered == transition.exited {
84
return;
85
}
86
let Some(exited) = &transition.exited else {
87
return;
88
};
89
for (entity, binding) in &query {
90
if binding.0 == *exited {
91
commands.entity(entity).try_despawn();
92
}
93
}
94
}
95
96
/// Entities marked with this component will be despawned
97
/// upon entering the given state.
98
///
99
/// ```
100
/// use bevy_state::prelude::*;
101
/// use bevy_ecs::{prelude::*, system::ScheduleSystem};
102
///
103
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
104
/// enum GameState {
105
/// #[default]
106
/// MainMenu,
107
/// SettingsMenu,
108
/// InGame,
109
/// }
110
///
111
/// # #[derive(Component)]
112
/// # struct Player;
113
///
114
/// fn spawn_player(mut commands: Commands) {
115
/// commands.spawn((
116
/// DespawnOnEnter(GameState::MainMenu),
117
/// Player
118
/// ));
119
/// }
120
///
121
/// # struct AppMock;
122
/// # impl AppMock {
123
/// # fn init_state<S>(&mut self) {}
124
/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
125
/// # }
126
/// # struct Update;
127
/// # let mut app = AppMock;
128
///
129
/// app.init_state::<GameState>();
130
/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
131
/// ```
132
#[derive(Component, Clone)]
133
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
134
pub struct DespawnOnEnter<S: States>(pub S);
135
136
/// Despawns entities marked with [`DespawnOnEnter<S>`] when their state
137
/// matches the world state.
138
///
139
/// If the entity has already been despawned no warning will be emitted.
140
pub fn despawn_entities_on_enter_state<S: States>(
141
mut commands: Commands,
142
mut transitions: MessageReader<StateTransitionEvent<S>>,
143
query: Query<(Entity, &DespawnOnEnter<S>), Allow<Disabled>>,
144
) {
145
// We use the latest event, because state machine internals generate at most 1
146
// transition event (per type) each frame. No event means no change happened
147
// and we skip iterating all entities.
148
let Some(transition) = transitions.read().last() else {
149
return;
150
};
151
if transition.entered == transition.exited {
152
return;
153
}
154
let Some(entered) = &transition.entered else {
155
return;
156
};
157
for (entity, binding) in &query {
158
if binding.0 == *entered {
159
commands.entity(entity).try_despawn();
160
}
161
}
162
}
163
164
#[cfg(test)]
165
mod tests {
166
use super::*;
167
168
use bevy_app::App;
169
170
use crate::{
171
app::{AppExtStates, StatesPlugin},
172
prelude::CommandsStatesExt,
173
};
174
175
#[test]
176
fn despawn_on_exit_from_computed_state() {
177
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States)]
178
enum State {
179
On,
180
Off,
181
}
182
183
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
184
struct ComputedState;
185
impl bevy_state::state::ComputedStates for ComputedState {
186
type SourceStates = State;
187
188
fn compute(sources: Self::SourceStates) -> Option<Self> {
189
match sources {
190
State::On => Some(ComputedState),
191
State::Off => None,
192
}
193
}
194
}
195
196
let mut app = App::new();
197
app.add_plugins(StatesPlugin);
198
199
app.insert_state(State::On);
200
app.add_computed_state::<ComputedState>();
201
app.update();
202
203
assert_eq!(
204
app.world()
205
.resource::<bevy_state::state::State<State>>()
206
.get(),
207
&State::On
208
);
209
assert_eq!(
210
app.world()
211
.resource::<bevy_state::state::State<ComputedState>>()
212
.get(),
213
&ComputedState
214
);
215
216
let entity = app.world_mut().spawn(DespawnOnExit(ComputedState)).id();
217
assert!(app.world().get_entity(entity).is_ok());
218
219
app.world_mut().commands().set_state(State::Off);
220
app.update();
221
222
assert_eq!(
223
app.world()
224
.resource::<bevy_state::state::State<State>>()
225
.get(),
226
&State::Off
227
);
228
assert!(app
229
.world()
230
.get_resource::<bevy_state::state::State<ComputedState>>()
231
.is_none());
232
assert!(app.world().get_entity(entity).is_err());
233
}
234
}
235
236