Accessible objects and Love

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Taroven
Prole
Posts: 3
Joined: Sat Oct 31, 2009 3:32 am

Accessible objects and Love

Post by Taroven »

I'm pretty good with Lua, but I seem to have hit a stumbling block here. It probably doesn't help that this is my first day using Love. :oops:

What I'm working on is a simulator to help myself and a friend work out character templating in another game that's in its concept stages. Love seems to have everything that we'd need to get things moving along, except for something I took for granted when developing addons for WoW: Static and accessible objects.

In WoW, any visible object can be manipulated pretty much at will. For example:

Code: Select all

local frame = CreateFrame("FrameType", "FrameNameGoesHere", "FrameParent")
local text = frame:CreateFontString("FontStringName","DrawLayer")
text:SetText("FOO!")
text:SetPoint("TOP", frame, "BOTTOM", 100, 100) -- Set the position of the TOP of text to the BOTTOM of frame with offset 100,100
if someVariable then
	text:SetText("Bar")
	frame:SetPoint("CENTER", UIParent, "CENTER", 0,0) -- Moves text as well when the frame is moved, since the text is anchored to it
end
I don't see the same sort of functionality in Love unless I missed something really obvious. So I guess the question is... once an object is drawn how would I go about manipulating it?
User avatar
bmelts
Party member
Posts: 380
Joined: Fri Jan 30, 2009 3:16 am
Location: Wiscönsin
Contact:

Re: Accessible objects and Love

Post by bmelts »

The problem is that LÖVE is lower-level than WoW addons. You're writing complete programs in LÖVE, not just enhancements to an existing base. So, it's on you to not just write the logic, but also implement drawing everything in its appropriate place (though LÖVE does handle the really low-level drawing (actually putting pixels on the screen) for you).

I don't know how much you know about how LÖVE's draw loop works (it's pretty standard as far as video game draw loops go), but it's basically something like this: every fraction of a second (which depends on framerate - 60 FPS means every 1/60th of a second), the game clears the screen, and then draws everything. Everything. The background, the GUI, the players, the enemies, the particle effects, the text - anything graphical in your game gets drawn at that point. Where something gets drawn is a simple matter of telling the engine where to draw it. So, in LÖVE, to draw "FOO!" at (100, 100), you'd put this in the draw() function:

Code: Select all

love.graphics.draw("FOO!", 100, 100) -- or in LÖVE 0.6.0, love.graphics.print("FOO!", 100, 100)
Thus, any sort of manipulation of a drawn object is really just manipulating the object, and then drawing it in its new state a fraction of a second later. It's easy enough to manage this with some simple x and y records:

Code: Select all

function load()
   character = {}
   character.sprite = love.graphics.newImage("sprite.png")
   character.x = 100
   character.y = 100
end

function draw()
   love.graphics.draw(character.sprite, character.x, character.y)
end
The above is not necessarily best practice coding (in fact, it definitely isn't) but it will draw the character at whatever its x and y positions are. Thus, it starts out at (100, 100), but if you implement the keypressed callback like so:

Code: Select all

function keypressed(k)
   if k == love.key_right then
     character.x = character.x + 5
   elseif k == love.key_left then
     character.x = character.x - 5
   end
end
Then if you press the right arrow key, the next time your game draws everything (which should be almost instantaneously), your character will be displayed at (105, 100) instead.

But let's say you want to display the character's health at all times. It's just numeric for now, but you want it always hanging over the character's head, static in relation to the character. Also easy! Add a new field to the character table:

Code: Select all

character.health = 100
And add a statement to the draw() function:

Code: Select all

love.graphics.draw(character.health, character.x, character.y - 50)
This is terrible code. It doesn't take into account the size of the character, nor the size of the health display, nor any sort of aesthetic considerations like having the health number centered. But what it does do is draw some text that will always be over the character, and the same amount over the character, regardless of where the character is. So if the character ends up at (300, 70), the character's health display will be over its head at (300, 20). That's the basic principle behind the text anchoring in your code snippet - lock the text so its position is static relative to the position of the frame.

Apologies if this is trying to teach you stuff you already know. But hey, maybe it'll be useful to someone else reading the forums with similar questions! :)

TL;DR: read the part in bold near the top.
Taroven
Prole
Posts: 3
Joined: Sat Oct 31, 2009 3:32 am

Re: Accessible objects and Love

Post by Taroven »

Yeah, I already knew all that, but I have no doubt it'll be useful information for quite a few people. Thanks for taking the time to write all that out. :ultrahappy:

LÖVE is a great base for a game, but without getting into writing libraries or building in frame functions it definitely lacks in interface development. Not a huge deal, turns out wxLua has everything I need for this little project. Just need to get used to the API, which doesn't seem overly complicated for my uses.

Now, that little tower defense game I've been thinking of writing... that's a whole other story. LÖVE shall be shared there, oh yes it will.
User avatar
bmelts
Party member
Posts: 380
Joined: Fri Jan 30, 2009 3:16 am
Location: Wiscönsin
Contact:

Re: Accessible objects and Love

Post by bmelts »

Oops. :oops:

As for interfaces: there are a bunch that have been written for LÖVE, in varying stages of completeness. You can browse the Projects & Demos forum, or there's a list available on the wiki. None of them are going to be as capable as what you'll get with wxLua (or WoW, for that matter), but it's possible you might find something suitable (or easily modifiable) for your project.

If not, good luck with wxLua! I haven't used it myself, but it seems like a decent enough library.
Taroven
Prole
Posts: 3
Joined: Sat Oct 31, 2009 3:32 am

Re: Accessible objects and Love

Post by Taroven »

No worries, already digging into learning wxLua. It's likely a lot more useful in general for me anyway.

With that little bit of insight from your first post, I can see some fun stuff happening between myself and LÖVE. For the present situation though, wxLua's exactly the ticket. :ultrahappy:
User avatar
Tenoch
Citizen
Posts: 76
Joined: Mon Jul 21, 2008 7:49 am

Re: Accessible objects and Love

Post by Tenoch »

wxLua is very nice.

Keep in mind however that it's not meant to be used for real time "games", but rather event based applications. In other words, nothing happens unless the user inputs somethings.
Now you have ways to go around that, and there are examples of tetris games made with wxWidgets. But if you want to make a game, I believe it's easier to do it in LÖVE, and code the few UI widgets you need yourself (or use one of the few LÖVE libs mentioned).

If you want to make an app, wxLua is perfect. Plus it's instant portability, triple happiness. Learning the API is not too easy, though, since it's a wrapper, the doc is rather thin, and redirects you to wxWidgets' doc. And converting examples from C++ to Lua is not always obvious: The usual way to do in C++ is to build new classes that derive from the widget classes. wxLua is used more (only?) in an imperative way.

Aaaanyhoo.
"When in doubt, use brute force." Ken Thompson
User avatar
Sketchy
Prole
Posts: 5
Joined: Fri Oct 30, 2009 2:54 pm
Location: Cörnwall, UK

Re: Accessible objects and Love

Post by Sketchy »

anjo wrote:

Code: Select all

function load()
   character = {}
   character.sprite = love.graphics.newImage("sprite.png")
   character.x = 100
   character.y = 100
end

function draw()
   love.graphics.draw(character.sprite, character.x, character.y)
end
The above is not necessarily best practice coding (in fact, it definitely isn't) but it will draw the character at whatever its x and y positions are. Thus, it starts out at (100, 100), but if you implement the keypressed callback like so:
Why do you say this isn't best practice? I'm just curious because I'm new and that's exactly what I've been doing...
User avatar
Carl
Prole
Posts: 18
Joined: Wed Oct 28, 2009 10:32 pm

Re: Accessible objects and Love

Post by Carl »

Sketchy wrote:
anjo wrote:

Code: Select all

function load()
   character = {}
   character.sprite = love.graphics.newImage("sprite.png")
   character.x = 100
   character.y = 100
end

function draw()
   love.graphics.draw(character.sprite, character.x, character.y)
end
The above is not necessarily best practice coding (in fact, it definitely isn't) but it will draw the character at whatever its x and y positions are. Thus, it starts out at (100, 100), but if you implement the keypressed callback like so:
Why do you say this isn't best practice? I'm just curious because I'm new and that's exactly what I've been doing...
I'm also curious to know why, that's also how I'm doing it.
User avatar
bmelts
Party member
Posts: 380
Joined: Fri Jan 30, 2009 3:16 am
Location: Wiscönsin
Contact:

Re: Accessible objects and Love

Post by bmelts »

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.
User avatar
Sketchy
Prole
Posts: 5
Joined: Fri Oct 30, 2009 2:54 pm
Location: Cörnwall, UK

Re: Accessible objects and Love

Post by Sketchy »

Thanks for the very thorough explanation.
I had realized it would get messy with loads of objects, but didn't get as far as trying to work out a solution - figured I'd cross that bridge as I came to it.
Think I need to read up on "ipairs" too, as I don't actually know what that does...
Post Reply

Who is online

Users browsing this forum: No registered users and 25 guests