Page 1 of 4

A very simple camera lib

Posted: Tue May 22, 2012 7:23 am
by kikito
Last week I implemented a very simple camera for Battle Cry.

My requirements were:
  • As easy to use as possible
  • Should be small, and don't rely on external libs or class systems
  • Only translation; no rotation/scaling
  • Should be "clampable" to a map area
  • Should use the whole screen; no "windows" needed
  • No paralax scrolling needed
I'm satisfied with the results. Here's the complete code so far:

Code: Select all

-- camera.lua
-- viewport and boundaries are expressed as left, top, width and height
local viewport = {
  l = 0,
  t = 0,
  w = love.graphics.getWidth(),
  h = love.graphics.getHeight()
}

local boundary = {
  l = 0,
  t = 0,
  w = viewport.width,
  h = viewport.height
}

local function clampNumber(v, min, max)
  if max < min then return 0 end -- this happens when viewport is bigger than boundary
  return v < min and min or (v > max and max or v)
end

local function clampCamera()
  viewport.l = clampNumber(viewport.l, boundary.l, boundary.l + boundary.w - viewport.w)
  viewport.t = clampNumber(viewport.t, boundary.t, boundary.t + boundary.h - viewport.h)
end

local function setViewport(l, t, w, h)
  viewport.l, viewport.t, viewport.w, viewport.h = l, t, w or viewport.w, h or viewport.h
  clampCamera()
end

local function setBoundary(l, t, w, h)
  boundary.l, boundary.t, boundary.w, boundary.h = l, t, w, h
  clampCamera()
end

local function lookAt(x,y)
  setViewport(math.floor(x - viewport.w / 2), math.floor(y - viewport.h / 2))
end

local function draw(f)
  love.graphics.push()
  love.graphics.translate(-viewport.l, -viewport.t)
  f()
  love.graphics.pop()
end

local function getViewport()
  return viewport.l, viewport.t, viewport.w, viewport.h
end

return {
  setViewport = setViewport,
  setBoundary = setBoundary,
  getViewport = getViewport,
  lookAt      = lookAt,
  draw        = draw
}
The latest version (for now) can be found here: https://github.com/kikito/battle-cry/bl ... camera.lua

Usage:

Code: Select all

local camera = require 'camera'

local worldWidth, worldHeight = 3000, 2000

function love.load()
  -- The camera is bound to a rectangle with corners in 0,0 and dimensions 3000x2000
  camera.setBoundary(0,0, worldWidth, worldHeight)
end

function love.update(dt)
...
  -- center the camera on coordinates 200, 300
  camera.lookAt(200, 300)
...
end

function love.draw()
  love.graphics.print(100, 100, "This will be drawn without the camera")
  camera.draw(function()
    love.graphics.print(100, 100, "This will be drawn with the camera")
  end)
end
That's pretty much it. In English:
  • You use camera.setBoundary to tell the camera how big is your "game world". This limits how you can move it.
  • You can orientate the camera with camera.LookAt - this will center the view in a particular coordinate. Usually, this means the player coordinates
  • camera.draw takes a function. What you put inside that function is drawn with the camera (this is, scrolled up/down/left/right accordingly to where the camera is looking at). The rest is drawn without it. In the sample above, the second message will be displaced.
Internally, the lib stores two "rectangles" - one representing the "visible section of the game" (viewport) and the other one representing the "world size" (boundary). That's why the first thing you must do is setting the boundary - the camera assumes that the initial viewport is the screen size. If you manually change it, you have to tell the camera with setViewport:

Code: Select all

camera.setViewport(0,0, love.graphics.getWidth(), love.graphics.getHeight())
-- you will probably want to "look at what you were looking" after updating the viewport; for example:
camera.lookAt(player.x, player.y)

Re: A very simple camera lib

Posted: Wed May 23, 2012 12:41 am
by Inny
It might be useful to have some kind of way to prevent overdraw. The easiest way I could think of supporting that would either be to pass an xywh tuple as parameters to the f in camera.draw, or some other means to query if an object would be visible when it is drawn.

Re: A very simple camera lib

Posted: Wed May 23, 2012 7:25 am
by kikito
I didn't include such thing because everyone is a unique beautiful flower :).

I would not go asking each individual object "should I draw you?". Instead, I prefer telling the world: "I'm looking at this rectangle; draw the objects visible on this area, and nothing else". When done properly, this is much more efficient - the non-visible objects are not even considered.

The thing is that how you do it on each game depends a lot on how you query the game world, and how the information is structured - are you using a spatial hash, like a quadtree or a grid? Is your game tile-based at all? Etc.

So, instead of checking each object individually, the lib just exposes the getViewPort method, so you can use it to query the world efficiently.

In Battle Cry, this is how the map is drawn:

Code: Select all

function Play:draw()
  camera.draw(function()
    map:draw(camera.getViewport())
  end)
end
map:draw() takes 4 parameters - left, top, width and height (which coincide with what getViewPort returns). These parameters define what tiles are drawn and what tiles aren't. Here is a simplified version:

Code: Select all

function Map:draw(wl, wt, ww, wh) -- left, top, witdh, height of the visible rectangle in the world
  local l, t = self:toMap(wl, wt) -- left, top tiles of the rectangle
  local r, b = self:toMap(wl + ww, wt + wh) -- right, bottom tiles of the rectangle

  -- draw only the tiles between left, top and right, bottom
  for x = l, r do
    for y = t, b do
      self.tiles[x][y]:draw()
    end
  end
end
Note that the method is actually a bit more complex than this. On the next version of Battle Cry, it will be split in two classes and heavily modifided. But the idea stays the same.

Re: A very simple camera lib

Posted: Wed May 23, 2012 8:30 pm
by Inny
The getViewport function completely escaped me when I looked at that code. Yeah, that's exactly what I was asking for.

As for querying each object, that's too intrusive. I was thinking from the otherway around, each object asks the camera "am I visible" before drawing themself. That would have been a camera:isVisible(x, y, w, h) function, but of course it would just be a convienience function for rectangleOverlap( x1, y1, w1, h1, x2, y2, w2, h2 ) anyway, so it's not strictly necessary.

Re: A very simple camera lib

Posted: Mon Aug 06, 2012 6:55 pm
by Petunien
Could you show an easy example of how that works?

I despair on making a world (5000, 3000) where the camera allways shows that part where the player currently is (the player could be centered the middle).
So the player can drive around that large world.

Re: A very simple camera lib

Posted: Mon Aug 06, 2012 10:23 pm
by kikito
Hi,

I've made this quick example of how a 5000x3000 world would work out. I tried to make the code easy to follow, please let me know if you have questions.

Re: A very simple camera lib

Posted: Tue Aug 07, 2012 4:35 pm
by Petunien
Thank you very much!

I'm having some troubles but I'll try to figure them out by myself.

I'll comeback if I get more questions. :)

Oh, I forgot.
In 2456321 years, when my game could be ready to release (it won't), what should I write into the credits (a very simple camera lib by kikito?)?

Re: A very simple camera lib

Posted: Tue Aug 07, 2012 6:16 pm
by Robin
Technically, you don't have to mention anything like that, you just need to bundle it with the MIT license.

Re: A very simple camera lib

Posted: Tue Aug 07, 2012 6:23 pm
by Petunien
Personally, I'd like to reward other person's work.

Re: A very simple camera lib

Posted: Tue Aug 07, 2012 11:13 pm
by kikito
Oh, just include the license and put me in the credits if you like :)