Programming Games

Class 3 - Tables, Game Loops

Play homework

Pong cabinet

Pong game test

Topics

Description

This week is all about Pong! Programming this game will challenge us to use all of our new skills. We need to create a ball, have it bounce around the screen, and take player input to move controllers. We’ll build the game one step at a time, in an iterative fashion.

Class Notes

Detecting collision

Let’s say we’re making a game where you can shoot down monsters. A monster should die when it is hit by a bullet. So what we need to check is: Is the monster colliding with a bullet?

We’re going to create a collision check function. We will be checking collision between rectangles. This is called AABB collision. So we need to know, when do two rectangles collide?

I created an image with three examples:

It’s time to turn on that programmer brain if you haven’t already. What is going on in the third example that isn’t happening in the first and second example?

“They are colliding”

Yes, but you have to be more specific. We need information that the computer can use.

Take a look at the positions of the rectangles. In the first example, Red is not colliding with Blue, because Red is too far to the left. If Red was a bit further to the right, they would touch. How far exactly? Well, if Red’s right side is further to the right than Blue’s left side. This is something that is true for example 3.

But it’s also true for example 2. We need more conditions to be sure there is collision. So example 2 shows we can’t go too far to the right. How far exactly can we go? How much would Red have to move to the left for there to be collision? When Red’s left side is further to the left than Blue’s right side.

So we have two conditions, is that enough to ensure there is collision?

Well no, look at the following image:

This situation agrees with our conditions. Red’s right side is further to the right than Blue’s left side. And Red’s left side is further to the left than Blue’s right side. Yet, there is no collision. That’s because Red is too high. It needs to move down. How far? Till Red’s bottom side is further to the bottom than Blue’s top side.

But if we move it too far down, there won’t be collision anymore. How far can Red move down, and still collide with Blue? As long as Red’s top side is further to the top than Blue’s bottom side.

Now we got four conditions. Are all four conditions true for these three examples?

Red’s right side is further to the right than Blue’s left side.

Red’s left side is further to the left than Blue’s right side.

Red’s bottom side is further to the bottom than Blue’s top side.

Red’s top side is further to the top than Blue’s bottom side.

Yes, they are! Now we need to turn this information into a function.

First let’s create two rectangles.

function love.load()
	--Create 2 rectangles
	r1 = {
		x = 10,
		y = 100,
		width = 100,
		height = 100
	}

	r2 = {
		x = 250,
		y = 120,
		width = 150,
		height = 120
	}
end


function love.update(dt)
	--Make one of rectangle move
	r1.x = r1.x + 100 * dt
end


function love.draw()
	love.graphics.rectangle("line", r1.x, r1.y, r1.width, r1.height)
	love.graphics.rectangle("line", r2.x, r2.y, r2.width, r2.height)
end

Now we create a new function called checkCollision(), with 2 rectangles as parameters.

function checkCollision(a, b)

end

First we need the sides of the rectangles. The left side is the x position, the right side is the x position + the width. Same with y and height.

function checkCollision(a, b)
	--With locals it's common usage to use underscores instead of camelCasing
	local a_left = a.x
	local a_right = a.x + a.width
	local a_top = a.y
	local a_bottom = a.y + a.height

	local b_left = b.x
	local b_right = b.x + b.width
	local b_top = b.y
	local b_bottom = b.y + b.height
end

Now that we have the four sides of each rectangle, we can use them to put our conditions in an if-statement.

function checkCollision(a, b)
	--With locals it's common usage to use underscores instead of camelCasing
	local a_left = a.x
	local a_right = a.x + a.width
	local a_top = a.y
	local a_bottom = a.y + a.height

	local b_left = b.x
	local b_right = b.x + b.width
	local b_top = b.y
	local b_bottom = b.y + b.height

	--If Red's right side is further to the right than Blue's left side.
	if  a_right > b_left
	--and Red's left side is further to the left than Blue's right side.
	and a_left < b_right
	--and Red's bottom side is further to the bottom than Blue's top side.
	and a_bottom > b_top
	--and Red's top side is further to the top than Blue's bottom side then..
	and a_top < b_bottom then
		--There is collision!
		return true
	else
		--If one of these statements is false, return false.
		return false
end

Notice that the if-condition itself is a boolean value. checkCollision returns true when the if-condition is true and vise-versa. Therefore checkCollision can be simplified into the following form:

function checkCollision(a, b)
	--With locals it's common usage to use underscores instead of camelCasing
	local a_left = a.x
	local a_right = a.x + a.width
	local a_top = a.y
	local a_bottom = a.y + a.height

	local b_left = b.x
	local b_right = b.x + b.width
	local b_top = b.y
	local b_bottom = b.y + b.height

	--Directly return this boolean value without using if-statement
	return  a_right > b_left
		and a_left < b_right
		and a_bottom > b_top
		and a_top < b_bottom
end

Okay, we have our function. Let’s try it out! We draw the rectangles filled or lined based on

function love.draw()
	--We create a local variable called mode
	local mode
	if checkCollision(r1, r2) then
		--If there is collision, draw the rectangles filled
		mode = "fill"
	else
		--else, draw the rectangles as a line
		mode = "line"
	end

	--Use the variable as first argument
    love.graphics.rectangle(mode, r1.x, r1.y, r1.width, r1.height)
    love.graphics.rectangle(mode, r2.x, r2.y, r2.width, r2.height)
end

It works! Now you know how to detect collision between two rectangles.


Summary

Collision between two rectangles can be checked with four conditions.

Where A and B are rectangles:

A’s right side is further to the right than B’s left side.

A’s left side is further to the left than B’s right side.

A’s bottom side is further to the bottom than B’s top side.

A’s top side is further to the top than B’s bottom side.

Tables and for-loops

Tables

Tables are basically lists in which we can store values.

You create a table with curly brackets ({ }):

function love.load()
	fruits = {}

end

We just created a table called fruits. Now we can store values inside the table. There are multiple ways to do this.

One way is to put the values inside the curly brackets.

function love.load()
	-- Each value is separated by a comma, just like with parameters and arguments
	fruits = {"apple", "banana"}
end

We can also use the function table.insert. The first argument is the table, the second argument is the value we want to store inside that table.

function love.load()
	--Each value is separated by a comma, just like with parameters and arguments
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")
end

So now after love.load our table will contain "apple", "banana" and "pear". To prove that, let’s put the values on screen. For that we’re going to use love.graphics.print(text, x, y).

function love.draw()
	--Arguments: (text, x-position, y-position)
	love.graphics.print("Test", 100, 100)
end

When you run the game, you should see the text “test” written. We can access the values of our table by writing the tables name, followed by brackets ([ ]) (So not curly but square brackets!). Inside these brackets, we write the position of the value we want.

Like I said, tables are a list of values. We first added "apple" and "banana", so those are on the first and second position in the list. Next we added "pear", so that’s on the third position in the list. On position 4 there is no value (yet), since we only added 3 values.

So if we want to print "apple", we have to get the first value of the list.

function love.draw()
	love.graphics.print(fruits[1], 100, 100)
end

And so now it should draw "apple". If you replace the [1] with [2], you should get "banana", and with [3] you get "pear".

Now we want to draw all 3 fruits. We could use love.graphics.print 3 times, each with a different table entry…

function love.draw()
	love.graphics.print(fruits[1], 100, 100)
	love.graphics.print(fruits[2], 100, 200)
	love.graphics.print(fruits[3], 100, 300)
end

…but imagine if we had 100 values in our table. Luckily, there’s a solution for this: for-loops!


for-loops

A for-loop is a way to repeat a piece of code a certain amount of times.

You create a for-loop like this:

function love.load()
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")

	for i=1,10 do
		print("hello", i)
	end
end

If you run the game you should see it prints hello 1, hello 2, hello 3, all the way to 10.

To create a for-loop, first you write for. Next you write a variable and give it a numeric value. This is where the for-loop starts. The variable can be named anything, but it’s common to use i. This variable can only be used inside the for-loop (just like with functions and parameters). Next you write the number to which it should count. So for example for i=6,18 do will start at 6 and keep looping till it’s at 18.

There is also a third, optional number. This is by how much the variable increases. for i=6,18,4 do would go: 6, 10, 14, 18. With this you can also make for-loops go backwards with -1.

Our table starts at 1 and has 3 values, so we will write:

function love.load()
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")

	for i=1,3 do
		print(fruits[i])
	end
end

When you run the game you’ll see that it prints all 3 fruits. In the first loop it prints fruits[1], then in the second loop fruits[2]and finally in the third loop fruits[3].


Editing tables

But what if we add or remove a value from a table? We would have to change the 3 into another number. For that we use #fruits. With the #-sign, we can get the length of a table. The length of a table refers to the number of things in that table. That length would be 3 in our case, since we have 3 entries: apple, banana, and pear in our fruits table.

function love.load()
	fruits = {"apple", "banana"}

	print(#fruits)
	--Output: 2

	table.insert(fruits, "pear")

	print(#fruits)
	--Output: 3

	for i=1,#fruits do
		print(fruits[i])
	end
end

Let’s use this knowledge to draw all 3 fruits.

function love.draw()
	for i=1,#fruits do
		love.graphics.print(fruits[i], 100, 100)
	end
end

If you run the game you should see it draws all 3 fruits, except they’re all drawn on the same position. We can fix this by printing each number on a different height.

function love.draw()
	for i=1,#fruits do
		love.graphics.print(fruits[i], 100, 100 + 50 * i)
	end
end

So now "apple" will be drawn on the y-position 100 + 50 * 1, which is 150. Then "banana" gets drawn on 200, and "pear" on 250.

If we were to add another fruit, we won’t have to change anything. It will automatically be drawn. Let’s add "pineapple".

function love.load()
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")
	table.insert(fruits, "pineapple")
end

We can also remove values from our table. For that we use table.remove. The first argument is the table we want to remove something from, the second argument is the position we want to remove. So if we want to remove banana, we do the following:

function love.load()
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")
	table.insert(fruits, "pineapple")
	table.remove(fruits, 2)
end

When you run the game you’ll see that banana is no longer drawn, and that pear and pineapple have moved up.

When you remove a value from a table with table.remove, all the following items in the table will move up. So what was on position 3 is now on position 2 in the table. And what was on position 4 is now on position 3.

You can also add or change the values inside the table directly. For example, we can change "apple" into "tomato":

function love.load()
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")
	table.insert(fruits, "pineapple")
	table.remove(fruits, 2)
	--The value of position 1 in the table becomes "tomato"
	fruits[1] = "tomato"
end

ipairs

Back to the for-loops. There is actually another way, and an easier way to loop through the table. We can use an ipairs loop.

function love.load()
	fruits = {"apple", "banana"}
	table.insert(fruits, "pear")
	table.insert(fruits, "pineapple")
	table.remove(fruits, 2)
	fruits[1] = "tomato"

	for i,v in ipairs(fruits) do
		print(i, v)
	end
	--Output:
	--1, "tomato"
	--2, "pear"
	--3, "pineapple"
end

This for-loop loops, or what we also call iterates, through all the values in the table. The variables i tells us the position of the table, v is the value of that position in the table. It’s basically a shorthand for fruits[i]. For example, in the first iteration the values for the variables i would be 1 and v would be "apple". In the second iteration, i and v would be 2 and "pear" respectively.

But how does it work? Why does the function ipairs allow for this? That is for another time. For now all you need to know is that ipairs is basically a shorthand for the following:

for i=1, #fruits do
	v = fruits[i]
end

Let’s use ipairs for drawing our tables.

function love.draw()
	-- i and v are variables, so we can name them whatever we want
	for i,frt in ipairs(fruits) do
		love.graphics.print(frt, 100, 100 + 50 * i)
	end
end

Summary

Tables are lists in which we can store values. We store these values when creating the table, with table.insert, or with table_name[1] = "some_value". We can get the length of the table with #table_name. With for-loops we can repeat a piece of code a number of times. We can also use for-loops to iterate through tables.


What is a game loop?

Photo taken from gameprogrammingpatterns.com/game-loop.html, where you can read more about game loops.

2D Coordinate System

Photo taken from rbwhitaker.wdfiles.com/local--files/monogame-introduction-to-2d-graphics/2DCoordinateSystem.png.

Game scope

Project Procedure

pong-0 (“The Day-0 Update”)

Important Functions

Important code

pong-1 (“The Low-Res Update”)

Important Functions

Important Code

pong-2 (“The Rectangle Update”)

Important Functions

Important Code

pong-3 (“The Paddle Update”)

Important Functions

Important Code

pong-4 (“The Ball Update”)

Important Functions

Important Code

Code homework

For homework, you will be implementing a bouncy ball.

Credits