Collisions
Il faut bien se cogner contre les murs

Ce qui bouge entre en collision
Maintenant qu’il y a des entities qui bougent, un ‘problème’ est apparu. Elles se traversent les unes les autres comme un fantôme 👻.
Il existe plusieurs façons de gérer ça, et j’ai peut-être fait ici un mauvais arbitrage : la ‘vitesse’. Chaque calcul prend du temps et on veut le réduire au minimum. La première décision à prendre était de choisir entre des calculs avec des polygones ou avec de simples rectangles (rect).
Mise à jour 2025 : avec l’expérience que j’ai aujourd’hui, je n’utiliserais que des polygones.
J’ai choisi les rect, puisque je n’ai que des formes simples. Quand j’ai une image transparente d’un personnage, elle a une hauteur et une largeur maximales, et c’est tout ce rectangle qu’on ne peut pas traverser.
Engine3 a été enrichi avec les fonctions de base suivantes
---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
Ensuite, une entity reçoit les informations sur la façon dont elle doit se comporter avec les autres entities.
Le plus drôle, c’est que je me suis vite rendu compte que c’est effectivement très rapide, mais aussi trop limité. Car on peut aussi avoir un cercle ou d’autres formes ‘bizarres’. Si on passe par exemple le curseur de la souris sur un tel objet, il indiquera ce que c’est déjà en dehors du cercle (autour duquel se trouve le rect invisible).
C’est pourquoi une entity utilise maintenant un polygone pour la collision avec le curseur et un rect pour le déplacement.
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)
Suivre le chemin
Maintenant que toutes les entities ont les données nécessaires, on peut déterminer avec aStar où l’entity peut marcher.
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
Quand on clique quelque part sur l’écran, on regarde sur la carte où le joueur peut marcher. La carte utilise la fonction ci-dessus et est en partie 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
Comme je ne veux pas toutes les coordonnées x,y, mais seulement les moments où l’entity doit changer de position, j’utilise aussi optimizePath et smoothPath, comme on le voit ci-dessous.
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
}
Pour finir
Une erreur que j’avais faite était de vouloir gérer ça dans Love2d via un thread en arrière-plan. Mon idée était que ce serait plus rapide. Résultat : c’était bien plus rapide, mais ça ne correspondait plus à ce qu’on voyait à l’écran, on voyait justement un ‘retard’.
Mise à jour 2025 Même si ça paraît simple ci-dessus, ce système va connaître plusieurs refactorisations et je vais en partie passer à une walkmap. C’est un simple calque dans Photoshop en noir et blanc, où le noir est praticable et le blanc ne l’est pas.