Skip to main content

bevy_state/
condition.rs

1use crate::state::{State, States};
2use bevy_ecs::{change_detection::DetectChanges, system::Res};
3
4/// A [`SystemCondition`](bevy_ecs::prelude::SystemCondition)-satisfying system that returns `true`
5/// if the state machine exists.
6///
7/// # Example
8///
9/// ```
10/// # use bevy_ecs::prelude::*;
11/// # use bevy_state::prelude::*;
12/// # use bevy_app::{App, Update};
13/// # use bevy_state::app::StatesPlugin;
14/// # #[derive(Resource, Default)]
15/// # struct Counter(u8);
16/// # let mut app = App::new();
17/// # app
18/// #   .init_resource::<Counter>()
19/// #   .add_plugins(StatesPlugin);
20/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
21/// enum GameState {
22///     #[default]
23///     Playing,
24///     Paused,
25/// }
26///
27/// app.add_systems(Update,
28///     // `state_exists` will only return true if the
29///     // given state exists
30///     my_system.run_if(state_exists::<GameState>),
31/// );
32///
33/// fn my_system(mut counter: ResMut<Counter>) {
34///     counter.0 += 1;
35/// }
36///
37/// // `GameState` does not yet exist so `my_system` won't run
38/// app.update();
39/// assert_eq!(app.world().resource::<Counter>().0, 0);
40///
41/// app.init_state::<GameState>();
42///
43/// // `GameState` now exists so `my_system` will run
44/// app.update();
45/// assert_eq!(app.world().resource::<Counter>().0, 1);
46/// ```
47pub fn state_exists<S: States>(current_state: Option<Res<State<S>>>) -> bool {
48    current_state.is_some()
49}
50
51/// Generates a [`SystemCondition`](bevy_ecs::prelude::SystemCondition)-satisfying closure that returns `true`
52/// if the state machine is currently in `state`.
53///
54/// Will return `false` if the state does not exist or if not in `state`.
55///
56/// # Example
57///
58/// ```
59/// # use bevy_ecs::prelude::*;
60/// # use bevy_state::prelude::*;
61/// # use bevy_app::{App, Update};
62/// # use bevy_state::app::StatesPlugin;
63/// # #[derive(Resource, Default)]
64/// # struct Counter(u8);
65/// # let mut app = App::new();
66/// # app
67/// #   .init_resource::<Counter>()
68/// #   .add_plugins(StatesPlugin);
69/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
70/// enum GameState {
71///     #[default]
72///     Playing,
73///     Paused,
74/// }
75///
76/// app
77///     .init_state::<GameState>()
78///     .add_systems(Update, (
79///         // `in_state` will only return true if the
80///         // given state equals the given value
81///         play_system.run_if(in_state(GameState::Playing)),
82///         pause_system.run_if(in_state(GameState::Paused)),
83///     ));
84///
85/// fn play_system(mut counter: ResMut<Counter>) {
86///     counter.0 += 1;
87/// }
88///
89/// fn pause_system(mut counter: ResMut<Counter>) {
90///     counter.0 -= 1;
91/// }
92///
93/// // We default to `GameState::Playing` so `play_system` runs
94/// app.update();
95/// assert_eq!(app.world().resource::<Counter>().0, 1);
96///
97/// app.insert_state(GameState::Paused);
98///
99/// // Now that we are in `GameState::Pause`, `pause_system` will run
100/// app.update();
101/// assert_eq!(app.world().resource::<Counter>().0, 0);
102/// ```
103pub fn in_state<S: States>(state: S) -> impl FnMut(Option<Res<State<S>>>) -> bool + Clone {
104    move |current_state: Option<Res<State<S>>>| match current_state {
105        Some(current_state) => *current_state == state,
106        None => false,
107    }
108}
109
110/// A [`SystemCondition`](bevy_ecs::prelude::SystemCondition)-satisfying system that returns `true`
111/// if the state machine changed state.
112///
113/// To do things on transitions to/from specific states, use their respective OnEnter/OnExit
114/// schedules. Use this run condition if you want to detect any change, regardless of the value.
115///
116/// Returns false if the state does not exist or the state has not changed.
117///
118/// # Example
119///
120/// ```
121/// # use bevy_ecs::prelude::*;
122/// # use bevy_state::prelude::*;
123/// # use bevy_state::app::StatesPlugin;
124/// # use bevy_app::{App, Update};
125/// # #[derive(Resource, Default)]
126/// # struct Counter(u8);
127/// # let mut app = App::new();
128/// # app
129/// #   .init_resource::<Counter>()
130/// #   .add_plugins(StatesPlugin);
131/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
132/// enum GameState {
133///     #[default]
134///     Playing,
135///     Paused,
136/// }
137///
138/// app
139///     .init_state::<GameState>()
140///     .add_systems(Update,
141///         // `state_changed` will only return true if the
142///         // given states value has just been updated or
143///         // the state has just been added
144///         my_system.run_if(state_changed::<GameState>),
145///     );
146///
147/// fn my_system(mut counter: ResMut<Counter>) {
148///     counter.0 += 1;
149/// }
150///
151/// // `GameState` has just been added so `my_system` will run
152/// app.update();
153/// assert_eq!(app.world().resource::<Counter>().0, 1);
154///
155/// // `GameState` has not been updated so `my_system` will not run
156/// app.update();
157/// assert_eq!(app.world().resource::<Counter>().0, 1);
158///
159/// app.insert_state(GameState::Paused);
160///
161/// // Now that `GameState` has been updated `my_system` will run
162/// app.update();
163/// assert_eq!(app.world().resource::<Counter>().0, 2);
164/// ```
165pub fn state_changed<S: States>(current_state: Option<Res<State<S>>>) -> bool {
166    let Some(current_state) = current_state else {
167        return false;
168    };
169    current_state.is_changed()
170}
171
172#[cfg(test)]
173mod tests {
174    use bevy_ecs::schedule::{IntoScheduleConfigs, Schedule, SystemCondition};
175
176    use crate::prelude::*;
177    use bevy_state_macros::States;
178
179    #[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
180    enum TestState {
181        #[default]
182        A,
183        B,
184    }
185
186    fn test_system() {}
187
188    // Ensure distributive_run_if compiles with the common conditions.
189    #[test]
190    fn distributive_run_if_compiles() {
191        Schedule::default().add_systems(
192            (test_system, test_system)
193                .distributive_run_if(state_exists::<TestState>)
194                .distributive_run_if(in_state(TestState::A).or(in_state(TestState::B)))
195                .distributive_run_if(state_changed::<TestState>),
196        );
197    }
198}