Using Löve2d's SpriteBatch

I keep forgetting how to use SpriteBatch, so I wrote a short snippet to remind myself and anyone else interested in Löve2d.

function love.load()
  -- Load our image we want to draw many times
  image = love.graphics.newImage("dirt.png")

  -- The number of tiles we want to draw is pretty much the number
  -- that will fit on the screen
  maxX = math.ceil(love.graphics.getWidth()  / image:getWidth())  + 2
  maxY = math.ceil(love.graphics.getHeight() / image:getHeight()) + 2
  local size = maxX * maxY
  print(size)

  -- Set up a sprite batch with our single image and the max number of times we
  -- want to be able to draw it. Later we will call spriteBatch:add() to tell
  -- Love where we want to draw our image
  spriteBatch = love.graphics.newSpriteBatch(image, size)
  setupSpriteBatch()
end

function love.update(dt)
  -- If we were using a moving background, we would want to only call
  -- setupSpriteBatch when its range or position changed
  setupSpriteBatch()
end

function setupSpriteBatch()
  spriteBatch:clear()

  -- Set up (but don't draw) our images in a grid
  for y = 0, maxY do
    for x = 0, maxX do
      -- Convert our x/y grid references to x/y pixel coordinates
      local xPos = x * image:getWidth()
      local yPos = y * image:getHeight()

      -- Add the image we previously set to this point
      spriteBatch:add(xPos, yPos)
    end
  end
end

function love.draw()
  -- Draw the spriteBatch with only one call!
  love.graphics.setColor(255,255,255)
  love.graphics.draw(spriteBatch)

  -- Draw FPS in the bottom right corner
  love.graphics.setColor(255,0,0)
  love.graphics.print(tostring(love.timer.getFPS()), love.graphics.getWidth()-30, love.graphics.getHeight()-20)
end

For a more advanced example using Quads, check out Efficient Tile-based Scrolling.

Subclassing Löve2d's HardonCollider Shapes

This took me way too long to get my head around, but here is how to subclass HardonCollider shapes.

To add these to the spatial hash that HC uses to track objects, I had to mess with HardonCollider’s init.lua.

hardoncollider/init.lua

-- Add this line to the main hardoncollider/init.lua file around line 136
-- to allow you to add preconstructed shapes.
function HC:addExistingShape(shape)
  return new_shape(self, shape)
end

main.lua

Sprite = require("sprite")

function love.load()
  -- Effectively Sprite:new(x, y, width, height)
  mySprite = common.instance(Sprite, 200, 200, 50, 200)
end

function love.draw()
  -- center() is from ConvexPolygonShape
  local x, y = mySprite:center()
  
  -- Run our custom method
  love.graphics.print(mySprite:hello(), x, y)

  -- Implemented by ConvexPolygonShape
  mySprite:draw('line')
end

sprite.lua

-- Have to require HC because it sets up the class stuff
HC = require("hardoncollider")

Shapes = require("hardoncollider.shapes")
Polygon = require("hardoncollider.polygon")

local Sprite = {}
function Sprite:init(x, y, width, height)
  -- ConvexPolygonShape requires an instance of Polygon
  -- set up a square with our size. Could be set based on the image
  -- in our sprite
  local polygon = common.instance(Polygon,
    x - width/2, y - height/2,
    x + width/2, y - height/2,
    x + width/2, y + height/2,
    x - width/2, y + height/2)

  -- Run our parent's constructor
	Shapes.ConvexPolygonShape.init(self, polygon)

  self.bacon = 42
end

function Sprite:hello()
  return "Hey, bacon is currently " .. self.bacon
end

Sprite = common.class("Sprite", Sprite, Shapes.ConvexPolygonShape)
return Sprite

Drawing a Textured Polygon in Löve2d

Löve2d makes a lot of stuff super-easy, but I found rendering a polygon with a texture is a bit tricky. Here’s how to do it.

▲ The final product.

I modified a dirt texture is from PixelJoint and erased some bits, just to show the transparency works.

▲ Semitransparent tiling dirt texture.

Code

function love.load()
  -- Load our repeating texture
  local img      = love.graphics.newImage("dirt.png")

  -- Points for our polygon
  local vertices = {12,0, 396,7, 500,375, 195,520, 78,377}
  
  texturedPoly = newTexturedPoly(vertices, img)
end

function newTexturedPoly(vertices, img)
  -- We want our images to tile
  img:setWrap("repeat", "repeat")

  -- We need a quad so the img is repeated
  -- The quad width/height should be the max x/y of the poly
  local quad = love.graphics.newQuad(0, 0, 500, 520, img:getWidth(), img:getHeight())

  -- Set up and store our clipped canvas once as it's expensive
  local canvas = love.graphics.newCanvas()

  love.graphics.setCanvas(canvas)

  -- Our clipping function, we want to render within a polygon shape
  local myStencilFunction = function()
    love.graphics.polygon("fill", unpack(vertices))
  end
  love.graphics.setStencil(myStencilFunction)
  
  -- Setting to premultiplied means that pixels just get overlaid ignoring
  -- their alpha values. Then when we render this canvas object itself, we
  -- will use the alpha of the canvas itself
  love.graphics.setBlendMode("premultiplied")

  -- Draw the repeating image within the quad
  love.graphics.drawq(img, quad, 0, 0)

  -- Reset everything back to normal
  love.graphics.setBlendMode("alpha")
  love.graphics.setStencil()
  love.graphics.setCanvas()
  
  return canvas
end

function love.draw()
  -- Draw our canvas object texturedPoly
  love.graphics.draw(texturedPoly, 50, 50)

  -- showfps
  love.graphics.setColor(255,255,255,200)
  love.graphics.print(tostring(love.timer.getFPS()), love.graphics.getWidth()-30, love.graphics.getHeight()-20)
end

If you have any feedback then message me on Twitter.

Typography Presentation

I made a short presentation on typography (20mb) based on what I’d learned reading Detail in Typography. What little text there is, is in a mixture of Japanese and English.

Parsing Wiki Text (Part 2)

More on parsing Wikipedia markup. So you’ve abandoned the idea of using regular expressions to parse the markup, congratulations. This should save yourself from insanity and oblivion.

mwlib is a Python library for parsing Wiki markup. It seems semi-official. Whatever, it’s what we’re going to use. It can parse the zipped XML dumps as I’ve shown in previous tutorials. However you have to provide it with an ariticle name, and today we’re feeling more John Wayne.

How can we feed mwlib raw Wiki markup and get the same hierarchical deliciousness as output? Here’s how:

from mwlib.refine import compat

raw = """
=Sup=
Je suis le voyageur dans le baked beans.
"""

parsed = compat.parse_txt(raw)

A more involved example:

from mwlib.refine import compat,core
from mwlib.parser import nodes
import pprint

def main():
  pp = pprint.PrettyPrinter(indent=2)

  raw = """
=Cake=
Modern cake, especially layer cakes, normally contain a
combination of [[flour]], [[sugar]], [[egg (food)|eggs]], and [[butter]].
==Varieties==
This is a subheading.
===Chocolate===
Oh and another nested subheading
=Related Cakes=
* Jaffa cakes!?
* This related section is not a subsection
Cool
"""

  # Returns raw 'token' classes that aren't much use
  #r = core.parse_txt(raw)

  ### This is the magic
  # Returns nicer classes, Paragraph, PreFormatted etc.
  parsed = compat.parse_txt(raw)
  
  ### Everything below here is just as an example

  f = open('test.txt', 'w')

  for section in parsed:
    recursive_parse(f, section, 0)

  f.close


def recursive_parse(f, node, indent):
  tabs    = indent * "  "
  indent += 1
  print tabs + node.__class__.__name__

  if isinstance(node, nodes.Section):
    level = u"=" * node.level
    title = level + node.children[0].asText()
    title = title.rstrip()
    f.write(title)

    # Skip the text child, move onto the rest
    for child in node.children[1:]:
      recursive_parse(f, child, indent)
    return

  elif isinstance(node, nodes.Table):
    # Don't process children
    print tabs + "Table: Skipping"
    return

  elif isinstance(node, nodes.Text):
    txt = node.asText()
    if txt != "\n": # and re.match("^\s+$", txt) == None:

      txt = txt.rstrip('\n')
      #re.sub("\n", '', txt)

      #f.write('<text>' + txt.encode('UTF-8') + '</text>')
      f.write(txt.encode('UTF-8'))

  else:
    f.write("???")

  if node.children is not None:
    for child in node.children:
      recursive_parse(f, child, indent)

if __name__ == '__main__':
  main()

subscribe via RSS