4
votes

I'm working on a game in Lua, and so far I have everything working with everything in one document. However, to better organize everything, I've decided to expand it into modules, and while I figure I could probably get it working more or less the same, I figure now might be an opportunity to make things a little more clear and elegant.

One example is enemies and enemy movement. I have an array called enemyTable, and here is the code in Update that moves each enemy:

    for i, bat in ipairs(enemyTable) do
        if bat.velocity < 1.1 * player.maxSpeed * pxPerMeter then
            bat.velocity = bat.velocity + 1.1 * player.maxSpeed * pxPerMeter * globalDelta / 10
        end

        tempX,tempY = math.normalize(player.x - bat.x,player.y - bat.y)

        bat.vectorX = (1 - .2) * bat.vectorX + (.2) * tempX
        bat.vectorY = (1 - .2) * bat.vectorY + (.2) * tempY

        bat.x = bat.x + (bat.velocity*bat.vectorX - player.velocity.x) * globalDelta
        bat.y = bat.y + bat.velocity * bat.vectorY * globalDelta

        if bat.x < 0 then
            table.remove(enemyTable,i)
        elseif bat.x > windowWidth then
            table.remove(enemyTable,i)
        end     
    end

This code does everything I want it to, but now I want to move it into a module called enemy.lua. My original plan was to create a function enemy.Move() inside enemy.lua that would do this exact thing, then return the updated enemyTable. Then the code inside main.lua would be something like:

enemyTable = enemy.Move(enemyTable)

What I'd prefer is something like:

enemyTable.Move()

...but I'm not sure if there's any way to do that in Lua? Does anyone have any suggestions for how to accomplish this?

2
Your table traversal is losing enemies. You cannot use table.remove on a table while traversing it with ipairs. Every time you do you lose/miss an element in the original table from your loop. Try it: t = {1,2,3,4,5,6,7,8,9,10}; for i, v in ipairs(t) do print(v) if v % 2 == 0 then table.remove(t, i) end end (or just see it here). - Etan Reisner
Ooh, good point, thanks! - Adam
So, wait, I'm trying to solve this and I'm realizing it's not exactly a trivial solution. My first thought was to create a "trash" table that stored any elements I wanted to delete, then traverse the original table removing the trash elements. But this leads to the same problem: values get shifted down every time you delete something. Is there some clever way around this that I'm missing? - Adam
You can delete while traversing as long as you do it in reverse (and therefore don't use ipairs). for i = #enemyTable, 1, -1 do ... table.remove(enemyTable, i) ... end is safe since you only ever shift elements you've already dealt with. The alternative is to not edit the table in-place but to copy elements you want to save to a new table as you go and then return that. The choices have trade-offs in space and cost and so, in part, depend on your table sizes, etc. - Etan Reisner

2 Answers

3
votes

Sounds like you just want the metatable of enemyTable to be the enemy module table. Lua 5.1 reference manual entry for metatables

Something like this.

enemy.lua

local enemy = {}

function enemy:move()
    for _, bat in ipairs(self) do
        ....
    end
end

return enemy

main.lua

local enemy = require("enemy")

enemyTable = setmetatable({}, {__index = enemy})

table.insert(enemyTable, enemy.new())
table.insert(enemyTable, enemy.new())
table.insert(enemyTable, enemy.new())

enemyTable:move()
1
votes

Of course you can do it. For what I can see, your Move function processes the table you pass it as an argument and returns another table, in a functional programming fashion, leaving the first table immutate. You just have to set your Move function so that it knows it has to operate on your enemy table instead of creating a new table. So in your module write:

local enemy = {}

-- insert whatever enemy you want in the enemy table

function enemy.Move()
    for _, bat in ipairs(enemy) do
        --operate on each value of the enemy table
    end
    --note that this function doesn't have to return anything: not a new enemy table, at least
end

return enemy

And in your love.load function you can call

enemyTable = require "enemymodule"

Then you just have to call enemyTable.Move()