Combo apnée/TP de Programmation sur le traitement des collisions

Ce sujet sera démarré en apnée et poursuivi en TP. Le but ici est de traiter les collisions entre objets dans notre casse briques. Comme nous l'avons présenté en cours, nous cherchons ici un calcul efficace de la détection et du traitement des collisions au prix de petites approximations. Le but n'est pas d'atteindre un réalisme maximal mais plutôt un rendu visuellement convaincant et le plus léger à calculer possible.

1 - Une première étape basique

Dans un premier temps, afin de se focaliser sur la mise en place du traitement dans notre programme, nous supposerons que les boites de collisions sont identiques aux boites englobantes et sont définies comme le plus petit rectangle dans lequel notre objet est inscrit. En partant de cette définition, nous n'avons aucun travail à fournir, les boites de collision et les boites englobantes sont définies par les méthodes posX, posY, l et h du ComposantGraphique. Les seules choses que nous devons définir sont le déclenchement du calcul de collisions et le traitement d'une collision entre rectangles.

Concernant le déclenchement du calcul de collisions, nous pourrions déplacer tous les objets mouvants avec la mise à jour du niveau et ensuite enchaîner sur le calcul des collisions entre toute paire de composants. Toutefois, nous ne ferons pas ainsi. Nous allons plutôt profiter de la structure déjà en place : les ObjetMouvants sont observateurs du niveau qui déclenche une mise à jour lors du rafraîchissement toutes les 16,5ms. Durant ce rafraîchissement, chaque objet mouvant est déplacé. Nous allons en profiter pour faire suivre ce déplacement d'une détection de collisions avec tous les autres objets. Ainsi le calcul du déplacement et la résolution des collisions seront complètement effectués pour chaque objet mouvant avant de passer au suivant. Procéder ainsi présente quelques inconvénients :

mais cela présente aussi quelques avantages :

Maintenant que nous avons décidé de l'endroit où nous allons déclencher le calcul de collisions, il faut nous ramener au calcul de collisions entre des rectangles que sont les boites englobantes de nos ComposantGraphiques. Malheureusement ce n'est pas si simple, certains composants, comme les bords, ne sont pas des ComposantGraphiques. Nous pourrions compléter le code des Composants et fabriquer une boite englobante suffisante pour nos bords mais cette solution serait un peu "artificielle".

Une meilleure solution est de tirer parti du mécanisme de résolution virtuelle de l'appel de méthodes : si nous définissons au niveau du Composant une méthode collision, nous pouvons la redéfinir dans les classes qui descendent de Composant pour spécialiser son comportement. Nous pouvons alors également surcharger la méthode collision pour en avoir une version différente selon le type de composant passé en paramètre. Dans les versions spécialisées de la collision, celles qui prennent un Composant en paramètre, nous pouvons appeler la méthode surchargée appropriée en passant l'objet lui même (this) en paramètre. Ceci est illustré par le petit programme ExempleResolution.java. Dans ce petit programme on ne manipule que des A mais lorsqu'on combine des A on finit par arriver dans la méthode de B ou C qui combine avec un autre B ou C. Ainsi, via la résolution virtuelle des appels de méthodes et via deux appels en cascade, si nous tenons compte du fait que les bords ne sont pas mobiles (toujours utilisés comme paramètre du premier appel à collision), tous nos calculs de collisions entre Composants se ramènent aux deux cas suivants :

Exercices

Testez votre solution et vérifiez que, maintenant, la balle rebondit.

2 - Affinage des collisions

Nous allons maintenant affiner notre calcul de collisions. Nous souhaitons toujours privilégier la simplicité du calcul, tel que présenté en cours, mais se limiter à des vecteur de réaction pris dans la base de notre repère est un peu trop basique. Pour augmenter le réalisme nous allons maintenant ajouter des boites de collision circulaires à notre traitement. Pour cela, nous allons raffiner un peu notre hiérarchie de classes afin de factoriser les choses : nous souhaitons que nos ComposantGraphiques se déclinent en deux variantes, le cercle et le rectangle, correspondant respectivement à nos deux types de boites de collision. Le cercle sera utilisé pour les balles, les bonus et (plus tard) les tirs, tandis que le rectangle sera utilisé pour les briques et la raquette (nous verrons plus tard comment affiner notre raquette). Nous pouvons alors remarquer un premier problème : les briques et la raquette sont tous deux des rectangles mais un seul est mouvant, le Rectangle ne peut donc pas hériter d'ObjetMouvant. L'inverse n'est pas non plus possible, ObjetMouvant ne peut pas hériter de Rectangle car certains objets mouvants seront des Cercle. Nous sommes dans un cas où nous aimerions disposer d'héritage multiple : Raquette devrait pouvoir hériter de Rectangle et d'ObjetMouvant. Bien entendu c'est impossible en java, mais nous pouvons contourner le problème grâce au pattern décorateur.

Exercices

Vos angles de collision devraient maintenant être beaucoup plus variés. Pensez à normaliser votre vecteur de réaction lors de la prise en compte de son effet sur la balle, autrement vous allez modifier la vitesse de la balle. Si le vecteur de réaction est très petit, vous pouvez même vous retrouver avec une balle qui colle ou qui longe les bords qu'elle touche !

3 - Elimination des collisions parasites

Avec la mise en place des collisions précédentes, vous devriez voir apparaître occasionnellement des collisions étranges. Typiquement, lorsqu'une balle, à l'issue de son déplacement, se retrouve à intersecter la jointure de deux briques contigües. Dans ce cas, l'une des collisions est sur un bord et l'autre sur un coin, mais uniquement la collision du bord est à prendre en compte car les deux briques forment un mur. Pour résoudre ce genre de problème, nous ne tiendrons compte que de la collision ayant la norme la plus grande. Nous allons résoudre cela en découpant le calcul des collisions en deux étapes : d'abord nous calculerons toutes les collisions concernant un objet, et ensuite nous agirons sur l'objet en fonction de l'ensemble des collisions qui le concernent.

Exercices

4 - On arrondit les angles de la raquette

Nous souhaitons maintenant que la raquette ressemble à autre chose qu'un simple rectangle. Nous aimerions que sa boite de collision ressemble à un rectangle garni de deux demi cercles à ses deux extrémités. Pour cela nous allons fabriquer une nouvelle boite de collision que nous appellerons ComposantGroupe sous forme de décorateur constitué d'une union de ComposantGraphique, c'est à dire des cercles et des rectangles. Ce composant aura une boite englobante qui contient tous ses composants et calculera ses propres collisions comme le maximum des collisions de ses constituants.

Exercices

5 - Le cas des bonus et la suppression des objets

Le cas des bonus est un peu particulier. Nous les avons placés dans une couche séparée car il ne sont concernés par quasiment aucune collision. Seule une collision avec la raquette est pertinente pour eux et le vecteur de réaction n'a pas d'importance, si un bonus touche la raquette il est juste ramassé : son effet s'applique et l'objet disparaît. Nous souhaitons donc mettre en place deux choses : un traitement spécifique de collision pour certains objets mouvants et une suppression propre à l'issue du calcul de collisions.

Exercices

6 - Bonus

Si vous avez un peu de temps, vous pouvez essayer d'affiner le calcul de collisions en le découpant en plusieurs étapes de mouvement puis detection et prise en compte des collisions entre deux rafraîchissements d'image. Essayez de faire varier le nombre d'étapes avant de vous fixer sur celui qui vous convient le mieux.