Introduction

Quand vous créez un jeu, il est souvent essentiel de gérer les collisions, entre votre héros et son environnement.

Ce tutoriel est destiné à vous apprendre à gérer les collisions, en utilisant la classe Rectangle fournie par XNA.

Cet article est une traduction d’un tuto du Creator’s club, vous pourrez trouver l’original ici : http://creators.xna.com/en-us/tutorial/collision2drectangle

Pour ce tutoriel, nous allons créer un jeu simpliste, qui consistera à faire éviter des triangles tombés du ciel à notre  héros.

Ici, le héros évite les triangles.

Safe

Ici, le héros est en collision avec un triangle.

Blocked

Quand vous aurez fini ce tutoriel, vous aurez toutes les connaissances nécessaires pour gérer vos collisions.

Step 1: Créer un nouveau projet, et inclure les artworks

La première chose dont vous aurez besoin avant de commencer à coder cotre jeu, est les textures pour votre héros et les blocs. Ces textures peuvent être de la taille que vous souhaitez et vous devriez utiliser la couleur magenta (100% de rouge, 0% de vert et 100% de bleu) pour toutes les parties devant être transparentes. Sauvez vos images en tant que .bmp ou .png, et non pas .gif ou .jpg, pour éviter tous problèmes liés à la compression de votre image.

Voici les textures utilisées dans cet exemple:

Texture TriangleTexture Personnage

Note

La couleur magenta est utilisée, pour que le processeur de texture par défaut, convertisse la couleur magenta en transparent. En effet, dans sa logique des couleurs, magenta correspond à la transparence. Je vous déconseille donc d’essayer d’habiller votre héros ou votre environnement de cette couleur (si vraiment vous avez besoin de magenta, diminuez de 1% en rouge ou en bleu, devrait suffire).

Vous pouvez ensuite créer votre nouveau projet, que nous appellerons ici RectangleCollisionWindows, et y inclure les textures vues précédemment.

Je vous invite à relire le tutorial Mon Premier Jeu Part 1, de  raptorpg si vous ne savez plus comment faire.

Step 2: Initialisation

.Nous allons maintenant initialiser et dessiner notre héros, ainsi que les blocs. Ce jeu étant réellement très simple, nous ne créerons pas de nouvelles classes pour le héros et les blocs.

Je passerais rapidement sur cette étape, celle-ci étant déjà abordée par raptorpg dans le tutoriEl Mon Premier Jeu Part 2.

   1: // Les images à dessiner 
   2: Texture2D personTexture;
   3: Texture2D blockTexture;
   4:
   5: // Les images seront dessinées avec ce SpriteBatch 
   6: SpriteBatch spriteBatch;
   7:
   8: // Le Héros 
   9: Vector2 personPosition;
  10: const int PersonMoveSpeed = 5;
  11:
  12: // Les Blocs 
  13: List<Vector2> blockPositions = new List<Vector2>();
  14: float BlockSpawnProbability = 0.01f;
  15: const int BlockFallSpeed = 2;
  16:
  17: //on créé un objet de type Random, pour l'apparition aléatoire des blocs 
  18: Random random = new Random();
  19:
  20: protected override void Update(GameTime gameTime)
  21: {
  22:     // On récupère les entrées utilisateur 
  23:     KeyboardState keyboard = Keyboard.GetState();
  24:     GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
  25:
  26:     // Allows the game to exit 
  27:     if (gamePad.Buttons.Back == ButtonState.Pressed)
  28:         this.Exit();
  29:
  30:     // On permet au joueur de bouger à gauche ou à droite 
  31:     if (keyboard.IsKeyDown(Keys.Left) || gamePad.DPad.Left == ButtonState.Pressed)
  32:     {
  33:         personPosition.X -= PersonMoveSpeed;
  34:     }
  35:
  36:     if (keyboard.IsKeyDown(Keys.Right) || gamePad.DPad.Right == ButtonState.Pressed)
  37:     {
  38:         personPosition.X += PersonMoveSpeed;
  39:     }
  40:
  41:     // On gère la creation des blocs 
  42:     if (random.NextDouble() < BlockSpawnOdds)
  43:     {
  44:         float x = (float)random.NextDouble() * (Window.ClientBounds.Width - blockTexture.Width);
  45:         blockPositions.Add(new Vector2(x, -blockTexture.Height));
  46:     }
  47:
  48:     // On met jour tous les blocs afin qu'ils tombent 
  49:     for (int i = 0; i < blockPositions.Count; i++)
  50:     {
  51:         // Animate this block falling 
  52:         blockPositions[i] = new Vector2(blockPositions[i].X, blockPositions[i].Y + BlockFallSpeed);
  53:     }
  54:
  55:     base.Update(gameTime);
  56: }
  57:
  58: protected override void Draw(GameTime gameTime)
  59: {
  60:     graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
  61:
  62:     spriteBatch.Begin();
  63:
  64:     // Dessine le Héros 
  65:     spriteBatch.Draw(personTexture, personPosition, Color.White);
  66:
  67:     // Dessine les blocs 
  68:     foreach (Vector2 blockPosition in blockPositions)
  69:         spriteBatch.Draw(blockTexture, blockPosition, Color.White);
  70:
  71:     spriteBatch.End();
  72:
  73:     base.Draw(gameTime);
  74: }

Avant de continuer, veuillez lire attentivement le code ci-dessus. Ce code nous permet de récupérer les entrées de l’utilisateur, d’ajuster la position du joueur en fonction de ses entrées, de créer de nouveaux blocs à certains moments du jeu, et de les faire apparaître à une coordonnée x aléatoire, et d’animer ces blocs.

Compilez votre projet, vous devriez voir maintenant les blocs tombés.

Step 3: Collisions avec l’écran

Vous aurez peut-être remarqué que vous pouvez bouger votre personnage hors de l’écran. Ce qui n’est peut-être pas visible immédiatement, est le fait que les blocs ne sont jamais supprimés (eux aussi pouvant, comme vous, dépasser les limites de l’écran). Si vous laissez donc votre jeu tourner, arrivera un moment ou le jeu consommera toute la mémoire de votre système.

Pour régler ces deux problèmes, vous devez emprisonner l’avatar du joueur dans les limites de l’écran, et supprimez les blocs qui dépassent le bas celui-ci.

Nous allons donc modifier la méthode Update (), afin de régler ces deux problèmes :

   1: protected override void Update(GameTime gameTime)
   2: {
   3:     // On récupère les entrées utilisateur 
   4:     KeyboardState keyboard = Keyboard.GetState();
   5:     GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
   6:
   7:     // Allows the game to exit 
   8:     if (gamePad.Buttons.Back == ButtonState.Pressed)
   9:         this.Exit();
  10:
  11:     // On permet au joueur de bouger à gauche ou à droite 
  12:     if (keyboard.IsKeyDown(Keys.Left) || gamePad.DPad.Left == ButtonState.Pressed)
  13:     {
  14:         personPosition.X -= PersonMoveSpeed;
  15:     }
  16:
  17:     if (keyboard.IsKeyDown(Keys.Right) || gamePad.DPad.Right == ButtonState.Pressed)
  18:     {
  19:         personPosition.X += PersonMoveSpeed;
  20:     }
  21:
  22:     // empêche la personne de bouger en dehors de l'écran 
  23:     personPosition.X = MathHelper.Clamp(personPosition.X, 0, Window.ClientBounds.Width - personTexture.Width);
  24:
  25:     // On gère la creation des blocs 
  26:     if (random.NextDouble() < BlockSpawnOdds)
  27:     {
  28:         float x = (float)random.NextDouble() * (Window.ClientBounds.Width - blockTexture.Width);
  29:         blockPositions.Add(new Vector2(x, -blockTexture.Height));
  30:     }
  31:
  32:     // On met jour tous les blocs afin qu'ils tombent 
  33:     for (int i = 0; i < blockPositions.Count; i++)
  34:     {
  35:         // On anime la chute des blocs 
  36:         blockPositions[i] = new Vector2(blockPositions[i].X, blockPositions[i].Y + BlockFallSpeed);
37:
  38:         // On enlève le bloc si il est en dehors de l'écran
  39:         if (blockPositions[i].Y > Window.ClientBounds.Height)
  40:         {
  41:             blockPositions.RemoveAt(i);
  42:
43:           // Quand on enlève un bloc, le bloc suivant aura le même index que le bloc courant

              //On décrémente i pour éviter de rater un bloc. 
  44:             i--;
  45:         }
  46:     }
  47:
  48:     base.Update(gameTime);
  49: }
  50:

La ligne 23, empêche simplement personPosition.X de devenir une valeur qui placerait le joueur en dehors des limites de l’écran. Les lignes 39 à 45 permettent d’identifier les blocs tombants en dessous de l’écran, et de les supprimer

Chaque nouveau bloc de code est un mécanisme de détection et de réponse à une collision extrêmement simple, n’utilisant pour ça que les coordonnées des objets et de la fenêtre.

Si vous essayez désormais de compiler, vous ne devriez plus pouvoir dépasser les limites de l’écran.

Step 4: Collision par Rectangle

Si vous êtes arrive jusqu’ici, vous n’êtes plus qu’à quelques pas de quelque chose qui ressemble à un jeu. Votre héros doit pouvoir être touché.

Dans cet exemple, le jeu changera simplement la couleur du fond du jeu en rouge, quand votre héros se fera toucher par un bloc.

Ajoutez cette declaration en haut de votre classe Game1:

   1: // Pour savoir si une collision est détectée
   2: bool personHit = false;

Modifiez la méthode Draw() afin qu’elle commence ainsi:

   1: protected override void Draw(GameTime gameTime)
   2: {
   3:     GraphicsDevice device = graphics.GraphicsDevice;
   4:
   5:     // Change the background to red when the person was hit by a block 
   6:     if (personHit)
   7:         device.Clear(Color.Red);
   8:     else
   9:         device.Clear(Color.CornflowerBlue);

Vous devez maintenant déterminer si aucun bloc ne touche votre héros. Une manière simple de faire cela, est de déterminer si les rectangles délimitant les sprites de vos blocs, croisent celui de votre personnage. Pour cela, le framework XNA nous fournit une méthode simple : Rectangle.Intersects().

Modifiez de nouveau la méthode Update(), afin qu’elle ressemble à cela :

  01:     // On permet au joueur de bouger à gauche ou à droite 
  02:     if (keyboard.IsKeyDown(Keys.Left) || gamePad.DPad.Left == ButtonState.Pressed)
  03:     {
  04:         personPosition.X -= PersonMoveSpeed;
  05:     }
  06:
  07:     {


  08:     // On gère la creation des blocs 
  09:     if (random.NextDouble() < BlockSpawnOdds)
  10:     {
  11:         float x = (float)random.NextDouble() * (Window.ClientBounds.Width - blockTexture.Width);
  12:         blockPositions.Add(new Vector2(x, -blockTexture.Height));
  13:     }
  14:
  15:     // Récupère le Rectangle délimitant le Sprite du héros
  16:     Rectangle personRectangle = new Rectangle((int)personPosition.X, (int)personPosition.Y, personTexture.Width, personTexture.Height);
  17:
  18:     // On met jour tous les blocs afin qu'ils tombent 
  19:     personHit = false;
  20:
  21:     for (int i = 0; i < blockPositions.Count; i++)
  22:     {
  23:         // Animate this block falling 
  24:         blockPositions[i] = new Vector2(blockPositions[i].X, blockPositions[i].Y + BlockFallSpeed);
  25:
  26:         // Récupère le Rectangle délimitant le Sprite des blocs
  27:         Rectangle blockRectangle = new Rectangle((int)blockPositions[i].X, (int)blockPositions[i].Y, blockTexture.Width, blockTexture.Height);
  28:
  29:         // Verifie la collision avec entre le Rectangle du bloc et celui du heros
  30:         if (personRectangle.Intersects(blockRectangle))
  31:             personHit = true;
  32:
  33:         // Remove this block if it have fallen off the screen 
  34:
  35:         if (blockPositions[i].Y > Window.ClientBounds.Height)
  36:         {
  37:             blockPositions.RemoveAt(i);
  38:
  39:             // When removing a block, the next block will have the same index as the current block. Decrement i to prevent skipping a block. 
  40:             i--;
  41:         }
  42:     }
  43:
  44:     base.Update(gameTime);
  45: }

Les lignes modifiées sont : l 33, l 36, l 44 à 48.

Ce code determine les rectangles délimitant les sprites du héros et des blocs. Chaque rectangle délimitant les sprites des blocs est testé afin de voir si il croise celui du joueur.

Le jeu est maintenant fini, vous pouvez compiler, et observer un peu votre travail.

Félicitations!

Vous savez maintenant comment gérer les collisions par Rectangle. Ces collisions réellement simples sont un élément très important de XNA, permettant aux codeurs débutants, ou non, de pouvoir créer un grand nombre de jeux 2D tirés de leur imagination.

Malheureusement, cette méthode de collision a ses limites. Vous l’aurez peut-être aperçu (dans le code ou en jouant), mais la détection de la collision entre le personnage et le bloc ne se fait pas quand le joueur touche le triangle noir.

Pour ce genre de problèmes, où la collision doit être très précise, il existe la méthode de Collision par Pixels.

Cette méthode sera explicité prochainement.

Ryoma.