Kollisioner
Vi måste kunna gå in i väggar

Det som rör sig krockar
Nu när det finns entities som rör sig har ett ‘problem’ uppstått. De går nämligen rakt igenom varandra som ett spöke 👻.
Det finns olika sätt att göra det här på, och kanske gjorde jag här en felaktig avvägning, nämligen ‘hastighet’. Varje beräkning kostar tid och den vill man hålla så låg som möjligt. Det första beslutet jag behövde fatta var om jag skulle använda polygonberäkningar eller enkla rektangelberäkningar (rect).
Uppdatering 2025: Med den erfarenhet jag har nu skulle jag bara använda polygoner.
Jag valde rect, eftersom jag bara har enkla figurer. När jag har en transparent bild av en figur har den en maximal höjd och bredd, och det är hela rektangeln som man inte får gå igenom.
engine3 har byggts ut med följande grundfunktioner
---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
Sedan får en entity information om hur den ska förhålla sig till andra entities.
Det roliga är att jag snabbt upptäckte att det här visserligen är väldigt snabbt men också för begränsat. Man kan ju också ha en cirkel eller andra ‘konstiga’ figurer. Om man då till exempel för muspekaren över ett sådant objekt visar den redan utanför cirkeln (som den osynliga rect ligger runt) vad det är.
Därför använder en entity nu en polygon för kollision med pekaren och en rect för rörelse.
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)
Följ vägen
Nu när alla entities har den data som behövs kan vi med aStar avgöra var entityn får gå.
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
När du klickar någonstans på skärmen tittar man på kartan för att se var spelaren får gå. Kartan använder funktionen ovan och ligger delvis i en cache.
local path = aStar()
if path then
path = self:optimizePath(path)
path = self:smoothPath(path, map)
return {targetX = targetX, targetY = targetY, path = path}
end
Eftersom jag inte vill ha alla x,y-koordinater, utan bara de tillfällen då entityn ska byta position, använder jag också optimizePath och smoothPath, som du ser nedan.
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
}
Till sist
Ett misstag jag gjorde var att jag ville hantera det här i Love2d via en bakgrundstråd. Min tanke var att det skulle gå snabbare då. Resultatet var att det visserligen gick snabbare, men det stämde inte längre med det man såg på skärmen. Man såg i stället en ‘fördröjning’.
Uppdatering 2025 Även om det ser enkelt ut ovan kommer systemet att få flera refactors, och jag går delvis över till en walkmap. Det är ett enkelt lager i Photoshop i svart och vitt, där svart går att gå på och vitt inte.