Programming Games

Class 7 - Debugging and the Camera

Play homework

Katamari Damacy

Sokpop games

Topics

Description

This class is all about mistakes: how to find them and solve them. We’ll also learn to work with the ‘camera’ so that our game can go beyond the limits of a single screen.

Class notes

Debugging

A bug is something that goes wrong in a program (or in our case game). Debugging is the art of fixing these bugs so that they don’t occur anymore. As a programmer it’s only natural to encounter lots of bugs, so debugging is a very valuable skill to have.

I wrote a short game.

function love.load()
	circle = {x = 100, y = 100}
	bullets = {}
end

function love.update(dt)
	for i,v in ipairs(bullets) do
		v.x = v.x + 400 * dt
	end
end

function love.draw()
	love.graphics.circle("fill", circle.x, circle.y, 50)

	for i,v in ipairs(bullets) do
		love.graphics.circle("fill", v.x, v.y, 10)
	end
end

function love.keypressed()
	if key == "space" then
		shoot()
	end
end

function shoot()
	table.insert(bullets, {circle.x, circle.y})
end

When you press space it will shoot a bullet. Or at least that’s what is supposed to happen, but it doesn’t work. I don’t see any bullets appearing. Let’s try to find out why that is.

Here are some of the reasons I can think of for why it’s not working.

To figure out where it goes wrong we can use print. For example, let’s use print inside the for-loop in love.update to check the x-position of the circle, and if it even gets there to begin with. Because if the bullets aren’t spawning, the print will never be reached.

function love.update(dt)
	for i,v in ipairs(bullets) do
		v.x = v.x + 400 * dt
		print(v.x)
	end
end

Remember that you can add the following code at the top of your main.lua to have the output appear right away, and you don’t need to close your game for it.

io.stdout:setvbuf("no")

Run the game and press space a few times to shoot. As you can see there are no prints in your output panel. Looks like we’re not filling our bullets table. Let’s make sure that we actually call the method shoot by putting a print there.

function shoot()
	table.insert(bullets, {circle.x, circle.y})
	
	-- Did you know that print takes infinite amount of arguments?
	print("How many bullets?", #bullets)
end

Try to shoot again and you’ll see that we still don’t see anything printed to your output panel. Weird, because our if-statement says if key == "space", and we’re definitely pressing space. But to be sure, let’s print the value of key. And who knows, maybe we spelled love.keypressed wrong and it doesn’t reach that code at all.

function love.keypressed()
	-- Adding texts like these gives context to your print.
	-- This is especially useful when you have multiple prints.
	print("What is the key?", key)
	if key == "space" then
		shoot()
	end
end

When you try to shoot again you’ll see that now there is something being printed. Looks like the value of key is nil. How is that possible, because LÖVE passes the key as first argument. But wait, we don’t have a parameter called key. I forgot to add it when making the function. So let’s fix that.

function love.keypressed(key)
	print("What is the key?", key)
	if key == "space" then
		shoot()
	end
end

Okay so now when you press space, it still doesn’t shoot, but something else happens. You get an error.


Reading and fixing errors

An error occurs when the code is trying to execute something that isn’t possible. For example, you can’t multiply a number by a string. This will give you an error

print(100 * "test")

Another example is trying to call a function that doesn’t exist.

doesNotExist()

In our bullet shooting game we get the following error:

So what exactly does the error tell us? Because a mistake that a lot of beginners make is that they don’t realize that the error message tells you exactly why and where the error occurs.

main.lua:10:

This tells us the error occurs on line 10 (this may be a different line for you).

attempt to perform arithmetic on field 'x' (a nil value)

Arithmetic means a calculation, e.g. using +, -, *, etc. It tried to calculate using the field x, but x is a nil value.

So that’s weird, because we give the table an x and y value, right? Well no. We add the values to the table, but we don’t assign them to a field. Let’s fix that.

function shoot()
	table.insert(bullets, {x = circle.x, y = circle.y})
end

And now it finally works. We can shoot bullets!

Let’s look at some new code where I create a circle class, and draw it a few times (you don’t necessarily have to write along).

Object = require "classic"

Circle = Object:extend()

function Circle:new()
	self.x = 100
	self.y = 100
end

function Circle:draw(offset)
	love.graphics.circle("fill", self.x + offset, self.y, 25)
end

function love.load()
	circle = Circle()
end

function love.draw()
	circle:draw(10)
	circle:draw(70)
	circle.draw(140)
	circle:draw(210)
end

Upon running this I get the following error:

“Attempt to index” means it tried to find a property of something. So in this case it tried to find the property x on the variable self. But according to the error, self is a number value, so it can’t do that. So how did this happen? We use a colon (:) for our function so that it automatically has self as first parameter, and we call the function with a colon so that it passes self as first argument. But apparently somewhere something went wrong. To know where it went wrong, we can use the Traceback.

The bottom part of the error tells us the “path” it took before reaching the error. It’s read from down to top. You can ignore the xpcall line. The next line says main.lua:21: in function 'draw'. Interesting, let’s take a look. Ah yes, I see. On line 21 I used a dot instead of a colon (circle.draw(140)). I changed it to a colon and now it works!


Syntax errors

A syntax error occurs when the game can’t start to begin with, because something is wrong with the code. It tries to “read” the code but can’t understand it. For example, remember how you can’t have a variable name starting with a number? When you try to do so it will give you an error:

Take a look at the following code (again, no need to type along)

function love.load()
	timer = 0
	show = true
end

function love.update(dt)
	show = false
	timer = timer + dt

	if timer > 1 then
		if love.keyboard.isDown("space") then
			show = true
		end
	if timer > 2 then
		timer = 0
	end 
end

function love.draw()
	if show then
		love.graphics.rectangle("fill", 100, 100, 100, 100)
	end
end

It gives me the following error:

<eof> means end of file. It expects an end at the end of the file. Is that how we fix it, placing an end at the end of the file? Well no. Somewhere I messed up, and we need to fix it correctly. It says that it expects the end to close the function at line 6, so let’s start from there and go down. After opening the function I start an if-statement, and then another if-statement. I close the second if-statement and start another if-statement. I close that if-statement as well and then close the outer if-statement. No, wait, that’s not right. I should be closing the function at that point. I forgot to add an end somewhere in the function. Let me fix that.

function love.update(dt)
	show = false
	timer = timer + dt

	if timer > 1 then
		if love.keyboard.isDown("space") then
			show = true
		end
		if timer > 2 then
			timer = 0
		end
	end
end

And now it works. This is why indentation in your code is so important. It helps you see where you made a mistake like this one.

Another common mistake is one like the following:

function love.load()
	t = {}
	table.insert(t, {x = 100, y = 50)
end

Which gives me the follow error:

It’s because I didn’t close the curly brackets.

function love.load()
	t = {}
	table.insert(t, {x = 100, y = 50})
end

Asking for help

Now maybe you have a bug, and you can’t fix it. You tried debugging it but you just can’t figure out why it won’t work, and you feel like you need help. Well then lucky for you there are plenty of people on the internet that are happy to help you. The best places to ask your question are on forums or in the Discord. But asking a question is not simply asking “Hey guys I got this bug where this happens, what do I do?”. You need to provide them information that they can use to help you. For example:

But before you ask for help you may want to search for your question. It just might be that it’s a common question and it has been answered multiple times.

And remember, no one is obligated to help you, and the ones that do all do it for free, so be kind :)


Rubber duck debugging

You could also get a rubber duck. There’s this thing called rubber duck debugging. The idea is that when you explain what you’re doing in your code, you often realize what you’re doing wrong and fix the bug yourself. So instead of explaining it to someone, you explain it to your rubber duck.


Summary

We can use print to find the source of our bug. Error message tell us exactly what is going wrong. Syntax errors are errors that occur because it can’t “read” the code correctly. Indentation is useful because it prevents us from getting end of line-errors. We can ask for help on the internet but we should provide enough information on what is going on to make it less difficult on the people helping us.

Cameras and canvases

Camera

When you have a big level that doesn’t fit the screen, we use a “camera” that follows the player around. Let’s add a camera to the game we made in the previous chapter, where we have a player that collects coins.

So what does a camera do exactly? Basically it moves everything that we draw in a certain way so that the player is in the center. Now how would you solve this? We could have a variable like camera_offset and pass that to everything we’re drawing.

love.graphics.circle("line", player.x - camera.offset.x, player.y - camera.offset.y, player.size)

But if we have a very big game with a lot of things to draw, that’s a lot of extra work. What we instead can do is use love.graphics.translate(). This function is part of the Coordinate System. Just like how you can move, rotate and scale an image, we can move, rotate and scale what we draw on. What we draw on is called the canvas (more on this later).

Normally when you draw something on position (x=0,y=0), it will be drawn in the upper-left corner. With love.graphics.translate(400, 200) we move our canvas. Now if we draw something on (0,0) it will be drawn on position (400,200) instead. If we use love.graphics.scale(2) then everything we draw will be scaled up. Play around with it to really understand what it’s doing.

So let’s use love.graphics.translate(x, y) to create a camera. The question is in what way do we move the screen so that the player will appear in the center. First let’s get the player located in the top-left of the screen. We do this by making the negative value of the player’s position the (0,0) point.

With the upper-left corner on (0,0) (e.g. the default) the player is drawn on its position (100, 50) like normal. But if we were to move that point with love.graphics.translate(-100, -50) it is now the player that is drawn in the upper-left corner. But we don’t want it there, we want it in the center. So we add half the width and half the height of the screen (400 and 300).

A demonstration of what is happening. First we translate with love.graphics.translate(-player.x, -player.y) to get the player in the upper-left corner. Next we move the player to the center with love.graphics.translate(-player.x + 400, -player.y + 300).

--code
function love.draw()
	love.graphics.translate(-player.x + 400, -player.y + 300)
	love.graphics.circle("line", player.x, player.y, player.size)
	love.graphics.draw(player.image, player.x, player.y,
		0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
	for i,v in ipairs(coins) do
		love.graphics.circle("line", v.x, v.y, v.size)
		love.graphics.draw(v.image, v.x, v.y,
			0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
	end
end

It works! Now let’s keep score of how many coins we have picked up. The score is something that you always want to see on screen. It’s not part of the play area, it’s part of the HUD. First let’s add the score

-- In love.load()
score = 0
for i=#coins,1,-1 do
	if checkCollision(player, coins[i]) then
		table.remove(coins, i)
		player.size = player.size + 1
		score = score + 1
	end
end
function love.draw()
	love.graphics.translate(-player.x + 400, -player.y + 300)
	love.graphics.circle("line", player.x, player.y, player.size)
	love.graphics.draw(player.image, player.x, player.y,
		0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
	for i,v in ipairs(coins) do
		love.graphics.circle("line", v.x, v.y, v.size)
		love.graphics.draw(v.image, v.x, v.y,
			0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
	end
	love.graphics.print(score, 10, 10)
end

When you run the game you can now see a number. Now how do we make it so that the score’s position is not relative to the camera? One thing we could do is translate back to the normal position

function love.draw()
	love.graphics.translate(-player.x + 400, -player.y + 300)
	love.graphics.circle("line", player.x, player.y, player.size)
	love.graphics.draw(player.image, player.x, player.y,
		0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
	for i,v in ipairs(coins) do
		love.graphics.circle("line", v.x, v.y, v.size)
		love.graphics.draw(v.image, v.x, v.y,
			0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
	end
	love.graphics.translate(player.x - 400, player.y - 300)
	love.graphics.print(score, 10, 10)
end

We can also use love.graphics.origin(). This resets all the coordinate transformations. This does not affect what you have already drawn.

function love.draw()
	love.graphics.translate(-player.x + 400, -player.y + 300)
	love.graphics.circle("line", player.x, player.y, player.size)
	love.graphics.draw(player.image, player.x, player.y,
		0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
	for i,v in ipairs(coins) do
		love.graphics.circle("line", v.x, v.y, v.size)
		love.graphics.draw(v.image, v.x, v.y,
			0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
	end
	love.graphics.origin()
	love.graphics.print(score, 10, 10)
end

But this is not always the best option. Perhaps you want to scale your game, so you always want to have love.graphics.scale(2) and not have it reset. To fix that we can use love.graphics.push() and love.graphics.pop(). With love.graphics.push(), we save our current coordinate transformations and add it to a stack. With love.graphics.pop() we take the saved coordinate transformations from the top of the stack and apply it. Let’s use these functions to draw our score correctly.

--- code
function love.draw()
	love.graphics.push() -- Make a copy of the current state and push it onto the stack.
		love.graphics.translate(-player.x + 400, -player.y + 300)
		love.graphics.circle("line", player.x, player.y, player.size)
		love.graphics.draw(player.image, player.x, player.y,
			0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop() -- Pull the copy of the state of the stack and apply it.
	love.graphics.print(score, 10, 10)
end

Nice, now we have a camera with a score that is drawn on top of the camera. Now let’s make things shake.


Screenshake

How about adding some screenshake? First we need a timer for how long the shake needs to last and then make that timer go down.

-- In love.load()
shakeDuration = 0
-- In love.update(dt)
if shakeDuration > 0 then
	shakeDuration = shakeDuration - dt
end

Next we translate a few pixels in random directions as long as the timer is higher than 0.

function love.draw()
	love.graphics.push() -- Make a copy of the current state and push it onto the stack.
		love.graphics.translate(-player.x + 400, -player.y + 300)

		if shakeDuration > 0 then
			-- Translate with a random number between -5 an 5.
			-- This second translate will be done based on the previous translate.
			-- So it will not reset the previous translate.
			love.graphics.translate(love.math.random(-5,5), love.math.random(-5,5))
		end

		love.graphics.circle("line", player.x, player.y, player.size)
		love.graphics.draw(player.image, player.x, player.y,
			0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop() -- Pull the copy of the state of the stack and apply it.
	love.graphics.print(score, 10, 10)
end

And finally we set the timer to a certain value, for example 0.3, every time we pick up a coin.

for i=#coins,1,-1 do
	if checkCollision(player, coins[i]) then
		table.remove(coins, i)
		player.size = player.size + 1
		score = score + 1
		shakeDuration = 0.3
	end
end

Try it out. It might be that the screen shakes too fast. And if not for you, it might happen on other computers. So again we need to use delta time. We can add a second timer to fix this. But then we also need an object to store our offset in.

-- In love.load()
shakeDuration = 0
shakeWait = 0
shakeOffset = {x = 0, y = 0}
-- In love.update(dt)
if shakeDuration > 0 then
	shakeDuration = shakeDuration - dt
	if shakeWait > 0 then
		shakeWait = shakeWait - dt
	else
		shakeOffset.x = love.math.random(-5,5)
		shakeOffset.y = love.math.random(-5,5)
		shakeWait = 0.05
	end
end
function love.draw()
	love.graphics.push()
		love.graphics.translate(-player.x + 400, -player.y + 300)
		if shakeDuration > 0 then
			love.graphics.translate(shakeOffset.x, shakeOffset.y)
		end
		love.graphics.circle("line", player.x, player.y, player.size)
		love.graphics.draw(player.image, player.x, player.y,
			0, 1, 1, player.image:getWidth()/2, player.image:getHeight()/2)
		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop()
	love.graphics.print(score, 10, 10)
end

And now it’s consistent.

Now how about we make this game a multiplayer game, and add split screen?


Canvas

Whenever you use love.graphics to draw something, it is drawn on a canvas. This canvas is an object, and we can create our own canvas objects. This can be very useful, for a multiplayer game with split screen for example. You would draw the game on a separate canvas for each player, and then draw those canvases to the main canvas. Because a canvas is a Drawable. This is a superclass, and all LÖVE object that have this superclass can be drawn with love.graphics.draw().

The supertypes of Canvas in order are:

Texture is a subtype of Drawable. Both Image and Canvas are a subtype of Texture, and both can be drawn using a Quad. All LÖVE objects are of the type Object. Let this not confuse you with the variable we use for classes, which we also name Object. These two are not related in any way, we just name them the same.

You can create a canvas with love.graphics.newCanvas.

Let’s use a canvas to create a split screen in our game. We start by adding another player that can move with WASD, and remove the screen shake and save/load system.

function love.load()
	player1 = {
		x = 100,
		y = 100,
		size = 25,
		image = love.graphics.newImage("face.png")
	}

	player2 = {
		x = 300,
		y = 100,
		size = 25,
		image = love.graphics.newImage("face.png")
	}

	coins = {}

	for i=1,25 do
		table.insert(coins,
			{
				x = math.random(50, 650),
				y = math.random(50, 450),
				size = 10,
				image = love.graphics.newImage("dollar.png")
			}
		)
	end

	score1 = 0
	score2 = 0
end

function love.update(dt)
	if love.keyboard.isDown("left") then
		player1.x = player1.x - 200 * dt
	elseif love.keyboard.isDown("right") then
		player1.x = player1.x + 200 * dt
	end

	if love.keyboard.isDown("up") then
		player1.y = player1.y - 200 * dt
	elseif love.keyboard.isDown("down") then
		player1.y = player1.y + 200 * dt
	end

	if love.keyboard.isDown("a") then
		player2.x = player2.x - 200 * dt
	elseif love.keyboard.isDown("d") then
		player2.x = player2.x + 200 * dt
	end

	if love.keyboard.isDown("w") then
		player2.y = player2.y - 200 * dt
	elseif love.keyboard.isDown("s") then
		player2.y = player2.y + 200 * dt
	end

	for i=#coins,1,-1 do
		if checkCollision(player1, coins[i]) then
			table.remove(coins, i)
			player1.size = player1.size + 1
			score1 = score1 + 1
		elseif checkCollision(player2, coins[i]) then
			table.remove(coins, i)
			player2.size = player2.size + 1
			score2 = score2 + 1
		end
	end
end

function love.draw()
	love.graphics.push()
		love.graphics.translate(-player1.x + 400, -player1.y + 300)

		love.graphics.circle("line", player1.x, player1.y, player1.size)
		love.graphics.draw(player1.image, player1.x, player1.y,
			0, 1, 1, player1.image:getWidth()/2, player1.image:getHeight()/2)

		love.graphics.circle("line", player2.x, player2.y, player2.size)
		love.graphics.draw(player2.image, player2.x, player2.y,
			0, 1, 1, player2.image:getWidth()/2, player2.image:getHeight()/2)

		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop()
	love.graphics.print("Player 1 - " .. score1, 10, 10)
	love.graphics.print("Player 2 - " .. score2, 10, 30)
end

function checkCollision(p1, p2)	
	-- Calculating distance in 1 line
	-- Subtract the x's and y's, square the difference, sum the squares and find the root of the sum.
	local distance = math.sqrt((p1.x - p2.x)^2 + (p1.y - p2.y)^2)
	-- Return whether the distance is lower than the sum of the sizes.
	return distance < p1.size + p2.size
end

Now we need a canvas to draw on. We’re going to draw the canvas left and right, so it should be 400x600.

-- In love.load()
screenCanvas = love.graphics.newCanvas(400, 600)

Next we need to draw the game on to our new canvas. With love.graphics.setCanvas() we can set the canvas we want to draw on. If we pass no arguments to this function then it will reset to the default canvas.

function love.draw()
	love.graphics.setCanvas(screenCanvas)
	love.graphics.push()
		love.graphics.translate(-player1.x + 400, -player1.y + 300)

		love.graphics.circle("line", player1.x, player1.y, player1.size)
		love.graphics.draw(player1.image, player1.x, player1.y,
			0, 1, 1, player1.image:getWidth()/2, player1.image:getHeight()/2)

		love.graphics.circle("line", player2.x, player2.y, player2.size)
		love.graphics.draw(player2.image, player2.x, player2.y,
			0, 1, 1, player2.image:getWidth()/2, player2.image:getHeight()/2)

		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop()
	love.graphics.setCanvas()

	love.graphics.print("Player 1 - " .. score1, 10, 10)
	love.graphics.print("Player 2 - " .. score2, 10, 30)
end

If you run the game you’ll see nothing. This is because the game is drawn onto our new canvas, but we’re not drawing our canvas onto the default canvas. Like I said before, a Canvas is a Drawable and Drawable’s can be drawn with love.graphics.draw.

function love.draw()
	love.graphics.setCanvas(screenCanvas)
	love.graphics.push()
		love.graphics.translate(-player1.x + 400, -player1.y + 300)

		love.graphics.circle("line", player1.x, player1.y, player1.size)
		love.graphics.draw(player1.image, player1.x, player1.y,
			0, 1, 1, player1.image:getWidth()/2, player1.image:getHeight()/2)

		love.graphics.circle("line", player2.x, player2.y, player2.size)
		love.graphics.draw(player2.image, player2.x, player2.y,
			0, 1, 1, player2.image:getWidth()/2, player2.image:getHeight()/2)

		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop()
	love.graphics.setCanvas()

	--Draw the canvas
	love.graphics.draw(screenCanvas)
	
	love.graphics.print("Player 1 - " .. score1, 10, 10)
	love.graphics.print("Player 2 - " .. score2, 10, 30)
end

Now you should be able to see your game. But you’ll notice something weird when you start moving. You’re leaving a trail behind. This is because we are not clearing the canvas, meaning that everything we draw on it remains on the canvas. This is done by default on our default canvas. We can clear the canvas with love.graphics.clear(), and we should put this right before drawing the game. And since we’re only using half of the screen, we need to reposition the camera to make the player in the center. Half the screen is now 200 instead of 400.

Since we’re drawing the game twice, let’s put everything we draw for our game in a function.

function love.draw()
	love.graphics.setCanvas(screenCanvas)
		love.graphics.clear()
		drawGame()
	love.graphics.setCanvas()
	love.graphics.draw(screenCanvas)
	
	love.graphics.setCanvas(screenCanvas)
		love.graphics.clear()
		drawGame()
	love.graphics.setCanvas()
	love.graphics.draw(screenCanvas, 400)

	-- Add a line to separate the screens
	love.graphics.line(400, 0, 400, 600)

	love.graphics.print("Player 1 - " .. score1, 10, 10)
	love.graphics.print("Player 2 - " .. score2, 10, 30)
end

function drawGame()
	love.graphics.push()
		love.graphics.translate(-player1.x + 200, -player1.y + 300)

		love.graphics.circle("line", player1.x, player1.y, player1.size)
		love.graphics.draw(player1.image, player1.x, player1.y,
			0, 1, 1, player1.image:getWidth()/2, player1.image:getHeight()/2)

		love.graphics.circle("line", player2.x, player2.y, player2.size)
		love.graphics.draw(player2.image, player2.x, player2.y,
			0, 1, 1, player2.image:getWidth()/2, player2.image:getHeight()/2)

		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop()
end

Now we have a split screen. The only problem is that both the left and the right screen focus on player 1. We need to make it so that the camera focuses on player 2 the second time we draw. We can fix this by adding a focus parameter to drawGame, and have the camera focus on what we pass as argument, which will be the players.

function love.draw()
	love.graphics.setCanvas(screenCanvas)
		love.graphics.clear()
		drawGame(player1)
	love.graphics.setCanvas()
	love.graphics.draw(screenCanvas)
	
	love.graphics.setCanvas(screenCanvas)
		love.graphics.clear()
		drawGame(player2)
	love.graphics.setCanvas()
	love.graphics.draw(screenCanvas, 400)

	love.graphics.line(400, 0, 400, 600)
	
	love.graphics.print("Player 1 - " .. score1, 10, 10)
	love.graphics.print("Player 2 - " .. score2, 10, 30)
end

function drawGame(focus)
	love.graphics.push()
		love.graphics.translate(-focus.x + 200, -focus.y + 300)

		love.graphics.circle("line", player1.x, player1.y, player1.size)
		love.graphics.draw(player1.image, player1.x, player1.y,
			0, 1, 1, player1.image:getWidth()/2, player1.image:getHeight()/2)

		love.graphics.circle("line", player2.x, player2.y, player2.size)
		love.graphics.draw(player2.image, player2.x, player2.y,
			0, 1, 1, player2.image:getWidth()/2, player2.image:getHeight()/2)

		for i,v in ipairs(coins) do
			love.graphics.circle("line", v.x, v.y, v.size)
			love.graphics.draw(v.image, v.x, v.y,
				0, 1, 1, v.image:getWidth()/2, v.image:getHeight()/2)
		end
	love.graphics.pop()
end


Libraries

We made a rather simple camera, but there’s much more to it. Like zooming in and borders. I recommend checking out these libraries that provide advanced camera functionality:


Summary

With the Coordinate System we can change how stuff is drawn on screen. We can use love.graphics.translate(x, y) create a camera. Everything that we draw is drawn on a canvas. We can create our own canvas that we can draw like an image, because they’re both of the LÖVE type Texture. By using a canvas we can create a split screen.


Code homework

For homework this week you will complete your escape game.

A complete game includes protagonist with animation states. Enemies with animation states. Collisions. A game objective. A win state. A lose state.

Your code should be complete, working, commented, and broken down into functions.

Your game should have sound effects.

The scene must have background graphics. Graphics should be consistent.

The game should have a clear objective, rules and gameplay and be bug-free.

Credits