Logo Signal From The Stars

Botsingen

We moeten tegen muren aanlopen

Martin avatar
  • Martin
  • 5 min read
Een botsing is onvoormijdelijk

Wat beweegt gaat botsen

Nu er bewegende entities zijn is er een ‘probleem’ ontstaan. Ze lopen namelijk dwars door elkaar heen zoals een geest πŸ‘».

Er zijn verschillende manieren om dit te doen, ik heb hier misschien een verkeerde afweging gemaakt namelijk ‘snelheid’. Elke berekening kost tijd en je wilt die zo laag mogelijk houden. Het eerste besluit wat ik moest nemen was of ik voor polygon of simpele vierkant (rect) berekeningen ga.

Update 2025 Met de ervaring die ik nu heb, zou ik alleen polygon gebruiken.

Mijn keuze was rect, aangezien ik alleen maar simpele figuren heb. Wanneer ik een transparante afbeelding heb van een character, dan heeft deze een maximale hoogte en breedte, dat is het totale vierkant waar men niet doorheen mag lopen.

De engine3 is uitgebreidt met de volgende basis functies

---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

Vervolgens krijgt een entity informatie hoe deze om moet gaan met andere entities.

De grap is dat ik er snel achter kwam dat dit inderdaad zeer snel is maar ook te beperkt. Aangezien je ook een cirkel figuur of andere ‘rare’ figuren kunt hebben. Wanneer je dan bijv. met je muis cursor over een dergelijk object gaat, zal deze al buiten het cirkel figuur (waar de onzichtbare rect omheen staat) aangeven wat het is.

Vandaar dat al een entity nu voor de cursor collision een polygon gebruikt en voor beweging een 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)

Volg het pad

Nu alle entities de benodigde data hebben, kunnen we aan de hand van aStar bepalen waar de entity mag lopen.

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

Wanneer je ergens op het scherm klikt, zal gekeken worden aan de hand van de map waar de speler mag lopen. De map gebruik de functie hierboven en staat deels in een cache.

local path = aStar()
if path then
    path = self:optimizePath(path)
    path = self:smoothPath(path, map)
    return {targetX = targetX, targetY = targetY, path = path}
end

Aangezien ik niet alle x,y coordinaten wil, maar alleen de momenten waar de entity een andere positie moet krijgen gebruik ik ook optimizePath en smoothPath, zoals je hieronder kunt zien.

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
}

Tot slot

Een fout die ik had gemaakt was dat ik dit in Love2d via een background thread wou afhandelen. Mijn idee hierbij was dat dit dan sneller ging. Het resultaat was dat het wel sneller was, alleen niet meer overeenkomt met wat je op het scherm zag, je kreeg dan juist een ‘vertraging’ te zien.

Update 2025 Ook al ziet het hierboven er simpel uit, dit systeem zal meerdere refactors krijgen en ik ga deels over tot een walkmap. Dit is een simpele layer in Photoshop met zwart en wit kleuren. Waarbij zwart loopbaar is en wit niet.