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.
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)]
pub struct MatchTime(Timer);
impl MatchTime {
pub fn new() -> Self {
Self(Timer::from_seconds(60.0, TimerMode::Once))
}
}
// We need to implement Default so that our timer can be initialized as
// a resource when we call `init_resource`
impl Default for MatchTime {
fn default() -> Self {
Self::new()
}
}
We create a MatchTime
resource and implement Default
so it can be easily initialized when we define our app. 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.
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.
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>>,
) {
let 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.