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}