@ -7,17 +7,17 @@ Contents:
|
||||
------------
|
||||
|
||||
* *gamestate.lua*: Easy gamestate management.
|
||||
* *timer.lua*: Delayed and time-limited function calls and tweening functionality.
|
||||
* *timer.lua*: Delayed and time-limited function calls and tweening.
|
||||
* *vector.lua*: 2D vector math.
|
||||
* *vector-light.lua*: Lightweight 2D vector math (for optimisation purposes - leads to potentially ugly code).
|
||||
* *class.lua*: Lightweight object orientation (class or prototype based).
|
||||
* *signal.lua*: Simple Signal/Slot (aka. Observer) implementation.
|
||||
* *camera.lua*: Move-, zoom- and rotatable camera.
|
||||
* *camera.lua*: Move-, zoom- and rotatable camera with camera locking and movement smoothing.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
You can find the documentation here: [http://vrld.github.com/hump/](http://vrld.github.com/hump/)
|
||||
You can find the documentation here: [hump.readthedocs.org](http://hump.readthedocs.org)
|
||||
|
||||
|
||||
License
|
||||
|
141
camera.lua
@ -1,5 +1,5 @@
|
||||
--[[
|
||||
Copyright (c) 2010-2013 Matthias Richter
|
||||
Copyright (c) 2010-2015 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
|
||||
@ -30,24 +30,55 @@ local cos, sin = math.cos, math.sin
|
||||
local camera = {}
|
||||
camera.__index = camera
|
||||
|
||||
local function new(x,y, zoom, rot)
|
||||
-- Movement interpolators (for camera locking/windowing)
|
||||
camera.smooth = {}
|
||||
|
||||
function camera.smooth.none()
|
||||
return function(dx,dy) return dx,dy end
|
||||
end
|
||||
|
||||
function camera.smooth.linear(speed)
|
||||
assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed))
|
||||
return function(dx,dy, s)
|
||||
-- normalize direction
|
||||
local d = math.sqrt(dx*dx+dy*dy)
|
||||
local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal
|
||||
if d > 0 then
|
||||
dx,dy = dx/d, dy/d
|
||||
end
|
||||
|
||||
return dx*dts, dy*dts
|
||||
end
|
||||
end
|
||||
|
||||
function camera.smooth.damped(stiffness)
|
||||
assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness))
|
||||
return function(dx,dy, s)
|
||||
local dts = love.timer.getDelta() * (s or stiffness)
|
||||
return dx*dts, dy*dts
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function new(x,y, zoom, rot, smoother)
|
||||
x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2
|
||||
zoom = zoom or 1
|
||||
rot = rot or 0
|
||||
return setmetatable({x = x, y = y, scale = zoom, rot = rot}, camera)
|
||||
smoother = smoother or camera.smooth.none() -- for locking, see below
|
||||
return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera)
|
||||
end
|
||||
|
||||
function camera:lookAt(x,y)
|
||||
self.x, self.y = x,y
|
||||
self.x, self.y = x, y
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:move(x,y)
|
||||
self.x, self.y = self.x + x, self.y + y
|
||||
function camera:move(dx,dy)
|
||||
self.x, self.y = self.x + dx, self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:pos()
|
||||
function camera:position()
|
||||
return self.x, self.y
|
||||
end
|
||||
|
||||
@ -71,47 +102,115 @@ function camera:zoomTo(zoom)
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:attach()
|
||||
local cx,cy = love.graphics.getWidth()/(2*self.scale), love.graphics.getHeight()/(2*self.scale)
|
||||
function camera:attach(x,y,w,h, noclip)
|
||||
x,y = x or 0, y or 0
|
||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||
|
||||
self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor()
|
||||
if not noclip then
|
||||
love.graphics.setScissor(x,y,w,h)
|
||||
end
|
||||
|
||||
local cx,cy = x+w/2, y+h/2
|
||||
love.graphics.push()
|
||||
love.graphics.scale(self.scale)
|
||||
love.graphics.translate(cx, cy)
|
||||
love.graphics.scale(self.scale)
|
||||
love.graphics.rotate(self.rot)
|
||||
love.graphics.translate(-self.x, -self.y)
|
||||
end
|
||||
|
||||
function camera:detach()
|
||||
love.graphics.pop()
|
||||
love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh)
|
||||
end
|
||||
|
||||
function camera:draw(func)
|
||||
self:attach()
|
||||
function camera:draw(...)
|
||||
local x,y,w,h,noclip,func
|
||||
local nargs = select("#", ...)
|
||||
if nargs == 1 then
|
||||
func = ...
|
||||
elseif nargs == 5 then
|
||||
x,y,w,h,func = ...
|
||||
elseif nargs == 6 then
|
||||
x,y,w,h,noclip,func = ...
|
||||
else
|
||||
error("Invalid arguments to camera:draw()")
|
||||
end
|
||||
|
||||
self:attach(x,y,w,h,noclip)
|
||||
func()
|
||||
self:detach()
|
||||
end
|
||||
|
||||
function camera:cameraCoords(x,y)
|
||||
-- world coordinates to camera coordinates
|
||||
function camera:cameraCoords(x,y, ox,oy,w,h)
|
||||
ox, oy = ox or 0, oy or 0
|
||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||
|
||||
-- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center
|
||||
local w,h = love.graphics.getWidth(), love.graphics.getHeight()
|
||||
local c,s = cos(self.rot), sin(self.rot)
|
||||
x,y = x - self.x, y - self.y
|
||||
x,y = c*x - s*y, s*x + c*y
|
||||
return x*self.scale + w/2, y*self.scale + h/2
|
||||
return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy
|
||||
end
|
||||
|
||||
function camera:worldCoords(x,y)
|
||||
-- camera coordinates to world coordinates
|
||||
function camera:worldCoords(x,y, ox,oy,w,h)
|
||||
ox, oy = ox or 0, oy or 0
|
||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||
|
||||
-- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y)
|
||||
local w,h = love.graphics.getWidth(), love.graphics.getHeight()
|
||||
local c,s = cos(-self.rot), sin(-self.rot)
|
||||
x,y = (x - w/2) / self.scale, (y - h/2) / self.scale
|
||||
x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale
|
||||
x,y = c*x - s*y, s*x + c*y
|
||||
return x+self.x, y+self.y
|
||||
end
|
||||
|
||||
function camera:mousepos()
|
||||
return self:worldCoords(love.mouse.getPosition())
|
||||
function camera:mousePosition(ox,oy,w,h)
|
||||
local mx,my = love.mouse.getPosition()
|
||||
return self:worldCoords(mx,my, ox,oy,w,h)
|
||||
end
|
||||
|
||||
-- camera scrolling utilities
|
||||
function camera:lockX(x, smoother, ...)
|
||||
local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...)
|
||||
self.x = self.x + dx
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:lockY(y, smoother, ...)
|
||||
local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...)
|
||||
self.y = self.y + dy
|
||||
return self
|
||||
end
|
||||
|
||||
function camera:lockPosition(x,y, smoother, ...)
|
||||
return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...))
|
||||
end
|
||||
|
||||
function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...)
|
||||
-- figure out displacement in camera coordinates
|
||||
x,y = self:cameraCoords(x,y)
|
||||
local dx, dy = 0,0
|
||||
if x < x_min then
|
||||
dx = x - x_min
|
||||
elseif x > x_max then
|
||||
dx = x - x_max
|
||||
end
|
||||
if y < y_min then
|
||||
dy = y - y_min
|
||||
elseif y > y_max then
|
||||
dy = y - y_max
|
||||
end
|
||||
|
||||
-- transform displacement to movement in world coordinates
|
||||
local c,s = cos(-self.rot), sin(-self.rot)
|
||||
dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale
|
||||
|
||||
-- move
|
||||
self:move((smoother or self.smoother)(dx,dy,...))
|
||||
end
|
||||
|
||||
-- the module
|
||||
return setmetatable({new = new},
|
||||
return setmetatable({new = new, smooth = camera.smooth},
|
||||
{__call = function(_, ...) return new(...) end})
|
||||
|
@ -36,7 +36,7 @@ local function include_helper(to, from, seen)
|
||||
seen[from] = to
|
||||
for k,v in pairs(from) do
|
||||
k = include_helper({}, k, seen) -- keys might also be tables
|
||||
if not to[k] then
|
||||
if to[k] == nil then
|
||||
to[k] = include_helper({}, v, seen)
|
||||
end
|
||||
end
|
||||
@ -51,7 +51,7 @@ end
|
||||
|
||||
-- returns a deep copy of `other'
|
||||
local function clone(other)
|
||||
return include({}, other)
|
||||
return setmetatable(include({}, other), getmetatable(other))
|
||||
end
|
||||
|
||||
local function new(class)
|
||||
@ -60,6 +60,9 @@ local function new(class)
|
||||
if getmetatable(inc) then inc = {inc} end
|
||||
|
||||
for _, other in ipairs(inc) do
|
||||
if type(other) == "string" then
|
||||
other = _G[other]
|
||||
end
|
||||
include(class, other)
|
||||
end
|
||||
|
||||
|
192
docs/Makefile
Normal file
@ -0,0 +1,192 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hump.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hump.qhc"
|
||||
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/hump"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hump"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
218
docs/_static/graph-tweens.js
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// DISCLAIMER: I just started learning d3, so this is certainly not good
|
||||
// idiomatic d3 code. But hey, it works (kinda).
|
||||
|
||||
var tweens = {
|
||||
'out': function(f) { return function(s) { return 1-f(1-s) } },
|
||||
'chain': function(f1, f2) { return function(s) { return ((s<.5) ? f1(2*s) : 1+f2(2*s-1)) * .5 } },
|
||||
'linear': function(s) { return s },
|
||||
'quad': function(s) { return s*s },
|
||||
'cubic': function(s) { return s*s*s },
|
||||
'quart': function(s) { return s*s*s*s },
|
||||
'quint': function(s) { return s*s*s*s*s },
|
||||
'sine': function(s) { return 1 - Math.cos(s*Math.PI/2) },
|
||||
'expo': function(s) { return Math.pow(2, 10*(s-1)) },
|
||||
'circ': function(s) { return 1 - Math.sqrt(Math.max(0,1-s*s)) },
|
||||
'back': function(s) { var b = 1.70158; return s*s*((b+1)*s - b) },
|
||||
'bounce': function(s) {
|
||||
return Math.min(
|
||||
7.5625 * Math.pow(s, 2),
|
||||
7.5625 * Math.pow((s - .545455), 2) + .75,
|
||||
7.5625 * Math.pow((s - .818182), 2) + .90375,
|
||||
7.5625 * Math.pow((s - .954546), 2) + .984375)
|
||||
},
|
||||
'elastic': function(s) {
|
||||
return -Math.sin(2/0.3 * Math.PI * (s-1) - Math.asin(1)) * Math.pow(2, 10*(s-1))
|
||||
},
|
||||
};
|
||||
var tweenfunc = tweens.linear;
|
||||
|
||||
|
||||
var width_graph = 320,
|
||||
width_anim_move = 110,
|
||||
width_anim_rotate = 110,
|
||||
width_anim_size = 110,
|
||||
height = 250;
|
||||
|
||||
// "UI"
|
||||
var graph_ui = d3.select("#tween-graph").append("div")
|
||||
.attr("id", "tween-graph-ui");
|
||||
// rest see below
|
||||
|
||||
// the graph
|
||||
var graph = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_graph).attr("height", height);
|
||||
|
||||
// background
|
||||
graph.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
var y_zero = height * .78, y_one = height * .22;
|
||||
graph.append("rect")
|
||||
.attr("y", y_one)
|
||||
.attr("width", "100%").attr("height", y_zero - y_one)
|
||||
.attr("style", "fill:steelblue;fill-opacity:.3;stroke-width:1;stroke:rgba(100,100,100,.7)");
|
||||
|
||||
// time arrow
|
||||
graph.append("defs")
|
||||
.append("marker")
|
||||
.attr("id", "triangle")
|
||||
.attr("viewBox", "0 0 10 10")
|
||||
.attr("refX", 1).attr("refY", 5)
|
||||
.attr("markerWidth", 4)
|
||||
.attr("markerHeight", 4)
|
||||
.attr("orient", "auto")
|
||||
.attr("style", "fill:rgba(0,0,0,.5)")
|
||||
.append("path").attr("d", "M 0 0 L 10 5 L 0 10 z");
|
||||
|
||||
graph.append("line")
|
||||
.attr("x1", width_graph/2-80)
|
||||
.attr("x2", width_graph/2+80)
|
||||
.attr("y1", y_zero + 40).attr("y2", y_zero + 40)
|
||||
.attr("style", "stroke-width:2;stroke:rgba(0,0,0,.5)")
|
||||
.attr("marker-end", "url(#triangle)");
|
||||
|
||||
graph.append("text")
|
||||
.text("Time")
|
||||
.attr("x", width_graph/2).attr("y", y_zero + 55)
|
||||
.attr("style", "text-anchor:middle;fill:rgba(0,0,0,.5);font-size:15px");
|
||||
|
||||
// the actual graph
|
||||
var curve = d3.svg.line()
|
||||
.x(function(x) { return x*width_graph; })
|
||||
.y(function(x) { return tweenfunc(x) * (y_one - y_zero) + y_zero; })
|
||||
|
||||
var graph_curve = graph.append("path").attr("d", curve(d3.range(0,1.05,.005)))
|
||||
.attr("style", "fill:none;stroke-width:2;stroke:seagreen;");
|
||||
|
||||
var graph_marker = graph.append("circle")
|
||||
.attr("r", 5)
|
||||
.attr("style", "stroke:goldenrod;fill:none;stroke-width:3");
|
||||
|
||||
// finally, a label
|
||||
var graph_label = graph.append("text")
|
||||
.text("linear")
|
||||
.attr("x", width_graph/2).attr("y", 20)
|
||||
.attr("style", "text-anchor:middle;font-weight:bold;font-size:15px;");
|
||||
|
||||
|
||||
// animation examples - moving ball
|
||||
var anim_move = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_anim_move).attr("height", height);
|
||||
|
||||
anim_move.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
anim_move.append("rect")
|
||||
.attr("width", 10).attr("height", (y_zero - y_one))
|
||||
.attr("x", width_anim_move/2-5).attr("y", y_one)
|
||||
.attr("style", "fill:black;opacity:.1");
|
||||
|
||||
var anim_move_ball = anim_move.append("circle")
|
||||
.attr("cx", width_anim_move/2).attr("cy", y_one)
|
||||
.attr("r", 17)
|
||||
.attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;");
|
||||
|
||||
// animation examples - rotating square
|
||||
var anim_rotate = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_anim_size).attr("height", height);
|
||||
|
||||
anim_rotate.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
var w = width_anim_size/2;
|
||||
var anim_rotate_square = anim_rotate.append("rect")
|
||||
.attr("x", -w/2).attr("y", -w/4)
|
||||
.attr("width", w).attr("height", w/2)
|
||||
.attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;");
|
||||
|
||||
// animation examples - resizing ellipse
|
||||
var anim_size = d3.select("#tween-graph").append("svg")
|
||||
.attr("width", width_anim_size).attr("height", height);
|
||||
|
||||
anim_size.append("rect")
|
||||
.attr("width", "100%").attr("height", "100%")
|
||||
.attr("style", "fill:rgb(240,240,240);stroke-width:1;stroke:rgb(100,100,100);");
|
||||
|
||||
anim_size.append("ellipse")
|
||||
.attr("cx", width_anim_size/2).attr("cy", height/2)
|
||||
.attr("rx", 40).attr("ry", 120)
|
||||
.attr("style", "fill:rgb(150,150,150);stroke:black;stroke-width:2;opacity:.1");
|
||||
|
||||
var anim_size_ellipse = anim_size.append("ellipse")
|
||||
.attr("cx", width_anim_size/2).attr("cy", height/2)
|
||||
.attr("rx", 40).attr("ry", 40)
|
||||
.attr("style", "fill:steelblue;stroke:rgb(90,90,90);stroke-width:5;");
|
||||
|
||||
|
||||
// make it move!
|
||||
var t = 0;
|
||||
window.setInterval(function() {
|
||||
t = (t + .025 / 3);
|
||||
if (t > 1.3) { t = -.3; }
|
||||
var tt = Math.max(Math.min(t, 1), 0);
|
||||
|
||||
var s = tweenfunc(tt)
|
||||
var yy = s * (y_one - y_zero) + y_zero;
|
||||
var translate = "translate("+(width_anim_size/2)+" "+(height/2)+")";
|
||||
var rotate = "rotate(" + (s * 360) + ")";
|
||||
|
||||
graph_marker.attr("cx", tt*width_graph).attr("cy", yy);
|
||||
anim_move_ball.attr("cy", y_one + y_zero - yy);
|
||||
anim_rotate_square.attr("transform", translate + " " + rotate);
|
||||
anim_size_ellipse.attr("ry", s * 80 + 40);
|
||||
}, 25);
|
||||
|
||||
|
||||
// ui continued
|
||||
graph_ui.append("strong").text("Function: ");
|
||||
var select_modifier = graph_ui.append("select");
|
||||
select_modifier.append("option").text("in");
|
||||
select_modifier.append("option").text("out");
|
||||
select_modifier.append("option").text("in-out");
|
||||
select_modifier.append("option").text("out-in");
|
||||
graph_ui.append("strong").text("-")
|
||||
|
||||
var select_func = graph_ui.append("select")
|
||||
var funcs = [];
|
||||
for (var k in tweens)
|
||||
{
|
||||
if (k != "out" && k != "chain")
|
||||
{
|
||||
select_func.append("option").text(k);
|
||||
funcs.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
var change_tweenfunc = function()
|
||||
{
|
||||
var fname = funcs[select_func.node().selectedIndex];
|
||||
var mod = select_modifier.node().selectedIndex;
|
||||
|
||||
tweenfunc = tweens[fname];
|
||||
if (mod == 1) // out
|
||||
tweenfunc = tweens.out(tweenfunc);
|
||||
else if (mod == 2) // in-out
|
||||
tweenfunc = tweens.chain(tweenfunc, tweens.out(tweenfunc));
|
||||
else if (mod == 3) // out-in
|
||||
tweenfunc = tweens.chain(tweens.out(tweenfunc), tweenfunc);
|
||||
|
||||
// update curve
|
||||
graph_curve.attr("d", curve(d3.range(0,1.05,.005)))
|
||||
|
||||
// update label
|
||||
if (mod != 0)
|
||||
graph_label.text((['in','out','in-out','out-in'])[mod] + "-" + fname);
|
||||
else
|
||||
graph_label.text(fname);
|
||||
}
|
||||
|
||||
select_func.on("change", change_tweenfunc);
|
||||
select_modifier.on("change", change_tweenfunc);
|
||||
|
||||
})();
|
BIN
docs/_static/in-out-interpolators.png
vendored
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
docs/_static/interpolators.png
vendored
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
docs/_static/inv-interpolators.png
vendored
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
docs/_static/vector-cross.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/_static/vector-mirrorOn.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/_static/vector-perpendicular.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/_static/vector-projectOn.png
vendored
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
docs/_static/vector-rotated.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
537
docs/camera.rst
Normal file
@ -0,0 +1,537 @@
|
||||
hump.camera
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
Camera = require "hump.camera"
|
||||
|
||||
A camera utility for LÖVE. A camera can "look" at a position. It can zoom in
|
||||
and out and it can rotate it's view. In the background, this is done by
|
||||
actually moving, scaling and rotating everything in the game world. But don't
|
||||
worry about that.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.load()
|
||||
camera = Camera(player.pos.x, player.pos.y)
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
local dx,dy = player.x - cam.x, player.y - cam.y
|
||||
camera:move(dx/2, dy/2)
|
||||
end
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Camera.new(x,y, zoom, rot)
|
||||
|
||||
:param numbers x,y: Point for the camera to look at. (optional)
|
||||
:param number zoom: Camera zoom. (optional)
|
||||
:param number rot: Camera rotation in radians. (optional)
|
||||
:returns: A new camera.
|
||||
|
||||
|
||||
Creates a new camera. You can access the camera position using ``camera.x,
|
||||
camera.y``, the zoom using ``camera.scale`` and the rotation using ``camera.rot``.
|
||||
|
||||
The module variable name can be used at a shortcut to ``new()``.
|
||||
|
||||
**Example**::
|
||||
|
||||
camera = require 'hump.camera'
|
||||
-- camera looking at (100,100) with zoom 2 and rotated by 45 degrees
|
||||
cam = camera(100,100, 2, math.pi/2)
|
||||
|
||||
|
||||
.. function:: camera:move(dx,dy)
|
||||
|
||||
:param numbers dx,dy: Direction to move the camera.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Move the camera *by* some vector. To set the position, use
|
||||
:func:`camera:lookAt`.
|
||||
|
||||
This function is shortcut to ``camera.x,camera.y = camera.x+dx, camera.y+dy``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
camera:move(dt * 5, dt * 6)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.update(dt)
|
||||
camera:move(dt * 5, dt * 6):rotate(dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:lookAt(x,y)
|
||||
|
||||
:param numbers x,y: Position to look at.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Let the camera look at a point. In other words, it sets the camera position. To
|
||||
move the camera *by* some amount, use :func:`camera:move`.
|
||||
|
||||
This function is shortcut to ``camera.x,camera.y = x, y``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
camera:lookAt(player.pos:unpack())
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.update(dt)
|
||||
camera:lookAt(player.pos:unpack()):rotate(player.rot)
|
||||
end
|
||||
|
||||
.. function:: camera:position()
|
||||
|
||||
:returns: ``x,y`` -- Camera position.
|
||||
|
||||
|
||||
Returns ``camera.x, camera.y``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- let the camera fly!
|
||||
local cam_dx, cam_dy = 0, 0
|
||||
|
||||
function love.mousereleased(x,y)
|
||||
local cx,cy = camera:position()
|
||||
dx, dy = x-cx, y-cy
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
camera:move(dx * dt, dy * dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:rotate(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Rotate the camera by some angle. To set the angle use :func:`camera:rotateTo`.
|
||||
|
||||
This function is shortcut to ``camera.rot = camera.rot + angle``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
camera:rotate(dt)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.update(dt)
|
||||
camera:rotate(dt):move(dt,dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:rotateTo(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians
|
||||
:returns: The camera.
|
||||
|
||||
Set rotation: ``camera.rot = angle``.
|
||||
|
||||
**Example**::
|
||||
|
||||
camera:rotateTo(math.pi/2)
|
||||
|
||||
|
||||
.. function:: camera:zoom(mul)
|
||||
|
||||
:param number mul: Zoom change. Should be > 0.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
*Multiply* zoom: ``camera.scale = camera.scale * mul``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
camera:zoom(2) -- make everything twice as big
|
||||
|
||||
::
|
||||
|
||||
camera:zoom(0.5) -- ... and back to normal
|
||||
|
||||
::
|
||||
|
||||
camera:zoom(-1) -- mirror and flip everything upside down
|
||||
|
||||
|
||||
.. function:: camera:zoomTo(zoom)
|
||||
|
||||
:param number zoom: New zoom.
|
||||
:returns: The camera.
|
||||
|
||||
|
||||
Set zoom: ``camera.scale = zoom``.
|
||||
|
||||
**Example**::
|
||||
|
||||
camera:zoomTo(1) -- reset zoom
|
||||
|
||||
|
||||
.. function:: camera:attach()
|
||||
|
||||
Start looking through the camera.
|
||||
|
||||
Apply camera transformations, i.e. move, scale and rotate everything until
|
||||
``camera:detach()`` as if looking through the camera.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
camera:attach()
|
||||
draw_world()
|
||||
camera:detach()
|
||||
|
||||
draw_hud()
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:detach()
|
||||
|
||||
Stop looking through the camera.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
camera:attach()
|
||||
draw_world()
|
||||
camera:detach()
|
||||
|
||||
draw_hud()
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:draw(func)
|
||||
|
||||
:param function func: Drawing function to be wrapped.
|
||||
|
||||
Wrap a function between a ``camera:attach()``/``camera:detach()`` pair.
|
||||
Equivalent to::
|
||||
|
||||
camera:attach()
|
||||
func()
|
||||
camera:detach()
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
camera:draw(draw_world)
|
||||
draw_hud()
|
||||
end
|
||||
|
||||
|
||||
.. function:: camera:worldCoords(x, y)
|
||||
|
||||
:param numbers x, y: Point to transform.
|
||||
:returns: ``x,y`` -- Transformed point.
|
||||
|
||||
Because a camera has a point it looks at, a rotation and a zoom factor, it
|
||||
defines a coordinate system. A point now has two sets of coordinates: One
|
||||
defines where the point is to be found in the game world, and the other
|
||||
describes the position on the computer screen. The first set of coordinates is
|
||||
called world coordinates, the second one camera coordinates. Sometimes it is
|
||||
needed to convert between the two coordinate systems, for example to get the
|
||||
position of a mouse click in the game world in a strategy game, or to see if an
|
||||
object is visible on the screen.
|
||||
|
||||
:func:`camera:worldCoords` and :func:`camera:cameraCoords` transform points
|
||||
between these two coordinate systems.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = camera:worldCoords(love.mouse.getPosition())
|
||||
selectedUnit:plotPath(x,y)
|
||||
|
||||
|
||||
.. function:: camera:cameraCoords(x, y)
|
||||
|
||||
:param numbers x, y: Point to transform.
|
||||
:returns: ``x,y`` -- Transformed point.
|
||||
|
||||
|
||||
Because a camera has a point it looks at, a rotation and a zoom factor, it
|
||||
defines a coordinate system. A point now has two sets of coordinates: One
|
||||
defines where the point is to be found in the game world, and the other
|
||||
describes the position on the computer screen. The first set of coordinates is
|
||||
called world coordinates, the second one camera coordinates. Sometimes it is
|
||||
needed to convert between the two coordinate systems, for example to get the
|
||||
position of a mouse click in the game world in a strategy game, or to see if an
|
||||
object is visible on the screen.
|
||||
|
||||
:func:`camera:worldCoords` and :func:`camera:cameraCoords` transform points
|
||||
between these two coordinate systems.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = camera:cameraCoords(player.pos.x, player.pos.y)
|
||||
love.graphics.line(x, y, love.mouse.getPosition())
|
||||
|
||||
|
||||
.. function:: camera:mousePosition()
|
||||
|
||||
:returns: Mouse position in world coordinates.
|
||||
|
||||
|
||||
Shortcut to ``camera:worldCoords(love.mouse.getPosition())``.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = camera:mousePosition()
|
||||
selectedUnit:plotPath(x,y)
|
||||
|
||||
|
||||
Camera Movement Control
|
||||
-----------------------
|
||||
|
||||
Camera movement is one of these things that go almost unnoticed when done well,
|
||||
but add a lot to the overall experience.
|
||||
The article `Scroll Back: The Theory and Practice of Cameras in SideScrollers
|
||||
<http://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php>`_
|
||||
by Itay Keren gives a lot of insight into how to design good camera systems.
|
||||
|
||||
**hump.camera** offers functions that help to implement most of the techniques
|
||||
discussed in the article. The functions :func:`camera:lockX`,
|
||||
:func:`camera:lockY`, :func:`camera:lockPosition`, and :func:`camera:lockWindow`
|
||||
move the camera so that the interesting content stays in frame.
|
||||
Note that the functions must be called every frame::
|
||||
|
||||
function love.update()
|
||||
-- vertical locking
|
||||
camera:lockX(player.pos.x)
|
||||
end
|
||||
|
||||
|
||||
All movements are subject to smoothing (see :ref:`Movement Smoothers
|
||||
<movement-smoothers>`).
|
||||
You can specify a default movement smoother by assigning the variable
|
||||
:attr:`camera.smoother`::
|
||||
|
||||
cam.smoother = Camera.smooth.linear(100)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockX(x, smoother, ...)
|
||||
|
||||
:param number x: X coordinate (in world coordinates) to lock to.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
Horizontal camera locking: Keep the camera locked on the defined ``x``-position
|
||||
(in *world coordinates*). The ``y``-position is not affected.
|
||||
|
||||
You can define an off-center locking position by "aiming" the camera left or
|
||||
right of your actual target. For example, to center the player 20 pixels to the
|
||||
*left* of the screen, aim 20 pixels to it's *right* (see examples).
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player vertically
|
||||
camera:lockX(player.x)
|
||||
|
||||
::
|
||||
|
||||
-- ... with linear smoothing at 25 px/s
|
||||
camera:lockX(player.x, Camera.smooth.linear(25))
|
||||
|
||||
::
|
||||
|
||||
-- lock player 20px left of center
|
||||
camera:lockX(player.x + 20)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockY(y, smoother, ...)
|
||||
|
||||
:param number y: Y coordinate (in world coordinates) to lock to.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
Vertical camera locking: Keep the camera locked on the defined ``y``-position
|
||||
(in *world coordinates*). The ``x``-position is not affected.
|
||||
|
||||
You can define an off-center locking position by "aiming" the camera above or
|
||||
below your actual target. For example, to center the player 20 pixels *below* the
|
||||
screen center, aim 20 pixels *above* it (see examples).
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player horizontally
|
||||
camera:lockY(player.y)
|
||||
|
||||
::
|
||||
|
||||
-- ... with damped smoothing with a stiffness of 10
|
||||
camera:lockY(player.y, Camera.smooth.damped(10))
|
||||
|
||||
::
|
||||
|
||||
-- lock player 20px below the screen center
|
||||
camera:lockY(player.y - 20)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockPosition(x,y, smoother, ...)
|
||||
|
||||
:param numbers x,y: Position (in world coordinates) to lock to.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
Horizontal and vertical camera locking: Keep the camera locked on the defined
|
||||
position (in *world coordinates*).
|
||||
|
||||
You can define an off-center locking position by "aiming" the camera to the
|
||||
opposite direction away from your real target.
|
||||
For example, to center the player 10 pixels to the *left* and 20 pixels *above*
|
||||
the screen center, aim 10 pixels to the *right* and 20 pixels *below*.
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player
|
||||
camera:lockPosition(player.x, player.y)
|
||||
|
||||
::
|
||||
|
||||
-- lock 50 pixels into player's aiming direction
|
||||
camera:lockPosition(player.x - player.aiming.x * 50, player.y - player.aiming.y * 50)
|
||||
|
||||
|
||||
|
||||
.. function:: camera:lockWindow(x,y, x_min, x_max, y_min, y_max, smoother, ...)
|
||||
|
||||
:param numbers x,y: Position (in world coordinates) to lock to.
|
||||
:param numbers x_min: Upper left X coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param numbers x_max: Lower right X coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param numbers y_min: Upper left Y coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param numbers y_max: Lower right Y coordinate of the camera window *(in camera coordinates!)*.
|
||||
:param function smoother: Movement smoothing override. (optional)
|
||||
:param mixed ...: Additional parameters to the smoothing function. (optional)
|
||||
|
||||
The most powerful locking method: Lock camera to ``x,y``, but only move the
|
||||
camera if the position would be out of the screen-rectangle defined by ``x_min``,
|
||||
``x_max``, ``y_min``, ``y_max``.
|
||||
|
||||
.. note::
|
||||
The locking window is defined in camera coordinates, whereas the position to
|
||||
lock to is defined in world coordinates!
|
||||
|
||||
All of the other locking methods can be implemented by window locking. For
|
||||
position locking, set ``x_min = x_max`` and ``y_min = y_max``.
|
||||
Off-center locking can be done by defining the locking window accordingly.
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- lock on player
|
||||
camera:lock(player.x, player.y)
|
||||
|
||||
.. attribute:: camera.smoother
|
||||
|
||||
The default smoothing operator. Must be a ``function`` with the following
|
||||
prototype::
|
||||
|
||||
function customSmoother(dx,dy, ...)
|
||||
do_stuff()
|
||||
return new_dx,new_dy
|
||||
end
|
||||
|
||||
where ``dx,dy`` is the offset the camera would move before smoothing and
|
||||
``new_dx, new_dy`` is the offset the camera should move after smoothing.
|
||||
|
||||
|
||||
.. _movement-smoothers:
|
||||
|
||||
Movement Smoothers
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is not always desirable that the camera instantly locks on a target.
|
||||
`Platform snapping
|
||||
<http://gamasutra.com/blogs/ItayKeren/20150511/243083/Scroll_Back_The_Theory_and_Practice_of_Cameras_in_SideScrollers.php#h.rncuomopycy0>`_,
|
||||
for example, would look terrible if the camera would instantly jump to the
|
||||
focussed platform.
|
||||
Smoothly moving the camera to the locked position can also give the illusion of
|
||||
a camera operator an add to the overall feel of your game.
|
||||
|
||||
**hump.camera** allows to smooth the movement by either passing movement
|
||||
smoother functions to the locking functions or by setting a default smoother
|
||||
(see :attr:`camera.smoother`).
|
||||
|
||||
Smoothing functions must have the following prototype::
|
||||
|
||||
function customSmoother(dx,dy, ...)
|
||||
do_stuff()
|
||||
return new_dx,new_dy
|
||||
end
|
||||
|
||||
where ``dx,dy`` is the offset the camera would move before smoothing and
|
||||
``new_dx, new_dy`` is the offset the camera should move after smoothing.
|
||||
|
||||
This is a simple "rubber-band" smoother::
|
||||
|
||||
function rubber_band(dx,dy)
|
||||
local dt = love.timer.getDelta()
|
||||
return dx*dt, dy*dt
|
||||
end
|
||||
|
||||
**hump.camera** defines generators for the most common smoothers:
|
||||
|
||||
.. function:: Camera.smooth.none()
|
||||
|
||||
:returns: Smoothing function.
|
||||
|
||||
Dummy smoother: does not smooth the motion.
|
||||
|
||||
**Example**::
|
||||
|
||||
cam.smoother = Camera.smooth.none()
|
||||
|
||||
|
||||
.. function:: Camera.smooth.linear(speed)
|
||||
|
||||
:param number speed: Smoothing speed.
|
||||
:returns: Smoothing function.
|
||||
|
||||
Smoothly moves the camera towards to snapping goal with constant speed.
|
||||
|
||||
**Examples**::
|
||||
|
||||
cam.smoother = Camera.smooth.linear(100)
|
||||
|
||||
::
|
||||
|
||||
-- warning: creates a function every frame!
|
||||
camera:lockX(player.x, Camera.smooth.linear(25))
|
||||
|
||||
|
||||
.. function:: Camera.smooth.damped(stiffness)
|
||||
|
||||
:param number stiffness: Speed of the camera movement.
|
||||
:returns: Smoothing function.
|
||||
|
||||
Smoothly moves the camera towards the goal with a speed proportional to the
|
||||
distance to the target.
|
||||
Stiffness defines the speed of the motion: Higher values mean that the camera
|
||||
moves more quickly.
|
||||
|
||||
**Examples**::
|
||||
|
||||
cam.smoother = Camera.smooth.damped(10)
|
||||
|
||||
::
|
||||
|
||||
-- warning: creates a function every frame!
|
||||
camera:lockPosition(player.x, player.y, Camera.smooth.damped(2))
|
344
docs/class.rst
Normal file
@ -0,0 +1,344 @@
|
||||
hump.class
|
||||
==========
|
||||
|
||||
::
|
||||
|
||||
Class = require "hump.class"
|
||||
|
||||
A small, fast class/prototype implementation with multiple inheritance.
|
||||
|
||||
Implements `class commons <https://github.com/bartbes/Class-Commons>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
Critter = Class{
|
||||
init = function(self, pos, img)
|
||||
self.pos = pos
|
||||
self.img = img
|
||||
end,
|
||||
speed = 5
|
||||
}
|
||||
|
||||
function Critter:update(dt, player)
|
||||
-- see hump.vector
|
||||
local dir = (player.pos - self.pos):normalize_inplace()
|
||||
self.pos = self.pos + dir * Critter.speed * dt
|
||||
end
|
||||
|
||||
function Critter:draw()
|
||||
love.graphics.draw(self.img, self.pos.x, self.pos.y)
|
||||
end
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Class.new({init = constructor, __includes = parents, ...})
|
||||
|
||||
:param function constructor: Class constructor. Can be accessed with ``theclass.init(object, ...)``. (optional)
|
||||
:param class or table of classes parents: Classes to inherit from. Can either be a single class or a table of classes. (optional)
|
||||
:param mixed ...: Any other fields or methods common to all instances of this class. (optional)
|
||||
:returns: The class.
|
||||
|
||||
|
||||
Declare a new class.
|
||||
|
||||
``init()`` will receive the new object instance as first argument. Any other
|
||||
arguments will also be forwarded (see examples), i.e. ``init()`` has the
|
||||
following signature::
|
||||
|
||||
function init(self, ...)
|
||||
|
||||
If you do not specify a constructor, an empty constructor will be used instead.
|
||||
|
||||
The name of the variable that holds the module can be used as a shortcut to
|
||||
``new()`` (see example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
Class = require 'hump.class' -- `Class' is now a shortcut to new()
|
||||
|
||||
-- define a class class
|
||||
Feline = Class{
|
||||
init = function(self, size, weight)
|
||||
self.size = size
|
||||
self.weight = weight
|
||||
end;
|
||||
-- define a method
|
||||
stats = function(self)
|
||||
return string.format("size: %.02f, weight: %.02f", self.size, self.weight)
|
||||
end;
|
||||
}
|
||||
|
||||
-- create two objects
|
||||
garfield = Feline(.7, 45)
|
||||
felix = Feline(.8, 12)
|
||||
|
||||
print("Garfield: " .. garfield:stats(), "Felix: " .. felix:stats())
|
||||
|
||||
::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
-- same as above, but with 'external' function definitions
|
||||
Feline = Class{}
|
||||
|
||||
function Feline:init(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)
|
||||
print(Feline, garfield)
|
||||
|
||||
::
|
||||
|
||||
Class = require 'hump.class'
|
||||
A = Class{
|
||||
foo = function() print('foo') end
|
||||
}
|
||||
|
||||
B = Class{
|
||||
bar = function() print('bar') end
|
||||
}
|
||||
|
||||
-- single inheritance
|
||||
C = Class{__includes = A}
|
||||
instance = C()
|
||||
instance:foo() -- prints 'foo'
|
||||
instance:bar() -- error: function not defined
|
||||
|
||||
-- multiple inheritance
|
||||
D = Class{__includes = {A,B}}
|
||||
instance = D()
|
||||
instance:foo() -- prints 'foo'
|
||||
instance:bar() -- prints 'bar'
|
||||
|
||||
::
|
||||
|
||||
-- class attributes are shared across instances
|
||||
A = Class{ foo = 'foo' } -- foo is a class attribute/static member
|
||||
|
||||
one, two, three = A(), A(), A()
|
||||
print(one.foo, two.foo, three.foo) --> prints 'foo foo foo'
|
||||
|
||||
one.foo = 'bar' -- overwrite/specify for instance `one' only
|
||||
print(one.foo, two.foo, three.foo) --> prints 'bar foo foo'
|
||||
|
||||
A.foo = 'baz' -- overwrite for all instances without specification
|
||||
print(one.foo, two.foo, three.foo) --> prints 'bar baz baz'
|
||||
|
||||
|
||||
.. function:: class.init(object, ...)
|
||||
|
||||
:param Object object: The object. Usually ``self``.
|
||||
:param mixed ...: Arguments to pass to the constructor.
|
||||
:returns: Whatever the parent class constructor returns.
|
||||
|
||||
|
||||
Calls class constructor of a class on an object.
|
||||
|
||||
Derived classes should use this function their constructors to initialize the
|
||||
parent class(es) portions of the object.
|
||||
|
||||
**Example**::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
Shape = Class{
|
||||
init = function(self, area)
|
||||
self.area = area
|
||||
end;
|
||||
__tostring = function(self)
|
||||
return "area = " .. self.area
|
||||
end
|
||||
}
|
||||
|
||||
Rectangle = Class{__includes = Shape,
|
||||
init = function(self, width, height)
|
||||
Shape.init(self, width * height)
|
||||
self.width = width
|
||||
self.height = height
|
||||
end;
|
||||
__tostring = function(self)
|
||||
local strs = {
|
||||
"width = " .. self.width,
|
||||
"height = " .. self.height,
|
||||
Shape.__tostring(self)
|
||||
}
|
||||
return table.concat(strs, ", ")
|
||||
end
|
||||
}
|
||||
|
||||
print( Rectangle(2,4) ) -- prints 'width = 2, height = 4, area = 8'
|
||||
|
||||
|
||||
.. function:: Class:include(other)
|
||||
|
||||
:param tables other: Parent classes/mixins.
|
||||
:returns: The class.
|
||||
|
||||
|
||||
Inherit functions and variables of another class, but only if they are not
|
||||
already defined. This is done by (deeply) copying the functions and variables
|
||||
over to the subclass.
|
||||
|
||||
.. note::
|
||||
``class:include()`` doesn't actually care if the arguments supplied are
|
||||
hump classes. Just any table will work.
|
||||
|
||||
.. note::
|
||||
You can use ``Class.include(a, b)`` to copy any fields from table ``a``
|
||||
to table ``b`` (see second example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
Entity = Class{
|
||||
init = function(self)
|
||||
GameObjects.register(self)
|
||||
end
|
||||
}
|
||||
|
||||
Collidable = {
|
||||
dispatch_collision = function(self, other, dx, dy)
|
||||
if self.collision_handler[other.type])
|
||||
return collision_handler[other.type](self, other, dx, dy)
|
||||
end
|
||||
return collision_handler["*"](self, other, dx, dy)
|
||||
end,
|
||||
|
||||
collision_handler = {["*"] = function() end},
|
||||
}
|
||||
|
||||
Spaceship = Class{
|
||||
init = function(self)
|
||||
self.type = "Spaceship"
|
||||
-- ...
|
||||
end
|
||||
}
|
||||
|
||||
-- make Spaceship collidable
|
||||
Spaceship:include(Collidable)
|
||||
|
||||
Spaceship.collision_handler["Spaceship"] = function(self, other, dx, dy)
|
||||
-- ...
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
-- using Class.include()
|
||||
Class = require 'hump.class'
|
||||
a = {
|
||||
foo = 'bar',
|
||||
bar = {one = 1, two = 2, three = 3},
|
||||
baz = function() print('baz') end,
|
||||
}
|
||||
b = {
|
||||
foo = 'nothing to see here...'
|
||||
}
|
||||
|
||||
Class.include(b, a) -- copy values from a to b
|
||||
-- note that neither a nor b are hump classes!
|
||||
|
||||
print(a.foo, b.foo) -- prints 'bar nothing to see here...'
|
||||
|
||||
b.baz() -- prints 'baz'
|
||||
|
||||
b.bar.one = 10 -- changes only values in b
|
||||
print(a.bar.one, b.bar.one) -- prints '1 10'
|
||||
|
||||
|
||||
.. function:: class:clone()
|
||||
|
||||
:returns: A deep copy of the class/table.
|
||||
|
||||
|
||||
Create a clone/deep copy of the class.
|
||||
|
||||
.. note::
|
||||
You can use ``Class.clone(a)`` to create a deep copy of any table (see
|
||||
second example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
point = Class{ x = 0, y = 0 }
|
||||
|
||||
a = point:clone()
|
||||
a.x, a.y = 10, 10
|
||||
print(a.x, a.y) --> prints '10 10'
|
||||
|
||||
b = point:clone()
|
||||
print(b.x, b.y) --> prints '0 0'
|
||||
|
||||
c = a:clone()
|
||||
print(c.x, c.y) --> prints '10 10'
|
||||
|
||||
::
|
||||
|
||||
-- using Class.clone() to copy tables
|
||||
Class = require 'hump.class'
|
||||
a = {
|
||||
foo = 'bar',
|
||||
bar = {one = 1, two = 2, three = 3},
|
||||
baz = function() print('baz') end,
|
||||
}
|
||||
b = Class.clone(a)
|
||||
|
||||
b.baz() -- prints 'baz'
|
||||
b.bar.one = 10
|
||||
print(a.bar.one, b.bar.one) -- prints '1 10'
|
||||
|
||||
|
||||
|
||||
Caveats
|
||||
-------
|
||||
|
||||
Be careful when using metamethods like ``__add`` or ``__mul``: If a 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::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
A = Class{init = function(self, x) self.x = x end}
|
||||
function A:__add(other) return A(self.x + other.x) end
|
||||
function A:show() print("A:", self.x) end
|
||||
|
||||
B = Class{init = function(self, x, y) A.init(self, x) self.y = y end}
|
||||
function B:show() print("B:", self.x, self.y) end
|
||||
function B:foo() print("foo") end
|
||||
B:include(A)
|
||||
|
||||
one, two = B(1,2), B(3,4)
|
||||
result = one + two -- result will be of type A, *not* B!
|
||||
result:show() -- prints "A: 4"
|
||||
result:foo() -- error: method does not exist
|
||||
|
||||
Note that while you can define the ``__index`` metamethod of the class, this is
|
||||
not a good idea: It will break the class mechanism. To add a custom ``__index``
|
||||
metamethod without breaking the class system, you have to use ``rawget()``. But
|
||||
beware that this won't affect subclasses::
|
||||
|
||||
Class = require 'hump.class'
|
||||
|
||||
A = Class{}
|
||||
function A:foo() print('bar') end
|
||||
|
||||
function A:__index(key)
|
||||
print(key)
|
||||
return rawget(A, key)
|
||||
end
|
||||
|
||||
instance = A()
|
||||
instance:foo() -- prints foo bar
|
||||
|
||||
B = Class{__includes = A}
|
||||
instance = B()
|
||||
instance:foo() -- prints only foo
|
||||
|
293
docs/conf.py
Normal file
@ -0,0 +1,293 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# hump documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Oct 10 13:10:12 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.mathjax',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'hump'
|
||||
copyright = u'2015, Matthias Richter'
|
||||
author = u'Matthias Richter'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'humpdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'hump.tex', u'hump Documentation',
|
||||
u'Matthias Richter', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'hump', u'hump Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'hump', u'hump Documentation',
|
||||
author, 'hump', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
primary_domain = "js"
|
||||
highlight_language = "lua"
|
||||
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
350
docs/gamestate.rst
Normal file
@ -0,0 +1,350 @@
|
||||
hump.gamestate
|
||||
==============
|
||||
|
||||
::
|
||||
|
||||
Gamestate = require "hump.gamestate"
|
||||
|
||||
A gamestate encapsulates independent data an behaviour in a single table.
|
||||
|
||||
A typical game could consist of a menu-state, a level-state and a game-over-state.
|
||||
|
||||
**Example**::
|
||||
|
||||
local menu = {} -- previously: Gamestate.new()
|
||||
local game = {}
|
||||
|
||||
function menu:draw()
|
||||
love.graphics.print("Press Enter to continue", 10, 10)
|
||||
end
|
||||
|
||||
function menu:keyreleased(key, code)
|
||||
if key == 'return' then
|
||||
Gamestate.switch(game)
|
||||
end
|
||||
end
|
||||
|
||||
function game:enter()
|
||||
Entities.clear()
|
||||
-- setup entities here
|
||||
end
|
||||
|
||||
function game:update(dt)
|
||||
Entities.update(dt)
|
||||
end
|
||||
|
||||
function game:draw()
|
||||
Entities.draw()
|
||||
end
|
||||
|
||||
function love.load()
|
||||
Gamestate.registerEvents()
|
||||
Gamestate.switch(menu)
|
||||
end
|
||||
|
||||
|
||||
.. _callbacks:
|
||||
|
||||
Gamestate Callbacks
|
||||
-------------------
|
||||
|
||||
A gamestate can define all callbacks that LÖVE defines. In addition, there are
|
||||
callbacks for initalizing, entering and leaving a state:
|
||||
|
||||
``init()``
|
||||
Called once, and only once, before entering the state the first time. See
|
||||
:func:`Gamestate.switch`.
|
||||
|
||||
``enter(previous, ...)``
|
||||
Called every time when entering the state. See :func:`Gamestate.switch`.
|
||||
|
||||
``leave()``
|
||||
Called when leaving a state. See :func:`Gamestate.switch` and :func:`Gamestate.push`.
|
||||
|
||||
``resume()``
|
||||
Called when re-entering a state by :func:`Gamestate.pop`-ing another state.
|
||||
|
||||
``update()``
|
||||
Update the game state. Called every frame.
|
||||
|
||||
``draw()``
|
||||
Draw on the screen. Called every frame.
|
||||
|
||||
``focus()``
|
||||
Called if the window gets or looses focus.
|
||||
|
||||
``keypressed()``
|
||||
Triggered when a key is pressed.
|
||||
|
||||
``keyreleased()``
|
||||
Triggered when a key is released.
|
||||
|
||||
``mousepressed()``
|
||||
Triggered when a mouse button is pressed.
|
||||
|
||||
``mousereleased()``
|
||||
Triggered when a mouse button is released.
|
||||
|
||||
``joystickpressed()``
|
||||
Triggered when a joystick button is pressed.
|
||||
|
||||
``joystickreleased()``
|
||||
Triggered when a joystick button is released.
|
||||
|
||||
``quit()``
|
||||
Called on quitting the game. Only called on the active gamestate.
|
||||
|
||||
When using :func:`Gamestate.registerEvents`, all these callbacks will be called by the
|
||||
corresponding LÖVE callbacks and receive receive the same arguments (e.g.
|
||||
``state:update(dt)`` will be called by ``love.update(dt)``).
|
||||
|
||||
**Example**::
|
||||
|
||||
menu = {} -- previously: Gamestate.new()
|
||||
|
||||
function menu:init()
|
||||
self.background = love.graphics.newImage('bg.jpg')
|
||||
Buttons.initialize()
|
||||
end
|
||||
|
||||
function menu:enter(previous) -- runs every time the state is entered
|
||||
Buttons.setActive(Buttons.start)
|
||||
end
|
||||
|
||||
function menu:update(dt) -- runs every frame
|
||||
Buttons.update(dt)
|
||||
end
|
||||
|
||||
function menu:draw()
|
||||
love.graphics.draw(self.background, 0, 0)
|
||||
Buttons.draw()
|
||||
end
|
||||
|
||||
function menu:keyreleased(key)
|
||||
if key == 'up' then
|
||||
Buttons.selectPrevious()
|
||||
elseif key == 'down' then
|
||||
Buttons.selectNext()
|
||||
elseif
|
||||
Buttons.active:onClick()
|
||||
end
|
||||
end
|
||||
|
||||
function menu:mousereleased(x,y, mouse_btn)
|
||||
local button = Buttons.hovered(x,y)
|
||||
if button then
|
||||
Button.select(button)
|
||||
if mouse_btn == 'l' then
|
||||
button:onClick()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Gamestate.new()
|
||||
|
||||
:returns: An empty table.
|
||||
|
||||
|
||||
**Deprecated: Use the table constructor instead (see example)**
|
||||
|
||||
Declare a new gamestate (just an empty table). A gamestate can define several
|
||||
callbacks.
|
||||
|
||||
**Example**::
|
||||
|
||||
menu = {}
|
||||
-- deprecated method:
|
||||
menu = Gamestate.new()
|
||||
|
||||
|
||||
.. function:: Gamestate.switch(to, ...)
|
||||
|
||||
:param Gamestate to: Target gamestate.
|
||||
:param mixed ...: Additional arguments to pass to ``to:enter(current, ...)``.
|
||||
:returns: The results of ``to:enter(current, ...)``.
|
||||
|
||||
|
||||
Switch to a gamestate, with any additional arguments passed to the new state.
|
||||
|
||||
Switching a gamestate will call the ``leave()`` callback on the current
|
||||
gamestate, replace the current gamestate with ``to``, call the ``init()`` function
|
||||
if, and only if, the state was not yet inialized and finally call
|
||||
``enter(old_state, ...)`` on the new gamestate.
|
||||
|
||||
.. note::
|
||||
Processing of callbacks is suspended until ``update()`` is called on the new
|
||||
gamestate, but the function calling :func:`Gamestate.switch` can still continue - it is
|
||||
your job to make sure this is handled correctly. See also the examples below.
|
||||
|
||||
|
||||
**Examples**::
|
||||
|
||||
Gamestate.switch(game, level_two)
|
||||
|
||||
::
|
||||
|
||||
-- stop execution of the current state by using return
|
||||
if player.has_died then
|
||||
return Gamestate.switch(game, level_two)
|
||||
end
|
||||
|
||||
-- this will not be called when the state is switched
|
||||
player:update()
|
||||
|
||||
|
||||
|
||||
.. function:: Gamestate.Gamestate.current()
|
||||
|
||||
:returns: The active gamestate.
|
||||
|
||||
Returns the currently activated gamestate.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.keypressed(key)
|
||||
if Gamestate.current() ~= menu and key == 'p' then
|
||||
Gamestate.push(pause)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.push(to, ...)
|
||||
|
||||
:param Gamestate to: Target gamestate.
|
||||
:param mixed ...: Additional arguments to pass to ``to:enter(current, ...)``.
|
||||
:returns: The results of ``to:enter(current, ...)``.
|
||||
|
||||
Pushes the ``to`` on top of the state stack, i.e. makes it the active state.
|
||||
Semantics are the same as ``switch(to, ...)``, except that ``leave()`` is *not*
|
||||
called on the previously active state.
|
||||
|
||||
Useful for pause screens, menus, etc.
|
||||
|
||||
.. note::
|
||||
Processing of callbacks is suspended until ``update()`` is called on the
|
||||
new gamestate, but the function calling ``GS.push()`` can still continue -
|
||||
it is your job to make sure this is handled correctly. See also the
|
||||
example below.
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
-- pause gamestate
|
||||
Pause = Gamestate.new()
|
||||
function Pause:enter(from)
|
||||
self.from = from -- record previous state
|
||||
end
|
||||
|
||||
function Pause:draw()
|
||||
local W, H = love.graphics.getWidth(), love.graphics.getHeight()
|
||||
-- draw previous screen
|
||||
self.from:draw()
|
||||
-- overlay with pause message
|
||||
love.graphics.setColor(0,0,0, 100)
|
||||
love.graphics.rectangle('fill', 0,0, W,H)
|
||||
love.graphics.setColor(255,255,255)
|
||||
love.graphics.printf('PAUSE', 0, H/2, W, 'center')
|
||||
end
|
||||
|
||||
-- [...]
|
||||
function love.keypressed(key)
|
||||
if Gamestate.current() ~= menu and key == 'p' then
|
||||
return Gamestate.push(pause)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.pop(...)
|
||||
|
||||
:returns: The results of ``new_state:resume(...)``.
|
||||
|
||||
Calls ``leave()`` on the current state and then removes it from the stack, making
|
||||
the state below the current state and calls ``resume(...)`` on the activated state.
|
||||
Does *not* call ``enter()`` on the activated state.
|
||||
|
||||
.. note::
|
||||
Processing of callbacks is suspended until ``update()`` is called on the
|
||||
new gamestate, but the function calling ``GS.pop()`` can still continue -
|
||||
it is your job to make sure this is handled correctly. See also the
|
||||
example below.
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
-- extending the example of Gamestate.push() above
|
||||
function Pause:keypressed(key)
|
||||
if key == 'p' then
|
||||
return Gamestate.pop() -- return to previous state
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.<callback>(...)
|
||||
|
||||
:param mixed ...: Arguments to pass to the corresponding function.
|
||||
:returns: The result of the callback function.
|
||||
|
||||
Calls a function on the current gamestate. Can be any function, but is intended
|
||||
to be one of the :ref:`callbacks`. Mostly useful when not using
|
||||
:func:`Gamestate.registerEvents`.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.draw()
|
||||
Gamestate.draw() -- <callback> is `draw'
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Gamestate.update(dt) -- pass dt to currentState:update(dt)
|
||||
end
|
||||
|
||||
function love.keypressed(key, code)
|
||||
Gamestate.keypressed(key, code) -- pass multiple arguments
|
||||
end
|
||||
|
||||
|
||||
.. function:: Gamestate.registerEvents([callbacks])
|
||||
|
||||
:param table callbacks: Names of the callbacks to register. If omitted,
|
||||
register all love callbacks (optional).
|
||||
|
||||
Overwrite love callbacks to call ``Gamestate.update()``, ``Gamestate.draw()``,
|
||||
etc. automatically. ``love`` callbacks (e.g. ``love.update()``) are still
|
||||
invoked as usual.
|
||||
|
||||
This is by done by overwriting the love callbacks, e.g.::
|
||||
|
||||
local old_update = love.update
|
||||
function love.update(dt)
|
||||
old_update(dt)
|
||||
return Gamestate.current:update(dt)
|
||||
end
|
||||
|
||||
.. note::
|
||||
Only works when called in love.load() or any other function that is
|
||||
executed *after* the whole file is loaded.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.load()
|
||||
Gamestate.registerEvents()
|
||||
Gamestate.switch(menu)
|
||||
end
|
||||
|
||||
-- love callback will still be invoked
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
-- no need for Gamestate.update(dt)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.load()
|
||||
-- only register draw, update and quit
|
||||
Gamestate.registerEvents{'draw', 'update', 'quit'}
|
||||
Gamestate.switch(menu)
|
||||
end
|
52
docs/index.rst
Normal file
@ -0,0 +1,52 @@
|
||||
**hump** - Helper Utilities for a Multitude of Problems
|
||||
=======================================================
|
||||
|
||||
**hump** is a set of lightweight helpers for the awesome `LÖVE
|
||||
<http://love2d.org>`_ game framework. It will help to get you over the initial
|
||||
hump when starting to build a new game.
|
||||
|
||||
**hump** differs from many other libraries in that every component is
|
||||
independent of the remaining ones.
|
||||
The footprint is very small, so the library should fit nicely into your
|
||||
projects.
|
||||
|
||||
Read on
|
||||
-------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
hump.gamestate <gamestate>
|
||||
hump.timer <timer>
|
||||
hump.vector <vector>
|
||||
hump.vector-light <vector-light>
|
||||
hump.class <class>
|
||||
hump.signal <signal>
|
||||
hump.camera <camera>
|
||||
license
|
||||
|
||||
|
||||
Get hump
|
||||
--------
|
||||
|
||||
You can view and download the individual modules on github: `vrld/hump
|
||||
<http://github.com/vrld/hump>`_.
|
||||
You may also download the whole packed sourcecode either in the `zip
|
||||
<http://github.com/vrld/hump/zipball/master>`_ or `tar
|
||||
<http://github.com/vrld/hump/tarball/master>`_ format.
|
||||
|
||||
Using `Git <http://git-scm.com>`_, you can clone the project by running:
|
||||
|
||||
git clone git://github.com/vrld/hump
|
||||
|
||||
Once done, tou can check for updates by running
|
||||
|
||||
git pull
|
||||
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
26
docs/license.rst
Normal file
@ -0,0 +1,26 @@
|
||||
License
|
||||
=======
|
||||
|
||||
Copyright (c) 2011-2015 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.
|
185
docs/signal.rst
Normal file
@ -0,0 +1,185 @@
|
||||
hump.signal
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
Signal = require 'hump.signal'
|
||||
|
||||
A simple yet effective implementation of `Signals and Slots
|
||||
<http://en.wikipedia.org/wiki/Signals_and_slots>`_, aka the `Observer pattern
|
||||
<http://en.wikipedia.org/wiki/Observer_pattern>`_: Functions can be dynamically
|
||||
bound to signals. When a *signal* is *emitted*, all registered functions will
|
||||
be invoked. Simple as that.
|
||||
|
||||
``hump.signal`` makes things a little more interesing by allowing to emit all
|
||||
signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- in AI.lua
|
||||
Signal.register('shoot', function(x,y, dx,dy)
|
||||
-- for every critter in the path of the bullet:
|
||||
-- try to avoid being hit
|
||||
for critter in pairs(critters) do
|
||||
if critter:intersectsRay(x,y, dx,dy) then
|
||||
critter:setMoveDirection(-dy, dx)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- in sounds.lua
|
||||
Signal.register('shoot', function()
|
||||
Sounds.fire_bullet:play()
|
||||
end)
|
||||
|
||||
-- in main.lua
|
||||
function love.keypressed(key)
|
||||
if key == ' ' then
|
||||
local x,y = player.pos:unpack()
|
||||
local dx,dy = player.direction:unpack()
|
||||
Signal.emit('shoot', x,y, dx,dy)
|
||||
end
|
||||
end
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Signal.new()
|
||||
|
||||
:returns: A new signal registry.
|
||||
|
||||
Creates a new signal registry that is independent of the default registry: It
|
||||
will manage it's own list of signals and does not in any way affect the the
|
||||
global registry. Likewise, the global registry does not affect the instance.
|
||||
|
||||
.. note::
|
||||
If you don't need multiple independent registries, you can use the
|
||||
global/default registry (see examples).
|
||||
|
||||
.. note::
|
||||
Unlike the default one, signal registry instances use the colon-syntax,
|
||||
i.e., you need to call ``instance:emit('foo', 23)`` instead of
|
||||
``Signal.mit('foo', 23)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
player.signals = Signal.new()
|
||||
|
||||
|
||||
.. function:: Signal.register(s, f)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
:param function f: The function to register.
|
||||
:returns: A function handle to use in :func:`Signal.remove()`.
|
||||
|
||||
|
||||
Registers a function ``f`` to be called when signal ``s`` is emitted.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Signal.register('level-complete', function() self.fanfare:play() end)
|
||||
|
||||
::
|
||||
|
||||
handle = Signal.register('level-load', function(level) level.show_help() end)
|
||||
|
||||
::
|
||||
|
||||
menu:register('key-left', select_previous_item)
|
||||
|
||||
|
||||
.. function:: Signal.emit(s, ...)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
:param mixed ...: Arguments to pass to the bound functions. (optional)
|
||||
|
||||
|
||||
Calls all functions bound to signal ``s`` with the supplied arguments.
|
||||
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.keypressed(key)
|
||||
-- using a signal instance
|
||||
if key == 'left' then menu:emit('key-left') end
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
if level.is_finished() then
|
||||
-- adding arguments
|
||||
Signal.emit('level-load', level.next_level)
|
||||
end
|
||||
|
||||
|
||||
.. function:: Signal.remove(s, ...)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
:param functions ...: Functions to unbind from the signal.
|
||||
|
||||
|
||||
Unbinds (removes) functions from signal ``s``.
|
||||
|
||||
**Example**::
|
||||
|
||||
Signal.remove('level-load', handle)
|
||||
|
||||
|
||||
.. function:: Signal.clear(s)
|
||||
|
||||
:param string s: The signal identifier.
|
||||
|
||||
|
||||
Removes all functions from signal ``s``.
|
||||
|
||||
**Example**::
|
||||
|
||||
Signal.clear('key-left')
|
||||
|
||||
|
||||
.. function:: Signal.emitPattern(p, ...)
|
||||
|
||||
:param string p: The signal identifier pattern.
|
||||
:param mixed ...: Arguments to pass to the bound functions. (optional)
|
||||
|
||||
|
||||
Emits all signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- emit all update signals
|
||||
Signal.emitPattern('^update%-.*', dt)
|
||||
|
||||
|
||||
.. function:: Signal.removePattern(p, ...)
|
||||
|
||||
:param string p: The signal identifier pattern.
|
||||
:param functions ...: Functions to unbind from the signals.
|
||||
|
||||
|
||||
Removes functions from all signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
Signal.removePattern('key%-.*', play_click_sound)
|
||||
|
||||
|
||||
.. function:: Signal.clearPattern(p)
|
||||
|
||||
:param string p: The signal identifier pattern.
|
||||
|
||||
|
||||
Removes **all** functions from all signals that match a `Lua string pattern
|
||||
<http://www.lua.org/manual/5.1/manual.html#5.4.1>`_.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Signal.clearPattern('sound%-.*')
|
||||
|
||||
::
|
||||
|
||||
player.signals:clearPattern('.*') -- clear all signals
|
||||
|
465
docs/timer.rst
Normal file
@ -0,0 +1,465 @@
|
||||
hump.timer
|
||||
==========
|
||||
|
||||
::
|
||||
|
||||
Timer = require "hump.timer"
|
||||
|
||||
hump.timer offers a simple interface to schedule the execution of functions. It
|
||||
is possible to run functions *after* and *for* some amount of time. For
|
||||
example, a timer could be set to move critters every 5 seconds or to make the
|
||||
player invincible for a short amount of time.
|
||||
|
||||
In addition to that, ``hump.timer`` offers various `tweening
|
||||
<http://en.wikipedia.org/wiki/Inbetweening>`_ functions that make it
|
||||
easier to produce `juicy games <http://www.youtube.com/watch?v=Fy0aCDmgnxg>`_.
|
||||
|
||||
**Example**::
|
||||
|
||||
function love.keypressed(key)
|
||||
if key == ' ' then
|
||||
Timer.after(1, function() print("Hello, world!") end)
|
||||
end
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: Timer.new()
|
||||
|
||||
:returns: A timer instance.
|
||||
|
||||
|
||||
Creates a new timer instance that is independent of the global timer: It will
|
||||
manage it's own list of scheduled functions and does not in any way affect the
|
||||
the global timer. Likewise, the global timer does not affect timer instances.
|
||||
|
||||
.. note::
|
||||
If you don't need multiple independent schedulers, you can use the
|
||||
global/default timer (see examples).
|
||||
|
||||
.. note::
|
||||
Unlike the default timer, timer instances use the colon-syntax, i.e.,
|
||||
you need to call ``instance:after(1, foo)`` instead of ``Timer.after(1,
|
||||
foo)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
menuTimer = Timer.new()
|
||||
|
||||
|
||||
.. function:: Timer.after(delay, func)
|
||||
|
||||
:param number delay: Number of seconds the function will be delayed.
|
||||
:param function func: The function to be delayed.
|
||||
:returns: The timer handle. See also :func:`Timer.cancel`.
|
||||
|
||||
|
||||
Schedule a function. The function will be executed after ``delay`` seconds have
|
||||
elapsed, given that ``update(dt)`` is called every frame.
|
||||
|
||||
.. note::
|
||||
There is no guarantee that the delay will not be exceeded, it is only
|
||||
guaranteed that the function will *not* be executed *before* the delay has
|
||||
passed.
|
||||
|
||||
``func`` will receive itself as only parameter. This is useful to implement
|
||||
periodic behavior (see the example).
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- grant the player 5 seconds of immortality
|
||||
player.isInvincible = true
|
||||
Timer.after(5, function() player.isInvincible = false end)
|
||||
|
||||
::
|
||||
|
||||
-- print "foo" every second. See also every()
|
||||
Timer.after(1, function(func) print("foo") Timer.after(1, func) end)
|
||||
|
||||
::
|
||||
|
||||
--Using a timer instance:
|
||||
menuTimer:after(1, finishAnimation)
|
||||
|
||||
|
||||
.. function:: Timer.script(func)
|
||||
|
||||
:param function func: Script to execute.
|
||||
|
||||
Execute a function that can be paused without causing the rest of the program to
|
||||
be suspended. ``func`` will receive a function - ``wait`` - to do interrupt the
|
||||
script (but not the whole program) as only argument. The function prototype of
|
||||
wait is: ``wait(delay)``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Timer.script(function(wait)
|
||||
print("Now")
|
||||
wait(1)
|
||||
print("After one second")
|
||||
wait(1)
|
||||
print("Bye!")
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
-- useful for splash screens
|
||||
Timer.script(function(wait)
|
||||
Timer.tween(0.5, splash.pos, {x = 300}, 'in-out-quad')
|
||||
wait(5) -- show the splash for 5 seconds
|
||||
Timer.tween(0.5, slpash.pos, {x = 800}, 'in-out-quad')
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
-- repeat something with a varying delay
|
||||
Timer.script(function(wait)
|
||||
while true do
|
||||
spawn_ship()
|
||||
wait(1 / (1-production_speed))
|
||||
end
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
-- jumping with timer.script
|
||||
self.timers:script(function(wait)
|
||||
local w = 1/12
|
||||
self.jumping = true
|
||||
Timer.tween(w*2, self, {z = -8}, "out-cubic", function()
|
||||
Timer.tween(w*2, self, {z = 0},"in-cubic")
|
||||
end)
|
||||
|
||||
self.quad = self.quads.jump[1]
|
||||
wait(w)
|
||||
|
||||
self.quad = self.quads.jump[2]
|
||||
wait(w)
|
||||
|
||||
self.quad = self.quads.jump[3]
|
||||
wait(w)
|
||||
|
||||
self.quad = self.quads.jump[4]
|
||||
wait(w)
|
||||
|
||||
self.jumping = false
|
||||
self.z = 0
|
||||
end)
|
||||
|
||||
|
||||
.. function:: Timer.every(delay, func[, count])
|
||||
|
||||
:param number delay: Number of seconds between two consecutive function calls.
|
||||
:param function func: The function to be called periodically.
|
||||
:param number count: Number of times the function is to be called (optional).
|
||||
:returns: The timer handle. See also :func:`Timer.cancel`.
|
||||
|
||||
|
||||
Add a function that will be called ``count`` times every ``delay`` seconds.
|
||||
|
||||
If ``count`` is omitted, the function will be called until it returns ``false``
|
||||
or :func:`Timer.cancel` or :func:`Timer.clear` is called on the timer instance.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- toggle light on and off every second
|
||||
Timer.every(1, function() lamp:toggleLight() end)
|
||||
|
||||
::
|
||||
|
||||
-- launch 5 fighters in quick succession (using a timer instance)
|
||||
mothership_timer:every(0.3, function() self:launchFighter() end, 5)
|
||||
|
||||
::
|
||||
|
||||
-- flicker player's image as long as he is invincible
|
||||
Timer.every(0.1, function()
|
||||
player:flipImage()
|
||||
return player.isInvincible
|
||||
end)
|
||||
|
||||
|
||||
.. function:: Timer.during(delay, func[, after])
|
||||
|
||||
:param number delay: Number of seconds the func will be called.
|
||||
:param function func: The function to be called on ``update(dt)``.
|
||||
:param function after: A function to be called after delay seconds (optional).
|
||||
:returns: The timer handle. See also :func:`Timer.cancel`.
|
||||
|
||||
|
||||
Run ``func(dt)`` for the next ``delay`` seconds. The function is called every
|
||||
time ``update(dt)`` is called. Optionally run ``after()`` once ``delay``
|
||||
seconds have passed.
|
||||
|
||||
``after()`` will receive itself as only parameter.
|
||||
|
||||
.. note::
|
||||
You should not add new timers in ``func(dt)``, as this can lead to random
|
||||
crashes.
|
||||
|
||||
**Examples**::
|
||||
|
||||
-- play an animation for 5 seconds
|
||||
Timer.during(5, function(dt) animation:update(dt) end)
|
||||
|
||||
::
|
||||
|
||||
-- shake the camera for one second
|
||||
local orig_x, orig_y = camera:pos()
|
||||
Timer.during(1, function()
|
||||
camera:lookAt(orig_x + math.random(-2,2), orig_y + math.random(-2,2))
|
||||
end, function()
|
||||
-- reset camera position
|
||||
camera:lookAt(orig_x, orig_y)
|
||||
end)
|
||||
|
||||
::
|
||||
|
||||
player.isInvincible = true
|
||||
-- flash player for 3 seconds
|
||||
local t = 0
|
||||
player.timer:during(3, function(dt)
|
||||
t = t + dt
|
||||
player.visible = (t % .2) < .1
|
||||
end, function()
|
||||
-- make sure the player is visible after three seconds
|
||||
player.visible = true
|
||||
player.isInvincible = false
|
||||
end)
|
||||
|
||||
|
||||
.. function:: Timer.cancel(handle)
|
||||
|
||||
:param table handle: The function to be canceled.
|
||||
|
||||
Prevent a timer from being executed in the future.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function tick()
|
||||
print('tick... tock...')
|
||||
end
|
||||
handle = Timer.every(1, tick)
|
||||
-- later
|
||||
Timer.cancel(handle) -- NOT: Timer.cancel(tick)
|
||||
|
||||
::
|
||||
|
||||
-- using a timer instance
|
||||
function tick()
|
||||
print('tick... tock...')
|
||||
end
|
||||
handle = menuTimer:every(1, tick)
|
||||
-- later
|
||||
menuTimer:cancel(handle)
|
||||
|
||||
|
||||
.. function:: Timer.clear()
|
||||
|
||||
Remove all timed and periodic functions. Functions that have not yet been
|
||||
executed will discarded.
|
||||
|
||||
**Examples**::
|
||||
|
||||
Timer.clear()
|
||||
|
||||
::
|
||||
|
||||
menuTimer:clear()
|
||||
|
||||
|
||||
.. function:: Timer.update(dt)
|
||||
|
||||
:param number dt: Time that has passed since the last ``update()``.
|
||||
|
||||
Update timers and execute functions if the deadline is reached. Call in
|
||||
``love.update(dt)``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.update(dt)
|
||||
do_stuff()
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
-- using hump.gamestate and a timer instance
|
||||
function menuState:update(dt)
|
||||
self.timers:update(dt)
|
||||
end
|
||||
|
||||
|
||||
.. function:: Timer.tween(duration, subject, target, method, after, ...)
|
||||
|
||||
:param number duration: Duration of the tween.
|
||||
:param table subject: Object to be tweened.
|
||||
:param table target: Target values.
|
||||
:param string method: Tweening method, defaults to 'linear' (:ref:`see here
|
||||
<tweening-methods>`, optional).
|
||||
:param function after: Function to execute after the tween has finished
|
||||
(optiona).
|
||||
:param mixed ...: Additional arguments to the *tweening* function.
|
||||
:returns: A timer handle.
|
||||
|
||||
|
||||
`Tweening <http://en.wikipedia.org/wiki/Inbetweening>`_ (short for
|
||||
in-betweening) is the process that happens between two defined states. For
|
||||
example, a tween can be used to gradually fade out a graphic or move a text
|
||||
message to the center of the screen. For more information why tweening should
|
||||
be important to you, check out this great talk on `juicy games
|
||||
<http://www.youtube.com/watch?v=Fy0aCDmgnxg>`_.
|
||||
|
||||
``hump.timer`` offers two interfaces for tweening: the low-level
|
||||
:func:`Timer.during` and the higher level interface :func:`Timer.tween`.
|
||||
|
||||
To see which tweening methods hump offers, :ref:`see below <tweening-methods>`.
|
||||
|
||||
**Examples**::
|
||||
|
||||
function love.load()
|
||||
color = {0, 0, 0}
|
||||
Timer.tween(10, color, {255, 255, 255}, 'in-out-quad')
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.setBackgroundColor(color)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.load()
|
||||
circle = {rad = 10, pos = {x = 400, y = 300}}
|
||||
-- multiple tweens can work on the same subject
|
||||
-- and nested values can be tweened, too
|
||||
Timer.tween(5, circle, {rad = 50}, 'in-out-quad')
|
||||
Timer.tween(2, circle, {pos = {y = 550}}, 'out-bounce')
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.circle('fill', circle.pos.x, circle.pos.y, circle.rad)
|
||||
end
|
||||
|
||||
::
|
||||
|
||||
function love.load()
|
||||
-- repeated tweening
|
||||
|
||||
circle = {rad = 10, x = 100, y = 100}
|
||||
local grow, shrink, move_down, move_up
|
||||
grow = function()
|
||||
Timer.tween(1, circle, {rad = 50}, 'in-out-quad', shrink)
|
||||
end
|
||||
shrink = function()
|
||||
Timer.tween(2, circle, {rad = 10}, 'in-out-quad', grow)
|
||||
end
|
||||
|
||||
move_down = function()
|
||||
Timer.tween(3, circle, {x = 700, y = 500}, 'bounce', move_up)
|
||||
end
|
||||
move_up = function()
|
||||
Timer.tween(5, circle, {x = 200, y = 200}, 'out-elastic', move_down)
|
||||
end
|
||||
|
||||
grow()
|
||||
move_down()
|
||||
end
|
||||
|
||||
function love.update(dt)
|
||||
Timer.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.circle('fill', circle.x, circle.y, circle.rad)
|
||||
end
|
||||
|
||||
|
||||
|
||||
.. _tweening-methods:
|
||||
|
||||
Tweening methods
|
||||
----------------
|
||||
|
||||
At the core of tweening lie interpolation methods. These methods define how the
|
||||
output should look depending on how much time has passed. For example, consider
|
||||
the following tween::
|
||||
|
||||
-- now: player.x = 0, player.y = 0
|
||||
Timer.tween(2, player, {x = 2})
|
||||
Timer.tween(4, player, {y = 8})
|
||||
|
||||
At the beginning of the tweens (no time passed), the interpolation method would
|
||||
place the player at ``x = 0, y = 0``. After one second, the player should be at
|
||||
``x = 1, y = 2``, and after two seconds the output is ``x = 2, y = 4``.
|
||||
|
||||
The actual duration of and time since starting the tween is not important, only
|
||||
the fraction of the two. Similarly, the starting value and output are not
|
||||
important to the interpolation method, since it can be calculated from the
|
||||
start and end point. Thus an interpolation method can be fully characterized by
|
||||
a function that takes a number between 0 and 1 and returns a number that
|
||||
defines the output (usually also between 0 and 1). The interpolation function
|
||||
must hold that the output is 0 for input 0 and 1 for input 1.
|
||||
|
||||
**hump** predefines several commonly used interpolation methods, which are
|
||||
generalized versions of `Robert Penner's easing
|
||||
functions <http://www.robertpenner.com/easing/>`_. Those are:
|
||||
|
||||
``'linear'``,
|
||||
``'quad'``,
|
||||
``'cubic'``,
|
||||
``'quart'``,
|
||||
``'quint'``,
|
||||
``'sine'``,
|
||||
``'expo'``,
|
||||
``'circ'``,
|
||||
``'back'``,
|
||||
``'bounce'``, and
|
||||
``'elastic'``.
|
||||
|
||||
It's hard to understand how these functions behave by staring at a graph, so
|
||||
below are some animation examples. You can change the type of the tween by
|
||||
changing the selections.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div id="tween-graph"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
|
||||
<script src="_static/graph-tweens.js"></script>
|
||||
|
||||
Note that while the animations above show tweening of shapes, other attributes
|
||||
(color, opacity, volume of a sound, ...) can be changed as well.
|
||||
|
||||
|
||||
Custom interpolators
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. warning:
|
||||
This is a stub
|
||||
|
||||
You can add custom interpolation methods by adding them to the `tween` table::
|
||||
|
||||
Timer.tween.sqrt = function(t) return math.sqrt(t) end
|
||||
-- or just Timer.tween.sqrt = math.sqrt
|
||||
|
||||
Access the your method like you would the predefined ones. You can even use the
|
||||
modyfing prefixes::
|
||||
|
||||
Timer.tween(5, 'in-out-sqrt', circle, {radius = 50})
|
||||
|
||||
You can also invert and chain functions::
|
||||
|
||||
outsqrt = Timer.tween.out(math.sqrt)
|
||||
inoutsqrt = Timer.tween.chain(math.sqrt, outsqrt)
|
400
docs/vector-light.rst
Normal file
@ -0,0 +1,400 @@
|
||||
hump.vector-light
|
||||
=================
|
||||
|
||||
::
|
||||
|
||||
vector = require "hump.vector-light"
|
||||
|
||||
An table-free version of :doc:`hump.vector <vector>`. Instead of a vector type,
|
||||
``hump.vector-light`` provides functions that operate on numbers.
|
||||
|
||||
.. note::
|
||||
|
||||
Using this module instead of :doc:`hump.vector <vector>` may result in
|
||||
faster code, but does so at the expense of speed of development and code
|
||||
readability. Unless you are absolutely sure that your code is
|
||||
significantly slowed down by :doc:`hump.vector <vector>`, I recommend using
|
||||
it instead.
|
||||
|
||||
**Example**::
|
||||
|
||||
function player:update(dt)
|
||||
local dx,dy = 0,0
|
||||
if love.keyboard.isDown('left') then
|
||||
dx = -1
|
||||
elseif love.keyboard.isDown('right') then
|
||||
dx = 1
|
||||
end
|
||||
if love.keyboard.isDown('up') then
|
||||
dy = -1
|
||||
elseif love.keyboard.isDown('down') then
|
||||
dy = 1
|
||||
end
|
||||
dx,dy = vector.normalize(dx, dy)
|
||||
|
||||
player.velx, player.vely = vector.add(player.velx, player.vely,
|
||||
vector.mul(dy, dx, dy))
|
||||
|
||||
if vector.len(player.velx, player.vely) > player.max_velocity then
|
||||
player.velx, player.vely = vector.mul(player.max_velocity,
|
||||
vector.normalize(player.velx, player.vely)
|
||||
end
|
||||
|
||||
player.x = player.x + dt * player.velx
|
||||
player.y = player.y + dt * player.vely
|
||||
end
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: vector.str(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: The string representation.
|
||||
|
||||
|
||||
Produce a human-readable string of the form ``(x,y)``.
|
||||
Useful for debugging.
|
||||
|
||||
**Example**::
|
||||
|
||||
print(vector.str(love.mouse.getPosition()))
|
||||
|
||||
|
||||
.. function:: vector.mul(s, x,y)
|
||||
|
||||
:param number s: A scalar.
|
||||
:param numbers x,y: A vector.
|
||||
:returns: ``x*s, y*s``.
|
||||
|
||||
|
||||
Computes ``x*s,y*s``. The order of arguments is chosen so that it's possible to
|
||||
chain operations (see example).
|
||||
|
||||
**Example**::
|
||||
|
||||
velx,vely = vec.mul(dt, vec.add(velx,vely, accx,accy))
|
||||
|
||||
|
||||
.. function:: vector.div(s, x,y)
|
||||
|
||||
:param number s: A scalar.
|
||||
:param numbers x,y: A vector.
|
||||
:returns: ``x/s, y/s``.
|
||||
|
||||
|
||||
Computes ``x/s,y/s``. The order of arguments is chosen so that it's possible to
|
||||
chain operations (see example).
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = vec.div(self.zoom, x-w/2, y-h/2)
|
||||
|
||||
|
||||
.. function:: vector.add(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1+x2, x1+x2``.
|
||||
|
||||
|
||||
Computes the sum \\((x1+x2, y1+y2)\\)`` of two vectors. Meant to be used in
|
||||
conjunction with other functions like :func:`vector.mul`.
|
||||
|
||||
**Example**::
|
||||
|
||||
player.x,player.y = vector.add(player.x,player.y, vector.mul(dt, dx,dy))
|
||||
|
||||
|
||||
.. function:: vector.sub(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1-x2, x1-x2``.
|
||||
|
||||
|
||||
Computes the difference \\((x1-x2, y1-y2)\\) of two vectors. Meant to be used in
|
||||
conjunction with other functions like :func:`vector.mul`.
|
||||
|
||||
**Example**::
|
||||
|
||||
dx,dy = vector.sub(400,300, love.mouse.getPosition())
|
||||
|
||||
|
||||
.. function:: vector.permul(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*x2, y1*y2``.
|
||||
|
||||
|
||||
Component-wise multiplication, i.e.: ``x1*x2, y1*y2``.
|
||||
|
||||
**Example**::
|
||||
|
||||
x,y = vector.permul(x,y, 1,1.5)
|
||||
|
||||
|
||||
.. function:: vector.dot(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*x2 + y1*y2``.
|
||||
|
||||
|
||||
Computes the `dot product <http://en.wikipedia.org/wiki/Dot_product>`_ of two
|
||||
vectors: ``x1*x2 + y1*y2``.
|
||||
|
||||
**Example**::
|
||||
|
||||
cosphi = vector.dot(rx,ry, vx,vy)
|
||||
|
||||
|
||||
.. function:: vector.cross(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*y2 - y1*x2``.
|
||||
|
||||
|
||||
Computes the `cross product <http://en.wikipedia.org/wiki/Cross_product>`_ of
|
||||
two vectors: ``x1*y2 - y1*x2``.
|
||||
|
||||
**Example**::
|
||||
|
||||
parallelogram_area = vector.cross(ax,ay, bx,by)
|
||||
|
||||
|
||||
.. function:: vector.vector.det(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1*y2 - y1*x2``.
|
||||
|
||||
|
||||
Alias to :func:`vector.cross`.
|
||||
|
||||
**Example**::
|
||||
|
||||
parallelogram_area = vector.det(ax,ay, bx,by)
|
||||
|
||||
|
||||
.. function:: vector.eq(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1 == x2 and y1 == y2``
|
||||
|
||||
Test for equality.
|
||||
|
||||
**Example**::
|
||||
|
||||
if vector.eq(x1,y1, x2,y2) then be.happy() end
|
||||
|
||||
|
||||
.. function:: vector.le(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1 <= x2 and y1 <= y2``.
|
||||
|
||||
Test for partial lexicographical order, ``<=``.
|
||||
|
||||
**Example**::
|
||||
|
||||
if vector.le(x1,y1, x2,y2) then be.happy() end
|
||||
|
||||
|
||||
.. function:: vector.lt(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: ``x1 < x2 or (x1 == x2) and y1 <= y2``.
|
||||
|
||||
|
||||
Test for strict lexicographical order, ``<``.
|
||||
|
||||
**Example**::
|
||||
|
||||
if vector.lt(x1,y1, x2,y2) then be.happy() end
|
||||
|
||||
|
||||
.. function:: vector.len(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: Length of the vector.
|
||||
|
||||
Get length of a vector, i.e. ``math.sqrt(x*x + y*y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
distance = vector.len(love.mouse.getPosition())
|
||||
|
||||
|
||||
.. function:: vector.len2(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: Squared length of the vector.
|
||||
|
||||
Get squared length of a vector, i.e. ``x*x + y*y``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
closest, dsq = vertices[1], vector.len2(px-vertices[1].x, py-vertices[1].y)
|
||||
for i = 2,#vertices do
|
||||
local temp = vector.len2(px-vertices[i].x, py-vertices[i].y)
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.dist(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: The distance of the points.
|
||||
|
||||
|
||||
Get distance of two points. The same as ``vector.len(x1-x2, y1-y2)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
-- slightly slower than the example using len2()
|
||||
closest, dist = vertices[1], vector.dist(px,py, vertices[1].x,vertices[1].y)
|
||||
for i = 2,#vertices do
|
||||
local temp = vector.dist(px,py, vertices[i].x,vertices[i].y)
|
||||
if temp < dist then
|
||||
closest, dist = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.dist2(x1,y1, x2,y2)
|
||||
|
||||
:param numbers x1,y1: First vector.
|
||||
:param numbers x2,y2: Second vector.
|
||||
:returns: The squared distance of two points.
|
||||
|
||||
Get squared distance of two points. The same as ``vector.len2(x1-x2, y1-y2)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
closest, dsq = vertices[1], vector.dist2(px,py, vertices[1].x,vertices[1].y)
|
||||
for i = 2,#vertices do
|
||||
local temp = vector.dist2(px,py, vertices[i].x,vertices[i].y)
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.normalize(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: Vector with same direction as the input vector, but length 1.
|
||||
|
||||
|
||||
Get normalized vector, i.e. a vector with the same direction as the input
|
||||
vector, but with length 1.
|
||||
|
||||
**Example**::
|
||||
|
||||
dx,dy = vector.normalize(vx,vy)
|
||||
|
||||
|
||||
.. function:: vector.rotate(phi, x,y)
|
||||
|
||||
:param number phi: Rotation angle in radians.
|
||||
:param numbers x,y: The vector.
|
||||
:returns: The rotated vector
|
||||
|
||||
|
||||
Get a rotated vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- approximate a circle
|
||||
circle = {}
|
||||
for i = 1,30 do
|
||||
local phi = 2 * math.pi * i / 30
|
||||
circle[i*2-1], circle[i*2] = vector.rotate(phi, 0,1)
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector.perpendicular(x,y)
|
||||
|
||||
:param numbers x,y: The vector.
|
||||
:returns: A vector perpendicular to the input vector
|
||||
|
||||
|
||||
Quick rotation by 90°. The same (but faster) as ``vector.rotate(math.pi/2, x,y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
nx,ny = vector.normalize(vector.perpendicular(bx-ax, by-ay))
|
||||
|
||||
|
||||
.. function:: vector.project(x,y, u,v)
|
||||
|
||||
:param numbers x,y: The vector to project.
|
||||
:param numbers u,v: The vector to project onto.
|
||||
:returns: The projected vector.
|
||||
|
||||
|
||||
Project vector onto another vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
vx_p,vy_p = vector.project(vx,vy, ax,ay)
|
||||
|
||||
|
||||
.. function:: vector.mirror(x,y, u,v)
|
||||
|
||||
:param numbers x,y: The vector to mirror.
|
||||
:param numbers u,v: The vector defining the axis.
|
||||
:returns: The mirrored vector.
|
||||
|
||||
|
||||
Mirrors vector on the axis defined by the other vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
vx,vy = vector.mirror(vx,vy, surface.x,surface.y)
|
||||
|
||||
|
||||
.. function:: vector.angleTo(ox,y, u,v)
|
||||
|
||||
:param numbers x,y: Vector to measure the angle.
|
||||
:param numbers u,v (optional): Reference vector.
|
||||
:returns: Angle in radians.
|
||||
|
||||
|
||||
Measures the angle between two vectors. ``u`` and ``v`` default to ``0`` if omitted,
|
||||
i.e. the function returns the angle to the coordinate system.
|
||||
|
||||
**Example**::
|
||||
|
||||
lean = vector.angleTo(self.upx, self.upy, 0,1)
|
||||
if lean > .1 then self:fallOver() end
|
||||
|
||||
|
||||
.. function:: vector.trim(max_length, x,y)
|
||||
|
||||
:param number max_length: Maximum allowed length of the vector.
|
||||
:param numbers x,y: Vector to trum.
|
||||
:returns: The trimmed vector.
|
||||
|
||||
Trim the vector to ``max_length``, i.e. return a vector that points in the same
|
||||
direction as the source vector, but has a magnitude smaller or equal to
|
||||
``max_length``.
|
||||
|
||||
**Example**::
|
||||
|
||||
vel_x, vel_y = vector.trim(299792458,
|
||||
vector.add(vel_x, vel_y,
|
||||
vector.mul(mass * dt, force_x, force_y)))
|
422
docs/vector.rst
Normal file
@ -0,0 +1,422 @@
|
||||
hump.vector
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
vector = require "hump.vector"
|
||||
|
||||
A handy 2D vector class providing most of the things you do with vectors.
|
||||
|
||||
You can access the individual coordinates by ``vec.x`` and ``vec.y``.
|
||||
|
||||
.. note::
|
||||
|
||||
The vectors are stored as tables. Most operations create new vectors and
|
||||
thus new tables, which *may* put the garbage collector under stress.
|
||||
If you experience slowdowns that are caused by hump.vector, try the
|
||||
table-less version :doc:`hump.vector-light <vector-light>`.
|
||||
|
||||
**Example**::
|
||||
|
||||
function player:update(dt)
|
||||
local delta = vector(0,0)
|
||||
if love.keyboard.isDown('left') then
|
||||
delta.x = -1
|
||||
elseif love.keyboard.isDown('right') then
|
||||
delta.x = 1
|
||||
end
|
||||
if love.keyboard.isDown('up') then
|
||||
delta.y = -1
|
||||
elseif love.keyboard.isDown('down') then
|
||||
delta.y = 1
|
||||
end
|
||||
delta:normalizeInplace()
|
||||
|
||||
player.velocity = player.velocity + delta * player.acceleration * dt
|
||||
|
||||
if player.velocity:len() > player.max_velocity then
|
||||
player.velocity = player.velocity:normalized() * player.max_velocity
|
||||
end
|
||||
|
||||
player.position = player.position + player.velocity * dt
|
||||
end
|
||||
|
||||
|
||||
Vector arithmetic
|
||||
-----------------
|
||||
|
||||
**hump** provides vector arithmetic by implement the corresponding metamethods
|
||||
(``__add``, ``__mul``, etc.). Here are the semantics:
|
||||
|
||||
``vector + vector = vector``
|
||||
Component wise sum: \\((a,b) + (x,y) = (a+x, b+y)\\)
|
||||
``vector - vector = vector``
|
||||
Component wise difference: \\((a,b) - (x,y) = (a-x, b-y)\\)
|
||||
``vector * vector = number``
|
||||
Dot product: \\((a,b) \\cdot (x,y) = a\\cdot x + b\\cdot y\\)
|
||||
``number * vector = vector``
|
||||
Scalar multiplication/scaling: \\((a,b) \\cdot s = (s\\cdot a, s\\cdot b)\\)
|
||||
``vector * number = vector``
|
||||
Scalar multiplication/scaling: \\(s \\cdot (x,y) = (s\\cdot x, s\\cdot y)\\)
|
||||
``vector / number = vector``
|
||||
Scalar multiplication/scaling: \\((a,b) / s = (a/s, b/s)\\).
|
||||
|
||||
Common relations are also defined:
|
||||
|
||||
``a == b``
|
||||
Same as ``a.x == b.x and a.y == b.y``.
|
||||
``a <= b``
|
||||
Same as ``a.x <= b.x and a.y <= b.y``.
|
||||
``a < b``
|
||||
Lexicographical order: ``a.x < b.x or (a.x == b.x and a.y < b.y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- acceleration, player.velocity and player.position are vectors
|
||||
acceleration = vector(0,-9)
|
||||
player.velocity = player.velocity + acceleration * dt
|
||||
player.position = player.position + player.velocity * dt
|
||||
|
||||
|
||||
Function Reference
|
||||
------------------
|
||||
|
||||
.. function:: vector.new(x,y)
|
||||
|
||||
:param numbers x,y: Coordinates.
|
||||
:returns: The vector.
|
||||
|
||||
|
||||
Create a new vector.
|
||||
|
||||
**Examples**::
|
||||
|
||||
a = vector.new(10,10)
|
||||
|
||||
::
|
||||
|
||||
-- as a shortcut, you can call the module like a function:
|
||||
vector = require "hump.vector"
|
||||
a = vector(10,10)
|
||||
|
||||
|
||||
.. function:: vector.isvector(v)
|
||||
|
||||
:param mixed v: The variable to test.
|
||||
:returns: ``true`` if ``v`` is a vector, ``false`` otherwise.
|
||||
|
||||
Test whether a variable is a vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
if not vector.isvector(v) then
|
||||
v = vector(v,0)
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:clone()
|
||||
|
||||
:returns: Copy of the vector.
|
||||
|
||||
Copy a vector. Assigning a vector to a variable will create a *reference*, so
|
||||
when modifying the vector referenced by the new variable would also change the
|
||||
old one::
|
||||
|
||||
a = vector(1,1) -- create vector
|
||||
b = a -- b references a
|
||||
c = a:clone() -- c is a copy of a
|
||||
b.x = 0 -- changes a,b and c
|
||||
print(a,b,c) -- prints '(1,0), (1,0), (1,1)'
|
||||
|
||||
**Example**::
|
||||
|
||||
copy = original:clone()
|
||||
|
||||
|
||||
.. function:: vector:unpack()
|
||||
|
||||
:returns: The coordinates ``x,y``.
|
||||
|
||||
|
||||
Extract coordinates.
|
||||
|
||||
**Examples**::
|
||||
|
||||
x,y = pos:unpack()
|
||||
|
||||
::
|
||||
|
||||
love.graphics.draw(self.image, self.pos:unpack())
|
||||
|
||||
|
||||
.. function:: vector:permul(other)
|
||||
|
||||
:param vector other: The second source vector.
|
||||
:returns: Vector whose components are products of the source vectors.
|
||||
|
||||
|
||||
Multiplies vectors coordinate wise, i.e. ``result = vector(a.x * b.x, a.y *
|
||||
b.y)``.
|
||||
|
||||
Does not change either argument vectors, but creates a new one.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- scale with different magnitudes
|
||||
scaled = original:permul(vector(1,1.5))
|
||||
|
||||
|
||||
.. function:: vector:len()
|
||||
|
||||
:returns: Length of the vector.
|
||||
|
||||
|
||||
Get length of the vector, i.e. ``math.sqrt(vec.x * vec.x + vec.y * vec.y)``.
|
||||
|
||||
**Example**::
|
||||
|
||||
distance = (a - b):len()
|
||||
|
||||
|
||||
.. function:: vector:len2()
|
||||
|
||||
:returns: Squared length of the vector.
|
||||
|
||||
|
||||
Get squared length of the vector, i.e. ``vec.x * vec.x + vec.y * vec.y``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
closest, dsq = vertices[1], (pos - vertices[1]):len2()
|
||||
for i = 2,#vertices do
|
||||
local temp = (pos - vertices[i]):len2()
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:dist(other)
|
||||
|
||||
:param vector other: Other vector to measure the distance to.
|
||||
:returns: The distance of the vectors.
|
||||
|
||||
|
||||
Get distance of two vectors. The same as ``(a - b):len()``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
-- slightly slower than the example using len2()
|
||||
closest, dist = vertices[1], pos:dist(vertices[1])
|
||||
for i = 2,#vertices do
|
||||
local temp = pos:dist(vertices[i])
|
||||
if temp < dist then
|
||||
closest, dist = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:dist2(other)
|
||||
|
||||
:param vector other: Other vector to measure the distance to.
|
||||
:returns: The squared distance of the vectors.
|
||||
|
||||
|
||||
Get squared distance of two vectors. The same as ``(a - b):len2()``.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- get closest vertex to a given vector
|
||||
-- slightly faster than the example using len2()
|
||||
closest, dsq = vertices[1], pos:dist2(vertices[1])
|
||||
for i = 2,#vertices do
|
||||
local temp = pos:dist2(vertices[i])
|
||||
if temp < dsq then
|
||||
closest, dsq = vertices[i], temp
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
.. function:: vector:normalized()
|
||||
|
||||
:returns: Vector with same direction as the input vector, but length 1.
|
||||
|
||||
|
||||
Get normalized vector: a vector with the same direction as the input vector,
|
||||
but with length 1.
|
||||
|
||||
Does not change the input vector, but creates a new vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
direction = velocity:normalized()
|
||||
|
||||
|
||||
.. function:: vector:normalizeInplace()
|
||||
|
||||
:returns: Itself -- the normalized vector
|
||||
|
||||
|
||||
Normalize a vector, i.e. make the vector to have length 1. Great to use on
|
||||
intermediate results.
|
||||
|
||||
.. warning::
|
||||
This modifies the vector. If in doubt, use :func:`vector:normalized()`.
|
||||
|
||||
**Example**::
|
||||
|
||||
normal = (b - a):perpendicular():normalizeInplace()
|
||||
|
||||
|
||||
.. function:: vector:rotated(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians.
|
||||
:returns: The rotated vector
|
||||
|
||||
|
||||
Get a vector with same length, but rotated by ``angle``:
|
||||
|
||||
.. image:: _static/vector-rotated.png
|
||||
:alt: Sketch of rotated vector.
|
||||
|
||||
Does not change the input vector, but creates a new vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- approximate a circle
|
||||
circle = {}
|
||||
for i = 1,30 do
|
||||
local phi = 2 * math.pi * i / 30
|
||||
circle[#circle+1] = vector(0,1):rotated(phi)
|
||||
end
|
||||
|
||||
.. function:: vector:rotateInplace(angle)
|
||||
|
||||
:param number angle: Rotation angle in radians.
|
||||
:returns: Itself -- the rotated vector
|
||||
|
||||
|
||||
Rotate a vector in-place. Great to use on intermediate results.
|
||||
|
||||
.. warning::
|
||||
Yhis modifies the vector. If in doubt, use :func:`vector:rotated()`.
|
||||
|
||||
**Example**::
|
||||
|
||||
-- ongoing rotation
|
||||
spawner.direction:rotateInplace(dt)
|
||||
|
||||
|
||||
.. function:: vector:perpendicular()
|
||||
|
||||
:returns: A vector perpendicular to the input vector
|
||||
|
||||
Quick rotation by 90°. Creates a new vector. The same (but faster) as
|
||||
``vec:rotate(math.pi/2)``:
|
||||
|
||||
.. image:: _static/vector-perpendicular.png
|
||||
:alt: Sketch of two perpendicular vectors
|
||||
|
||||
**Example**::
|
||||
|
||||
normal = (b - a):perpendicular():normalizeInplace()
|
||||
|
||||
|
||||
|
||||
.. function:: vector:projectOn(v)
|
||||
|
||||
:param vector v: The vector to project on.
|
||||
:returns: ``vector`` The projected vector.
|
||||
|
||||
|
||||
Project vector onto another vector:
|
||||
|
||||
.. image:: _static/vector-projectOn.png
|
||||
:alt: Sketch of vector projection.
|
||||
|
||||
**Example**::
|
||||
|
||||
velocity_component = velocity:projectOn(axis)
|
||||
|
||||
|
||||
|
||||
.. function:: vector:mirrorOn(v)
|
||||
|
||||
:param vector v: The vector to mirror on.
|
||||
:returns: The mirrored vector.
|
||||
|
||||
|
||||
Mirrors vector on the axis defined by the other vector:
|
||||
|
||||
.. image: _static/vector-mirrorOn.png
|
||||
:alt: Sketch of a vector mirrored on another vector
|
||||
|
||||
**Example**::
|
||||
|
||||
deflected_velocity = ball.velocity:mirrorOn(surface_normal)
|
||||
|
||||
|
||||
.. function:: vector:cross(other)
|
||||
|
||||
:param vector other: Vector to compute the cross product with.
|
||||
:returns: ``number`` Cross product of both vectors.
|
||||
|
||||
|
||||
Get cross product of two vectors. Equals the area of the parallelogram spanned
|
||||
by both vectors.
|
||||
|
||||
**Example**::
|
||||
|
||||
parallelogram_area = a:cross(b)
|
||||
|
||||
|
||||
.. function:: vector:angleTo(other)
|
||||
|
||||
:param vector other: Vector to measure the angle to (optional).
|
||||
:returns: Angle in radians.
|
||||
|
||||
|
||||
Measures the angle between two vectors. If ``other`` is omitted it defaults
|
||||
to the vector ``(0,0)``, i.e. the function returns the angle to the coordinate
|
||||
system.
|
||||
|
||||
**Example**::
|
||||
|
||||
lean = self.upvector:angleTo(vector(0,1))
|
||||
if lean > .1 then self:fallOver() end
|
||||
|
||||
.. function:: vector:trimmed(max_length)
|
||||
|
||||
:param number max_length: Maximum allowed length of the vector.
|
||||
:returns: A trimmed vector.
|
||||
|
||||
Trim the vector to ``max_length``, i.e. return a vector that points in the same
|
||||
direction as the source vector, but has a magnitude smaller or equal to
|
||||
``max_length``.
|
||||
|
||||
Does not change the input vector, but creates a new vector.
|
||||
|
||||
**Example**::
|
||||
|
||||
ship.velocity = ship.force * ship.mass * dt
|
||||
ship.velocity = ship.velocity:trimmed(299792458)
|
||||
|
||||
|
||||
.. function:: vector:trimInplace(max_length)
|
||||
|
||||
:param number max_length: Maximum allowed length of the vector.
|
||||
:returns: Itself -- the trimmed vector.
|
||||
|
||||
Trim the vector to ``max_length``, i.e. return a vector that points in the same
|
||||
direction as the source vector, but has a magnitude smaller or equal to
|
||||
``max_length``.
|
||||
|
||||
.. warning::
|
||||
Yhis modifies the vector. If in doubt, use :func:`vector:trimmed()`.
|
||||
|
||||
|
||||
**Example**::
|
||||
|
||||
ship.velocity = (ship.velocity + ship.force * ship.mass * dt):trimInplace(299792458)
|
@ -30,63 +30,55 @@ local function __NULL__() end
|
||||
local state_init = setmetatable({leave = __NULL__},
|
||||
{__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end})
|
||||
local stack = {state_init}
|
||||
local initialized_states = setmetatable({}, {__mode = "k"})
|
||||
local state_is_dirty = true
|
||||
|
||||
local GS = {}
|
||||
function GS.new(t) return t or {} end -- constructor - deprecated!
|
||||
|
||||
local function change_state(stack_offset, to, ...)
|
||||
local pre = stack[#stack]
|
||||
|
||||
-- initialize only on first call
|
||||
;(initialized_states[to] or to.init or __NULL__)(to)
|
||||
initialized_states[to] = __NULL__
|
||||
|
||||
stack[#stack+stack_offset] = to
|
||||
state_is_dirty = true
|
||||
return (to.enter or __NULL__)(to, pre, ...)
|
||||
end
|
||||
|
||||
function GS.switch(to, ...)
|
||||
assert(to, "Missing argument: Gamestate to switch to")
|
||||
local pre = stack[#stack]
|
||||
;(pre.leave or __NULL__)(pre)
|
||||
;(to.init or __NULL__)(to)
|
||||
to.init = nil
|
||||
stack[#stack] = to
|
||||
return (to.enter or __NULL__)(to, pre, ...)
|
||||
assert(to ~= GS, "Can't call switch with colon operator")
|
||||
;(stack[#stack].leave or __NULL__)(stack[#stack])
|
||||
return change_state(0, to, ...)
|
||||
end
|
||||
|
||||
function GS.push(to, ...)
|
||||
assert(to, "Missing argument: Gamestate to switch to")
|
||||
local pre = stack[#stack]
|
||||
;(to.init or __NULL__)(to)
|
||||
to.init = nil
|
||||
stack[#stack+1] = to
|
||||
return (to.enter or __NULL__)(to, pre, ...)
|
||||
assert(to ~= GS, "Can't call push with colon operator")
|
||||
return change_state(1, to, ...)
|
||||
end
|
||||
|
||||
function GS.pop()
|
||||
function GS.pop(...)
|
||||
assert(#stack > 1, "No more states to pop!")
|
||||
local pre = stack[#stack]
|
||||
local pre, to = stack[#stack], stack[#stack-1]
|
||||
stack[#stack] = nil
|
||||
return (pre.leave or __NULL__)(pre)
|
||||
;(pre.leave or __NULL__)(pre)
|
||||
state_is_dirty = true
|
||||
return (to.resume or __NULL__)(to, pre, ...)
|
||||
end
|
||||
|
||||
function GS.current()
|
||||
return stack[#stack]
|
||||
end
|
||||
|
||||
local all_callbacks = {
|
||||
"update",
|
||||
"draw",
|
||||
"focus",
|
||||
"keypressed",
|
||||
"keyreleased",
|
||||
"mousefocus",
|
||||
"mousepressed",
|
||||
"mousereleased",
|
||||
"resize",
|
||||
"textinput",
|
||||
"visible",
|
||||
"quit",
|
||||
"joystickadded",
|
||||
"joystickremoved",
|
||||
"joystickpressed",
|
||||
"joystickreleased",
|
||||
"joystickaxis",
|
||||
"joystickhat",
|
||||
"gamepadpressed",
|
||||
"gamepadreleased",
|
||||
"gamepadaxis"
|
||||
}
|
||||
-- fetch event callbacks from love.handlers
|
||||
local all_callbacks = { 'draw', 'errhand', 'update' }
|
||||
for k in pairs(love.handlers) do
|
||||
all_callbacks[#all_callbacks+1] = k
|
||||
end
|
||||
|
||||
function GS.registerEvents(callbacks)
|
||||
local registry = {}
|
||||
@ -102,9 +94,15 @@ end
|
||||
|
||||
-- forward any undefined functions
|
||||
setmetatable(GS, {__index = function(_, func)
|
||||
return function(...)
|
||||
return (stack[#stack][func] or __NULL__)(stack[#stack], ...)
|
||||
-- call function only if at least one 'update' was called beforehand
|
||||
-- (see issue #46)
|
||||
if not state_is_dirty or func == 'update' then
|
||||
state_is_dirty = false
|
||||
return function(...)
|
||||
return (stack[#stack][func] or __NULL__)(stack[#stack], ...)
|
||||
end
|
||||
end
|
||||
return __NULL__
|
||||
end})
|
||||
|
||||
return GS
|
||||
|
32
signal.lua
@ -58,36 +58,38 @@ function Registry:clear(...)
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:emit_pattern(p, ...)
|
||||
function Registry:emitPattern(p, ...)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self:emit(s, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:remove_pattern(p, ...)
|
||||
function Registry:removePattern(p, ...)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self:remove(s, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
function Registry:clear_pattern(p)
|
||||
function Registry:clearPattern(p)
|
||||
for s in pairs(self) do
|
||||
if s:match(p) then self[s] = {} end
|
||||
end
|
||||
end
|
||||
|
||||
local function new()
|
||||
-- instancing
|
||||
function Registry.new()
|
||||
return setmetatable({}, Registry)
|
||||
end
|
||||
local default = new()
|
||||
|
||||
return setmetatable({
|
||||
new = new,
|
||||
register = function(...) default:register(...) end,
|
||||
emit = function(...) default:emit(...) end,
|
||||
remove = function(...) default:remove(...) end,
|
||||
clear = function(...) default:clear(...) end,
|
||||
emit_pattern = function(...) default:emit_pattern(...) end,
|
||||
remove_pattern = function(...) default:remove_pattern(...) end,
|
||||
clear_pattern = function(...) default:clear_pattern(...) end,
|
||||
}, {__call = new})
|
||||
-- default instance
|
||||
local default = Registry.new()
|
||||
|
||||
-- module forwards calls to default instance
|
||||
local module = {}
|
||||
for k in pairs(Registry) do
|
||||
if k ~= "__index" then
|
||||
module[k] = function(...) return default[k](default, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(module, {__call = Registry.new})
|
||||
|
62
timer.lua
@ -29,10 +29,6 @@ Timer.__index = Timer
|
||||
|
||||
local function _nothing_() end
|
||||
|
||||
local function new()
|
||||
return setmetatable({functions = {}, tween = Timer.tween}, Timer)
|
||||
end
|
||||
|
||||
function Timer:update(dt)
|
||||
local to_remove = {}
|
||||
for handle, delay in pairs(self.functions) do
|
||||
@ -49,20 +45,20 @@ function Timer:update(dt)
|
||||
end
|
||||
end
|
||||
|
||||
function Timer:do_for(delay, func, after)
|
||||
function Timer:during(delay, func, after)
|
||||
local handle = {func = func, after = after or _nothing_}
|
||||
self.functions[handle] = delay
|
||||
return handle
|
||||
end
|
||||
|
||||
function Timer:add(delay, func)
|
||||
return self:do_for(delay, _nothing_, func)
|
||||
function Timer:after(delay, func)
|
||||
return self:during(delay, _nothing_, func)
|
||||
end
|
||||
|
||||
function Timer:addPeriodic(delay, func, count)
|
||||
function Timer:every(delay, func, count)
|
||||
local count, handle = count or math.huge -- exploit below: math.huge - 1 = math.huge
|
||||
|
||||
handle = self:add(delay, function(f)
|
||||
handle = self:after(delay, function(f)
|
||||
if func(func) == false then return end
|
||||
count = count - 1
|
||||
if count > 0 then
|
||||
@ -80,6 +76,14 @@ function Timer:clear()
|
||||
self.functions = {}
|
||||
end
|
||||
|
||||
function Timer:script(f)
|
||||
local co = coroutine.wrap(f)
|
||||
co(function(t)
|
||||
self:after(t, co)
|
||||
coroutine.yield()
|
||||
end)
|
||||
end
|
||||
|
||||
Timer.tween = setmetatable({
|
||||
-- helper functions
|
||||
out = function(f) -- 'rotates' a function
|
||||
@ -137,7 +141,7 @@ __call = function(tween, self, len, subject, target, method, after, ...)
|
||||
local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...}
|
||||
|
||||
local last_s = 0
|
||||
return self:do_for(len, function(dt)
|
||||
return self:during(len, function(dt)
|
||||
t = t + dt
|
||||
local s = method(math.min(1, t/len), unpack(args))
|
||||
local ds = s - last_s
|
||||
@ -170,21 +174,25 @@ __index = function(tweens, key)
|
||||
or error('Unknown interpolation method: ' .. key)
|
||||
end})
|
||||
|
||||
-- default timer
|
||||
local default = new()
|
||||
-- Timer instancing
|
||||
function Timer.new()
|
||||
return setmetatable({functions = {}, tween = Timer.tween}, Timer)
|
||||
end
|
||||
|
||||
-- the module
|
||||
return setmetatable({
|
||||
new = new,
|
||||
update = function(...) return default:update(...) end,
|
||||
do_for = function(...) return default:do_for(...) end,
|
||||
add = function(...) return default:add(...) end,
|
||||
addPeriodic = function(...) return default:addPeriodic(...) end,
|
||||
cancel = function(...) return default:cancel(...) end,
|
||||
clear = function(...) return default:clear(...) end,
|
||||
tween = setmetatable({}, {
|
||||
__index = Timer.tween,
|
||||
__newindex = function(_,k,v) Timer.tween[k] = v end,
|
||||
__call = function(t,...) return default:tween(...) end,
|
||||
})
|
||||
}, {__call = new})
|
||||
-- default instance
|
||||
local default = Timer.new()
|
||||
|
||||
-- module forwards calls to default instance
|
||||
local module = {}
|
||||
for k in pairs(Timer) do
|
||||
if k ~= "__index" then
|
||||
module[k] = function(...) return default[k](default, ...) end
|
||||
end
|
||||
end
|
||||
module.tween = setmetatable({}, {
|
||||
__index = Timer.tween,
|
||||
__newindex = function(k,v) Timer.tween[k] = v end,
|
||||
__call = function(t, ...) return default:tween(...) end,
|
||||
})
|
||||
|
||||
return setmetatable(module, {__call = Timer.new})
|
||||
|
@ -116,7 +116,7 @@ end
|
||||
-- ref.: http://blog.signalsondisplay.com/?p=336
|
||||
local function trim(maxLen, x, y)
|
||||
local s = maxLen * maxLen / len2(x, y)
|
||||
s = s < 1 and 1 or math.sqrt(s)
|
||||
s = s > 1 and 1 or math.sqrt(s)
|
||||
return x * s, y * s
|
||||
end
|
||||
|
||||
|
20
vector.lua
@ -36,7 +36,7 @@ end
|
||||
local zero = new(0,0)
|
||||
|
||||
local function isvector(v)
|
||||
return getmetatable(v) == vector
|
||||
return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number'
|
||||
end
|
||||
|
||||
function vector:clone()
|
||||
@ -120,7 +120,7 @@ function vector.dist2(a, b)
|
||||
return (dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
function vector:normalize_inplace()
|
||||
function vector:normalizeInplace()
|
||||
local l = self:len()
|
||||
if l > 0 then
|
||||
self.x, self.y = self.x / l, self.y / l
|
||||
@ -129,10 +129,10 @@ function vector:normalize_inplace()
|
||||
end
|
||||
|
||||
function vector:normalized()
|
||||
return self:clone():normalize_inplace()
|
||||
return self:clone():normalizeInplace()
|
||||
end
|
||||
|
||||
function vector:rotate_inplace(phi)
|
||||
function vector:rotateInplace(phi)
|
||||
local c, s = cos(phi), sin(phi)
|
||||
self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y
|
||||
return self
|
||||
@ -167,25 +167,25 @@ function vector:cross(v)
|
||||
end
|
||||
|
||||
-- ref.: http://blog.signalsondisplay.com/?p=336
|
||||
function vector:trim_inplace(maxLen)
|
||||
function vector:trimInplace(maxLen)
|
||||
local s = maxLen * maxLen / self:len2()
|
||||
s = s < 1 and 1 or math.sqrt(s)
|
||||
s = (s > 1 and 1) or math.sqrt(s)
|
||||
self.x, self.y = self.x * s, self.y * s
|
||||
return self
|
||||
end
|
||||
|
||||
function vector:angleTo(other)
|
||||
if other then
|
||||
return atan2(self.y, self.y) - atan2(other.y, other.x)
|
||||
return atan2(self.y, self.x) - atan2(other.y, other.x)
|
||||
end
|
||||
return atan2(self.y, self.y)
|
||||
return atan2(self.y, self.x)
|
||||
end
|
||||
|
||||
function vector:trimmed(maxLen)
|
||||
return self:clone():trim_inplace(maxLen)
|
||||
return self:clone():trimInplace(maxLen)
|
||||
end
|
||||
|
||||
|
||||
-- the module
|
||||
return setmetatable({new = new, isvector = isvector, zero = zero},
|
||||
{__call = function(_, ...) return new(...) end})
|
||||
{__call = function(_, ...) return new(...) end})
|
||||
|