碰撞
我们得能撞到墙

会动的东西就会碰撞
现在有了会移动的 entity,就出现了一个“问题”。它们会像幽灵 👻 一样互相穿过。
解决这个问题的方法有好几种,而我在这里也许权衡错了,那就是“速度”。 每次计算都要花时间,你会希望把它降到最低。我要做的第一个决定是:用多边形计算,还是用简单的矩形(rect)计算。
2025年更新:以我现在的经验,我只会用多边形。
我当时选择了 rect,因为我只有简单的形状。当我有一张角色的透明图片时,它有最大的高度和宽度,这就是整个不能穿过的矩形。
engine3 增加了以下基础函数
---Check if a point is inside the rect.
-- This function will include the border
---@param x integer
---@param y integer
---@param rect Rect The you want to use for the collision check
local function pointInRect(x, y, rect)
return x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
end
---@param x integer
---@param y integer
---@param rects table<Rect> The (collidable)Rects you want to use for the collision check
local function pointInRects(x, y, rects)
for _, rect in pairs(rects) do
if pointInRect(x, y, rect) then
return true
end
end
return false
end
--- Check if a point is inside the rect
-- https://2dengine.com/?p=intersections
---@param x integer X point coordinate
---@param y integer Y point coordinate
---@param l integer X left corner coordinate
---@param t integer Y top corner coordinate
---@param r integer X right corner coordinate
---@param b integer Y bottom corner coordinate
local function pointInAABB(x, y, l, t, r, b)
if x < l or x > r then
return false
end
if y < t or y > b then
return false
end
return true
end
---Check if two boxes overlap
-- https://love2d.org/wiki/BoundingBox.lua
---@param x1 integer X left corner coordinate box 1
---@param y1 integer Y top corner coordinate box 1
---@param w1 integer Width box 1
---@param h1 integer Height box 1
---@param x2 integer X left corner coordinate box 2
---@param y2 integer Y top corner coordinate box 2
---@param w2 integer Width box 2
---@param h2 integer Height box 2
local function rectsOverlap(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
---Check if two boxes overlap using AABB
---@param x1a integer X left corner coordinate box 1
---@param y1a integer Y top corner coordinate box 1
---@param x2a integer X right corner coordinate box 1
---@param y2a integer Y bottom corner coordinate box 1
---@param x1b integer X left corner coordinate box 2
---@param y1b integer Y top corner coordinate box 2
---@param x2b integer X right corner coordinate box 2
---@param y2b integer Y bottom corner coordinate box 2
local function AABBVsAABB(x1a, y1a, x2a, y2a, x1b, y1b, x2b, y2b)
if x1a > x2b or x1b > x2a then
return false
end
if y1a > y2b or y1b > y2a then
return false
end
return true
end
---Check if two boxes overlap using AABB using width and height
---@param o1x integer
---@param o1y integer
---@param o1w integer
---@param o1h integer
---@param o2x integer
---@param o2y integer
---@param o2w integer
---@param o2h integer
local function AABBWHVsAABBWH(o1x, o1y, o1w, o1h, o2x, o2y, o2w, o2h)
return not ((o2x > o1x + o1w) or (o2x + o2w < o1x) or (o2y > o1y + o1h) or (o2y + o2h < o1y))
end
然后 entity 会得到与其他 entity 如何交互的信息。
有意思的是,我很快就发现这种方法确实非常快,但也太受限了。因为你也可能有圆形或其他“奇怪”的形状。比如你把鼠标指针移到这样的物体上时,指针还在圆形外面(被看不见的 rect 包围的部分),就已经显示出它是什么了。
所以现在 entity 在与指针碰撞时使用多边形,在移动时使用 rect。
local Collision = require "lib.engine3.src.framework.helper.math.collision"
---@field collidableA? table {1[opt](left),2([opt]right),3([opt]top),4([opt]bottom)}
---@field collidableB? table {1[opt](left),2([opt]right),3([opt]top),4([opt]bottom)}
---@field collidableRects? table Collidable rects [{x1,y1,x2,y2}]
function Frameset:new(data)
沿着路径走
现在所有 entity 都有了需要的数据,我们就可以用 aStar 来决定 entity 能走到哪里。
function PathFinding:isPointInCollision(collisionRectsTable, x, y)
for _, collisionRectList in pairs(collisionRectsTable) do
for _, rect in pairs(collisionRectList) do
if Collision.pointInRect(x, y, rect) then
return true
end
end
end
return false
end
当你点击屏幕上的某个地方时,会根据地图检查玩家可以走到哪里。 地图使用了上面的函数,并且部分内容做了缓存。
local path = aStar()
if path then
path = self:optimizePath(path)
path = self:smoothPath(path, map)
return {targetX = targetX, targetY = targetY, path = path}
end
因为我不需要所有的 x,y 坐标,只需要 entity 必须改变位置的那些时刻,所以我还用了 optimizePath 和 smoothPath,如下所示。
path {
x = 1, y = 1
x = 2, y = 1
x = 3, y = 1
x = 4, y = 1
x = 5, y = 1
x = 6, y = 1
}
path {
x = 1, y = 1
x = 6, y = 1
}
最后
我犯过的一个错误,是想在 Love2d 里通过后台线程来处理这件事。我以为这样会更快。结果确实更快了,但和屏幕上看到的不再一致,反而会看到“延迟”。
2025年更新 虽然上面看起来很简单,但这个系统还会经历多次重构,我会部分改用 walkmap。它是 Photoshop 里一个简单的黑白图层,黑色可以行走,白色不行。