Détection de Collisions et Physique Simple : Rendre Vos Objets Interactifs
Bienvenue à cette leçon cruciale pour tout développeur de jeux ! Nous allons explorer deux piliers fondamentaux de la création d'expériences ludiques interactives : la détection de collisions et la physique simple. Comprendre et implémenter ces concepts est essentiel pour donner vie à vos personnages, rendre vos mondes réactifs et permettre des interactions significatives entre les différents éléments de votre jeu.
Que ce soit pour qu'un personnage ne traverse pas un mur, qu'une balle rebondisse sur une raquette, ou qu'un objet tombe sous l'effet de la gravité, les mécanismes que nous allons aborder sont omniprésents. Préparez-vous à transformer des pixels statiques en objets dynamiques et interactifs !
Pourquoi la Détection de Collisions est-elle Indispensable ?
Dans le monde réel, les objets occupent un espace et ne peuvent pas se traverser mutuellement. Les jeux vidéo doivent simuler cette réalité pour être crédibles et offrir une expérience cohérente. La détection de collisions est le processus par lequel nous déterminons si deux objets ou plus se chevauchent ou se touchent dans l'espace de notre jeu.
Sans détection de collisions, vos jeux seraient :
- Incohérents : Les personnages traverseraient les murs et les sols.
- Non interactifs : Il serait impossible de ramasser des objets, de frapper des ennemis ou de franchir des portes.
- Dépourvus de défi : Pas de dangers, pas d'obstacles à éviter.
C'est la base de toute interaction spatiale dans un jeu.
Principes Fondamentaux de la Détection de Collisions
La détection de collisions est avant tout une question de géométrie et de mathématiques. Pour simplifier les calculs, nous utilisons souvent des formes englobantes (bounding shapes) ou formes de collision (collision shapes) qui sont des représentations simplifiées de nos objets. Plutôt que de vérifier chaque pixel d'un sprite complexe, nous utilisons des formes géométriques simples comme des rectangles ou des cercles qui "englobent" l'objet réel.
Les formes de collision les plus courantes sont :
- Boîtes englobantes alignées sur les axes (AABB - Axis-Aligned Bounding Box) : Des rectangles dont les côtés sont toujours parallèles aux axes X et Y de votre système de coordonnées. Très simples à gérer.
- Cercles englobants (Bounding Circles) : Des cercles. Idéaux pour les objets de forme ronde ou lorsque l'orientation n'a pas d'importance.
- Polygones englobants : Des polygones arbitraires. Plus précis mais plus complexes à calculer.
- Capsules : Un cylindre avec des hémisphères à chaque extrémité. Utiles pour les personnages debout.
Compromis : Précision vs. Performance
Le choix de la forme de collision dépend d'un compromis entre la précision et la performance.
- Précision : Une forme qui colle parfaitement aux contours de l'objet réduira les "faux positifs" (une collision est détectée alors qu'il n'y en a pas visuellement).
- Performance : Des formes simples comme les AABB et les cercles nécessitent moins de calculs, ce qui est crucial pour les jeux avec de nombreux objets.
Pour la plupart des jeux 2D, les AABB et les cercles sont amplement suffisants et offrent un excellent équilibre.
Détection de Collisions par Boîtes Englobantes Alignées sur les Axes (AABB)
L'AABB est la méthode la plus simple et la plus rapide pour la détection de collisions entre deux objets rectangulaires. Elle est particulièrement efficace car elle ne nécessite aucune trigonométrie et est facile à comprendre.
Un rectangle est défini par sa position (x, y), sa largeur (width) et sa hauteur (height).
Deux AABB, rectA et rectB, sont en collision si les quatre conditions suivantes sont VRAIES simultanément :
- Le côté droit de
rectAest à droite du côté gauche derectB. - Le côté gauche de
rectAest à gauche du côté droit derectB. - Le bas de
rectAest en dessous du haut derectB. - Le haut de
rectAest au-dessus du bas derectB.
Formulé mathématiquement :
rectA.x < rectB.x + rectB.width
rectA.x + rectA.width > rectB.x
rectA.y < rectB.y + rectB.height
rectA.y + rectA.height > rectB.y
Si toutes ces conditions sont remplies, alors il y a collision. Notez que nous vérifions si elles se chevauchent, pas seulement si elles se touchent.
Implémentation en JavaScript pour l'AABB
Voici une fonction JavaScript simple pour détecter une collision entre deux objets qui ont les propriétés x, y, width et height.
/**
* Détecte une collision entre deux boîtes englobantes alignées sur les axes (AABB).
* @param {object} rect1 - Le premier objet rectangle avec x, y, width, height.
* @param {object} rect2 - Le second objet rectangle avec x, y, width, height.
* @returns {boolean} Vrai s'il y a collision, faux sinon.
*/
function checkAABBCollision(rect1, rect2) {
// Vérifie si les rectangles se chevauchent sur l'axe X
if (rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
// Vérifie si les rectangles se chevauchent sur l'axe Y
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y) {
return true; // Collision détectée
}
return false; // Pas de collision
}
// Exemple d'utilisation :
const player = { x: 50, y: 50, width: 30, height: 30 };
const wall = { x: 70, y: 40, width: 20, height: 80 };
const coin = { x: 100, y: 100, width: 10, height: 10 };
console.log("Collision joueur-mur :", checkAABBCollision(player, wall)); // Output: true
console.log("Collision joueur-coin :", checkAABBCollision(player, coin)); // Output: false
- La fonction
checkAABBCollisionprend deux objets en paramètres, chacun représentant un rectangle avec ses coordonnéesx,yet ses dimensionswidth,height. - Les quatre conditions logiques sont combinées avec l'opérateur
&&(ET logique). Si toutes sont vraies, cela signifie que les rectangles se chevauchent, et la fonction retournetrue. - Sinon, si l'une des conditions est fausse, il n'y a pas de chevauchement sur cet axe, et donc pas de collision. La fonction retourne
false.
Détection de Collisions Circulaires
Pour les objets de forme circulaire ou pour des collisions moins précises mais plus fluides, la détection de collisions par cercles est une excellente alternative. Elle est également relativement simple et efficace.
Un cercle est défini par son centre (x, y) et son rayon (radius).
Deux cercles, circleA et circleB, sont en collision si la distance entre leurs centres est inférieure ou égale à la somme de leurs rayons.
La formule de la distance entre deux points (x1, y1) et (x2, y2) est :
distance = sqrt((x2 - x1)^2 + (y2 - y1)^2)
Donc, la condition de collision est :
sqrt((circleB.x - circleA.x)^2 + (circleB.y - circleA.y)^2) <= (circleA.radius + circleB.radius)
Pour optimiser les performances, il est courant d'éviter l'opération coûteuse sqrt(). Au lieu de comparer les distances, nous comparons les distances au carré avec les sommes des rayons au carré. Cela donne le même résultat sans le calcul de la racine carrée.
Condition optimisée :
(circleB.x - circleA.x)^2 + (circleB.y - circleA.y)^2 <= (circleA.radius + circleB.radius)^2
Implémentation en JavaScript pour les Collisions Circulaires
/**
* Détecte une collision entre deux cercles.
* @param {object} circle1 - Le premier objet cercle avec x, y (centre) et radius.
* @param {object} circle2 - Le second objet cercle avec x, y (centre) et radius.
* @returns {boolean} Vrai s'il y a collision, faux sinon.
*/
function checkCircleCollision(circle1, circle2) {
// Calcul de la distance entre les centres des cercles
const dx = circle2.x - circle1.x;
const dy = circle2.y - circle1.y;
const distanceSquared = (dx * dx) + (dy * dy);
// Calcul de la somme des rayons au carré
const radiusSum = circle1.radius + circle2.radius;
const radiusSumSquared = radiusSum * radiusSum;
// Comparaison de la distance au carré avec la somme des rayons au carré
if (distanceSquared <= radiusSumSquared) {
return true; // Collision détectée
}
return false; // Pas de collision
}
// Exemple d'utilisation :
const ball1 = { x: 100, y: 100, radius: 20 };
const ball2 = { x: 110, y: 110, radius: 15 };
const ball3 = { x: 200, y: 200, radius: 10 };
console.log("Collision ball1-ball2 :", checkCircleCollision(ball1, ball2)); // Output: true
console.log("Collision ball1-ball3 :", checkCircleCollision(ball1, ball3)); // Output: false
- La fonction
checkCircleCollisioncalcule la différence sur l'axe X (dx) et l'axe Y (dy) entre les centres des deux cercles. - Elle calcule ensuite la distance au carré entre les centres (
distanceSquared). - La somme des rayons au carré (
radiusSumSquared) est également calculée. - Si
distanceSquaredest inférieure ou égale àradiusSumSquared, les cercles se chevauchent ou se touchent, et il y a collision.
Détection de Collisions Plus Avancées (Vue d'Ensemble)
Bien que les AABB et les cercles couvrent la majorité des besoins, des scénarios plus complexes peuvent nécessiter des techniques plus sophistiquées :
- SAT (Separating Axis Theorem) : Pour la détection de collisions entre polygones convexes arbitraires. C'est plus complexe mais très précis.
- Pixel-Perfect Collision : Compare les pixels non transparents de deux images. Très coûteux en performance, généralement utilisé pour des cas spécifiques ou entre un petit nombre d'objets critiques.
- Partitionnement Spatial : Lorsque vous avez un grand nombre d'objets, vérifier chaque objet avec chaque autre objet (O(n²)) devient inefficace. Des techniques comme les Quadtrees, les Grilles uniformes ou les BSP Trees permettent de limiter le nombre de paires à tester en divisant l'espace de jeu.
Introduction à la Physique Simple
La détection de collisions vous dit qu'une interaction s'est produite. La physique définit comment les objets réagissent à cette interaction, ou même comment ils se comportent sans interaction directe (gravité, mouvement).
La "physique simple" dans un jeu 2D fait généralement référence à :
- Mouvement et Vélocité : Comment les objets se déplacent.
- Gravité : L'attraction vers le bas.
- Friction / Résistance de l'air : Ralentissement du mouvement.
- Restitution / Rebond : Comment les objets rebondissent après une collision.
- Résolution des chevauchements : Empêcher les objets de rester imbriqués après une collision.
Gestion des Mouvements de Base
La base du mouvement est la mise à jour de la position d'un objet en fonction de sa vélocité (vitesse et direction).
nouvelle_position = ancienne_position + vélocité * temps_écoulé
En JavaScript, dans votre boucle de jeu, vous feriez :
// Définition de l'objet
const object = { x: 0, y: 0, vx: 5, vy: 0 }; // vx, vy sont les composantes de la vélocité
// Dans la boucle de jeu, à chaque frame
function update(deltaTime) { // deltaTime est le temps écoulé depuis la dernière frame
object.x += object.vx * deltaTime;
object.y += object.vy * deltaTime;
}
vxetvyreprésentent la vélocité sur les axes X et Y respectivement.deltaTimeest crucial pour rendre les mouvements fluides et indépendants de la fréquence d'images (frame rate). SideltaTimen'est pas utilisé, un jeu tournant à 60 FPS déplacerait les objets deux fois plus vite qu'un jeu à 30 FPS.
Gravité
La gravité est une force constante qui tire les objets vers le bas. C'est une accélération, ce qui signifie qu'elle modifie la vélocité, pas directement la position.
nouvelle_vélocité_Y = ancienne_vélocité_Y + gravité * temps_écoulé
const object = { x: 0, y: 0, vx: 0, vy: 0, mass: 1 };
const gravity = 9.8; // Une valeur arbitraire pour la gravité (peut être ajustée)
function applyGravity(object, deltaTime) {
object.vy += gravity * deltaTime; // La vélocité verticale augmente avec le temps
}
function updateObject(object, deltaTime) {
applyGravity(object, deltaTime);
object.x += object.vx * deltaTime;
object.y += object.vy * deltaTime;
}
- La variable
gravityreprésente la force de gravité par unité de temps. applyGravityincrémente la vélocité verticale (vy) de l'objet, simulant l'accélération.
Réponse aux Collisions (Post-collision)
Une fois qu'une collision est détectée, que se passe-t-il ? C'est là que la physique de réponse entre en jeu. Les réponses de base incluent :
- Arrêt : L'objet arrête simplement son mouvement dans la direction de la collision (ex: un joueur contre un mur).
- Rebond (Restitution) : L'objet change de direction et peut perdre une partie de son énergie (ex: une balle rebondissant sur le sol).
nouvelle_vélocité = -ancienne_vélocité * coefficient_de_restitution- Le
coefficient_de_restitutionest une valeur entre 0 (pas de rebond, absorption totale) et 1 (rebond parfait, pas de perte d'énergie).
- Résolution du chevauchement : Si les objets se chevauchent après une collision, il faut les déplacer pour les "séparer" afin qu'ils ne soient plus imbriqués.
Mise en Pratique : Un Monde Simple avec Collisions et Gravité
Combinons ces concepts dans un petit exemple où une balle tombe sous l'effet de la gravité et rebondit sur le sol (représenté par un rectangle).
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Collision et Physique Simple</title>
<style>
body { margin: 0; overflow: hidden; background-color: #333; }
canvas { display: block; background-color: #eee; border: 1px solid #ccc; }
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// --- Paramètres du jeu ---
const GRAVITY = 9.8; // Accélération de la gravité
const RESTITUTION = 0.7; // Coefficient de restitution (0=pas de rebond, 1=rebond parfait)
let lastTime = 0;
let deltaTime = 0;
// --- Objet Balle ---
const ball = {
x: canvas.width / 2,
y: 50,
radius: 20,
color: 'red',
vx: 50, // vélocité horizontale (pixels/seconde)
vy: 0, // vélocité verticale (pixels/seconde)
mass: 1 // pour des calculs futurs si besoin
};
// --- Objet Sol (AABB) ---
const ground = {
x: 0,
y: canvas.height - 50,
width: canvas.width,
height: 50,
color: 'green'
};
// --- Fonctions de collision ---
/**
* Détecte une collision entre deux AABB.
* (Réutilisation de la fonction précédente)
*/
function checkAABBCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
// --- Fonctions de dessin ---
function drawBall() {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
}
function drawGround() {
ctx.fillStyle = ground.color;
ctx.fillRect(ground.x, ground.y, ground.width, ground.height);
}
// --- Boucle de jeu principale ---
function gameLoop(currentTime) {
deltaTime = (currentTime - lastTime) / 1000; // Convertir en secondes
lastTime = currentTime;
// 1. Mise à jour de la physique de la balle
updateBallPhysics(deltaTime);
// 2. Détection et réponse aux collisions
handleCollisions();
// 3. Dessin des éléments
ctx.clearRect(0, 0, canvas.width, canvas.height); // Effacer le canvas
drawBall();
drawGround();
requestAnimationFrame(gameLoop); // Appel de la prochaine frame
}
function updateBallPhysics(dt) {
// Appliquer la gravité (affecte la vélocité verticale)
ball.vy += GRAVITY * dt * 100; // Multiplié par 100 pour une gravité plus visible
// Mettre à jour la position de la balle en fonction de sa vélocité
ball.x += ball.vx * dt;
ball.y += ball.vy * dt;
// Gérer les bords du canvas pour la balle (pour le x)
if (ball.x + ball.radius > canvas.width) {
ball.x = canvas.width - ball.radius;
ball.vx *= -1; // Inverser la direction horizontale
} else if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx *= -1;
}
}
function handleCollisions() {
// Collision balle-sol (la balle est un cercle, le sol est une AABB)
// Pour simplifier, nous allons traiter la balle comme un AABB pour la collision avec le sol
// ou considérer le point le plus bas de la balle pour la collision avec le sol.
// Optons pour la collision avec le point le plus bas de la balle sur le Y du sol.
// Si le bas de la balle touche ou dépasse le haut du sol
if (ball.y + ball.radius > ground.y) {
// Résolution du chevauchement : repositionner la balle juste au-dessus du sol
ball.y = ground.y - ball.radius;
// Réponse au rebond : inverser la vélocité verticale et appliquer la restitution
ball.vy *= -RESTITUTION;
// Si la vélocité est très faible, l'arrêter pour éviter des micro-rebonds infinis
if (Math.abs(ball.vy) < 50) { // Seuil arbitraire, ajustez si besoin
ball.vy = 0;
}
}
}
// --- Démarrer la boucle de jeu ---
requestAnimationFrame(gameLoop);
</script>
</body>
</html>
Explication du Code Intégré
- Structure HTML et Canvas : Un simple fichier HTML avec un élément
<canvas>où le jeu sera dessiné. - Variables Globales :
canvasetctx: Références au canvas et à son contexte de dessin 2D.GRAVITYetRESTITUTION: Constantes pour la simulation physique.lastTimeetdeltaTime: Variables essentielles pour un jeu frame-rate indépendant.deltaTimeest le temps écoulé entre deux frames, permettant de scaler les mouvements.
- Objets
balletground:ball: Un objet avec des propriétés de position (x,y), rayon (radius), couleur, et surtout de vélocité (vx,vy).ground: Un objet AABB simple représentant le sol avec position, dimensions et couleur.
- Fonctions de Dessin (
drawBall,drawGround) : Ces fonctions utilisent l'API Canvas 2D pour dessiner les objets à leur position actuelle. gameLoop(currentTime): C'est le cœur du jeu, appelé à chaque frame parrequestAnimationFrame.- Il calcule
deltaTime. - Il appelle
updateBallPhysicspour mettre à jour la position et la vélocité de la balle. - Il appelle
handleCollisionspour détecter et gérer les interactions. - Il efface le canvas (
ctx.clearRect) et redessine tous les objets.
- Il calcule
updateBallPhysics(dt):- Gravité :
ball.vy += GRAVITY * dt * 100;augmente la vélocité verticale de la balle. Le* 100est un facteur d'échelle pour que la gravité soit plus visible à l'écran, car les pixels ne sont pas des mètres. - Mouvement :
ball.x += ball.vx * dt;etball.y += ball.vy * dt;mettent à jour la position en fonction de la vélocité et du temps écoulé. - Bords latéraux : Simple gestion des bords gauche et droit du canvas pour que la balle rebondisse horizontalement.
- Gravité :
handleCollisions():- Collision balle-sol : Nous vérifions si le bas de la balle (
ball.y + ball.radius) dépasse le haut du sol (ground.y). - Résolution du chevauchement : Si une collision est détectée,
ball.y = ground.y - ball.radius;repositionne la balle juste au-dessus du sol pour qu'elle ne s'enfonce pas. - Réponse au rebond :
ball.vy *= -RESTITUTION;inverse la vélocité verticale et réduit son intensité en fonction duRESTITUTION. Une petite vérification (Math.abs(ball.vy) < 50) est ajoutée pour arrêter la balle si elle n'a presque plus d'énergie, évitant un rebond infini minuscule.
- Collision balle-sol : Nous vérifions si le bas de la balle (
Cet exemple démontre comment lier la détection de collision avec une réponse physique simple pour créer un comportement réaliste.
Optimisation et Considérations Avancées
Pour des jeux plus complexes avec de nombreux objets, l'efficacité de la détection de collisions devient primordiale :
- Collision Filtering/Layers : Ne pas vérifier les collisions entre des objets qui n'ont aucune raison de jamais interagir (ex: des balles de joueur ne se touchent pas entre elles).
- Phase de Broad-Phase / Narrow-Phase :
- Broad-Phase : Utilise des techniques de partitionnement spatial (comme les quadtrees mentionnés précédemment) pour rapidement identifier un sous-ensemble de paires d'objets susceptibles d'entrer en collision.
- Narrow-Phase : Applique ensuite des tests de collision précis (AABB, Cercles, SAT, etc.) uniquement sur ces paires réduites.
- Moteurs Physiques : Pour des simulations plus réalistes et complexes (corps rigides, articulations, contraintes, empilements), l'utilisation d'une bibliothèque ou d'un moteur physique dédié est recommandée. Des exemples populaires en JavaScript incluent :
- Matter.js : Un moteur physique 2D populaire.
- P2.js : Un autre moteur 2D robuste, successeur de Box2D.
- Box2D.js : Port JavaScript du célèbre moteur physique Box2D.
Ces moteurs s'occupent de la détection de collisions, de la résolution des chevauchements, des réponses physiques, de la gravité, et bien plus encore, vous permettant de vous concentrer sur la logique de jeu.
Conclusion et Résumé
Félicitations ! Vous avez maintenant une solide compréhension des fondamentaux de la détection de collisions et de la physique simple en développement de jeux web.
Voici les points clés à retenir :
- La détection de collisions est essentielle pour rendre vos objets interactifs et votre monde crédible.
- Les formes englobantes (AABB, Cercles) sont des simplifications géométriques qui optimisent les calculs de collision.
- Les AABB sont parfaites pour les rectangles alignés et sont très rapides à tester.
- Les collisions circulaires sont idéales pour les objets ronds et sont également efficaces en évitant la racine carrée.
- La physique simple gère la manière dont les objets se déplacent et réagissent aux forces (comme la gravité) et aux collisions (rebond, arrêt).
- L'utilisation de
deltaTimeest cruciale pour une simulation physique cohérente et indépendante du framerate. - Pour des jeux complexes, considérez l'optimisation (partitionnement spatial) et l'utilisation de moteurs physiques dédiés.
La maîtrise de ces concepts vous ouvre les portes vers la création d'expériences de jeu beaucoup plus riches et dynamiques. N'hésitez pas à expérimenter avec les valeurs de gravité et de restitution dans l'exemple de code pour voir comment elles affectent le comportement de la balle !