## A function/tables question(I think?)

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
dementia_patient567
Prole
Posts: 12
Joined: Sun Jan 27, 2013 6:00 am

### A function/tables question(I think?)

I'm making a sort of 360degree space shooter(geowars, I guess) and I'm working on the enemy files right now.
Rather than just explain the code, Here it is.

Code: Select all

enemies = {}
enemies.spawnpoint={}
enemies.type = {}
enemies.type[1] = love.graphics.newImage("Images/redenemy1.png")
enemies.type[2] = love.graphics.newImage("Images/greenenemy1.png")
enemies.type[3] = love.graphics.newImage("Images/blueenemy1.png")

enemyCount = 0

function enemySpawn(x, y)
table.insert(enemies, {x=x, y=y, img = enemies.type[enemies.type.typeOfEnemy]})
end

function newSpawnPoint(x, y, typeOfEnemy, timer)

x = x or math.random(border.leftside+30, border.rightside-30)
y = y or math.random(border.top+30, border.bottom-30)
typeOfEnemy = typeOfEnemy or 1
timer = timer or 5

table.insert(enemies.spawnpoint, {x=x, y=y, typeOfEnemy=typeOfEnemy, timer = timer, default = timer})
end

function spawnpointInit()

newSpawnPoint(20, 20, 1, 2)
newSpawnPoint(20, 100, 2)
newSpawnPoint(2000, 500, 3, 10)
end

function enemyDraw()

for i,v in ipairs(enemies) do
love.graphics.draw(v.img, v.x, v.y, 0, .5 ,.5)
end

for ei,ev in ipairs(enemies.spawnpoint) do
love.graphics.print("spawn", ev.x, ev.y)
love.graphics.print(math.round(ev.timer, 3), ev.x, ev.y+50)
end
end

function spawnEnemies(dt)
for si,sv in ipairs(enemies.spawnpoint) do
if sv.timer ~= 0 then
sv.timer = sv.timer - 1*dt
end
if math.round(sv.timer, 3)<  0 then
enemySpawn(sv.x, sv.y)
enemyCount = enemyCount+ 1
sv.timer = sv.default
end
end
end

function enemyUpdate(dt)
spawnEnemies(dt)
end

My issue is the love.graphics.draw(v.img, x,y) part. I have no idea, under the current way I've done my tables, how I'd work my code to allow each spawn point to produce a specific type of enemy.

I know my code's probably a really ugly mess...Sorry. That's just me being a noob. I'm sort of lost when it comes to meta tables and whatnot, so I'm just winging it with what I know right now. Any questions about it, I'll answer. Thanks a ton.

Edit: Alright. Nevermind the question, I suppose. I haven't figured it out, but I really need to learn how to use classes and metatables. I've tried so many times since I've started learning lua, but I can't figure it out...Anyways, if any of you could explain it in a way that it'd make sense to a toddler, there's a chance I might understand it. If a few dozen read throughs of the PIL classes and MT sections won't do it, Idk what will..
Last edited by dementia_patient567 on Sat May 04, 2013 5:44 pm, edited 1 time in total.

stampede247
Prole
Posts: 11
Joined: Thu May 02, 2013 8:21 pm
Location: United States
Contact:

### Re: A function/tables question(I think?)

so.. yeah it's kinda of a mess haha I was trying to follow what you were addign to the table there and it got really jumbled around.. I think what you should do is go look into "class" making in lua. If you'd like you could take a look at my little ball example. The class.lua file is something I took from someone else and you can reuse it as much as you like (I would suggest reusing it). It makes things a lot easier and more readable.
Attachments
BouncingBalls.love

dementia_patient567
Prole
Posts: 12
Joined: Sun Jan 27, 2013 6:00 am

### Re: A function/tables question(I think?)

Sorry it's such an ugly mess...I know I need to use metatables and whatnot, but they're confusing the hell out of me. For some reason no matter how many times I read over the page for the in the PIL, I can't figure it out. I understand what they allow, for the most part, I just can't figure out how to use them myself...

Santos
Party member
Posts: 384
Joined: Sat Oct 22, 2011 7:37 am

### Re: A function/tables question(I think?)

So, please note that this is just my opinion and it's subject to change and I don't know what I'm talking about, but, maybe it might help?

About classes and metatables, I'd say: don't worry about them at all! I bet you can make your game without either of them. Using what you know, with tables and functions and everything else, and understanding what every line does, I'd say you could make whatever you want to make.

If you're used to thinking in classes though from other languages, there are some class libraries which you could use.

If you're finding what you're making overwhelming, I'd suggest just doing the simplest thing possible where you understand exactly what is going on. Like, even dividing a game into separate files... I mean, I did this too! And if you have a lot of lines of code, this makes sense, but I used to do it even when I'd first start something, and I'd try to use classes and I didn't understand everything that was going on and... it was kind of confusing!

I have a feeling that maybe it makes more sense to kind of "organise as you go", you know what I mean? Like, get the simplest possible thing working, make everything super easy to understand, and when you're like "eh, this is getting confusing", add some comments, and when you're like "eh, I keep doing this same thing again and again", make a function out of it, and when you're like "eh, there are just too many lines", separate it into some different files.

Pencil and paper are great for planning stuff out by the way! And also you can make different game folders and "main.lua"s for different things, like you might have one with just the player, and one with just the enemies, and then combine them later, if you find this useful.

PIL is a pretty advanced book IMO! lua-users wiki (http://lua-users.org/wiki/) is a pretty good place for information too, but again, it can be pretty advanced.

Also, it can be helpful to upload your .love file, because then people can check out how everything is working and experiment with the code and see how it runs!

So here's how I might approach what you're doing...

So for your enemies, the simplest thing might be to have each enemy have...
- An x position
- A y position

Like...

Code: Select all

function love.load()
enemy = {x = 10, y = 100}
end
And then you might want to draw it...

Code: Select all

function love.load()
enemyImage = love.graphics.newImage('Images/redenemy1.png')
enemy = {x = 10, y = 100}
end

function love.draw()
love.graphics.draw(enemyImage, enemy.x, enemy.y)
end
And then you might want to have different types of enemies with different colours. The simplest way to specify the type of enemy might be a string representing its colour...

Code: Select all

function love.load()
enemy = {x = 10, y = 100, type = 'red'}
end
And then you could store the enemy images in a table, and index it by the enemy type...

Code: Select all

function love.load()
enemyImages = {
red = love.graphics.newImage('Images/redenemy1.png'),
green = love.graphics.newImage('Images/greenenemy1.png'),
blue = love.graphics.newImage('Images/blueenemy1.png'),
}

enemy = {x = 10, y = 100, type = 'red'}
end

function love.draw()
love.graphics.draw(enemyImages[enemy.type], enemy.x, enemy.y)
end
And you might want multiple enemies, so you can make a table to house them...

Code: Select all

function love.load()
enemies = {}
end
And then insert an enemy table into the table of enemies whenever you make a new one...

Code: Select all

function love.load()
enemyImages = {
red = love.graphics.newImage('Images/redenemy1.png'),
green = love.graphics.newImage('Images/greenenemy1.png'),
blue = love.graphics.newImage('Images/blueenemy1.png'),
}

enemies = {}

table.insert(enemies, {x = 10, y = 100, type = 'red'})
table.insert(enemies, {x = 20, y = 200, type = 'green'})
table.insert(enemies, {x = 30, y = 300, type = 'blue'})
end
And then loop through this table to draw them...

Code: Select all

function love.draw()
for _, enemy in ipairs(enemies) do
love.graphics.draw(enemyImages[enemy.type], enemy.x, enemy.y)
end
end
And then you might want to spawn them from a position after 3 seconds, so you could have something like...

Code: Select all

function love.load()
-- There is other stuff here. :D

spawnpoint = {
currentTime = 0	,
timeLimit = 3,
x = 100,
y = 200,
type = 'red',
}
end

Code: Select all

function love.update(dt)
spawnpoint.currentTime = spawnpoint.currentTime + dt

if spawnpoint.currentTime >= spawnpoint.timeLimit then
table.insert(enemies, {x = spawnpoint.x, y = spawnpoint.y, type = spawnpoint.type})

-- Reset the timer.
spawnpoint.currentTime = spawnpoint.currentTime - spawnpoint.timeLimit
end
end
And then, like the enemies, you might want multiple spawn points, so you can create a table to house them, and then insert them into this table whenever you want...

Code: Select all

function love.load()
spawnpoints = {)

table.insert(spawnpoints, {
currentTime = 0	,
timeLimit = 3,
x = 100,
y = 200,
type = 'red',
})

table.insert(spawnpoints, {
currentTime = 0	,
timeLimit = 2,
x = 400,
y = 400,
type = 'green',
})
end
And like drawing the enemies, update them by looping through them...

Code: Select all

function love.update(dt)

-- Update the spawnpoints.
for _, spawnpoint in ipairs(spawnpoints) do
spawnpoint.currentTime = spawnpoint.currentTime + dt

if spawnpoint.currentTime >= spawnpoint.timeLimit then
table.insert(enemies, {x = spawnpoint.x, y = spawnpoint.y, type = spawnpoint.type})

-- Reset the timer.
spawnpoint.currentTime = spawnpoint.currentTime - spawnpoint.timeLimit
end
end
end
And then maybe the enemies move to the right at some speed...

Code: Select all

function love.load()
enemySpeed = 200
end

function love.update(dt)
-- Move the enemies to the right.
for _, enemy in ipairs(enemies) do
enemy.x = enemy.x + enemySpeed * dt
end

-- Update the spawnpoints.
end
And maybe the enemies move at different speeds depending on their type, so you could add the speed to the table for each enemy... But wait, if all the enemies of the same type move at the same speed, you can just create a table for the different speeds and index them by the type, just like the images!

Code: Select all

function love.load()
enemySpeeds = {
red = 100,
green = 250,
blue = 400,
}
end

function love.update(dt)
-- Move the enemies to the right.
for _, enemy in ipairs(enemies) do
enemy.x = enemy.x + enemySpeeds[enemy.type] * dt
end
end

I got somewhat carried away.

This is all probably completely useless, but that's how I'd approach it at the moment.

rhezalouis
Party member
Posts: 100
Joined: Mon Dec 07, 2009 10:27 am
Location: Indonesia
Contact:

### Re: A function/tables question(I think?)

Hi dementia_patient567,
dementia_patient567 wrote: I've tried so many times since I've started learning lua, but I can't figure it out...Anyways, if any of you could explain it in a way that it'd make sense to a toddler, there's a chance I might understand it. If a few dozen read throughs of the PIL classes and MT sections won't do it, Idk what will..
Try to read the PiL more carefully and play around each sample regarding the Lua metatable. The concept is a bit complicated, but surely graspable after a while of reading sample codes from other people, trial and error.
dementia_patient567 wrote: I know my code's probably a really ugly mess...Sorry. That's just me being a noob.
Don't give up and don't be sorry, everyone starts a beginner.

The attached code hopefully could help you a bit. I'll leave you to write the spawn mechanism. Cheers.

Attachments
dementia_patient567-enemyPrototype.love
LÖVE 0.8.0
Aargh, I am wasting my posts! My citizenshiiiip...

dementia_patient567
Prole
Posts: 12
Joined: Sun Jan 27, 2013 6:00 am

### Re: A function/tables question(I think?)

Your code definitely confused me a bit, but the more I connect the dots, the more I can read it. I definitely love how you've set your code up...A million times more efficient than what I can do >.<
colored polygon battles.love
I figured out how to do what my initial question was thanks to Santos' little rant. The code is still uglier than ugly can be, but it "works" right now. I'm not really sure whether to keep coding with my noob practices or rework the whole game to be more organized and efficient.

ferdielance
Prole
Posts: 6
Joined: Tue May 07, 2013 10:35 am

### Re: A function/tables question(I think?)

I'm also learning metatables, so let me try to work you through as straightforward an explanation as I can manage, building a jury-rigged class-ish thing one step at a time. Maybe explaining to you will improve my own understanding. Please follow along as I go; you're more likely to feel it really click for you if you do the examples and mess around than if you just passively read.

Making objects using metatables (and why it works)

We're going to make a kind of object called a BadGuy. For now, a BadGuy has an xPosition (defaults to 0), a yPosition (defaults to 0), and a behavior function (arbitrary code).

We want to be able to:

* Make multiple instances of the BadGuy class.
* Make different kinds of BadGuy
* Change the details of a BadGuy without actually worrying about how they're implemented too much.

Of course, we can add more stuff later, but let's start simple. I'm going to go through several versions of BadGuy, each with a problem, and fix the problems one-by-one until we have a prototype.

Step 1: Making the namespace.

We first create a table called BadGuy to hold the names of all the BadGuy object-related stuff. This table is BadGuy's namespace.

Code: Select all

-- Define our namespace

-- Test this:
end
Tables are the workhorse of all data types in Lua. You can even store functions in tables. This is important.

Step 2: The first steps towards a constructor - dealing with syntactic sugar

Now, we want to be able to spawn new instances of BadGuy, right? So let's do that. Normally, my function declaration for this constructor might look like this:

Code: Select all

-- Constructor
-- constructor code
end
But this obscures what's going on! Let's take that line "function BadGuy:new()" apart, piece by piece. First, what's the colon doing? It's syntactic sugar, a shorthand for:

Code: Select all

-- Constructor
-- constructor code
end
But we don't really want to mess around with this "self" thing yet. We'll get to that later; remove it for now. But what does that BadGuy.new mean?

Well, in .lua, a dot is another piece of syntactic sugar. table.key is the same thing as table["key"]

In other words, this prints the string:

Code: Select all

testTable = {["testKey"] = "This string is stored in this table under the key testKey"}
print(testTable.testKey)
There's another piece of syntactic sugar that can impede understanding:

Code: Select all

testTable = {["stringKey"] = "stringValue"}
...is the same thing as...

Code: Select all

testTable = {stringKey = "stringValue"}
stringKey is not a variable, even though it looks like one! It's a string, but the quotes are removed for convenience. In my example, I will NOT use that shortcut, but most examples you see will.

Here's where our code stands when we remove the sugar:

Code: Select all

-- Define our namespace

-- Constructor
-- constructor code
end

-- Test this:
end
Did you see what I did with our love.load() function? Why did that work?

(Answer: We defined a function with no arguments to run when LOVE loads. And when LOVE loads, apparently it checks the table love for the key "load" and runs the associated function!)

3: What should our constructor construct? It's not obvious at first...

Now, our constructor isn't very useful yet. It doesn't return anything! So... let's try a first crack at this. We want to make BadGuy instances, so... maybe it should return BadGuy? We'll test this, too, using the load routine.

Code: Select all

-- Define our namespace

-- Constructor
end

-- Test this:
end
This prints that there's a table, as expected. Did it work? Well, it's too early to pop the champagne, because take a look at what happens when we test a little more carefully:

Code: Select all

-- Define our namespace

-- Constructor
end

-- Test this:
end
If you run this, you'll see that both variables, testBadGuy and anotherBadGuy... are referring to the exact same table in memory. See, we never actually made a new copy of BadGuy. When you say x = {"1"} and y = x, you just assign the variable y to refer to the table that x is referring to. This can be really messy and confusing when tables start changing, and I still get surprised by what happens.

No, no, what we need is for our constructor to return some kind of BadGuy object. So let's try that instead. We'll give it default x and y positions, as well as a behavior to perform. Of course, the object will be a table.

Code: Select all

-- Define our namespace

-- Constructor
local object = {
["xPosition"] = 0,
["yPosition"] = 0,
["behavior"] = function () print("Look! I'm behaving!") end
}
return(object)
end

-- Test this:
end
Hey, that's pretty cool! We've spawned little objects that have a behavior. But there's STILL a problem. See, a BadGuy instance should have ALL the properties of the BadGuy class, right? It should inherit them. But what about the ability to spawn new BadGuys? Add this line to the test:

Code: Select all

anotherBadGuy["new"]()
and that breaks it. Darn it, we're so close, and we got this far without even using metatables.

4: Metatables!

Here's our dilemma. If we return the internal object table, we can make a bunch of pseudo-BadGuys with the internal properties, but they no longer have the actual properties of a BadGuy. But if we return a BadGuy... it's just the original BadGuy. We need a way to make an instance of BadGuy that inherits the default properties... and any other useful functions, without losing its internal properties. To do that, we need to add just one more line to our constructor:

What is that line? We'll see soon enough. But first... we need to understand what we're doing! An example may help.

Code: Select all

testTable = {["testIndex"] = 5}
print(testTable["nonexistentKey"])
This should print nil, because there's no key "nonexistentKey" in the table. But what if we assign it a special behavior when it's checking for indices and doesn't find one? We do this by using the function setmetatable(). The first argument of setmetatable is a table, and the second is basically a list of special behaviors indexed by keys that have double underscores. We're going to affect its "__index" behavior:

Code: Select all

testTable = {["testIndex"] = 5}
setmetatable(testTable, {["__index"] = "This index doesn't exist!"})
print(testTable["nonexistentKey"])
print(testTable["testIndex"])
There! Now it returns "This index doesn't exist" if we give it an index that's not in the table, and returns a 5 if we give it the key. What if we set "__index" to a table value?

Code: Select all

testTable = {["testIndex"] = "The original value for the key testIndex."}
setmetatable(testTable, {["__index"] =
{["inheritedKey"] = "This key was inherited via a metatable!"},
{["testIndex"] = "Will we see this instead of the original value?"}
}
)
print(testTable["inheritedKey"])
print(testTable["testIndex"])
Well, well, well. We can make it so that a table returns:

*** Values corresponding to its own keys
AND
*** Values corresponding to the keys in another table... if the original table doesn't have those keys.

Do you see how this solves our problem? Our constructor will make an object that is a table of keys and values. If a key is not found... it will return the very table that constructed it instead:

Code: Select all

-- Define our namespace

-- Constructor
local object = {
["xPosition"] = 0,
["yPosition"] = 0,
["behavior"] = function () print("Look! I'm behaving!") end
}
return(object)
end

-- Test this:
end
There we go! We're VERY close now... but how do we change the x and y values? We could directly do cloneBadGuy["xPosition"] = newX, but that's not stable. What if you change how your objects store "xPosition"? Should it really be dependent on that specific key name? We can do way better. We'll make it so that every BadGuy has a built-in function to set its x and y positions.

Self: A bit of very useful syntactic sugar.

We gave our BadGuy a constructor function, but there's nothing to stop us from adding another two functions:

Code: Select all

-- Define our namespace

-- Constructor
local object = {
["xPosition"] = 0,
["yPosition"] = 0,
["behavior"] = function () print("Look! I'm behaving!") end
}
return(object)
end

-- x, y setter function:
-- somehow set the BadGuy's x and y?
end

-- x, y getter function
-- somehow return the BadGuy's x and y?
end

-- Test this:
end
Well, how are we going to fill those out? Well, let's simplify. We know that the object they need to get and set coordinates for is the BadGuy instance itself... so let's just call those arguments self. And we know what key BadGuy uses to store its x and y positions, so let's add that info. And finally, we know what object we want to get and set the XY of... it's testBadGuy! So let's make those changes. We'll set testBadGuy's coordinates to 50, 100, and print them to see if it worked:

Code: Select all

-- Define our namespace

-- Constructor
local object = {
["xPosition"] = 0,
["yPosition"] = 0,
["behavior"] = function () print("Look! I'm behaving!") end
}
return(object)
end

-- x, y setter function:
self["xPosition"] = x
self["yPosition"] = y
end

-- x, y getter function
return self["xPosition"], self["yPosition"]
end

-- Test this:
end
There we go! We have some basic getters and setters and an object! Of course, we could make more BadGuys if we wanted. You may now be wondering, "why does this code look so different from examples of objects?" Well, part of the answer is that I removed all the shortcuts and syntactic sugar for clarity. What does it look like with the syntactic sugar replaced?

First, we replace all instances of table["value"] with table.value:

Code: Select all

-- Define our namespace

-- Constructor
local object = {
["xPosition"] = 0,
["yPosition"] = 0,
["behavior"] = function () print("Look! I'm behaving!") end
}
return(object)
end

-- x, y setter function:
self.xPosition = x
self.yPosition = y
end

-- x, y getter function
return self.xPosition, self.yPosition
end

-- Test this:
end
Next we use colon syntax to shorten things further. The colon is just a way of avoiding having to write "self" over and over, or replacing all instances of:

class.functionName = function(self)

with

function class:functionName()

and all instances of

x.functionName(x)

with

x:functionName()

...including any other arguments in parentheses, if need be. And let's make our constructor take self for consistency's sake.

Code: Select all

-- Define our namespace

-- Constructor
local object = {
["xPosition"] = 0,
["yPosition"] = 0,
["behavior"] = function () print("Look! I'm behaving!") end
}
return(object)
end

-- x, y setter function:
self.xPosition = x
self.yPosition = y
end

-- x, y getter function
return self.xPosition, self.yPosition
end

-- Test this:
end
And there we go! We now have a simple BadGuy object! Of course, I haven't managed locals vs. globals well, or used the namespace well, or whatever, but this should explain the principles!

pielago
Party member
Posts: 142
Joined: Fri Jun 14, 2013 10:41 am

### Re: A function/tables question(I think?)

ferdielance.
your example was awesome!! ...i am also learning metatables...
I practice metatables usign lua but not with love2D
can you send me to a link where I can use both.. most examples I see are just print functions! which is good.
but I am using a mac pc so print wont help me much
are there tutorials of metatables in small games so I can learn how to used them beside print???

### Who is online

Users browsing this forum: No registered users and 54 guests