If you have access to pluralsight there's a nice intro to classes/inheritance and metatables called "Object Oriented Code Organization" here:
http://www.pluralsight.com/courses/beginning-lua
Otherwise, it's worth reading up a bit on object oriented coding and then metatables:
http://www.lua.org/pil/13.html then
http://lua-users.org/wiki/SimpleLuaClasses
Classes in lua are just tables, but when they look up a method and can't find it, they look in another table instead using metatables.
Lets say I make a table called player with variables for x, y and draw a cube to represent a player:
Code: Select all
local player = {}
player.x = 200
player.y = 200
function player.GetX()
return player.x
end
function player.GetY()
return player.y
end
function love.draw()
love.graphics.rectangle("fill",player.GetX(),player.GetY(),20,20 )
end
This works fine for one player, but what if I want to add another player? I'll need to add player2.GetX() and player2.GetY(), which does exactly the same thing but for a new player. As we add more players, and properties to those players, we end up adding even more code duplication, which adds more places that our code can go wrong, and makes things messy.
It would be much nicer if we could define GetX() and GetY once, and have anything that acts like a player use this new generic GetX() method.
We do this by telling the our player table that if it can't find something it's trying to access in its own table, then it should look in another table instead. Setmetatable tells it where to look, and __index is a metamethod that describes the action being performed on the table:
Code: Select all
local GenericPlayer = {}
GenericPlayer.__index = GenericPlayer
function GenericPlayer.GetX(self)
return self.x
end
function GenericPlayer.GetY(self)
return self.y
end
function GenericPlayer.New(x,y)
local t = {}
t.x = x
t.y = y
setmetatable(t,GenericPlayer)
return t
end
function love.load()
player1 = GenericPlayer.New(100, 130)
player2 = GenericPlayer.New(200,130)
end
function love.draw()
love.graphics.rectangle("fill",player1.GetX(player1),player1.GetY(player1),20,20 )
love.graphics.rectangle("fill",player2.GetX(player2),player2.GetY(player2),20,20 )
end
So player1.GetX(player1) looks for a GetX() method in player1, which it doesn't find, so it looks in GenericPlayer instead and finds it there. We also pass in player1 so that the GetX() method knows which table to access the x variable of, note that we dont want GenericPlayer to looks for GenericPlayer in itself because GenericPlayer has no x variable.
Since passing the table itself into itself is done very often, they added a nice syntax to make it look cleaner. Instead of
Code: Select all
function GenericPlayer.GetY(self)
return self.y
end
player1.GetX(player1)
We can do
Code: Select all
function GenericPlayer:GetY()
return self.y
end
player1:GetX()
By using the colon during the function definition and call, the table itself is passed in as a hidden variable called self.
So now if we want to add a new method to all of our new players, we can do it just once, on the GenericPlayer table, and it will be there for any player table to access.
Below I've neatened up the functions to use the colon syntax, neatened up the setmetatable creation, and added SetX(x) and SetY(y) methods.
Code: Select all
local GenericPlayer = {}
GenericPlayer.__index = GenericPlayer
function GenericPlayer:GetX()
return self.x
end
function GenericPlayer:GetY()
return self.y
end
function GenericPlayer:SetX(x)
self.x = x
end
function GenericPlayer:SetY(y)
self.y = y
end
function GenericPlayer.New(x,y)
local t = setmetatable({}, GenericPlayer)
t:SetX(x)
t:SetY(y)
return t
end
function love.load()
player1 = GenericPlayer.New(100, 130)
player2 = GenericPlayer.New(200,130)
end
function love.draw()
love.graphics.rectangle("fill",player1:GetX(),player1:GetY(),20,20 )
love.graphics.rectangle("fill",player2:GetX(),player2:GetY(),20,20 )
end