-1

I'm making a simple shooter in lua using love2d. For some reason when i launch the game the program thinks the enemy has been shot and doesn't spawn it. I think theres an issue on line 80. It seems to think enemy is nil at all times no matter what. I'm not getting any errors. Ill link to a pastebin with the code.

Edit: I've updated my code quite a bit and have solved the issue above. I think im checking for collision using the bounding box incorrectly. No matter where the bullet passes the enemy is never set to nil. I think it's because it checks with bullets.x instead of o.x but i cant check with o.x because its a local variable in the for loop earlier in the code.

http://pastebin.com/iwL0QHsc

2 Answers2

3

Currently, your code does

if (CheckCollision) then

If you don't provide any parameters, it will check if the variable 'CheckCollision' exists. In this case, it does because you have declared it as a function on line 53, so every update 'enemy' will be set to nil.

if CheckCollision(x3,y3,x2,y2,w2,h2) then

Use this line but replace the variables with the variables of the respective entity/s.
Using this, you can use

if enemy then

In your draw call, which will check if 'enemy' exists.

Just by the way, in lua an if statement doesnt need to be within brackets.

if (x > 3) then

Functions exactly the same as

if x > 3 then

Edit: In the new code you have provided, when you declare the function you are naming its arguments as variables that already exist. Usually, you put in some arbitrary variables you haven't used as arguments. Eg.

function test(a, b)
    print(a + b)
end

Then to use it.

test(1, 2)

Or if you want to use variables.

var1 = 1
var2 = 2
test(var1, var2)

Using variables that already exist isn't too bad, it just means you can't use them. Using variables in a table, lua probably isn't too happy about.

So where you have

function CheckCollision(o.x,o.y,o.w,o.h, enemy.x,enemy.y,enemy.w,enemy.h)
    return o.x < enemy.x+enemy.w and
     enemy.x < o.x+o.w and
     o.y < enemy.y+enemy.h and
     enemy.y < o.y+o.h
end

Use something like this instead.

function CheckCollision(x1,y1,w1,h1, x2,y2,w2,h2)
    return x1 < x2+w2 and
     x2 < x1+w1 and
     y1 < y2+h2 and
     y2 < y1+h1
 end

Alternatively, you could skip out on the parameters and have it hard coded.

function CheckCollision()
    return o.x < enemy.x+enemy.w and
     enemy.x < o.x+o.w and
     o.y < enemy.y+enemy.h and
     enemy.y < o.y+o.h
end

I'm not sure if this is the source of your error as I don't have access to a proper computer to try it but it is useful information anyway.

Adam
  • 81
  • 5
  • Thanks for the reply! I'm not used to the lua syntax so i wasnt sure about the if statement brackets. I'm way more experienced in c++ and java. I've updated my code a bit and now im getting an error. Its definitely a syntax issue. The error is: main.lua:57: ')' expected near '.' Updated code: http://pastebin.com/RDCmYj11 – user3103529 Jun 22 '16 at 22:03
1

When you load/run a file in Lua, Lua looks through the whole file in a sequential manner once, so your line to check collisions only occurs upon main.lua's loading, never to be looked at again.

As your code stands now, it only checks the collision of the enemy and bullet once

if CheckCollision(enemy.x,enemy.y,enemy.w,enemy.h,bullets.x,bullets.y,bullets.w,bullets.h) then enemy = nil
end

If you put this into the love.update(dt) method, it will achieve your desired effect.

I'd like to note that once the enemy is set to nil (collision occurs), you will throw an error about attempting to index a nil value since your enemy variable is no longer a table.

Also noteworthy, these lines

bullets.x = o.x
bullets.y = o.y

in the for loop

for i, o in ipairs(bullets) do

cause your bullets to behave improperly (at least, I assume you don't wish for the behavior they have) Each time a new bullet is fired, it is added to the bullets table with the code

table.insert(bullets, {
        x = player.x,
        y = player.y,

        dir = direction,
        speed = 400
    })

This puts each new table into the #bullets + 1 (the table's last index + 1) index of bullets. Since your for loop iterates over each bullet object in the bullets table, the last assignment that occurs is always on the last bullet in the table.

Let me try to explain this simpler.

Say a player fires two bullets. The first bullet firing will invoke the table.insert(...) call I mentioned before. So, our bullets table will look like this

bullets = {
    x = 100,
    y = 100, -- This is what you set player.x and player.y to in the start.
    w = 15,
    h = 15,

    -- This is that new table we added - the 1st bullet fired.
    {
        -- This will all be inside it according to the table.insert(...) call.
        x = 100, -- What player.x is equal to
        y = 100, -- What player.y is equal to

        dir = ... -- Who knows what it is, just some radians that you calculated.
        speed = 400
    }
}

Now, you used an ipairs(...) call which means that our for loop will only look at the tables inside bullets - smart thinking. But there is a problem with its implementation.

-- With our new table inside bullets, we will only have that table to look at for the entire loop. So, lets jump right into the loop implementation.
local i, o
for i, o in ipairs(bullets) do
    -- This is fine. These lines look at the new table's x and y values and move them correctly.
    o.x = o.x + math.cos(o.dir) * o.speed * dt
    o.y = o.y + math.sin(o.dir) * o.speed * dt

    -- This is the problem.
    bullets.x = o.x  -- Now, those x and y values in the bullets table are set to the new table's x and y values.
    bullets.y = o.y

    -- The rest of the loop works fine.
    ...
end

Now, for one new bullet, it works fine. Each update will update bullets.x and bullets.y correctly as the new bullet travels. But now lets considered the second bullet our player fired.

The new look of bullets is like this

bullets = {
    x = 150, -- These are equal to the first bullet's values - for now, at least.
    y = 150,
    w = 15,
    h = 15,

    -- This 1st bullet is still here.
    {
        x = 150, -- Lets say the bullet has moved 50 pixels in both directions.
        y = 150,

        dir = ...
        speed = 400
    },

    -- This is the new second bullet.
    {
       x = 100, -- Remember player.x and player.y are both 100
       y = 100,

       dir = ...
       speed = 400
    }
}

See where this is going yet? Lets jump to the for loop on the first iteration.

-- First iteration occurs. We're looking at the first bullet.
for i, o in ipairs(bullets) do
    o.x = o.x + math.cos(o.dir) * o.speed * dt -- Lets say o.x = 160 now
    o.y = o.y + math.sin(o.dir) * o.speed * dt -- and o.y = 160

    bullets.x = o.x  -- bullets.x = o.x, so bullets.x = 160
    bullets.y = o.y  -- bullets.y = o.y, so bullets.y = 160

    ...
end

But then we get to the second bullet...

-- Second iteration, second bullet.
for i, o in ipairs(bullets) do
     -- Since it's the new table, o.x and o.y start at 100 
     o.x = o.x + math.cos(o.dir) * o.speed * dt  -- Lets say o.x = 110
     o.y = o.y + math.sin(o.dir) * o.speed * dt  -- Lets say o.y = 110 as well

     bullets.x = o.x
     bullets.y = o.y
     -- But now our bullets.x and bullets.y have moved to the new bullet!
     -- The position of the 1st bullet is completely forgotten about!

     ...
end

And this is the problem. The way the loop is written currently, the program only cares about the most recently fired bullet because it is going to be placed last in the bullets table - it is checked and assigned to bullets.x and bullets.y last. This causes the collision check to only care if the most recently fired bullet is touching the enemy, none of the other ones.

There are two ways to fix this. Either evaluate the position of each bullet separately on collisions and add widths and heights to their tables, like this

-- When you add a bullet
table.insert(bullets, {
    x = player.x,
    y = player.y,
    w = bullets.w,
    h = bullets.h,

    dir = direction,
    speed = 400
})

-- When looking for collisions
local i, o
for i, o in ipairs(bullets) do
    o.x = o.x + math.cos(o.dir) * o.speed * dt
    o.y = o.y + math.sin(o.dir) * o.speed * dt

    -- This will destroy an enemy correctly
    if CheckCollision(enemy.x,enemy.y,enemy.w,enemy.h, o.x, o.y, o.w, o.h) then enemy = nil end

    if (o.x < -10) or (o.x > love.graphics.getWidth() + 10)
    or (o.y < -10) or (o.y > love.graphics.getHeight() + 10) then
        table.remove(bullets, i)
    end
end

This way you just move the collision checker's position to inside the loop and change its parameters.

The other way is to make class-like tables that can be "instantiated," whose objects' metatables point to the class-like table. Harder, but better practice and much easier to write methods and what not for. Makes generic inspections and evaluations of multiple players, enemies, bullets, etc. much easier as well.

Liam Mueller
  • 1,360
  • 2
  • 10
  • 17
  • Thank you for the reply. I made the changes you suggested and ran into the error with enemy as you said but then i rewrote the code for enemy and how its removed and now it works fine. You have no idea how much your reply helped me. Thanks again! – user3103529 Jun 26 '16 at 11:14