Logo Signal From The Stars

Collision

We have to run into walls

Martin avatar
  • Martin
  • 5 min read
A collision is inevitable

What moves is going to collide

Now that there are moving entities, a “problem” has arisen. Namely, they walk right through each other like a ghost 👻.

There are several ways to do this, I may have made a wrong trade-off here namely ‘speed’. Every calculation takes time and you want to keep it as low as possible. The first decision I had to make was whether to go for polygon or simple square (rect) calculations.

Update 2025 With the experience I have now, I would only use polygon.

My choice was rect, since I only have simple figures. When I have a transparent image of a character, it has a maximum height and width, which is the total square that one cannot walk through.

The engine3 is extended with the following basic functions

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

Then an entity is given information on how to interact with other entities.

The joke is that I quickly found out that this is indeed very fast but also too limited. Since you can also have a circle figure or other “weird” figures. When you then e.g. move your mouse cursor over such an object, it will already indicate what it is outside the circle figure (around which the invisible rect is).

Hence, already an entity now uses a polygon for cursor collision and a rect for movement.


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

Now that all entities have the necessary data, we can use aStar to determine where the entity can run.

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

When you click somewhere on the screen, it will be checked on the map where the player is allowed to walk. The map uses the function above and is partly in a cache.

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

Since I don’t want all x,y coordinates, but only the moments where the entity should get a different position, I also use optimizePath and smoothPath, as you can see below.

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
}

Finally

One mistake I had made was that I wanted to handle this in Love2d via a background thread. My idea was that it would be faster. The result was that although it was faster, it no longer matched what you saw on the screen, you actually got a “delay”.

Update 2025 Even though it looks simple above, this system will have multiple refactors and I am partially moving to a walkmap. This is a simple layer in Photoshop with black and white colors. Where black is walkable and white is not.


Loading external comment system...

✉️ Stay informed!

Receive the latest news for free and motivate me to make this adventure a success!