Kollisjoner
Vi må kunne gå inn i vegger

Det som beveger seg, kolliderer
Nå som det finnes entities som beveger seg, har det oppstått et ‘problem’. De går nemlig rett gjennom hverandre som et spøkelse 👻.
Det finnes flere måter å gjøre dette på, og kanskje har jeg gjort en feil avveining her, nemlig ‘hastighet’. Hver beregning koster tid, og den vil man holde så lav som mulig. Den første avgjørelsen jeg måtte ta, var om jeg skulle bruke polygon- eller enkle rektangelberegninger (rect).
Oppdatering 2025: Med erfaringen jeg har nå, ville jeg bare brukt polygoner.
Valget mitt ble rect, siden jeg bare har enkle figurer. Når jeg har et gjennomsiktig bilde av en figur, har det en maksimal høyde og bredde, og det er hele rektangelet man ikke får gå gjennom.
engine3 er utvidet med følgende grunnfunksjoner
---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
Deretter får en entity informasjon om hvordan den skal forholde seg til andre entities.
Det morsomme er at jeg raskt fant ut at dette riktignok er veldig raskt, men også for begrenset. Du kan jo også ha en sirkel eller andre ‘rare’ figurer. Hvis du da for eksempel holder musepekeren over et slikt objekt, viser den allerede utenfor sirkelen (som det usynlige rect-et ligger rundt) hva det er.
Derfor bruker en entity nå en polygon for kollisjon med pekeren og et rect for bevegelse.
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ølg stien
Nå som alle entities har dataene de trenger, kan vi med aStar bestemme hvor entityen 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 klikker et sted på skjermen, sjekkes kartet for å se hvor spilleren får gå. Kartet bruker funksjonen over og 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
Siden jeg ikke vil ha alle x,y-koordinatene, bare øyeblikkene der entityen må få en ny posisjon, bruker jeg også optimizePath og smoothPath, som du ser under.
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
}
Til slutt
En feil jeg gjorde, var at jeg ville håndtere dette i Love2d via en bakgrunnstråd. Tanken var at det da ville gå raskere. Resultatet var at det riktignok gikk raskere, men det stemte ikke lenger med det du så på skjermen. Du så heller en ‘forsinkelse’.
Oppdatering 2025 Selv om det ser enkelt ut over, kommer dette systemet til å få flere refaktoreringer, og jeg går delvis over til et walkmap. Det er et enkelt lag i Photoshop i svart og hvitt, der svart kan gås på og hvitt ikke.