Trouble understanding classes
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
Trouble understanding classes
Can someone explain how to create and use classes to me as basic as they can? I'm having trouble understanding them and I'd much rather use classes to make menu buttons or at least some other method as opposed to the way I do now. I've been using tables to create buttons and I was looking at some other projects and it seemed like making them classes was much more manageable. I just don't know how to.
-
- Prole
- Posts: 32
- Joined: Thu Aug 28, 2014 8:38 am
- Location: Russia
- Contact:
Re: Trouble understanding classes
Love2D Exporter (Soft)
Не следует обманывать инспектора (Love2D Game)
Новогодняя ёлка (Love2D Game)
Создание танка на движке Love2D (Love2D Tank, Article)
Love2D-Helpers (Tools)
Не следует обманывать инспектора (Love2D Game)
Новогодняя ёлка (Love2D Game)
Создание танка на движке Love2D (Love2D Tank, Article)
Love2D-Helpers (Tools)
Re: Trouble understanding classes
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:
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:
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
We can do
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.
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
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
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)
Code: Select all
function GenericPlayer:GetY()
return self.y
end
player1:GetX()
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
Re: Trouble understanding classes
In my opinion closure based classes are the simplest ones you can create. I use them in all of my projects / games and never had a problem with them.
Example class:
Usage:
It's just a quick and dirty example because I have to leave in a few minutes, but if you have further questions just ask. The links kraftman gave above are also worth a read. In the end it just comes down to personal preference and what you find easier to work with
Example class:
Code: Select all
-- ------------------------------------------------
-- Module (CLASS)
-- ------------------------------------------------
local Foo = {};
-- ------------------------------------------------
-- Constructor (CREATES OBJECTS)
-- ------------------------------------------------
function Foo.new()
local self = {};
self.health = 100; -- public variable
local dead = false; -- local variable
local function secret()
-- invisible on the outside
end
function self:update(dt)
-- call local function.
secret();
-- do stuff
end
function self:draw()
end
return self;
end
-- ------------------------------------------------
-- Return Module
-- ------------------------------------------------
return Foo;
Code: Select all
local Foo = require('Foo'); -- require Foo module
local npc = Foo.new() -- create new object from foo class
function love.update(dt)
npc:update(dt);
end
function love.draw()
npc:draw();
end
Re: Trouble understanding classes
Using this method, how would I create independent movement for each player? This is throwing me an errorkraftman wrote:stuff
Code: Select all
function player:Update(dt)
if love.keyboard.isDown("d") then
player1:setX() = player1:setX() + 10
end
end
Sorry for the late response, got super busy with life stuff.
Edit:
Wow I'm an idiot. I fixed it within 2 minutes of posting lol. My thinking skills need to improve before posting, rather than after lol. I just did this:
Code: Select all
player1.x = player1.x + 10
I prefer to see how things are created and used by scratch, rather than using libraries. After I know how it works I start to use the libraries. Reading the wiki only gets me so far as well, I need good examples before I start understanding things well.
Re: Trouble understanding classes
That's not really OO or a class - at least from what I can tell from that code snippet. Where does the player1 table come from?AlexCalv wrote:Code: Select all
function player:Update(dt) if love.keyboard.isDown("d") then player1:setX() = player1:setX() + 10 end end
What you (probably) want is this:
Code: Select all
function player:Update(dt)
if love.keyboard.isDown("d") then
self:setX(self:getX() + 10);
end
end
Again, it is hard to tell what you are doing here without seeing the complete code
Re: Trouble understanding classes
This is the complete code
Also, how would I, I guess, delete or add these tables constantly? Like if I were to do bullets, or drop-in and drop-out with players. Would I just use tables without classes like using ipairs for that?
Code: Select all
player = {}
player.__index = player
function player:getX()
return self.x
end
function player:getY()
return self.y
end
function player:setX(x)
self.x = x
end
function player:setY(y)
self.y = y
end
function player.New(x, y)
local p = setmetatable({}, player)
p:setX(x)
p:setY(y)
return p
end
function player:Load()
player1 = player.New(0, 25)
player2 = player.New(0, 100)
end
function player:Update(dt)
--Player 1
if love.keyboard.isDown("d") then
player1.x = player1.x + 100 * dt
end
if love.keyboard.isDown("a") then
player1.x = player1.x - 100 * dt
end
--Player 2
if love.keyboard.isDown("right") then
player2.x = player2.x + 100 * dt
end
if love.keyboard.isDown("left") then
player2.x = player2.x - 100 * dt
end
end
function player:Draw()
love.graphics.setColor(255, 0, 0)
love.graphics.rectangle("fill", player1:getX(), player1:getY(), 20, 20)
love.graphics.setColor(0, 0, 255)
love.graphics.rectangle("fill", player2:getX(), player2:getY(), 20, 20)
end
Re: Trouble understanding classes
When it comes to OO I prefer to use a slightly lower-level approach instead of libraries:
Note that the last and optional parameter "mt" in the constructor is a metatable.
This allows us to implement inheritance:
For this approach to work we pass the "container" metatable to the "object" constructor.
Also note that we call the "object" destructor explicitly from "container": "Object.destroy(self)"
Lastly, I would advise against code like:
I would just add "player:set_movement(direction)" and call it from outside.
The reason is that it messes up the encapsulation, by making your object dependent on arbitrary external functions.
For certain things this is fine.
For example, if your class is "CollidiableObject" then you may expect it to depend on an external collision library.
But in this case, I wouldn't expect a "player" class to be dependent or "coupled" to "keyboard".
In theory, you should be able to add joystick/touchscreen support without changing the "player" class.
The theory is that you should be able run a simulation of your game without any graphics, input or audio libraries at all. Unfortunately in practice this is rarely possible for most games...
Just my opinion, I'm sure some people would disagree.
Code: Select all
Object = {}
ObjectMT = { __index = Object }
function Object:create(x, y, mt)
local self = {}
setmetatable(self, mt or ObjectMT)
self.x = x
self.y = y
return self
end
function Object:destroy(self)
self.x = nil
self.y = nil
end
This allows us to implement inheritance:
Code: Select all
Container = {}
ContainerMT = { __index = Container }
setmetatable(Container, { __index = Object })
function Container:create(image, x, y, mt)
local self = Object:create(x, y, mt or ContainerMT)
self.image = image
return self
end
function Container:destroy()
self.image = nil
Object.destroy(self)
end
Also note that we call the "object" destructor explicitly from "container": "Object.destroy(self)"
Lastly, I would advise against code like:
Code: Select all
function player:update(dt)
if love.keyboard:isDown("k") then
....
end
end
The reason is that it messes up the encapsulation, by making your object dependent on arbitrary external functions.
For certain things this is fine.
For example, if your class is "CollidiableObject" then you may expect it to depend on an external collision library.
But in this case, I wouldn't expect a "player" class to be dependent or "coupled" to "keyboard".
In theory, you should be able to add joystick/touchscreen support without changing the "player" class.
The theory is that you should be able run a simulation of your game without any graphics, input or audio libraries at all. Unfortunately in practice this is rarely possible for most games...
Just my opinion, I'm sure some people would disagree.
- zorg
- Party member
- Posts: 3465
- Joined: Thu Dec 13, 2012 2:55 pm
- Location: Absurdistan, Hungary
- Contact:
Re: Trouble understanding classes
I would agree thoughivan wrote:The theory is that you should be able run a simulation of your game without any graphics, input or audio libraries at all. Unfortunately in practice this is rarely possible for most games...
Just my opinion, I'm sure some people would disagree.
But encapsulation depends highly on the coding style (as in OOP or ECS or a mix of them, or something else) you choose to do.
Nevertheless, doing classes closure style would be my advice and choice as well... at least in the beginning.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
Re: Trouble understanding classes
I took a short break from this and what partial understanding I had, has faded away. So for the method you posted, do I have to type both code boxes you posted? And how do I actually start to interact with these classes. Like, how do I move them, or even draw them?ivan wrote:When it comes to OO I prefer to use a slightly lower-level approach instead of libraries:Note that the last and optional parameter "mt" in the constructor is a metatable.Code: Select all
Object = {} ObjectMT = { __index = Object } function Object:create(x, y, mt) local self = {} setmetatable(self, mt or ObjectMT) self.x = x self.y = y return self end function Object:destroy(self) self.x = nil self.y = nil end
This allows us to implement inheritance:For this approach to work we pass the "container" metatable to the "object" constructor.Code: Select all
Container = {} ContainerMT = { __index = Container } setmetatable(Container, { __index = Object }) function Container:create(image, x, y, mt) local self = Object:create(x, y, mt or ContainerMT) self.image = image return self end function Container:destroy() self.image = nil Object.destroy(self) end
Also note that we call the "object" destructor explicitly from "container": "Object.destroy(self)"
Lastly, I would advise against code like:I would just add "player:set_movement(direction)" and call it from outside.Code: Select all
function player:update(dt) if love.keyboard:isDown("k") then .... end end
The reason is that it messes up the encapsulation, by making your object dependent on arbitrary external functions.
For certain things this is fine.
For example, if your class is "CollidiableObject" then you may expect it to depend on an external collision library.
But in this case, I wouldn't expect a "player" class to be dependent or "coupled" to "keyboard".
In theory, you should be able to add joystick/touchscreen support without changing the "player" class.
The theory is that you should be able run a simulation of your game without any graphics, input or audio libraries at all. Unfortunately in practice this is rarely possible for most games...
Just my opinion, I'm sure some people would disagree.
Who is online
Users browsing this forum: Bing [Bot], Google [Bot] and 6 guests