Skip to content

169. Why tables and classes are so useful

October 1, 2014

So you’ve started programming a game, and you find you are repeating chunks of code, and making any changes is time consuming.

In this post, I’ll show you how to take the next steps to making your code more compact and flexible, with tables and classes. (This is not an explanation of classes – I have those elsewhere – but more a demonstration of their value).

I’ll take an extremely simple example to begin with. Suppose you have 3 rectangles which move up the screen. So you start like this….

--set the (x,y) positions
rect1Pos=vec2(100,200)
rect2Pos=vec2(200,200)
rect3Pos=vec2(300,200)

--and then in the draw function, move them up
rect1Pos.y = rect1Pos.y + 5 --different speeds
rect2Pos.y = rect2Pos.y + 8
rect3Pos.y = rect3Pos.y + 3
--and draw them
ellipse(rect1Pos.x,rect1Pos.y,50)
ellipse(rect2Pos.x,rect2Pos.y,75)
ellipse(rect3Pos.x,rect3Pos.y,40)

And maybe you need more code for testing whether the rectangles go off the edge of the screen, or to check for collisions.

You can see that any time I want to do anything with the rectangles, I have to write three virtually identical lines of code each time. And if I want to add more rectangles, I am going to spend a lot of time adding lines in, all the way down my code.

So is there a better way? Yes, there is.

Basic table

If we put our rectangles in a table, we can use for loops to do most of the work, like this.

--in setup
rect={} --create empty table
--add positions and speed
--the pos sub-table holds values for all 3 rectangles
rect.pos={vec2(100,200),vec2(200,200),vec2(300,200)}
--these are in the same order
rect.speed={5,8,3}
rect.size={50,75,40}

--in draw, move the rectangles
for i=1,#rect.pos do --loop through
    rect.pos[i].y=rect.pos[i].y+rect.speed[i]
    --draw them 
    ellipse(rect.pos[i].x,rect.pos[i].y,rect.size[i])
end

This does two main things:

  1. All the rectangle properties like position and speed are defined in one place, making it easy to change them
  2. We can add or remove rectangles very easily in setup, without changing any of the code in draw or anywhere else

Alternate table

There is another way of using tables. (I am not just showing off, I am going somewhere with this).

--in setup
--create table with positions and speed
rect={
    {pos=vec2(100,200),speed=5,size=50}, --rectangle 1
    {pos=vec2(200,200),speed=8,size=75},  -rectangle 2
    {pos=vec2(300,200),speed=3,size=40}  --rectangle 3
   }

--in draw, move the rectangles
for i,r in pairs(rect) do --loop through
    r.pos.y=r.pos.y+r.speed
    --draw them
    ellipse(r.pos.x,r.pos.y,r.pos.size)
end

OK, so what is different? Previously, I had several tables with all the rectangle settings, and now I have one big table, with each rectangle having its own sub-table with all its settings.

In the previous version, you have to be careful to get your settings in the right order in each of the tables like speed and size, to make sure they apply to the correct rectangle. Here, you add a new rectangle by just adding a new line in the rect table, rather than having to modify several existing lines of code.

In draw, I use a for loop that loops through all the sub-tables in the rect table. That type of loop gives me two items for each of those tables – one is an index number (ie its position in the rect table) and the second is the sub-table itself.

So for example, suppose the first object I am given is the first rectangle (it doesn’t have to be the first, Lua can use any order it likes). I have defined i and r as the two variables in my loop, so in this example, i=1, and r= rect[1]. So the position of my first rectangle is rect[1].pos, but I can write that as r.pos because r is the same as my first rectangle.

Naming the items

You’ll see I named all the items in the tables. I didn’t have to. If I had defined the first rectangle as

    {vec2(100,200),5,50}

then I could have said this in draw

    r[1].y=r[1].y+r[2]

because the position is the first item, speed is the second, etc.

But I’m sure you’ll agree this is hard to read and easy to get wrong. So named items are much safer.

Classes

There isn’t a huge difference between the two ways of using tables, above.

But suppose the rectangles get more complicated. Suppose they need special code to check for collisions with other objects, or the edge of the screen. You can put this code into your draw and collide functions, but it gets messy fast, especially if you have other objects which need their own special code as well. This is where classes come to the rescue.

You can set up a rectangle “class” that will be used for all your rectangles, and it can contain all the code for drawing, moving, colliding, etc. Then your main draw function will be extremely simple.

So a class is like the table we just built above, but it can also contain code.

I’ll start by showing you the results – in fact, everything except the class we are going to create. This is because I want you to see just what a difference it makes to the rest of your code.

--create a table with three items, each of them is a rectangle class
--pass through all the information about each rectangle
rect={
    {classRect(vec2(100,200),5,50)},
    {classRect(vec2(200,200),8,75)},
    {classRect(vec2(300,200),3,40)}
   }

--in draw, move and draw the rectangles
--the classes do all the hard work for us
for i,r in pairs(rect) do --loop through
    r:move()
    r:draw()
end

My rect table is now made up of classRect items, each of which has its own settings for position, speed, etc.

So there isn’t much difference in defining your rectangles to start with, but when you start moving, drawing, colliding, etc, you can see we’ve removed all the code from our main functions and put it into our “class”, and the draw function just asks the class to run that code.

Imagine if you had other objects, each with their own way of moving, drawing and colliding. If all that code is in separate classes, your draw function will stay very neat.

There are some big benefits from this

  1. it gets all the mess out of your main functions like draw, collide etc
  2. it puts all the code for rectangles in one place
  3. it is easier to test, if it’s all in one place
  4. it’s easier to change, if it’s all in one place
  5. other code can’t mess with the rectangle code because it’s locked away in the class

So let’s see what this class looks like.

classRect=class() --tell Codea that this is a class

--this gets run when we set up a new rectangle in this class
--we pass through the values for the rectangle
function classRect:init(pos,speed,size)
    self.pos=pos --store the values provided
    self.speed=speed
    self.size=size
end

function classRect:move()
    self.pos.y=self.pos.y+self.speed
end

function classRect:draw()
    ellipse(self.pos.x,self.pos.y,self.pos.size)
end

If you have questions about what “self” means, why the function names all start with classRect, why the names include colons, etc, I have answered them here and here, and in my ebook on Lua. Here, I am just going to show you the value of using classes. For now, just accept that the word “self” refers to whichever rectangle is currently using this code.

The most important thing I’m trying to show is how clean this makes your code.

I’ll give you another example. I recently built a 2D side scroller, which I wrote up in some posts which you’ll find on this blog. I included several objects that “interacted” with the player, like coins and lights (which can be collected), and enemies (who hurt the player). Each object had special animations, movement paths, collision checks and reactions to colliding with the player.

So I set up a special class for each object, each with its own code. But all the classes had the same function names like draw, collide etc, so in my main draw function, if I want to draw all my objects, I simply loop through them all like this

for i,o in pairs(objects) do
    o:collide(p) --p is player position
    o:draw()
end

and each object class runs its own code for collision testing, drawing etc. This makes it really easy to include new objects without touching the rest of my code, no matter how messy they get.

So if you haven’t done so already, I suggest you get to know tables and classes, and how they can help you in your coding. They are not as scary as they look at first!

Note – I have another post that is quite similar to this one, in that it takes a game and looks at different ways of coding it using tables and classes, here. Sure, there is a big overlap in the two posts, but this is very important.

 

 

From → Programming

Leave a comment