Bevy Timers
Timers can be created as either resources or components. A Timer
on its own won't do much. Its up to us to tick
them ahead until they are done.
Timers come in two modes:
TimerMode::Once
which will tick down to 0 once, and only resets manuallyTimerMode::Repeat
which will tick down to 0 then reset itself automatically
In Bevy, timers don't tick down from their initial value. Instead they tick up from zero until they reach their Duration
.
We can then call finished
or just_finished
to switch behavior when they are done. The difference between them is that just_finished
only returns true
if the timer finished in the last tick.
Timers as a resource
Resources are useful when we want a timer that belongs to no particular entity in our game. For example, imagine we wanted to program a timer for a match like in Rocket League.
The match timer wouldn't make sense as an entity of its own, we only ever want to spawn a single one.
So we can create ourselves a resource:
#[derive(Resource, Default)]
pub struct MatchTime(Timer);
impl MatchTime {
pub fn new() -> Self {
Self(Timer::from_seconds(60.0, TimerMode::Once))
}
}
We create a MatchTime
resource and derive Default
so it can be easily initialized when we add it to our app with init_resource
. This makes it available right at the start of our game without any additional setup.
fn countdown(time: Res<Time>, mut match_time: ResMut<MatchTime>) {
match_time.0.tick(time.delta());
}
fn end_match(match_time: Res<MatchTime>) {
if match_time.0.finished() {
// Here we would rest our game
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<MatchTime>()
.add_systems(Update, (countdown, end_match.after(countdown)))
.run();
}
The match time won't tick on its own. We need a system to update according to the time that has passed since the last tick which is being handled by countdown
above.
Bevy has a built in Time
resource we can use to get the delta
in seconds of the time between this tick and the last. We simply tick ahead our timer according to this value.
fn time_passed(time: Res<Time>) {
info!("Duration passed: {:?}", time.delta());
info!("Seconds passed: {:?}", time.delta_secs_f64());
info!("Total time since startup: {:?}", time.elapsed());
}
Timers as a component
Now lets say we wanted to have a Cooldown
for one of our abilities. This wouldn't make sense as a resource because the cooldown would be specific to one of our players.
#[derive(Component)]
struct Cooldown(Timer);
#[derive(Component)]
struct Player;
fn cast_spell(
mut commands: Commands,
mut player_query: Query<Entity, With<Player>>,
cooldowns: Query<&Cooldown, With<Player>>,
) {
if let Ok(player) = player_query.single() {
if let Ok(cooldown) = cooldowns.get(player) {
info!(
"You cannot cast yet. Your cooldown is {:0.0}% complete!",
cooldown.0.fraction() * 100.0
)
} else {
// Add an entity to the world with a timer
commands
.entity(player)
.insert(Cooldown(Timer::from_seconds(5.0, TimerMode::Once)));
// Cast the spell here
}
}
}
fn tick_cooldowns(
mut commands: Commands,
mut cooldowns: Query<(Entity, &mut Cooldown)>,
time: Res<Time>,
) {
for (entity, mut cooldown) in &mut cooldowns {
cooldown.0.tick(time.delta());
if cooldown.0.finished() {
commands.entity(entity).remove::<Cooldown>();
}
}
}
We add a cooldown when the player casts their spell and then tick down those cooldowns until they finish.
Finally we remove the cooldown component which lets them cast a spell again.
Timers for a single system
Sometimes you will want a timer that is only ever used in a single system. For these cases a Resource
would be too public and you might prefer a Local
system parameter instead:
fn local_timer(time: Res<Time>, mut timer: Local<Timer>) {
timer.tick(time.delta());
if timer.just_finished() {
info!("The timer is finished");
}
}
This timer would only be available in this one system. This can be useful for only printing debug information after a certain period of time, or delaying a systems functionality.