1

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?

1
  • add a new method in Zombie Zombie = require("zombie"):new() and Curtis answer will show how to use __index for new objects Commented Sep 22, 2018 at 1:24

1 Answer 1

3

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).

Sign up to request clarification or add additional context in comments.

4 Comments

I assume Zombie.new() is a typo and should be Zombie.create()? In that case I get a new error: zombie.lua:31: attempt to call method 'setPos' (a nil value)'. My 'Zombie:setPos(x, y) only has self.position.x = x self.position.y = y. What does that error mean? Also is there a reason to assign the sprite outside of the 'constructor'?
You're write about the new typo, that's fixed now. The code above omitted the definition of setPos, I've added it in case you've done something differently. That error means that setPos isn't defined on your zombie instances.
The sprite is assigned outside of the constructor because love.graphics.newImage is expensive: "This function can be slow if it is called repeatedly, such as from love.update or love.draw. If you need to use a specific resource often, create it once and store it somewhere it can be reused!"
Regarding the sprite: we assign each zombie's sprite in the constructor and we only create zombies using the constructor so we call that function repeatedly, right? If I got it correctly, we could assign a local variable and then assign that variable to a member in the constructor?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.