Cameras in Love2D Part 1: The Basics

Posted on April 19, 2011 in Tutorials

This is the first of a couple of blogs on creating cameras in the LÖVE engine. This part will deal with the fundamentals of creating a camera. Part two will deal with parallax scrolling and creating layers. So, let's get to it!

Update: I've actually ended up writing a part 3, which covers restricting camera movement.

The Functions

The functions we'll need are these:

Love2D (I'll use this name from now on, as it's easier to type) has a transformation stack, as in a stack of separate collections of transformation information. love.graphics.push allows us to push the current transformation upwards in the stack, and insert a new transformation under it, so to speak. love.graphics.pop discards the current transformation and sets the current transformation to whatever was before the old one in the stack. Let's see some ASCII art:

Starting:
|-----------|
| Default   |
|-----------|

love.graphics.push:
|-----------|
| Default   |       ^ pushed up
|-----------|
|-----------|
| New       |       <- pushed under
|-----------|

love.graphics.pop:
|-----------|         |-----------|
| Default   |         | New       |  -> discarded
|-----------|         |-----------|

Now, translate, scale, and rotate modify the information in the current transformation. The x and y parameters given to translate are added to every x and y coordinate used to draw to the screen. Every x and y coordinate is multiplied by the values given to scale. Finally, every position (an x/y vector) given is rotated by the value given to rotate, with the top-left corner of the screen as the rotation centre.

With that description, you could construct the most minimal camera like this:

function love.draw()
  love.graphics.translate(-x, -y)
  -- draw
end

Here translate is operating on the default transformation. The transformation stack is reset every frame, so we must apply our changes every frame. For a more complete implementation of a camera we could do this:

function love.draw()
  love.graphics.rotate(-rotation) -- this is in radians
  love.graphics.scale(1 / zoom, 1 / zoom)
  love.graphics.translate(-x, -y)
end

-rotation and -x, -y makes it so that all positions are "left behind" if they don't move with the camera. The values passed to scale makes it so that zoom values less than 1 zoom the camera in, and values greater than 1 zoom the camera out.

The camera Module

Let's organise this a bit to make the camera module. Here's the code for it:

camera = {}
camera.x = 0
camera.y = 0
camera.scaleX = 1
camera.scaleY = 1
camera.rotation = 0

function camera:set()
  love.graphics.push()
  love.graphics.rotate(-self.rotation)
  love.graphics.scale(1 / self.scaleX, 1 / self.scaleY)
  love.graphics.translate(-self.x, -self.y)
end

function camera:unset()
  love.graphics.pop()
end

function camera:move(dx, dy)
  self.x = self.x + (dx or 0)
  self.y = self.y + (dy or 0)
end

function camera:rotate(dr)
  self.rotation = self.rotation + dr
end

function camera:scale(sx, sy)
  sx = sx or 1
  self.scaleX = self.scaleX * sx
  self.scaleY = self.scaleY * (sy or sx)
end

function camera:setPosition(x, y)
  self.x = x or self.x
  self.y = y or self.y
end

function camera:setScale(sx, sy)
  self.scaleX = sx or self.scaleX
  self.scaleY = sy or self.scaleY
end

The camera module is a table, that contains the information it needs to be positioned. The key functions here are set and unset. set applies the information in the camera module, and also creating a new transformation for it (to support multiple views in one frame). unset simply removes the latest transformation using pop. The rest of the functions are simply to aid in adjusting the properties of camera.

You can use this camera by putting this code in love.draw:

function love.draw()
  camera:set()
  -- draw stuff
  camera:unset()
end

Then in the update stage of things you can change the camera's position, zoom, or rotation.

camera.x = 50
camera:scale(3) -- zoom by 3

The Mouse

Now, if you want to interact with the mouse inside the game world, you're going to need to adjust the coordinates returned by love.mouse.getX/Y/Position(). You can do this either by redefining those functions, or by adding some new functions to the camera module.

function camera:mousePosition()
  return love.mouse.getX() * self.scaleX + self.x, love.mouse.getY() * self.scaleY + self.y
end

That accounts for scaling and translation for the mouse coordinates. If you want to account for rotation, you'll need to do some vector rotation. See HUMP's vector class for that.

Conclusion

Well thanks for reading. I hope you learnt something, please tell me what you think in the comment box. See you in part two!

» Part Two


Tagged in display programming, game development, Love2D, Lua, programming
blog comments powered by Disqus