hoot.lua (Handleless Object Oriented Timer)
A smarter way to deal with timers. Intended for LÖVE
To cleanup this type of code
self.fire_cooldown = 0
function Player:update(dt)
self.fire_cooldown = self.fire_cooldown - dt
if fire_pressed and self.fire_cooldown <= 0 then
self.fire_cooldown = 1
self:fire()
end
end
And replace it with this type of code
function Player:update()
if fire_pressed and hoot(self):set('fire_cooldown', 1, {onlyif='notexists'}) then
-- We were able to set a new cooldown timer!
self:fire()
end
end
hoot = require 'hoot'
function love.update(dt) hoot.update(dt) end
If the timer's key exists on the object as a function, it'll be called when the timer expires
function Bullet:destroy()
-- TODO: Remove object from game
end
hoot(self):set('destroy', 5)
When hit, toggle being flipped on our side. If flipped for 2 seconds, automatically jump back up
function Creep:hit()
self.is_flipped = not self.is_flipped
if self.is_flipped then
hoot(self):set('hit', 2)
else
hoot(self):clear('hit')
end
-- Jump up
self.vy = -200
self.body:setLinearVelocity(self.vx, self.vy)
end
Get stuck on walls for a bit, so we don't need frame perfect timing to wall jump away instead of fall off
if self.inputs.dir == self.x_normal then
-- We're trying to leave; start timer to allow us to leave
hoot(self):set('stuck_on_wall', 1/6, {onlyif='notactive'})
else
-- Keep us stuck
hoot(self):set('stuck_on_wall', -1)
end
if not hoot(self):get('stuck_on_wall') then
-- TODO: Accept inputs to move off the wall
end
Double tap right to dash
if right_pressed then
if not hoot(self):set('dash_right_timer', 1/10, {onlyif='notexists'}) then
-- We were unable to set the dash_right_timer because it already existed!
-- TODO: Dash to the right
end
end
When hitting ceiling, get stuck on it for a bit instead of box2d making us instantly fall down
if hit_ceiling then
hoot(self):set(function() self.body:setGravityScale(1) end, -self.vy/1000)
self.body:setGravityScale(0)
self.vy = 0; self.body:setLinearVelocity(self.vx, self.vy)
end
hoot vs hump.timer
hump is a great library, and hump.timer is a great timer, but hoot is smarter
Wakeup after 1 second (hump.timer)
self.is_down = false
self.wakeup_timer = nil
function Enemy:knockdown()
-- We have to cancel the previous wakeup_timer
-- Otherwise, if knockdown is called while already knocked down
-- the old knockdown timer will trigger halfway through the new
-- knockdown and wake it up!
if self.wakeup_timer then Timer.cancel(self.wakeup_timer) end
self.is_down = true
self.wakeup_timer = Timer.add(1, function() self.is_down = false end)
end
function Enemy:update()
if self.is_down then print('Im down!') end
end
Wakeup after 1 second (hoot)
function Enemy:knockdown()
-- If a previous is_down timer is running, hoot knows about it and replaces it for us by default
hoot(self):set('is_down', 1)
end
function Enemy:update()
if hoot(self):get('is_down') then print('Im down!') end
end
Set a new timer that will trigger after delay
seconds.
f
can be a function
or string
of a function name on self
. If it exists it'll be called with self
as the first argument when delay
counts down to 0
.
You can also set f
to a string that doesn't exist as a function, to use the timer as a boolean variable like in the wall jump example. stuck_on_wall
isn't a function
options.onlyif
only set the timer if it currently: 'exists'
'notexists'
'active'
'notactive'
Returns timer info if currently exists, else nil
Timer info looks like this {delay=0.8, f='mycallback'}
. delay
is the seconds left until it triggers
Clear the timer. It'll never trigger
Cleanup all hoot
memory associated with self
Don't forget to put this in love.update
. Otherwise nothing will happen!
Shortcut for hoot(hoot):set
Shortcut for hoot(hoot):get
Shortcut for hoot(hoot):clear
If you can't get away with 1 global hoot object. Use new hoot instances in your gamestates local hoot = hoot.new()
Destroy hoot.new()
objects when they're no longer needed
-
hoot
does not store anything on your table, it's left completely untouched. -
Each timer has a
key
, which is set tof
if it's a string, oroptions.key
If you call
set
and thekey
already exists, the default behavior is to clear the old timer and replace it.You can change this behavior by setting
options.
which will instead leave the old timer alone and not set the new oneOr you can leave
key
empty, which will always stack more timers.options.key=false
can be used to clear the key -
Setting a
delay
of-1
will make the timer exist forever (get
will return it), but it won't be active (options.
won't trigger)