Sorry
Sorry

Reputation: 569

Lua - problems iterating through table

I'm trying to program a game.

The gist is that the game has the player and I will spawn zombies that will walk towards the player.

These are my files:

main.lua

local Player = require("player")
local Zombie = require("zombie")

love.window.setTitle("Shooter")

function love.load()
    sprites = {}
    sprites.background = love.graphics.newImage('sprites/background.png')

    player1 = Player
    player1.setPos(300, 300)

    zombies = {}
end

function love.update(dt)
    player1.move(dt)
    player1.rotate()

    for i, z in ipairs(zombies) do
        z.rotate(player1)
        z.move(dt)
    end
end

function love.draw()
    love.graphics.draw(sprites.background, 0, 0)
    love.graphics.draw(player1.sprite, player1.position.x, player1.position.y, player1.angle, nil, nil, player1.sprite:getWidth()/2, player1.sprite:getHeight()/2)

    for i,z in ipairs(zombies) do
        love.graphics.draw(z.sprite, z.position.x, z.position.y, z.angle, nil, nil, z.sprite:getWidth()/2, z.sprite:getHeight()/2)
        love.graphics.printf("order" ..i, 0, 50, love.graphics.getWidth(), "center")
    end
end

function distance(player, enemy)
    return math.sqrt((player.position.x - enemy.position.x)^2 + (player.position.y - enemy.position.y)^2)
end

function love.keypressed(key, scancode, isrepeat)
    if key == "u" then
        spawnZombie(zombies)
    end
end

zombie.lua

local Zombie =  {
    position = {},
    speed = 1,
    angle = 0,
    sprite = love.graphics.newImage('sprites/zombie.png')
}

function Zombie.setPos(x, y)
    Zombie.position.x = x
    Zombie.position.y = y
end

function Zombie.move(dt)
    distance = Zombie.speed * dt * 60  
    Zombie.position.x = Zombie.position.x + math.cos(Zombie.angle) * distance
    Zombie.position.y = Zombie.position.y + math.sin(Zombie.angle) * distance
end

function Zombie.rotate(player) 
    Zombie.angle = math.atan2(player.position.y - Zombie.position.y, player.position.x - Zombie.position.x)
end

function spawnZombie(zombieTable)
    zombie = Zombie
    zombie.setPos(math.random(0, love.graphics.getWidth()), math.random(0, love.graphics.getHeight()))

    table.insert(zombieTable, zombie)
end

return Zombie

Finally, player.lua file for completion.

spawnZombie() should create a new zombie and insert it into the zombies table. love.draw() should iterate through the zombies table and draw them all.

There are two problems I'm encountering, very likely related:

  1. Only one zombie ever spawns.

  2. Each spawned zombie is faster than the last one.

This probably means that only one zombie (the first one in the table) is ever drawn and his speed is increased in the love.update() function. If that assumption is correct, then the problem is probably in the spawnZombie() function.

How do I fix my program?

Upvotes: 1

Views: 153

Answers (1)

Curtis Fenner
Curtis Fenner

Reputation: 1411

Zombie is your one-and-only zombie. Each "new" zombie that you create is really just an alias for it:

zombie = Zombie does not create a copy, but says that zombie is just another name for the same object.

You need to create a new table for each new zombie. You probably want to use methods so that you can use the same functions for each instance.

A method is defined with :, and automatically gets an invisible parameter called self which is the object that the method was invoked on. Methods are called like :move(0.5) instead of .move(0.5):

function Zombie:move(dt)
    local distance = self.speed * dt * 60  
    self.position.x = self.position.x + math.cos(self.angle) * distance
    self.position.y = self.position.y + math.sin(self.angle) * distance
end

To make a new zombie instance, you need to make a new table with the initial properties that you want

local newZombie = {
    position = {},
    speed = 1,
    angle = 0,
}

and then give it all of the methods that a Zombie should have. The briefest way to do that is to use metatables. The __index metamethod explains to Lua what to do when a field/method is asked for that hasn't been explicitly set. This lets us "copy" the Zombie "class" onto new instances:

setmetatable(newZombie, {__index = Zombie})

This can be wrapped up into a "constructor" method on the Zombie "class". Since this constructor doesn't act on an existing zombie object, you should define and call it with . instead of ::

local Zombie = {}

-- This property is shared between ALL zombies,
-- and changing it will change it for ALL zombies
Zombie.sprite = love.graphics.newImage('sprites/zombie.png')

-- RETURNS a freshly created Zombie instance
function Zombie.create()
    local newZombie = {
        position = {},
        speed = 1,
        angle = 0,
        sprite = love.graphics.newImage('sprites/zombie.png')
    }
    return setmetatable(newZombie, {__index = Zombie})
end

-- MODIFIES the zombie that this is called on
function Zombie:move(dt)
    local distance = self.speed * dt * 60  
    self.position.x = self.position.x + math.cos(self.angle) * distance
    self.position.y = self.position.y + math.sin(self.angle) * distance
end

function Zombie:setPos(x, y)
    self.position.x = x
    self.position.y = y
end

-- MODIFIES zombies by adding a freshly created zombie to it
function spawnZombie(zombies)
    local newZombie = Zombie.create()
    newZombie:setPos(math.random(0, love.graphics.getWidth()), math.random(0, love.graphics.getHeight()))

    table.insert(zombieTable, newZombie)
end


-- In update function:
for i, z in ipairs(zombies) do
    z:rotate(player1)
    z:move(dt)
end

You should get into the habit of using local variables in your functions, such as distance in Zombie:move. Global variables are likely to cause mistakes, and also take a (small) performance penalty, because sharing the variable with everyone takes more time than keeping it hidden (and also makes the JIT optimizer's job more difficult).

Upvotes: 3

Related Questions