A very simple camera lib

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

A very simple camera lib

Post 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)
When I write def I mean function.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: A very simple camera lib

Post 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.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: A very simple camera lib

Post 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.
When I write def I mean function.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: A very simple camera lib

Post 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.
User avatar
Petunien
Party member
Posts: 191
Joined: Fri Feb 03, 2012 8:02 pm
Location: South Tyrol (Italy)

Re: A very simple camera lib

Post 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.
"Docendo discimus" - Lucius Annaeus Seneca
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: A very simple camera lib

Post 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.
Attachments
camera.love
(1.43 KiB) Downloaded 287 times
When I write def I mean function.
User avatar
Petunien
Party member
Posts: 191
Joined: Fri Feb 03, 2012 8:02 pm
Location: South Tyrol (Italy)

Re: A very simple camera lib

Post 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?)?
"Docendo discimus" - Lucius Annaeus Seneca
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: A very simple camera lib

Post by Robin »

Technically, you don't have to mention anything like that, you just need to bundle it with the MIT license.
Help us help you: attach a .love.
User avatar
Petunien
Party member
Posts: 191
Joined: Fri Feb 03, 2012 8:02 pm
Location: South Tyrol (Italy)

Re: A very simple camera lib

Post by Petunien »

Personally, I'd like to reward other person's work.
"Docendo discimus" - Lucius Annaeus Seneca
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: A very simple camera lib

Post by kikito »

Oh, just include the license and put me in the credits if you like :)
When I write def I mean function.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Roland Chastain and 226 guests