Lua:在简单的射击游戏中制作碰撞系统,我在移除敌方物体时遇到问题

Lua: Making a collision system in a simple shooter and im having issues with removing the enemy object

我正在 lua 使用 love2d 制作一个简单的射击游戏。出于某种原因,当我启动游戏时,程序认为敌人已被击中并且不会生成它。我认为第 80 行存在问题。无论如何,它似乎始终认为 enemy 为零。我没有收到任何错误。生病 link 到一个带有代码的 pastebin。

编辑:我已经更新了我的代码并解决了上述问题。我认为我不正确地使用边界框检查碰撞。无论子弹从哪里经过,敌人都不会被设置为零。我认为这是因为它使用 bullets.x 而不是 o.x 检查,但我无法使用 o.x 检查,因为它是代码前面的 for 循环中的局部变量。

http://pastebin.com/iwL0QHsc

目前,您的代码

if (CheckCollision) then

如果您不提供任何参数,它将检查变量 'CheckCollision' 是否存在。在这种情况下,它确实是因为您在第 53 行将其声明为函数,因此每次更新 'enemy' 都将设置为 nil。

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

使用此行,但将变量替换为相应 entity/s.
的变量 使用这个,你可以使用

if enemy then

在您的绘图调用中,它将检查 'enemy' 是否存在。

顺便说一下,在 lua 中,if 语句不需要放在方括号中。

if (x > 3) then

功能完全一样

if x > 3 then

编辑: 在您提供的新代码中,当您声明函数时,您将其参数命名为已经存在的变量。通常,您会放入一些您没有用作参数的任意变量。例如

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

然后使用它。

test(1, 2)

或者如果你想使用变量。

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

使用已经存在的变量并不太糟糕,这只是意味着你不能使用它们。在 table、lua 中使用变量可能不太高兴。

所以你有

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

改用类似这样的东西。

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

或者,您可以跳过参数并对其进行硬编码。

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

我不确定这是否是您错误的根源,因为我无法访问合适的计算机来尝试它,但无论如何它都是有用的信息。

当您 load/run Lua 中的文件时,Lua 按顺序查看整个文件一次,因此您检查冲突的行仅发生在 main.lua的加载中,再也不会被看到了。

按照你现在的代码,它只检查一次敌人和子弹的碰撞

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

如果你把它放到love.update(dt)方法中,它会达到你想要的效果。

我想指出,一旦 enemy 设置为 nil(发生冲突),您将抛出一个关于尝试索引 nil 值的错误,因为您的 enemy 变量不再是 table.

同样值得注意的是,这些行

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

在for循环中

for i, o in ipairs(bullets) do

导致你的子弹行为不当(至少,我假设你不希望他们有这种行为)每次发射新子弹时,它都会被添加到 bullets table 代码

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

        dir = direction,
        speed = 400
    })

这会将每个新的 table 放入 bullets#bullets + 1(table 的最后一个索引 + 1)索引中。由于您的 for 循环遍历 bullets table 中的每个项目符号对象,因此发生的最后一个赋值总是在 table 中的最后一个项目符号上。

让我试着解释得更简单一些。

假设玩家发射了两发子弹。第一个子弹发射将调用我之前提到的 table.insert(...) 调用。所以,我们的 bullets table 看起来像这样

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
    }
}

现在,您使用了 ipairs(...) 调用,这意味着我们的 for 循环将只查看 bullets 中的 tables - 聪明的想法。但是它的实现有问题。

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

现在,对于一颗新子弹,它工作正常。随着新子弹的移动,每次更新都会正确更新 bullets.xbullets.y。但是现在让我们考虑一下我们的玩家发射的第二颗子弹。

bullets的新造型是这样的

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
    }
}

看到这是怎么回事了吗?让我们跳到第一次迭代的 for 循环。

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

然后我们到了第二个项目符号...

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

这就是问题所在。目前循环的编写方式,程序只关心最近发射的子弹,因为它将放在 bullets table 的最后 - 它被检查并分配给 bullets.x bullets.y 最后。这会导致碰撞检查只关心最近发射的子弹是否接触到敌人,其他的 none。

有两种方法可以解决这个问题。分别评估每个子弹在碰撞时的位置,然后将宽度和高度添加到它们的 table 中,就像这样

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

这样您只需将碰撞检查器的位置移动到循环内部并更改其参数。

另一种方法是使 class 类 tables 可以是 "instantiated," 其对象的元 tables 指向 class -喜欢 table。更难,但更好的练习,更容易编写方法,而不是为了什么。使对多个玩家、敌人、子弹等的一般检查和评估也变得更加容易。