It's actually not a terrible way to implement that functionality. Especially if your game is fairly simple, that may be all that you need to make your game work - if so, that's just fine.
Where I take issue with it is that it's not
scalable. What if, in addition to the character, I have enemies, some of which may not be visible at all times? Or if I randomly generate enemies? Or if I decide to make my game multiplayer so that up to eight different people can play characters at once? Assuming you continue along with that style of coding, your draw() function quickly becomes a cluttered mess, full of if statements and all sorts of stuff that doesn't have much to do directly with putting pixels on the screen. Not only that, it's
inflexible - if I have five enemies which are clones of each other, and I want to change something fundamental about how they're drawn, I have to change that over and over again for each enemy. Five isn't so bad (just boring), but what about fifty?
Let's come up with a solution to the enemy problem above. What if, instead, we have one basic "draw an enemy" function? Might look something like this:
Code: Select all
function drawEnemy(x, y)
love.graphics.draw(enemySprite, x, y) -- assuming enemySprite is a global Image with the enemy sprite in it
end
Then, in draw(), we only have to do:
Code: Select all
drawEnemy(enemy1.x, enemy1.y)
drawEnemy(enemy2.x, enemy2.y)
drawEnemy(enemy3.x, enemy3.y)
drawEnemy(enemy4.x, enemy4.y)
drawEnemy(enemy5.x, enemy5.y)
But what if we wanted to add or take away an enemy? Or if we could generate enemies on the fly? Maybe it'd be easier to use a table here:
Code: Select all
enemyTable = { }
local enemy1 = { }
enemy1.x = 100
enemy1.y = 100
enemy1.sprite = love.graphics.newImage("enemySprite.png")
enemyTable[1] = enemy1
-- repeat for as many enemies as you like...
That way, in draw(), all we need is a simple for loop:
Code: Select all
for i,v in ipairs(enemyTable) do
drawEnemy(v.x, v.y)
end
So far so good! But what if we want different types of enemies? We could use different tables (goombaTable, koopaTable) and a different drawEnemy function (drawGoomba, drawKoopa), but if we have more than a few types of enemies, that becomes pretty unwieldy, especially if there's a lot of shared code in the draw functions. And we're not even considering the absolute mess the update(dt) function must be in at this point with all this crap to keep track of.
Your first instinct might be to put everything in the original enemyTable and make the drawEnemy function more flexible, like so:
Code: Select all
function drawEnemy(sprite, x, y)
love.graphics.draw(sprite, x, y)
end
function draw()
-- ...
for i, v in ipairs(enemyTable) do
drawEnemy(v.sprite, v.x, v.y)
end
-- ...
end
Unfortunately, as a quick perusal of the drawEnemy() function shows, it now serves no purpose whatsoever - it could be completely replaced by the one function call it makes. So we're back to where we started. Almost. Now we have a table.
But hey, maybe this got you to thinking: "If I'm just drawing things based on the sprite, x, and y members of those things, couldn't I put the character in that loop as well?" And you absolutely can. As well as obstacles and, really, anything that stores its own sprite, x, and y position. So, let's rename the table to be more welcoming, say, thingTable. And for every "thing" put in that table, it will draw that thing:
Code: Select all
function draw()
for i, v in ipairs(thingTable) do
love.graphics.draw(v.sprite, v.x, v.y)
end
end
And that's great. Our draw() function is practically pristine. But what if some "things" have special drawing requirements? What if the player has a gun that changes color depending on how charged up it is? That's not going to fit into a single, generic drawing function -- it requires all sorts of tomfoolery, like changing the color mode. And the logic to check for special cases becomes very awkward, very fast, if you have more than one or two.
But then, an idea! In Lua, anything can be stored in a table. Including functions! What if we just had each "thing" handle its own drawing function?
Code: Select all
function enemy1:draw()
love.graphics.draw(self.sprite, self.x, self.y)
end
function colorGun:draw()
love.graphics.setMode(love.color_modulate)
love.graphics.setColor(self.color)
love.graphics.draw(self.sprite, self.x, self.y)
love.graphics.setMode(love.color_normal)
end
This solves our problem elegantly and simply:
Code: Select all
function draw()
for i, v in ipairs(thingTable) do
v:draw()
end
end
And that's it. The same logic can be applied to clean up the update(dt) function - have each game object store its own update(dt) method, and just call each one every update(dt). You already have the table, even!
TL;DR: my original example is okay if your project is small, otherwise, it's a bad idea.
This post adapted from my eternally unpublished book, Anjo Talks Too Damn Much.