Kollisioner
Vi skal kunne gå ind i vægge

Det, der bevæger sig, støder sammen
Nu hvor der er entities, der bevæger sig, er der opstået et ‘problem’. De går nemlig lige igennem hinanden som et spøgelse 👻.
Der er forskellige måder at gøre det på, og måske har jeg her lavet en forkert afvejning, nemlig ‘hastighed’. Hver beregning koster tid, og den vil man holde så lav som muligt. Den første beslutning, jeg skulle træffe, var, om jeg ville bruge polygon- eller simple rektangelberegninger (rect).
Opdatering 2025: Med den erfaring, jeg har nu, ville jeg kun bruge polygoner.
Mit valg blev rect, da jeg kun har simple figurer. Når jeg har et gennemsigtigt billede af en person, har det en maksimal højde og bredde, og det er hele det rektangel, man ikke må gå igennem.
engine3 er udvidet med følgende basisfunktioner
---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
Derefter får en entity information om, hvordan den skal forholde sig til andre entities.
Det sjove er, at jeg hurtigt fandt ud af, at det ganske rigtigt er meget hurtigt, men også for begrænset. Man kan jo også have en cirkel eller andre ‘mærkelige’ figurer. Hvis man så fx holder musemarkøren over sådan et objekt, viser den allerede uden for cirklen (som det usynlige rect sidder omkring), hvad det er.
Derfor bruger en entity nu en polygon til kollision med markøren og et rect til bevægelse.
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
Nu hvor alle entities har de nødvendige data, kan vi med aStar afgøre, hvor entity må 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å skærmen, bliver der kigget på kortet for at se, hvor spilleren må gå. Kortet bruger funktionen ovenfor og ligger delvist 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
Da jeg ikke vil have alle x,y-koordinater, men kun de øjeblikke, hvor entity skal have en ny position, bruger jeg også optimizePath og smoothPath, som du kan se herunder.
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 sidst
En fejl, jeg havde lavet, var, at jeg ville håndtere det i Love2d via en baggrundstråd. Min idé var, at det så ville gå hurtigere. Resultatet var, at det godt nok var hurtigere, men det passede ikke længere med det, man så på skærmen. Man så netop en ‘forsinkelse’.
Opdatering 2025 Selvom det ser simpelt ud ovenfor, kommer systemet til at få flere refactors, og jeg går delvist over til et walkmap. Det er et simpelt lag i Photoshop med sort og hvid, hvor sort kan gås på og hvid ikke kan.