This article has been localized into French by the community.
In this article series, we're building a complete Snake game from scratch. It makes sense to start with the Introduction and then work your way through the articles one by one, to get the full understanding.
If you want to get the complete source code for the game at once, to get started modifying and learning from it right now, consider downloading all our samples!
Détection des collisions
Maintenant que la zone de jeu, la nourriture, le Snake, ainsi que ses déplacements, sont implémentés, nous n'avons besoin que d'une dernière chose pour que cela ressemble et se joue vraiment comme un jeu : Une détection des collisions. Le concept se base sur une vérification continuelle de la position du Snake, pour savoir si il a percuté quelque chose et nous en avons actuellement besoin pour deux raisons : Pour savoir si le Snake vient de manger ou si il vient de toucher un obstacle (un mur ou bien sa propre queue).
La méthode DoCollisionCheck()
La détection des collisions sera effectué par la méthode DoCollisionCheck(), nous avons donc besoin de l'implémenter. Voici ce à quoi elle devrait ressembler :
private void DoCollisionCheck()
{
SnakePart snakeHead = snakeParts[snakeParts.Count - 1];
if((snakeHead.Position.X == Canvas.GetLeft(snakeFood)) && (snakeHead.Position.Y == Canvas.GetTop(snakeFood)))
{
EatSnakeFood();
return;
}
if((snakeHead.Position.Y < 0) || (snakeHead.Position.Y >= GameArea.ActualHeight) ||
(snakeHead.Position.X < 0) || (snakeHead.Position.X >= GameArea.ActualWidth))
{
EndGame();
}
foreach(SnakePart snakeBodyPart in snakeParts.Take(snakeParts.Count - 1))
{
if((snakeHead.Position.X == snakeBodyPart.Position.X) && (snakeHead.Position.Y == snakeBodyPart.Position.Y))
EndGame();
}
}
Comme prévu, nous faisons deux contrôles: premièrement, on vérifie si la position de la tête du serpent correspond à la position d'un bloc de nourriture. si c'est le cas, on appelle la méthode EatSnakeFood()(plus de détails plus tard). Puis on vérifie si la position de la tête dépasse les limites de la zone de jeu, pour voir si le serpent est en train de franchir une de ces limites. Si c'est le cas, on appelle la méthode EndGame(). Enfin, on vérifie si la position de la tête du serpent correspond à la position d'une des parties de son corps, ce qui signifie qu'il est en train d'entrer en collision avec son propre corps, ce qui mettra également fin à la partie, par un appel à la méthode EndGame().
La méthode EatSnakeFood()
La méthode EatSnakeFood() est en charge d'un certain nombre de choses, car dès que le serpent mange un bloc de nourriture, on doit en ajouter un nouveau, à un nouvel emplacement, ainsi que mettre à jour le score, la longueur du serpent et la vitesse du jeu. Pour gérer le score, on déclare une nouvelle variable locale appelée currentScore:
public partial class SnakeWPFSample : Window
{
....
private int snakeLength;
private int currentScore = 0;
....
Quand c'est fait, ajoutons la méthode EatSnakeFood() :
private void EatSnakeFood()
{
snakeLength++;
currentScore++;
int timerInterval = Math.Max(SnakeSpeedThreshold, (int)gameTickTimer.Interval.TotalMilliseconds - (currentScore * 2));
gameTickTimer.Interval = TimeSpan.FromMilliseconds(timerInterval);
GameArea.Children.Remove(snakeFood);
DrawSnakeFood();
UpdateGameStatus();
}
Comme dit précédemment, plusieurs choses se passent ici :
- On incrémente snakeLength et currentScore de un pour refléter le fait que le Snake vient de manger une unité de nourriture.
- On ajuste l'intervalle du gameTickTimer, d'après la règle suivante : Le currentScore est multiplié par deux puis soustrait à la valeur actuelle de l'intervalle (la vitesse). Cela va permettre une évolution exponentielle de la vitesse suivant la longueur du Snake, rendant le jeu de plus en plus difficile. On a défini précédemment une limite inférieur pour la vitesse, avec la constante SnakeSpeedThreshold, donc la vitesse de jeu ne pourra jamais descendre en dessous d'un intervalle de 100ms.
- On supprime ensuite le bloc de nourriture consommé par le serpent , et on appelle la méthode DrawSnakeFood() qui va ajouter un nouveau bloc de nourriture à un nouvel emplacement.
- Enfin, on appelle la méthode UpdateGameStatus(), qui se présente come ceci :
private void UpdateGameStatus()
{
this.Title = "SnakeWPF - Score: " + currentScore + " - Game speed: " + gameTickTimer.Interval.TotalMilliseconds;
}
Cette méthode va simplement mettre à jour la propriété Title de la fenêtre pour afficher le score courant ainsi que la vitesse du jeu. c'est un moyen simple d'afficher le statut courant, qui peut être facilement étendu plus tard si on le désire.
La méthode EndGame()
Nous avons un petit peu de code à ajouter quand le jeu se termine. Cela va se faire grâce à la méthode EndGame(), qui est actuellement appelée depuis la méthode DoCollisionCheck(). Comme vous pouvez le voir, c'est vraiment très simple :
private void EndGame()
{
gameTickTimer.IsEnabled = false;
MessageBox.Show("Oooops, you died!\n\nTo start a new game, just press the Space bar...", "SnakeWPF");
}
En plus de montrer un message à l'utilisateur l'informant du décès de notre serpent adoré, on stoppe simplement le gameTickTimer. Comme ce timer est à l'origine de tout ce qui se passe dans le jeu, dès qu'il est arrêté, tous les mouvements et affichages le sont également.
Derniers ajustements
Nous sommes maintenant presque prêts pour le premier essai de notre jeu Snake totalement fonctionnel - en fait, nous avons juste besoin de faire deux petits ajustements. premièrement, nous devont être certains que la méthode DoCollisionCheck() est appelée - cela doit arriver après l'exécution de la dernière action dans la méthode MoveSnake(), ce que nous avons mis en place précédemment :
private void MoveSnake()
{
.....
//... and then have it drawn!
DrawSnake();
// Finally: Check if it just hit something!
DoCollisionCheck();
}
Maintenant, la détection de collision est exécutée dès que le serpent bouge! Vous souvenez vous de l'évolution que nous avons fait sur la méthode StartNewGame()? On doit l'améliorer encore un peu, pour être sûrs qu'on remet à zéro le score chaque fois que le jeu (re)démarre, et deux ou trois petites choses supplémentaires. Alors , remplaçons la méthode StartNewGame() par cette version améliorée :
private void StartNewGame()
{
// Remove potential dead snake parts and leftover food...
foreach(SnakePart snakeBodyPart in snakeParts)
{
if(snakeBodyPart.UiElement != null)
GameArea.Children.Remove(snakeBodyPart.UiElement);
}
snakeParts.Clear();
if(snakeFood != null)
GameArea.Children.Remove(snakeFood);
// Reset stuff
currentScore = 0;
snakeLength = SnakeStartLength;
snakeDirection = SnakeDirection.Right;
snakeParts.Add(new SnakePart() { Position = new Point(SnakeSquareSize * 5, SnakeSquareSize * 5) });
gameTickTimer.Interval = TimeSpan.FromMilliseconds(SnakeStartSpeed);
// Draw the snake again and some new food...
DrawSnake();
DrawSnakeFood();
// Update status
UpdateGameStatus();
// Go!
gameTickTimer.IsEnabled = true;
}
Quand le jeu démarre, Il se produit ceci :
- Comme ce n'est peut être pas la première partie qui est jouée, on doit s'assurer qu'il ne reste rien de la partie précédente : cela inclut des parties du serpent, ainsi que des restes de nourriture.
- On doit également réinitialiser un certain nombre de variables, comme score, the length, the direction and the speed du timer. On doit également ajouter la tête du serpent (qui sera automatiquement étendu par la méthode MoveSnake()).
- Puis on doit appeler les méthodes DrawSnake() et DrawSnakeFood()pour gérer l'affichage du nouveau jeu
- On appelle ensuite la méthode UpdateGameStatus().
- Et enfin, nous sommes prêt à démarrer le gameTickTimer - il se lancera immédiatement, mettant le jeu en route.
Résumé
Si vous avez réussi toutes les étapes de cette série d'articles : Félicitations- vous venez de créer votre premier jeu WPF! Profitez de tout votre travail en lançant votre projet, en appuyant sur la touche Espace pour commencer à jouer : même dans cette version très simple, Snake est un jeu amusant et addictif!