I find myself writing cooldowns and countdowns all the time. Bullets can only fire so fast, bombs explode after five seconds, enemies spawn every 10-15 seconds. Every time I do it, I write the same boilerplate in the initialization:
goomba.cooldown_max = 5 // 5 seconds to cool down
goomba.cooldown_timer = goomba.cooldown_max
and the same boilerplate in the update function:
if goomba.cooldown_timer > 0:
goomba.cooldown_timer -= time_step
if goomba.cooldown_timer < 0:
// insert end behavior here
and somewhere, I activate this behavior (in response to a keypress, or proximit, or contact):
goomba.cooldown_timer = goomba.cooldown_max
I do this kind of thing so often that I feel like it should be one of the basic functionalities offered when I'm trying to describe game entity behavior.
// initialization
goomba.shot_timer = new CooldownTimer(length=5s, on_end=function() {...})
// update functionality should be taken care of automatically, but if not:
goomba.shot_timer.step(time_step)
// activating the timer:
goomba.shot_timer.start()
Now, wasn't that easy?
Of course, that's a pretty simple case! There are obvious extensions, though:
goomba.blink_timer = new EndlessTimer()
goomba.blink_timer.interval = 5s
goomba.blink_timer.at_progress(0%, goomba.change_color) // change color at the start of each interval
goomba.blink_timer.at_progress(50%, goomba.take_step) // take a step once every interval halfway through
And now making timed behavior for our goomba is easy! We can even speed up the goomba easily: goomba.blink_timer.interval = 3s
Ideally, you don't need to even specify when to step
your timers - that should be taken care of automatically. Of course, that means we'd want to register these timers instead of simply declaring them as a member - but maybe that's a matter of a grander architectural problem.
Although! If we do let the developer take care of stepping the timers, then we can also use it for things that aren't time-dependent:
player.limit_bar = new Meter(length=100) // general verison of the CooldownTimer
player.on('hit by enemy', player.limit_bar.step)
player.limit_bar.at_progress(100%, player.limit_break)
And now we've got a way for limit breaks to engage. Assuming there's a pre-existing event system and everything is architected carefully, it's now incredibly easy to fill meters in response to events.