Helper Utilities for a Multitude of Problems is a set of lightweight helpers for the excellent LÖVE Engine.
It currently features
hump differs from other libraries in that every component is independent of the remaining ones (apart from camera.lua, which depends on the vector type, and sequence.lua which requires ringbuffer.lua). hump's footprint is very small and thus should fit nicely into your projects.
Useful to separate different states of your game (hence "gamestate") like title screens,
level loading, main game, etc. Each gamestate can have it's own update()
, draw()
,
keyreleased()
, keypressed()
and mousereleased()
which correspond
to the ones defined in love. See the callback list for more details.
Additionally, each gamestate can define a enter(pre, ...)
and leave()
function,
which are called when using Gamestate.switch(to, ...)
. Do not attempt to use these functions
yourself to initialize the first gamestate, use the switch
function instead.
The module defines the following functions:
Gamestate.new() | New gamestate |
Gamestate.switch(to, ...) | Switch gamestate |
Gamestate.update(dt) | Call update callback |
Gamestate.draw() | Call draw callback |
Gamestate.keypressed(key, unicode) | Call keypressed callback |
Gamestate.keyreleased(key) | Call keyreleased callback |
Gamestate.mousereleased(x,y,btn) | Call mousereleased callback |
Gamestate.registerEvents() | Register all callbacks |
Returns: | The new (but empty) gamestate object. |
---|
Calls leave()
on the currently active gamestate.
Calls enter(current, ...)
on the target gamestate, where current
is the gamestate before the switch and ...
are the additionals arguments given
to Gamestate.switch
.
Parameters: | [gamestate]to : | target gamestate. |
---|---|---|
... : | additional arguments to pass | |
Returns: | the result of to:enter(current, ...) |
update(dt)
on current gamestate.
draw()
on current gamestate.
keypressed(key, unicode)
on current gamestate.
keyreleased(key)
on current gamestate.
mousereleased(x,y,btn)
on the current gamestate.
love.*
routines.
It is an error to call this anywhere else than love.load()
, since it overwrites the
callbacks. Don't worry though, your callbacks will still be executed.
enter(previous, ...) |
Gets called upon entering the state. Don't call this yourself, use
Gamestate.switch instead. |
leave() |
Gets called upon leaving the state. The same warning as with enter applies. |
update(dt) |
Manually called by Gamestate.update , or automatically like love.update
when using Gamestate.registerEvents() . |
draw() |
Manually called by Gamestate.draw , or automatically like love.draw |
keypressed(key, unicode) |
Manually called by Gamestate.keypressed , or automatically like love.keypressed |
keyreleased(key) |
Manually called by Gamestate.keyreleased , or automatically like love.keyreleased |
mousereleased(x,y,btn) |
Manually called by Gamestate.mousereleased , or automatically like love.mousereleased |
This module consists of two parts:
Timer
to enable delayed function calls andInterpolator
for easy abstraction of functions that interpolate something over time.Functions:
Timer.add(delay, func) | Add a delayed function |
Timer.addPeriodic(delay, func, count) | Add a periodic function |
Timer.clear() | Clear functions |
Timer.update(dt) | Update timer |
Interpolator(length, func) | Create interpolating function |
Note the . (dot) in the function names. It is an error to call Timer.add
with a colon! If you
get weird errors, that might be the cause.
The Timer
stores it's timed functions in the table Timer.functions
.
You can manipulate it, but probably shouldn't.
delay
seconds have elapsed.
Note that there is no guarantee that the delay will be exceeded. It is, however, guaranteed that the function will not be executed before the delay has passed.
If the function is called, it will receive itself as only parameter. This may be useful to implement the
periodic behavior of Timer.addPeriodic
(see the example).
Parameters: | [number]delay : |
Time that has to pass before the function is called |
---|---|---|
[function]func : | The function to be called |
delay
seconds.
The same things as with Timer.add
apply.
Parameters: | [number]delay : |
Time that has to pass before the function is called |
---|---|---|
[function]func : | The function to be called. | |
[optional number]count : |
Number of times the function should be called. If omitted, the function loops indefinitely. |
Timer.functions
.
love.update(dt)
.
Parameters: | [number]dt : |
Time that has passed since the last Timer.update . |
---|
The interpolating function func
will receive the fraction (in respect to
the supplied length
) of time that has passed, but 1.0 at max.
Interpolator
creates a function that will have to be called in love.update
, which
will itself execute func
with the right parameters. It returns true
as long as
the interpolation is not yet finished or nil
if the interpolation stopped as well
as any parameters that the function returns.
Parameters: | [number]length : | Interpolation length. |
---|---|---|
[function]func : | Interpolating function. | |
Returns | A function inter(dt) with argument dt that has
to be called in love.update . |
time = 0 circles = {} function love.load() Timer.add(1, function() circles[#circles+1] = {x = 100, y = 100, r = 10} end) Timer.add(2, function() circles[#circles+1] = {x = 130, y = 130, r = 20} end) Timer.add(3, function() circles[#circles+1] = {x = 180, y = 180, r = 30} end) Timer.add(4, function() circles[#circles+1] = {x = 250, y = 250, r = 40} end) Timer.add(5, function() circles[#circles+1] = {x = 340, y = 340, r = 50} end) Timer.addPeriodic(1, function() time = time + 1 end) love.graphics.setBackgroundColor(255,255,255) love.graphics.setColor(0,0,0) end function love.update(dt) Timer.update(dt) end function love.draw() for _,c in ipairs(circle) do love.graphics.circle('fill', c.x, c.y, c.r) end end
Timer.addPeriodic()
^ top
Since the function will receive itself as parameter when called, it is easy to implement
a function that will reschedule itself:
Timer.add(1, function(func) print("foo") Timer.add(1, func) end)
function love.load() love.graphics.setBackgroundColor(0,0,0) love.graphics.setColor(0,0,0) end xpos = 100 fader = Interpolator(5, function(frac) love.graphics.setBackgroundColor(frac*255,frac*255,frac*255) end) mover = Interpolator(10, function(frac) xpos = 10 + 600 * frac end) function love.update(dt) fader(dt) mover(dt) end function love.draw() love.graphics.circle(xpos, 300, 80) end
A vector class implementing nearly everything you want to do with vectors, and some more.
Accessors:
[number] vector.x | x -coordinate of the vector |
[number] vector.y | y -coordinate of the vector |
Function overview (click item for more details):
vector(x,y) | constructor |
isvector(v) | type check |
vector:clone() | deep copy |
vector:unpack() | element unfolding |
Operators | Arithmetics and ordering |
vector:permul(other) | per-element multiplication |
vector:len() | vector length |
vector:len2() | squared vector length |
vector:dist(other) | distance to vector |
vector:normalized() | normalize vector |
vector:normalize_inplace() | normalize vector in-place |
vector:rotated(phi) | rotate vector |
vector:rotate_inplace(phi) | rotate vector in-place |
vector:perpendicular() | quick rotation by 90° |
Vecor:projectOn(v) | projection on other vector |
Vecor:cross(v) | cross product of two vectors |
v.x
and v.y
.
Parameters: | [number]x : | x coordinate |
---|---|---|
[number]y : | y coordinate | |
Returns: | the vector |
Parameters: | v : | variable to test |
---|---|---|
Returns: | true if v is a vector |
Returns: | New vector with the same coordinates. |
---|
Returns: | the coordinate tuple x, y | |
---|---|---|
Example: | ||
v = vector(1,2) print(v:unpack()) -- prints "1 2" |
Arithmetic (+
, -
, *
, /
) and
comparative operators (==
, <=
, <
) are defined:
+
and -
only work on vectors. -
is also
the unary minus (e.g. print(-vector(1,2)) -- prints (-1,-2)
)a * b
works on vectors and numbers:a
is a number and b
is a vector (or vice versa),
the result the scalar multiplication.a
and b
both are vectors, then the result is the
dot product.a / b
is only defined for a
being a vector and b
being a number. Same as a * 1/b
.<=
and <
sort lexically, i.e. a < b
is true if it holds: a.x < b.x
or a.y < b.y
if
a.x == b.x
(2,3) x (3,4) = (6,12)
Example: |
---|
a,b = vector(0,1), vector(1,0) print(a:dist(b)) -- prints 1.4142135623731 |
Warning: This will change the state of all references to this vector.
Parameters: | [number]phi : | Rotation angle in radians. | |
---|---|---|---|
Sketch: |
Warning: This will change the state of all references to this vector.
(0,1)
results in (1,0)
.
Sketch: |
---|
(2,2)
projected onto (1,0)
results in (2,0)
.
Parameters: | [vector]v | Vector to project onto |
---|---|---|
Returns: | Vector with direction of v and length according to projection. | |
Sketch: |
Treats both vectors as vectors of the xy-plane (z-coordinate =0) and calculates a vector perpendicular on them. Because the result is perpendicular on to the input vectors, it has the form (0,0,z). The z-coordinate is returned.
Parameters: | [vector]v | Vector to calculate the cross product with |
---|---|---|
Returns: | Cross product as described above |
class.lua provides a simple and easy to use class system. Albeit it's limitations, class.lua offers multiple inheritance and minimal reflection capabilities. Note that only methods will be inherited and runtime changes of a superclass won't affect subclasses.
The module only consists of three functions:
Class(constructor) | define class |
Class{name = name, constructor} | define named class |
Interface(name) | define interface |
Inherit(class, super, ...) | subclassing |
For an example, see below. Also be sure to read the warning regarding metamethods.
Parameters: | [optional function]constructor |
A function used to construct the class. The first parameter of this function is the object, the others are parameters given upon construction. |
---|---|---|
Returns: | the new class | |
Example | ||
Feline = Class(function(self, size, weight) self.size = size self.weight = weight end) function Feline:stats() return string.format("size: %.02f, weight %.02f", self.size, self.weight) end garfield = Feline(.7, 45) felix = Feline(.8, 12) print("Garfield: " .. garfield:stats(), "Felix: " .. felix:stats()) |
__tostring
metamethod. Parameters are the same as above.
Great for debugging. Both name
and constructor
can be omitted (but why would you want to?)
Example: |
---|
Feline = Class{name = "Feline", function(self, size, weight) self.size = size self.weight = weight end} print(Feline) -- prints 'Feline' |
Class{name = name}
, i.e. a possibly named class without constructor.
super
to class
. Multiple interfaces can be defined.
super
's constructor can be accessed via super.construct(self). See example below.
Feline = Class{name = "Feline", function(self, size, weight) self.size = size self.weight = weight end} function Feline:stats() return string.format("size: %.02f, weight %.02f", self.size, self.weight) end function Feline:speak() print("meow") end Cat = Class{name = "Cat", function(self, name, size, weight) Feline.construct(self, size, weight) self.name = name end} Inherit(Cat, Feline) function Cat:stats() return string.format("name: %s, %s", self.name, Feline.stats(self)) end Tiger = Class{name = "tiger", function(self, size, weight) Feline.construct(self, size, weight) end} Inherit(Tiger, Feline) function Tiger:speak() print("ROAR!") end felix = Cat("Felix", .8, 12) hobbes = Tiger(2.2, 68) print(felix:stats(), hobbes:stats()) felix:speak() hobbes:speak()
Be careful when using metamethods like __add
or __mul
:
When subclass inherits those methods from a superclass, but does not overwrite
them, the result of the operation may be of the type superclass. Consider the following:
A = Class(function(self, x) self.x = x end) function A:__add(other) return A(self.x + other.x) end function A:print() print("A:", self.x) end B = Class(function(self, x, y) A.construct(self, x) self.y = y end) Inherit(B, A) function B:print() print("B:", self.x, self.y) end function B:foo() print("foo") end one, two = B(1,2), B(3,4) result = one + two result:print() -- prints "A: 4" result:foo() -- error: method does not exist
Camera object to display only a partial region of the game world. The region can be zoomed and rotated. You can transform camera coordinates to world coordinated (e.g. get the location of the mouse in the game world). It is possible to have more than one camera per game.
Accessors:
[vector] camera.pos | The camera's position. This is also the position it looks at. |
[number] camera.zoom | The camera's zoom level. |
[number] camera.rot | The camera's rotation. |
The module defined the following funtions:
Camera(pos, zoom, rotation) | create new camera |
camera:rotate(phi) | rotate camera |
camera:translate(t) | move camera |
camera:draw(func) | apply camera transformations on function |
camera:predraw() | apply camera transformations |
camera:postdraw() | revert camera transformations |
camera:toCameraCoords(p) | get camera coordinates of a point |
camera:toWorldCoords(p) | get world coordinates of a point |
camera:mousepos() | get world coordinates of mouse |
Parameters: | [optional vector]pos : |
Initial position of the camera. Defaults to (0,0) . |
---|---|---|
[optional number]zoom : |
Initial zoom. Defaults to 1 . | |
[optional number]rotation : |
Initial rotation in radians. Defaults to 0 . | |
Returns: | a new camera object |
phi
radians. Same as camera.rot = camera.rot + phi.
t
. Same as camera.pos = camera.pos + t
.
Parameters: | [vector]t : |
Translation vector |
---|
func
.
Shortcut to camera:apply()
and camera:deapply()
(see below).
Example |
---|
cam:draw(function() love.graphics.rectangle('fill', -100,-100, 200,200) end) |
camera:postdraw()
.
Example: | (equivalent to the cam:draw() example above) |
---|---|
camera:predraw() love.graphics.rectangle('fill', -100,-100, 200,200) camera:postdraw() |
p
from world coordinates to camera coordinates, i.e.
you have the position of an object in the world and want to know where it will be
drawn on the screen.
Parameters: | [vector]p : |
Vector to transform to camera coordinates |
---|---|---|
Returns: | [vector] transformed vector. |
p
from camera coordinates to world coordinates, i.e.
you have the coordinates of a point on the screen (e.g. the mouse), and wan't to
know where it's location is in the world.
Parameters: | [vector]p : |
Vector to transform to world coordinates |
---|---|---|
Returns: | [vector] transformed vector. |
Returns: | [vector] Mouse position in world coordinates. |
---|
A circular container that can hold arbitrary items. It can be used to create Tomb Raider style inventories, a looping music playlist, recurring dialog sequences and much more.
Accessors:
[table]buffer.items | Items table. It is not recommended to read or write this directly. |
[number]buffer.current | Index of current selection. Be careful with this. |
ringbuffer.lua defines the following functions:
Ringbuffer(...) | Create new ringbuffer |
ringbuffer:insert(item, ...) | Insert items |
ringbuffer:append(item, ...) | Append items to the "end" |
ringbuffer:remove() | Remove item |
ringbuffer:removeAt(k) | Remove item at position |
ringbuffer:get() | Get item |
ringbuffer:size() | Number of items in buffer |
ringbuffer:next() | Get next item |
ringbuffer:prev() | Get previous item |
Parameters | [optional]... : | Initial items of the buffer. |
---|
Parameters: | item : | Item to insert |
---|---|---|
[optional]... : | Additional items to insert | |
Example | ||
rb = Ringbuffer(1,2,5,6) -- rb = {1 ,2,5,6}, rb:get() == 1 rb:next() -- rb = {1, 2 ,5,6}, rb:get() == 2 rb:insert(3,4) -- rb = {1, 2 ,3,4,5,6}, rb:get() == 2 rb:next() -- rb = {1,2, 3 ,4,5,6}, rb:get() == 3 |
This is less intuitive than ringbuffer:insert(item, ...)
since it appears the items are added
at a random location. Use is only recommended before using ringbuffer:next()
or ringbuffer:prev()
.
Parameters: | item : | Item to append |
---|---|---|
[optional]... : | Additional items to append | |
Example | ||
rb = Ringbuffer(1,2,5,6) -- rb = {1 ,2,5,6}, rb:get() == 1 rb:next() -- rb = {1, 2 ,5,6}, rb:get() == 2 rb:append(3,4) -- rb = {1, 2 ,5,6,3,4}, rb:get() == 2 rb:next() -- rb = {1,2, 5 ,6,3,4}, rb:get() == 5 |
Example |
---|
rb = Ringbuffer(1,2,3) -- rb = {1 ,2,3}, rb:get() == 1 rb:next() -- rb = {1, 2 ,3}, rb:get() == 2 rb:remove() -- rb = {1, 3 }, rb:get() == 3 |
k
. k
may be positive or negative and
even bigger than ringbuffer:size()
.
Parameters: | [number]k | Relative position of item to remove. |
---|---|---|
Example: | ||
rb = Ringbuffer(1,2,3,4,5) -- rb = { 1 ,2,3,4,5}, rb:get() == 1 rb:removeAt(2) -- rb = { 1 ,2,4,5}, rb:get() == 1 rb:next() -- rb = {1, 2 ,4,5}, rb:get() == 2 rb:removeAt(-1) -- rb = { 2 ,4,5}, rb:get() == 2 |
Returns: | The currently selected item. |
---|
Returns: | Number of items in the buffer. |
---|
Return: | Selected item after operation. |
---|
Return: | Selected item after operation. |
---|
sequence.lua offers basic support for automated switching of Scenes. You can use it to add intros and cutscenes to your games.
sequence.lua defines two types: Scene
and Sequence
. A Sequence
holds several Scene
s, but one Scene
can belong to different Sequence
s
(but due to side effects it shouldn't).
The module consists of the following:
Sequence(...) | Create new sequence |
sequence:add(...) | Add scenes |
sequence:select(k) | Select scene manually |
sequence:rewind() | Rewind sequence |
sequence:prevScene | Manually select previous scene |
sequence:nextScene | Manually select next scene |
sequence:draw() | Draw current scene |
sequence:update(dt) | Update and switch current scene |
Scene(length) | New scene object |
scene:isFinished() | Indicate end of scene |
A Scene
defines three callback functions. Do not try to
access them yourself. Let Sequence
do the job:
scene:draw() : | Draw contents on screen (mandatory) |
scene:update(dt) : | Update scene (optional) |
scene:enter() : | Called when entering scene (optional) |
scene:leave() : | Called when leaving scene (optional) |
Additional to that, each Scene
has a time
variable corresponding to the time
spent in the scene.
The example shows some of the concepts involved.
Parameters: | [optional Scenes]... |
Initial scenes to add to sequence |
---|---|---|
Returns: | Sequence object with initial scenes |
Parameters: | [Scenes]... |
Scenes to add to sequence |
---|
k
. Usually this is not necessary but provided anyway.
Parameters: | [number] k : | Number of the scene to select |
---|
Warning: May select the last scene when the current scene is the first scene in sequence.
Warning: May select the first scene when the current scene is the last scene in sequence.
Calls scene:draw()
on currently active scene
scene.time
.
Switches scene if current scene is finished (see scene:isFinished()
).
length
Parameters: | [optional]length |
Length of scene. If < 0 scene will never finish. Defaults to -1 |
---|---|---|
Returns: | New scene object |
sequence:update(dt)
.
This can be overwritten, though it is not advised to do so.
Returns: | true if the scene is finished. |
---|
local sc = {} -- moving and fading looks cool sc.idea = Scene(5) function sc.idea:draw() local frac = (self.time / self.length) ^ 3 love.graphics.setColor(255,255,255,160 - frac * 160) love.graphics.print("Idea", 200 + frac * 30, 200) love.graphics.setColor(255,255,255,255 - frac * 255) love.graphics.print("Max Power", 300 - frac * 40, 250) end sc.code = Scene(5) function sc.code:draw() local frac = (self.time / self.length) ^ 3 love.graphics.setColor(255,255,255,160 - frac * 160) love.graphics.print("Code", 200 + frac * 30, 200) love.graphics.setColor(255,255,255,255 - frac * 255) love.graphics.print("Max Power", 300 - frac * 40, 250) end sc.gfx = Scene(5) function sc.gfx:draw() local frac = (self.time / self.length) ^ 3 love.graphics.setColor(255,255,255,160 - frac * 160) love.graphics.print("GFX", 200 + frac * 30, 200) love.graphics.setColor(255,255,255,255 - frac * 255) love.graphics.print("also Max Power", 300 - frac * 40, 250) end sc.endscreen = Scene(-1) -- last scene. no looping function sc.endscreen:enter() self.frac = 0 end function sc.endscreen:update(dt) self.frac = math.min(1, self.time / 3) -- 3 sec fade in end function sc.endscreen:draw() love.graphics.setColor(255,255,255, self.frac * 255) love.graphics.print("The most awesome game in history", 280, 280) end -- create credit sequence credits = Sequence(sc.idea, sc.code, sc.gfx) credits:add(sc.endscreen) -- show and update sequence function love.draw() credits:draw() end function love.update(dt) credits:update(dt) end
Yay, free software:
Copyright (c) 2010 Matthias Richter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
You can view and download the individual modules on github: vrld/hump. You may also download the whole packed sourcecode either in zip or tar formats.
You can clone the project with Git by running:
git clone git://github.com/vrld/humpOnce done, tou can check for updates by running
git pull