Aller au menu - Aller au contenu

Icône Les collisions

Mise à jour : 08/07/2010
4 613 visites depuis 7 jours, dont 77 sur ce chapitre classé 39/786
Ce mini-tuto ne traitera que d'une seule méthode de la classe ActionScript, mais quelle méthode ! :D Il s'agit d'une fonction qui permet de tester la collision entre des clips, ou entre des coordonnées et un clip.
Elle possède peu d'options, mais puisque rien ne vaut la pratique pour la comprendre, vous allez avoir des exercices jusqu'à plus soif. :p
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Approche des collisions

Désolé, mais on ne va pas rentrer directement dans le tas ! On va déjà faire travailler un peu les méninges. :D

On va travailler tout au long de ce chapitre avec une base toute simple : un clip carré de taille 20*20 px, que vous appellerez boite. Ce clip boite se déplace de 2 pixels dans toutes les directions, à l'aide des flèches du clavier. Cadencez l'animation à 24 images par seconde (on sentira moins les "saccades" dans le déplacement de boite). Dimensionnez ensuite l'animation à 400 * 400 px.

Prenez l'habitude de toujours centrer les formes de vos clips. C'est-à-dire qu'à l'intérieur du boite, la forme qui fait 20 * 20 px doit être placée aux coordonnées (-10;-10), comme ça, le centre du clip est au centre de la forme. Vous verrez que cela à son importance ! (Imaginez par exemple une rotation, qui serait complètement différente selon l'endroit du centre du clip.)


Pour les feignants, et ceux qui voudraient voir s'ils ne se sont pas trompés dans le code, voilà la source au format Flash MX 2004.


La première chose que nous allons faire, c'est délimiter la zone où peut se déplacer librement boite. On va dire tout simplement que la boîte n'a pas le droit de sortir de l'animation.

Qu'est-ce que cela signifie ? C'est la question que vous devez vous poser quand vous êtes face à ce genre de problème.
Cela signifie qu'à tout instant :
  • boite._y > 0 // Plus bas que le haut de l'animation
  • boite._y < 400 // Plus haut que le bas de l'animation
  • boite._x > 0 // Plus à droite que la gauche de l'animation
  • boite._x < 400 // Plus à gauche que la droite de l'animation


Alors, maintenant branchez les neurones, et pondez-moi le code que je dois rajouter dans le bloc boite.onEnterFrame !

Bon, je présente ici une solution parmi d'autres, n'hésitez pas à me proposer les vôtres pour évaluation dans les commentaires du tuto :

Secret (cliquez pour afficher)
Code : Actionscript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if (this._y<0) {
        this._y = 0
}
if (this._y>400) {
        this._y = 400;
}
if (this._x<0) {
        this._x = 0;
}
if (this._x>400) {
        this._x = 400;
}


Vous l'avez compris, si le clip dépasse une limite de quelque côté que ce soit, il est repoussé à la position limite !

Voilà pour la première partie de ce tutorial. ;)

Collision rectangulaire

Maintenant, on a notre animation de base, avec boite qui ne peut plus sortir de l'écran !

On va placer un obstacle, rectangulaire, sur sa route.
Ce qu'on souhaite, c'est que boite ne puisse pas passer par-dessus cet obstacle, qu'on va appeler obstacle_rectangulaire.

Pour les gros feignants, voilà encore une fois la source de l'animation au stade actuel : format flash mx 2004.


Alors, on pourrait de nouveau faire plusieurs conditions, qui vérifieraient que boite ne dépasse pas les limites de obstacle_rectangulaire. Mais vu que le programmeur est toujours un gros paresseux, on va plutôt utiliser la méthode qui va le faire pour nous. :p

Rajoutez ce code, juste avant la fin du bloc boite.onEnterFrame :

Code : Actionscript
1
2
3
if (this.hitTest(_root.obstacle_rectangulaire)) {
        trace('Collision !!!');
}


Ai-je besoin de vous expliquer ? Bon, en clair, la méthode hitTest() va vérifier si le clip this (c'est-à-dire boite, vu qu'on est dans son bloc de code) et _root.obstacle_rectangulaire se chevauchent. Le cas échéant, il va afficher une alerte "Collision !!!" dans la fenêtre de Sortie.
Vous pouvez essayer !

Mais comment faire pour empêcher le clip d'aller sur l'obstacle ? :D

Eh bien il existe de multiples possibilités pour répondre à cette question, et encore une fois j'attends vos solutions. ;)

Voici la mienne, elle déplace le clip, teste la collision, et s'il y a effectivement collision, elle revient en arrière. :D

Voilà le code complet :

Code : Actionscript
 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
27
28
29
30
31
32
33
34
35
36
37
var vitesse:Number = 2;
boite.onEnterFrame = function() {       
        /* On enregistre les positions avant de bouger le clip, pour pouvoir le remettre à sa place s'il y a collision */
        posx = this._x;
        posy = this._y;
        /* Pour déplacer le clip */
        if (Key.isDown(Key.UP)) {
                this._y -= vitesse;
        }
        if (Key.isDown(Key.DOWN)) {
                this._y += vitesse;
        }
        if (Key.isDown(Key.LEFT)) {
                this._x -= vitesse;
        }
        if (Key.isDown(Key.RIGHT)) {
                this._x += vitesse;
        }
        /* Vérifions que le clip ne sorte pas de l'animation ! */
        if (this._y<0) {
                this._y = 0
        }
        if (this._y>400) {
                this._y = 400;
        }
        if (this._x<0) {
                this._x = 0;
        }
        if (this._x>400) {
                this._x = 400;
        }
        /* On teste s'il y a collision : si c'est bien le cas, on redonne au clip la position enregistrée au début. C'est instantané, l'utilisateur ne voit pas le déplacement qui a eu lieu, avant l'affichage de l'image */
        if (this.hitTest(_root.obstacle_rectangulaire)) {
                this._x = posx;
                this._y = posy;
        }
}


Ingénieux, non ? ;)

Collision circulaire

Maintenant, créez un clip obstacle_circulaire, qui, comme son nom l'indique, sera un obstacle constitué d'un cercle de diamètre 75 px. N'oubliez pas de bien mettre le centre du clip en phase avec le centre de la forme, sinon le script que nous allons étudier ne fonctionnera pas !

Dans mon infinie bonté, voilà la source !


Pour l'instant, on va utiliser le même script pour l'obstacle circulaire que pour l'obstacle rectangulaire : rajoutez donc dans le bloc boite.onEnterFrame :

Code : Actionscript
1
2
3
4
if (this.hitTest(_root.obstacle_circulaire)) {
        this._x = posx;
        this._y = posy;
}


Et testez votre animation !

Ça marche, mais pas parfaitement, comment ça se fait que boite se bloque des fois sans avoir touché le cercle ?


Et bien, vous venez de rencontrer l'énorme limite de la méthode hitTest !
Cette méthode ne peut pas prendre en compte à la fois la forme circulaire du clip obstacle_circulaire, et la forme de boite !
Elle considère qu'il y a collision à partir du moment où la plus petite zone rectangulaire de chaque clip en chevauche une autre. En fait, il faut vous imaginer qu'autour du cercle, il y a un rectangle invisible, qui est responsable de ce comportement embêtant de la méthode hittest. Ce rectangle s'appelle le cadre de délimitation.

Il va donc falloir feinter. :D
Qu'est-ce qu'on sait sur un cercle ? On sait que tous les points du cercle sont équidistants du centre. La distance entre le centre du disque et son bord vaudra toujours la valeur de son rayon, c'est-à-dire : 75/2 = 37.5 px.

On va donc calculer la distance entre le centre de notre clip boite, qui est tellement petit qu'il peut être assimilé à un cercle de rayon 10px, et le centre de l'autre cercle.

Ressortez vos souvenirs de maths, qu'est-ce qu'il a dit le bon sieur Pythagore ? :D
Il a dit ça :

Image utilisateur


Nous, ce qu'on cherche, c'est D (la Distance), et ce qu'on a, ce sont les coordonnées des centres de chaque cercle.
Donc, la longueur X correspond à la différence entre les deux abscisses des centres, et la longueur Y à la différence entre les deux ordonnées des centres.

On a donc :
X = this._x - _root.obstacle_circulaire._x;
Y = this._y - _root.obstacle_circulaire._y;

On met au carré et on additionne, ce qui nous donne en ActionScript :

Math.pow(this._x - _root.obstacle_circulaire._x,2) + Math.pow(this._y - _root.obstacle_circulaire._y,2)

Et on doit trouver la racine carrée de tout ça pour avoir D :

Code : Actionscript
1
distance = Math.sqrt(Math.pow(this._x - _root.obstacle_circulaire._x,2) + Math.pow(this._y - _root.obstacle_circulaire._y,2));


Il reste donc à vérifier que la distance est plus grande que le rayon des deux clips additionnés.

Le rayon de obstacle_circulaire est 37.5px
On ne peut pas vraiment dire que boite a un rayon, puisque c'est un carré, mais puisqu'il est relativement petit, ça ne se verra pas trop. On suppose donc son rayon = 10px.

Ce qui nous fait une distance limite de : 37.5+10 = 47.5 px !

On peut donc rajouter à la place du script, qui fonctionnait mal, cela :

Code : Actionscript
1
2
3
4
5
distance = Math.sqrt(Math.pow(this._x-_root.obstacle_circulaire._x,2)+Math.pow(this._y-_root.obstacle_circulaire._y,2));
if (distance<47.5) {
        this._x = posx;
        this._y = posy;
}


Ce n'est pas parfait, mais c'est déjà pas mal. :D
Et puis ce qui compte, c'est que vous ayez compris que souvent, les problèmes de collision peuvent se régler sans même utiliser hitTest. :D

Collision ponctuelle

Je vous ai dit que hitTest ne pouvait pas calculer de collision entre deux clips aux formes complexes (c'est-à-dire autres que rectangulaires). Mais je n'ai pas dit qu'il n'était pas possible de vérifier la collision entre un point et une forme complexe. :D

Supprimez le script qu'on a rajouté dans la partie précédente pour la collision avec l'obstacle circulaire.

Et copiez-collez le code suivant, qui présente une autre façon d'utiliser hitTest :

Code : Actionscript
1
2
3
4
if (_root.obstacle_circulaire.hitTest(this._x,this._y,true)) {
        this._x = posx;
        this._y = posy;
}


Ce code vérifie la collision entre les coordonnées de boite (ce qui explique les this._x et this._y), et la forme du clip obstacle_circulaire. L'argument true permet de préciser qu'on souhaite bien que la collision se calcule à partir de la forme complexe du clip, et non pas de son cadre de délimitation rectangulaire.

Comme vous pouvez le vérifier, le code fonctionne assez bien, mais les clips se chevauchent un peu, puisque dans la collision on considère boite comme un point. Ce code n'est donc à utiliser que pour un clip "ponctuel", c'est-à-dire suffisamment petit pour pouvoir être assimilé à un point, pour que l'utilisateur ne se rende pas compte qu'il chevauche un peu la forme complexe.

Collision bourrine

Sous ce titre à priori fort peut explicite, se cache une astuce qui permet de réaliser des collisions entre un clip dont on connaît la forme complexe à l'avance, et un autre clip à la forme complexe. Oui, oui, vous avez bien entendu, on va réaliser le calcul de collision entre deux formes complexes. :D

En fait, l'astuce est toute simple, on va prendre des points importants de notre clip boite (ça sera les quatres coins, en toute logique), et on va effectuer un test de collision entre obstacle_circulaire et chacun des points ! :p Si je dis que c'est bourrin, c'est parce que, pour 4 points ce n'est pas bien lourd, mais avec des formes vraiment complexes, vous arriverez à des tests de plusieurs dizaines de points, ce qui risque de faire nettement flancher la fluidité de l'animation. ;)

Vous allez donc créer un clip point représenté par un petit cercle rouge de 2 ou 3 pixels. Vous ne placerez pas ce clip sur la scène principale, mais dans le clip boite. Vous copierez-collerez ce clip 3 fois, pour en placer un à chaque coin. Vous nommerez ces clips : point_1, point_2, point_3, et point_4.

Et pour la dernière fois, voilà la source de base, pour pouvoir travailler dans de bonnes conditions. ;)


On va donc changer le code précédent, qui gérait la collision entre le centre de boite et la forme de obstacle_circulaire.
On va seulement changer ce qu'il faut pour que la collision s'effectue entre point_1 et la forme de obstacle_circulaire.

Ce qui nous donne :

Code : Actionscript
1
2
3
4
if (_root.obstacle_circulaire.hitTest(this.point_1._x,this.point_1._y,true)) {
        this._x = posx;
        this._y = posy;
}


Testez l'animation.

:o Pourquoi ça ne marche pas ? o_O

Eh bien en fait, la question à se poser est : quelles sont les coordonnées de point_1 ?
Dans la mesure où le référentiel dans lequel on se trouve est boite, on aura donc les coordonnées de point_1 par rapport à boite. Ce qui pose un problème, car dans ce cas, les coordonnées restent toujours les mêmes, soit (-12;-12), car point_1 ne change pas de position par rapport à boite.

Il faut donc convertir ces coordonnées pour qu'elles correspondent aux coordonnés de point_1 dans _root.

Pour cela je vais vous présenter deux solutions.

Un peu de géométrie



Voilà un schéma tout simple, qui va vous aider à comprendre.
Si on est dans le référentiel boite, alors les coordonnées de point_1 sont :
this.point_1._x
this.point_1._y
(avec this qui correspond à boite);

Mais si on veut ces mêmes coordonnées dans le référentiel _root, il faut ajouter les coordonnées de boite :
this.point_1._x + this._x
this.point_1._y + this._y
(avec this qui correspond à boite);

Image utilisateur


Bref, le code donnerait :

Code : Actionscript
1
2
3
4
5
6
vrai_x = this.point_1._x+this._x;
vrai_y = this.point_1._y+this._y;
if (_root.obstacle_circulaire.hitTest(vrai_x,vrai_y,true)) {
        this._x = posx;
        this._y = posy;
}


Et ça fonctionne très bien. ;)

Un peu d'ActionScript


Si vous ne voulez pas vous embêter à réfléchir en dessinant des petits schémas, Flash vous offre une fonction qui va faire le travail pour vous : localToGlobal()
Cette méthode de la classe MovieClip permet de convertir des coordonnées locales d'un clip vers des coordonnées globales avec _root comme référentiel, et ça, quelque soit le clip de départ. L'avantage est donc que cette méthode fonctionne dans tous les cas. :D

Voilà comment l'utiliser :

Code : Actionscript
1
2
3
var un_point:Object = new Object(); // On créé un objet quelconque
un_point.x = this.point_1._x; // On donne à cet objet des propriétés x et y
un_point.y = this.point_1._y;


Il faut tout d'abord créer un objet quelconque comme ci-dessus, et initialiser deux propriétés : x et y (et non pas _x et _y).

Ensuite, la fonction localToGlobal() va travailler un peu :

Code : Actionscript
1
this.localToGlobal(un_point);


On précise avec this quel était le référentiel local de départ ; ici, il s'agit de boite : or, vu qu'on est dans un bloc de code lui appartenant, on utilise this.
Entre les parenthèses, on indique juste l'objet portant les propriétés x et y, qui doit être converti.

Et c'est tout. :D

Si on adapte ça à notre cas, on arrive à :

Code : Actionscript
1
2
3
4
5
6
7
8
var un_point:Object = new Object();
un_point.x = this.point_1._x;
un_point.y = this.point_1._y;
this.localToGlobal(un_point);
if (_root.obstacle_circulaire.hitTest(un_point.x,un_point.y,true)) {
        this._x = posx;
        this._y = posy;
}


Comme vous pouvez le voir, c'est plus long. Mais le problème avec l'autre méthode, c'est que quand on cherche les coordonnées globales d'un clip, lui même dans un clip, qui est lui même dans un clip .. ça devient difficile. :D Alors que là tout se fait automatiquement. :p

Les 3 autres points



Bon, on va donc garder la seconde méthode. Mais maintenant vous savez tout ! Alors, un peu à vous de travailler ! Il faut adapter le code précédent pour que la vérification s'effectue sur les 4 points ! Il faudra bien entendu utiliser une boucle et ce qu'on a vu ICI.

Correction :

Secret (cliquez pour afficher)
Code : Actionscript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
for (i=1; i<=4; i++) { // Boucle de 1 à 4 compris
        // Conversion des coordonnées
        var un_point:Object = new Object();
        un_point.x = this['point_'+i]._x;
        un_point.y = this['point_'+i]._y;
        this.localToGlobal(un_point);
        // Test de collision
        if (_root.obstacle_circulaire.hitTest(un_point.x,un_point.y,true)) {
                this._x = posx;
                this._y = posy;
        }
}


Et voilà, maintenant la méthode bourrin n'a plus de secret pour vous. ;)
Vous voulez apprendre quelques bonnes astuces pour éviter que votre personnage s'enfonce dans les murs lors des collisions ? C'est par ici : Régler les problèmes de collision

N'oubliez pas, ce qu'il faut pour utiliser les collisions, c'est surtout de l'ingéniosité. ;)
Chapitre précédent Sommaire Chapitre suivant

Partager

14 commentaires pour "Les collisions"
Note moyenne : 3.24 / 4 (137 votes)
Pseudo Commentaire
Hors ligne megalex # Posté le 25/12/2008 à 13:11:56
ouaip c'est moi
Avatar

très bon tuto mais tes liens de telechargement ne marchent pas!!

bon alors j'ai fait moi-même les code qui detecte l'appui sur les touches et qui fait avancer le cube mais ce serait mieux si on pouvait avoir un lien valide car pour faire un carré de 20 sur 20 puis faire le code de déplacement, sa prends du temps et on est pas sur de réussir(mais moi jsuis trop fier, j'ai reussi tout seul :o flash me parrait si simple maintenant ^^ )

donc 20/20 pour ce tuto qui explique bien, a++

|projet en cours|


-Like a Smoothie, un logiciel d'animation bitmap capable de sauvegarder au format gif à la manière d'easytoon mais avec plus de fonctions et un support approffondi pour la communauté francophone(voir peut-être la communauté anglaise).
note: réalisé avec Qt et open source.
 
Hors ligne fas-amateur # Posté le 07/08/2009 à 16:28:05

super tutorial :) ;sauf que les fichiers sources ne sont pas accessibles.dommmmage :(
Hors ligne Chaise # Posté le 19/09/2009 à 04:12:29
Avatar

merci... c'est le seul guide bien expliqué. xD
Hors ligne tozo # Posté le 29/01/2011 à 17:16:03

Bonjour je voulais savoir s'il était possible d'empêcher un objet d'entrer dans une zone plutôt que d'en sortir
Hors ligne Saiyajin # Posté le 31/01/2012 à 17:10:01
Time Of Your Life...
Avatar

Ville : Renes
Pays : Suisse
Études : ERACOM Lausanne

#tozo
Suffit de faire le contraire par rapport aux x et y.
que tu veux.

Le post date d'un an et 14 jour :/
He be..XD U_u :) ^^

ActionScript/JavaScript
En pleine étude du language HaXe.
 

Voir tous les commentaires