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}