bevy_auto_timer/
lib.rs

1// Copyright 2025 Trung Do <dothanhtrung@pm.me>
2
3//! ### bevy_auto_timer
4//!
5//! A convenient timer which ticks automatically.
6
7use bevy::prelude::{
8    in_state, App, Commands, Component, Entity, EntityEvent, IntoScheduleConfigs, Plugin, Query, Res, States,
9    Time, Timer, TimerMode, Update,
10};
11
12macro_rules! plugin_systems {
13    ( ) => {
14        (auto_tick)
15    };
16}
17
18/// The main plugin
19#[derive(Default)]
20pub struct AutoTimerPlugin<T>
21where
22    T: States,
23{
24    /// List of game state this plugin will run in
25    pub states: Vec<T>,
26}
27
28impl<T> Plugin for AutoTimerPlugin<T>
29where
30    T: States,
31{
32    fn build(&self, app: &mut App) {
33        if self.states.is_empty() {
34            app.add_systems(Update, plugin_systems!());
35        } else {
36            for state in self.states.iter() {
37                app.add_systems(Update, plugin_systems!().run_if(in_state(state.clone())));
38            }
39        }
40    }
41}
42
43impl<T> AutoTimerPlugin<T>
44where
45    T: States,
46{
47    pub fn new(states: Vec<T>) -> Self {
48        Self { states }
49    }
50
51    pub fn any() -> Self {
52        Self::new(Vec::new())
53    }
54}
55
56/// Fake state to trick plugin work in any state.
57/// You may not need this.
58#[derive(States, Clone, Debug, Hash, Eq, PartialEq)]
59pub enum DummyState {}
60
61/// Use this if you don't care to state and want this plugin's systems run all the time.
62/// ```rust
63/// use bevy::prelude::App;
64/// use bevy_auto_timer::AutoTimerPluginAnyState;
65/// fn main() {
66///     App::new().add_plugins(AutoTimerPluginAnyState::any());
67/// }
68/// ```
69#[derive(Default)]
70pub struct AutoTimerPluginAnyState;
71
72impl AutoTimerPluginAnyState {
73    pub fn any() -> AutoTimerPlugin<DummyState> {
74        AutoTimerPlugin::new(Vec::new())
75    }
76}
77
78#[derive(Default)]
79pub enum ActionOnFinish {
80    #[default]
81    /// Do nothing
82    Nothing,
83    /// Despawn entity
84    Despawn,
85    /// Remove `AutoTimer` out of entity
86    Remove,
87}
88
89/// Timer component which ticks automatically
90#[derive(Component, Default)]
91pub struct AutoTimer {
92    pub timer: Timer,
93    pub action_on_finish: ActionOnFinish,
94}
95
96/// Triggered when the timer is finished
97#[derive(EntityEvent)]
98pub struct AutoTimerFinished {
99    pub entity: Entity,
100}
101
102impl AutoTimer {
103    pub fn from_seconds(duration: f32, mode: TimerMode) -> Self {
104        Self {
105            timer: Timer::from_seconds(duration, mode),
106            ..Self::default()
107        }
108    }
109
110    /// Progress in percentage
111    pub fn progress(&self) -> f32 {
112        self.timer.elapsed().as_secs_f32() / self.timer.duration().as_secs_f32()
113    }
114}
115
116fn auto_tick(mut commands: Commands, time: Res<Time>, mut query: Query<(&mut AutoTimer, Entity)>) {
117    for (mut timer, e) in query.iter_mut() {
118        timer.timer.tick(time.delta());
119        if timer.timer.just_finished() {
120            commands.trigger(AutoTimerFinished { entity: e });
121            match timer.action_on_finish {
122                ActionOnFinish::Nothing => {}
123                ActionOnFinish::Despawn => {
124                    commands.entity(e).despawn();
125                }
126                ActionOnFinish::Remove => {
127                    commands.entity(e).remove::<AutoTimer>();
128                }
129            }
130        }
131    }
132}