【问题标题】:Lua: Making a collision system in a simple shooter and im having issues with removing the enemy objectLua:在一个简单的射击游戏中制作碰撞系统,但我在移除敌人物体时遇到了问题
【发布时间】:2016-10-23 18:02:15
【问题描述】:

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

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

http://pastebin.com/iwL0QHsc

【问题讨论】:

    标签: lua collision-detection love2d


    【解决方案1】:

    目前,您的代码可以

    if (CheckCollision) then
    

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

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

    使用此行,但将变量替换为相应实体的变量。
    使用这个,你可以使用

    if enemy then
    

    在您的绘图调用中,它将检查“敌人”是否存在。

    顺便说一句,在 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)
    

    使用已经存在的变量并不算太糟糕,它只是意味着你不能使用它们。在表中使用变量,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
    

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

    【讨论】:

    • 感谢您的回复!我不习惯 lua 语法,所以我不确定 if 语句括号。我在 c++ 和 java 方面更有经验。我已经更新了我的代码,现在我得到了一个错误。它绝对是一个语法问题。错误是: main.lua:57: ')' 预计在 '.' 附近更新代码:pastebin.com/RDCmYj11
    【解决方案2】:

    当您在 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 变量不再是表。

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

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

    在for循环中

    for i, o in ipairs(bullets) do
    

    导致您的子弹行为不正常(至少,我假设您不希望它们的行为)每次发射新子弹时,它都会与代码一起添加到 bullets 表中

    table.insert(bullets, {
            x = player.x,
            y = player.y,
    
            dir = direction,
            speed = 400
        })
    

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

    让我试着更简单地解释一下。

    假设一名玩家发射了两颗子弹。第一次子弹发射将调用我之前提到的table.insert(...) 调用。因此,我们的bullets 表将如下所示

    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 中的表格 - 聪明的想法。但是它的实现存在问题。

    -- 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 表中的最后 - 它被检查并最后分配给bullets.xbullets.y。这会导致碰撞检查只关心最近发射的子弹是否触及敌人,而不关心其他子弹。

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

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

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

    另一种方法是制作可以“实例化”的类类表,其对象的元表指向类类表。更难,但更好的实践,更容易编写方法和什么不适合。也使对多个玩家、敌人、子弹等的通用检查和评估变得更加容易。

    【讨论】:

    • 感谢您的回复。我按照您的建议进行了更改并遇到了与敌人有关的错误,但随后我重写了敌人的代码以及如何删除它,现在它可以正常工作了。你不知道你的回复对我有多大帮助。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-05
    相关资源
    最近更新 更多