Les collisions avec LÖVE2D

Imaginons que nous développons un jeu où nous pouvons éliminer des monstres. Un monstre devrait disparaître lorsqu’il est touché par une balle. Nous devons donc vérifier la condition suivante :
le monstre est-il entré en collision avec la balle ?

Nous allons créer une fonction qui vérifie les collisions. Pour cela, nous devons comprendre quand deux rectangles se percutent.

Voici une image avec trois exemples pour illustrer différentes situations :

Représentation de plusieurs rectangles dans diverses dispositions pour comprendre les collisions
Représentation de plusieurs rectangles dans diverses dispositions pour comprendre les collisions

Il est temps de mettre en marche notre cerveau de programmeur. Qu’est-ce qui se passe dans le 3ème exemple qui ne se produit pas dans les deux premiers ? “Il y a collision.”

Oui, mais nous devons être plus précis. Nous avons besoin de conditions que l’ordinateur peut utiliser pour détecter cette collision.

Analysons la position des rectangles. Dans le 1er exemple, le rectangle rouge n’est pas en collision avec le bleu parce qu’il est trop à gauche. Si le rouge était un peu plus à droite, ils pourraient se toucher. Jusqu’où exactement ? Eh bien, une collision se produit lorsque le côté droit du rouge dépasse le côté gauche du bleu. C’est le cas dans l’exemple 3.

Mais cette condition est également vraie pour l’exemple 2. Nous avons donc besoin de conditions supplémentaires pour confirmer une collision. L’exemple 2 nous montre que le rectangle rouge ne peut pas être trop à droite non plus. À quelle distance précisément ? De combien le rouge devrait-il se déplacer vers la gauche pour qu’il y ait collision ? Une collision se produit lorsque le côté gauche du rouge est à gauche du côté droit du bleu.

Nous avons maintenant deux conditions. Sont-elles suffisantes pour garantir une collision ?

En fait, non. Regardez l’image suivante :

Représentation de 2 rectangles éloignés verticalement pour comprendre les collisions
Représentation de 2 rectangles éloignés verticalement pour comprendre les collisions

Cette situation satisfait nos deux premières conditions. Le côté droit du rouge dépasse le côté gauche du bleu, et le côté gauche du rouge est à gauche du côté droit du bleu. Pourtant, il n’y a pas collision. C’est parce que le rectangle rouge est trop haut. Il devrait descendre, mais de combien ? Une collision se produit lorsque le bas du rectangle rouge dépasse le haut du rectangle bleu.

Mais si nous descendons trop le rectangle rouge, il n’y aura plus de collision. Jusqu’où le rouge peut-il descendre tout en maintenant une collision avec le bleu ? Une collision existe tant que le haut du rectangle rouge reste au-dessus du bas du rectangle bleu.

Nous avons maintenant quatre conditions. Vérifions si elles sont toutes vraies pour ces trois exemples :

Représentation de plusieurs rectangles dans diverses dispositions pour vérifier nos conditions de collision
Représentation de plusieurs rectangles dans diverses dispositions pour vérifier nos conditions de collision
  1. Le côté droit du rouge dépasse le côté gauche du bleu.
  2. Le côté gauche du rouge est à gauche du côté droit du bleu.
  3. Le bas du rouge dépasse le haut du bleu.
  4. Le haut du rouge est au-dessus du bas du bleu.

Seul l’exemple 3 satisfait toutes ces conditions !
Maintenant, traduisons ces informations en une fonction.

Commençons par créer deux rectangles :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function love.load()
  -- Création de deux rectangles
  r1 = {
    x = 10,
    y = 100,
    width = 100,
    height = 100
  }

  r2 = {
    x = 250,
    y = 120,
    width = 150,
    height = 120
  }
end

function love.update(dt)
  -- Déplaçons un des rectangles
  r1.x = r1.x + 100 * dt
end

function love.draw()
  love.graphics.rectangle("line", r1.x, r1.y, r1.width, r1.height)
  love.graphics.rectangle("line", r2.x, r2.y, r2.width, r2.height)
end

Maintenant, créons une fonction appelée checkCollision() qui prendra deux rectangles comme paramètres :

1
2
3
function checkCollision(a, b)

end

D’abord, nous avons besoin de définir les côtés des rectangles. Le côté gauche correspond à la position x, et le côté droit à la position x + la largeur. De même, le haut correspond à la position y, et le bas à la position y + la hauteur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function checkCollision(a, b)
  -- Pour les variables locales, on utilise souvent des noms avec des underscores plutôt que du camelCase
  local a_left = a.x
  local a_right = a.x + a.width
  local a_top = a.y
  local a_bottom = a.y + a.height

  local b_left = b.x
  local b_right = b.x + b.width
  local b_top = b.y
  local b_bottom = b.y + b.height
end

Maintenant que nous avons défini les quatre côtés de chaque rectangle, nous pouvons utiliser nos quatre conditions dans une instruction if :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function checkCollision(a, b)
  local a_left = a.x
  local a_right = a.x + a.width
  local a_top = a.y
  local a_bottom = a.y + a.height

  local b_left = b.x
  local b_right = b.x + b.width
  local b_top = b.y
  local b_bottom = b.y + b.height

  -- Si le côté droit de a dépasse le côté gauche de b
  if a_right > b_left and
     -- et si le côté gauche de a est à gauche du côté droit de b
     a_left < b_right and
     -- et si le bas de a dépasse le haut de b
     a_bottom > b_top and
     -- et si le haut de a est au-dessus du bas de b
     a_top < b_bottom then
    -- Il y a collision!
    return true
  else
    -- Au moins une de ces conditions n'est pas remplie, donc pas de collision
    return false
  end
end

Parfait, notre fonction est prête. Testons-la ! Nous allons dessiner les rectangles pleins en cas de collision, ou simplement leurs contours s’il n’y a pas de collision :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function love.draw()
  -- Nous créons une variable locale appelée mode
  local mode
  if checkCollision(r1, r2) then
    -- S'il y a collision, dessinons les rectangles en plein
    mode = "fill"
  else
    -- Sinon, dessinons seulement leurs contours
    mode = "line"
  end

  -- Utilisons la variable mode comme premier argument
  love.graphics.rectangle(mode, r1.x, r1.y, r1.width, r1.height)
  love.graphics.rectangle(mode, r2.x, r2.y, r2.width, r2.height)
end

Ça fonctionne !
Vous savez maintenant comment détecter une collision entre deux rectangles.

Résumé

La collision entre deux rectangles peut être vérifiée avec quatre conditions.

Si A et B sont deux rectangles, il y a collision lorsque toutes ces conditions sont vraies simultanément :

  1. Le côté droit de A dépasse le côté gauche de B
  2. Le côté gauche de A est à gauche du côté droit de B
  3. Le bas de A dépasse le haut de B
  4. Le haut de A est au-dessus du bas de B

Cette technique, connue sous le nom de “test de collision par boîtes englobantes” (AABB collision), est simple à mettre en œuvre et efficace pour de nombreux jeux 2D.