LuaWeaver wrote:Wait a minute---
wait.
I understand OOP now
. Everything is just a table with "children" that can have things other then "children" such as functions and values and even subclasses mixed in with those and those can have subclasses and those...
*dies*
It's a bit more complex than that ... and more simple too.
OOP has two main concepts: classes and instances.
Classes are just a bunch of properties - mostly functions.
When you create a class, you do it because you need a way to represent "common properties shared by a bundh of elements in your game". Those "elements", are usually the "instances" of the class. The "properties" are usually functions.
For example, if you are going to create a game with lots of balls bouncing around. A first approach using simple tables (no oop) could be this one:
Code: Select all
function love.load()
ball1 = { x=100, y=100 }
ball2 = { x=200, y=100 }
ball3 = { x=300, y=100 }
end
function love.draw()
love.graphics.circle('line', ball1.x, ball1.y, 100)
love.graphics.circle('line', ball2.x, ball1.y, 100)
love.graphics.circle('line', ball3.x, ball1.y, 100)
end
Imagine that now you want to have a different radius for each ball. You could do it like this:
Code: Select all
function love.load()
ball1 = { x=100, y=100, radius = 10 }
ball2 = { x=200, y=100, radius = 20 }
ball3 = { x=300, y=100, radius = 30 }
end
function love.draw()
love.graphics.circle('line', ball1.x, ball1.y, ball1.radius)
love.graphics.circle('line', ball2.x, ball1.y, ball2.radius)
love.graphics.circle('line', ball3.x, ball1.y, ball3.radius)
end
Creating balls starts getting complicated when start needing more properties. For example, a color. It makes sense to have a createBall function so you don't have to type that much. And you will probably want a "drawBall" function too:
Code: Select all
function createBall(x,y,radius,color)
return {x=x, y=y, radius=radius, color=color}
end
function drawBall(ball)
love.graphics.setcolor(unpack(ball.color))
love.graphics.circle('line', ball.x, ball.y, ball.radius)
end
function love.load()
ball1 = createBall(100, 100, 10, { 255, 0, 0 })
ball2 = createBall(200, 100, 20, { 0, 255, 0 })
ball3 = createBall(300, 100, 30, { 0, 0, 255 })
end
function love.draw()
drawBall(ball1)
drawBall(ball2)
drawBall(ball3)
end
Remember what I told you about classes? They are thought to "group together" common properties (functions) of elements.
Look at createBall and drawBall. Don't they seem to be somehow "shared" by ball1, ball2 and ball3?
If we were to use OOP with this program, ball1, ball2 and ball3 would be instances of a class; and usually that class would be called Ball. createBall and drawBall would be
methods of Ball; Since drawBall is going to be part of Ball, we can simply call it "Ball:draw" (instead of "Ball:drawBall"). For implementation reasons, createBall will get transformed into a method called "initialize" - that's just how it should be called in middleclass. Also, inside those methods, the "ball" parameter will be replaced by a parameter called "self". Let me show you:
Code: Select all
require 'middleclass'
Ball = class('Ball')
function Ball:initialize(x, y, radius, color) -- this was createBall
self.x, self.y, self.radius, self.color = x, y, radius, color
end
function Ball:draw() -- this was drawball
love.graphics.setcolor(unpack(self.color))
love.graphics.circle('line', self.x, self.y, self.radius)
end
function love.load()
ball1 = Ball:new(100, 100, 10, { 255, 0, 0 })
ball2 = Ball:new(200, 100, 20, { 0, 255, 0 })
ball3 = Ball:new(300, 100, 30, { 0, 0, 255 })
end
function love.draw()
ball1:draw()
ball2:draw()
ball3:draw()
end
It doesn't look like much. But there are several things going on here. First, the amount of global variables used has been reduced. That is always good. Second, ball1:draw(...) works. That's because ball1, ball2, and ball3 are built so that they "know how to ask" their class (Ball) for its methods. If you add another method to Ball (for example, Ball:update), and it will also be shared by ball1, ball2, and ball3.
So that's the first part of OOP, in a very simple way: common functions go into a class, and specifics (like the x coordinate or the color) go into the instances. The instances "ask their class for help" when needed.
But that is not all.
A class can have a "superclasses" (a "father", if you want) and "subclasses" (the "children"). (A class could have no parents or children though).
When a class A is a subclass of class B, it's also common to say that is "inherits" from B. And the process of creating a subclass is often called "inheritance".
When the instances of class A find a method they don't know, they ask A. If A doesn't know about that method,
it asks B!. That continues until no superclasses are left (nil is returned on that case).
This is very useful to organize the common functions into "groups and subgroups". This is not so easy to implement with regular non-oop tables and functions, and I'm not going to attempt it. For an example of inheritance, see the
Person and AgedPerson example in the middleclass wiki.
The typical example: You could have a class called "Enemy" with one single method, called "damagePlayer", and then a subclass of Enemy called "Orc" that contained an orc-specific "Orc:draw()" method, and yet another subclass of Orc called MegaOrc that modified the damagePlayer method and increased the damage.
In middleclass, classes can only have 1 superclass, but they can have as many subclasses as needed. Every class generated has a superclass called Object, which provides some boilerplate methods. Object is the only class that doesn't have a superclass.
Finally, I'd also want to point out that the fact that classes and instances are tables on middleclass is just an implementation detail; other OOP implementations could use other stuff (for example Userdata). The important thing is not
how OOP is implemented, but
how it behaves.
Sorry for the long post. I hope it helps.