bevy_time/common_conditions.rs
1use crate::{Real, Time, Timer, TimerMode, Virtual};
2use bevy_ecs::system::Res;
3use bevy_utils::Duration;
4
5/// Run condition that is active on a regular time interval, using [`Time`] to advance
6/// the timer. The timer ticks at the rate of [`Time::relative_speed`].
7///
8/// ```no_run
9/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
10/// # use bevy_ecs::schedule::IntoSystemConfigs;
11/// # use bevy_utils::Duration;
12/// # use bevy_time::common_conditions::on_timer;
13/// fn main() {
14/// App::new()
15/// .add_plugins(DefaultPlugins)
16/// .add_systems(
17/// Update,
18/// tick.run_if(on_timer(Duration::from_secs(1))),
19/// )
20/// .run();
21/// }
22/// fn tick() {
23/// // ran once a second
24/// }
25/// ```
26///
27/// Note that this does **not** guarantee that systems will run at exactly the
28/// specified interval. If delta time is larger than the specified `duration` then
29/// the system will only run once even though the timer may have completed multiple
30/// times. This condition should only be used with large time durations (relative to
31/// delta time).
32///
33/// For more accurate timers, use the [`Timer`] class directly (see
34/// [`Timer::times_finished_this_tick`] to address the problem mentioned above), or
35/// use fixed timesteps that allow systems to run multiple times per frame.
36pub fn on_timer(duration: Duration) -> impl FnMut(Res<Time>) -> bool + Clone {
37 let mut timer = Timer::new(duration, TimerMode::Repeating);
38 move |time: Res<Time>| {
39 timer.tick(time.delta());
40 timer.just_finished()
41 }
42}
43
44/// Run condition that is active on a regular time interval,
45/// using [`Time<Real>`] to advance the timer.
46/// The timer ticks are not scaled.
47///
48/// ```no_run
49/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
50/// # use bevy_ecs::schedule::IntoSystemConfigs;
51/// # use bevy_utils::Duration;
52/// # use bevy_time::common_conditions::on_real_timer;
53/// fn main() {
54/// App::new()
55/// .add_plugins(DefaultPlugins)
56/// .add_systems(
57/// Update,
58/// tick.run_if(on_real_timer(Duration::from_secs(1))),
59/// )
60/// .run();
61/// }
62/// fn tick() {
63/// // ran once a second
64/// }
65/// ```
66///
67/// Note that this does **not** guarantee that systems will run at exactly the
68/// specified interval. If delta time is larger than the specified `duration` then
69/// the system will only run once even though the timer may have completed multiple
70/// times. This condition should only be used with large time durations (relative to
71/// delta time).
72///
73/// For more accurate timers, use the [`Timer`] class directly (see
74/// [`Timer::times_finished_this_tick`] to address the problem mentioned above), or
75/// use fixed timesteps that allow systems to run multiple times per frame.
76pub fn on_real_timer(duration: Duration) -> impl FnMut(Res<Time<Real>>) -> bool + Clone {
77 let mut timer = Timer::new(duration, TimerMode::Repeating);
78 move |time: Res<Time<Real>>| {
79 timer.tick(time.delta());
80 timer.just_finished()
81 }
82}
83
84/// Run condition that is active *once* after the specified delay,
85/// using [`Time`] to advance the timer.
86/// The timer ticks at the rate of [`Time::relative_speed`].
87///
88/// ```rust,no_run
89/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
90/// # use bevy_ecs::schedule::IntoSystemConfigs;
91/// # use bevy_utils::Duration;
92/// # use bevy_time::common_conditions::once_after_delay;
93/// fn main() {
94/// App::new()
95/// .add_plugins(DefaultPlugins)
96/// .add_systems(
97/// Update,
98/// tick.run_if(once_after_delay(Duration::from_secs(1))),
99/// )
100/// .run();
101/// }
102/// fn tick() {
103/// // ran once, after a second
104/// }
105/// ```
106pub fn once_after_delay(duration: Duration) -> impl FnMut(Res<Time>) -> bool + Clone {
107 let mut timer = Timer::new(duration, TimerMode::Once);
108 move |time: Res<Time>| {
109 timer.tick(time.delta());
110 timer.just_finished()
111 }
112}
113
114/// Run condition that is active *once* after the specified delay,
115/// using [`Time<Real>`] to advance the timer.
116/// The timer ticks are not scaled.
117///
118/// ```rust,no_run
119/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
120/// # use bevy_ecs::schedule::IntoSystemConfigs;
121/// # use bevy_utils::Duration;
122/// # use bevy_time::common_conditions::once_after_delay;
123/// fn main() {
124/// App::new()
125/// .add_plugins(DefaultPlugins)
126/// .add_systems(
127/// Update,
128/// tick.run_if(once_after_delay(Duration::from_secs(1))),
129/// )
130/// .run();
131/// }
132/// fn tick() {
133/// // ran once, after a second
134/// }
135/// ```
136pub fn once_after_real_delay(duration: Duration) -> impl FnMut(Res<Time<Real>>) -> bool + Clone {
137 let mut timer = Timer::new(duration, TimerMode::Once);
138 move |time: Res<Time<Real>>| {
139 timer.tick(time.delta());
140 timer.just_finished()
141 }
142}
143
144/// Run condition that is active *indefinitely* after the specified delay,
145/// using [`Time`] to advance the timer.
146/// The timer ticks at the rate of [`Time::relative_speed`].
147///
148/// ```rust,no_run
149/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
150/// # use bevy_ecs::schedule::IntoSystemConfigs;
151/// # use bevy_utils::Duration;
152/// # use bevy_time::common_conditions::repeating_after_delay;
153/// fn main() {
154/// App::new()
155/// .add_plugins(DefaultPlugins)
156/// .add_systems(
157/// Update,
158/// tick.run_if(repeating_after_delay(Duration::from_secs(1))),
159/// )
160/// .run();
161/// }
162/// fn tick() {
163/// // ran every frame, after a second
164/// }
165/// ```
166pub fn repeating_after_delay(duration: Duration) -> impl FnMut(Res<Time>) -> bool + Clone {
167 let mut timer = Timer::new(duration, TimerMode::Once);
168 move |time: Res<Time>| {
169 timer.tick(time.delta());
170 timer.finished()
171 }
172}
173
174/// Run condition that is active *indefinitely* after the specified delay,
175/// using [`Time<Real>`] to advance the timer.
176/// The timer ticks are not scaled.
177///
178/// ```rust,no_run
179/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
180/// # use bevy_ecs::schedule::IntoSystemConfigs;
181/// # use bevy_utils::Duration;
182/// # use bevy_time::common_conditions::repeating_after_real_delay;
183/// fn main() {
184/// App::new()
185/// .add_plugins(DefaultPlugins)
186/// .add_systems(
187/// Update,
188/// tick.run_if(repeating_after_real_delay(Duration::from_secs(1))),
189/// )
190/// .run();
191/// }
192/// fn tick() {
193/// // ran every frame, after a second
194/// }
195/// ```
196pub fn repeating_after_real_delay(
197 duration: Duration,
198) -> impl FnMut(Res<Time<Real>>) -> bool + Clone {
199 let mut timer = Timer::new(duration, TimerMode::Once);
200 move |time: Res<Time<Real>>| {
201 timer.tick(time.delta());
202 timer.finished()
203 }
204}
205
206/// Run condition that is active when the [`Time<Virtual>`] clock is paused.
207/// Use [`bevy_ecs::schedule::common_conditions::not`] to make it active when
208/// it's not paused.
209///
210/// ```rust,no_run
211/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
212/// # use bevy_ecs::schedule::{common_conditions::not, IntoSystemConfigs};
213/// # use bevy_time::common_conditions::paused;
214/// fn main() {
215/// App::new()
216/// .add_plugins(DefaultPlugins)
217/// .add_systems(
218/// Update,
219/// (
220/// is_paused.run_if(paused),
221/// not_paused.run_if(not(paused)),
222/// )
223/// )
224/// .run();
225/// }
226/// fn is_paused() {
227/// // ran when time is paused
228/// }
229///
230/// fn not_paused() {
231/// // ran when time is not paused
232/// }
233/// ```
234pub fn paused(time: Res<Time<Virtual>>) -> bool {
235 time.is_paused()
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use bevy_ecs::schedule::{IntoSystemConfigs, Schedule};
242
243 fn test_system() {}
244
245 // Ensure distributive_run_if compiles with the common conditions.
246 #[test]
247 fn distributive_run_if_compiles() {
248 Schedule::default().add_systems(
249 (test_system, test_system)
250 .distributive_run_if(on_timer(Duration::new(1, 0)))
251 .distributive_run_if(paused),
252 );
253 }
254}