如何在 Love2d 中使用边界框?

How to use bounding box in Love2d?

我一直在使用一些极其庞大的代码来检测简单对象之间的碰撞,而且我听说过边界框。我找不到任何关于如何使用它的教程,所以我在询问如何使用它。这是我检测碰撞的方法:

    function platform.collision()
if player.x + player.width / 2 <= platform.x + platform.width and
    player.x + player.width / 2 >= platform.x and
    player.y + player.height <= platform.y + platform.height and 
    player.y + player.height >= platform.y then

MDN has a rather concise article on 2D collision detection。作为 MDN,示例在 javascript 中,但很容易翻译成任何语言并适用于任何语言 - 包括 Lua.

一起来看看:

Axis-Aligned Bounding Box

One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning no rotation. The algorithm works by ensuring there is no gap between any of the 4 sides of the rectangles. Any gap means a collision does not exist.

他们的例子,翻译成Lua:

local rect1 = { x = 5, y = 5, width = 50, height = 50 }
local rect2 = { x = 20, y = 10, width = 10, height = 10 }

if 
    rect1.x < rect2.x + rect2.width and
    rect1.x + rect1.width > rect2.x and
    rect1.y < rect2.y + rect2.height and
    rect1.height + rect1.y > rect2.y 
then
    -- collision detected!
end

-- filling in the values =>

if 
    5 < 30 and
    55 > 20 and
    5 < 20 and
    55 > 10
then
    -- collision detected!
end

A live example,在 JavaScript 中再次证明了这一点。


这是一个快速(但不完美)的 Love2D 示例,您可以将其放入 main.lua 中并进行试验。

local function rect (x, y, w, h, color)
    return { x = x, y = y, width = w, height = h, color = color }
end

local function draw_rect (rect)
    love.graphics.setColor(unpack(rect.color))

    love.graphics.rectangle('fill', rect.x, rect.y,
        rect.width, rect.height)
end

local function collides (one, two)
    return (
        one.x < two.x + two.width and
        one.x + one.width > two.x and
        one.y < two.y + two.height and
        one.y + one.height > two.y
    )
end

local kp = love.keyboard.isDown
local red = { 255, 0, 0, 255 }
local green = { 0, 255, 0, 255 }
local blue = { 0, 0, 255, 255 }

local dim1 = rect(5, 5, 50, 50, red)
local dim2 = rect(20, 10, 60, 40, green)

function love.update ()
    if kp('up') then
        dim2.y = dim2.y - 1
    end

    if kp('down') then
        dim2.y = dim2.y + 1
    end

    if kp('left') then
        dim2.x = dim2.x - 1
    end

    if kp('right') then
        dim2.x = dim2.x + 1
    end

    dim2.color = collides(dim1, dim2) and green or blue
end

function love.draw ()
    draw_rect(dim1)
    draw_rect(dim2)
end

Oka 解释得很好。这适用于所有矩形、未旋转和轴对齐的东西。你甚至已经这样做了。这非常适合按钮之类的东西!

但我喜欢做的是在物体周围使用(不可见的)圆圈,看看它们是否发生碰撞。这适用于高度与宽度大致相同的所有内容(许多横向卷轴平台游戏或自上而下的角色扮演游戏就是这种情况)。
如果你想让对象在当前位置居中,这是非常方便的。在触摸屏设备上模拟手指特别有用,因为手指比鼠标光标大很多。 ;)

下面是有关如何使用此方法的示例。您可以将其复制为实际游戏,它会起作用。

--[[ Some initial default settings. ]]

function love.load()
    settings = {
        mouseHitbox = 5,   -- A diameter around the mouse cursor.
        -- For a finger (thouchscreen) this could be bigger!
    }

    objects = {
        [1] = {
            x = 250,   -- Initial X position of object.
            y = 200,   -- Initial Y position of object.
            hitbox = 100,   -- A diameter around the CENTER of the object.
            isHit = false    -- A flag for when the object has been hit.
        },
        [2] = {
            x = 400,
            y = 250,
            hitbox = 250,
            isHit = false
        }
    }
end

--[[ This is the actual function to detect collision between two objects. ]]

function collisionDetected(x1,y1,x2,y2,d1,d2)
    -- Uses the x and y coordinates of two different points along with a diameter around them.
    -- As long as these two diameters collide/overlap, this function returns true!
    -- If d1 and/or d2 is missing, use the a default diameter of 1 instead.
    local d1 = d1 or 1
    local d2 = d2 or 1
    local delta_x = x2 - x1
    local delta_y = y2 - y1
    local delta_d = (d1 / 2) + (d2 / 2)
    if ( delta_x^2 + delta_y^2 < delta_d^2 ) then
        return true
    end
end

--[[ Now, some LÖVE functions to give the collisionDetection() some context. ]]

function love.draw()
    for i=1,#objects do   -- Loop through all objects and draw them.
        if ( objects[i].isHit ) then
            love.graphics.setColor(255, 0, 0)   -- If an object is hit, it will flash red for a frame.
            objects[i].isHit = false
        else
            love.graphics.setColor(255, 255, 255)
        end
        love.graphics.circle("line", objects[i].x, objects[i].y, objects[i].hitbox/2)
    end
end

-- You can use the following to check, if any object has been clicked on (or tapped on a touch screen device).

function love.mousepressed(x,y,button)
    if ( button == 1 ) then
        local i = objectIsHit(x,y)   -- Check, if an object has been hit.
        if ( i ) then
            -- The object number 'i' has been hit. Do something with this information!
            objects[i].isHit = true
        end
    end
end

function objectIsHit(x,y)
    for i=1,#objects do   -- Loop through all objects and see, if one of them has been hit.
        if ( collisionDetected(x, y, objects[i].x, objects[i].y, settings.mouseHitbox, objects[i].hitbox) ) then
            return i   -- This object has been hit!
        end
    end
end

-- For the sake of completeness: You can use something like the following to check, if the objects themselves collide.
-- This would come in handy, if the objects would move around the screen and then bounce from each other, for example.

function love.update(dt)
    if ( collisionDetected(objects[1].x, objects[1].y, objects[2].x, objects[2].y, objects[1].hitbox, objects[2].hitbox) ) then
        -- The objects collided. Do something with this information!
    end
end

如您所见,collisionDetection() 函数使用起来非常简单直观。

希望我能给你更多的见解。 并与 LÖVE 2D 一起玩得开心! :)