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!
Créer et mettre le serpent en mouvement
Dans le dernier article, nous avons fait une belle zone dans laquelle va pouvoir évoluer le serpent de notre implémentation SnakeWPF. Avec tout ceci en place, il est maintenant temps de créer le vrai serpent puis de le faire avancer dans la zone. Une fois de plus, nous utiliserons la classe Rectangle de WPF pour former un serpent d'une longueur donnée, chaque élément étant de la même largeur et hauteur que les carrés du fond, autrement dit : la constante SnakeSquareSize !
Création du Snake
Nous dessinerons le serpent grâce à la méthode DrawSnake() - la méthode est en réalité bien simple, mais cela nécessite un peu de code supplémentaire, notamment une nouvelle classe SnakePart, ainsi que quelques champs supplémentaires sur la classe Window. Commençons avec la classe SnakePart, que vous devriez définir dans un nouveau fichier (SnakePart.cs, par exemple):
using System.Windows;
namespace WpfTutorialSamples.Games
{
public class SnakePart
{
public UIElement UiElement { get; set; }
public Point Position { get; set; }
public bool IsHead { get; set; }
}
}
Cette classe simple contiendra des informations concernant chaque partie du serpent : où est positionné l'élément dans notre zone de jeu, quel UIElement (un rectangle dans notre cas) représente cette partie, et est-ce la partie correspondant à la tête de notre serpent ou pas ? Nous utiliserons tout ceci plus tard, mais pour le moment, dans notre classe Window, nous devons définir quelques champs qui seront utilisés dans notre méthode DrawSnake() (et dans d'autres méthodes plus tard):
public partial class SnakeWPFSample : Window
{
const int SnakeSquareSize = 20;
private SolidColorBrush snakeBodyBrush = Brushes.Green;
private SolidColorBrush snakeHeadBrush = Brushes.YellowGreen;
private List<SnakePart> snakeParts = new List<SnakePart>();
......
Nous définissons deux SolidColorBrush, un pour le corps et l'autre pour la tête. Nous définissions également une List<SnakePart> qui gardera la référence de chacune des parties du serpent. Une fois ceci en place, nous pouvons maintenant implémenter notre méthode DrawSnake():
private void DrawSnake()
{
foreach(SnakePart snakePart in snakeParts)
{
if(snakePart.UiElement == null)
{
snakePart.UiElement = new Rectangle()
{
Width = SnakeSquareSize,
Height = SnakeSquareSize,
Fill = (snakePart.IsHead ? snakeHeadBrush : snakeBodyBrush)
};
GameArea.Children.Add(snakePart.UiElement);
Canvas.SetTop(snakePart.UiElement, snakePart.Position.Y);
Canvas.SetLeft(snakePart.UiElement, snakePart.Position.X);
}
}
}
Comme vous le voyez, cette méthode n'est pas particulièrement compliquée : nous bouclons sur la liste des snakeParts, et pour chaque partie, nous vérifions si un UIElement a été spécifié pour cette partie - sinon, on le crée (représentée par un Rectangle) et on l'ajoute à la zone de jeu, tandis que nous sauvegardons sa référence sur la propriété UIElement de l'instance de SnakePart. Notez la manière dont nous utilisons la propriété Position de l'instance de SnakePart pour positionner l'élément actuel dans notre canvas (zone de jeu).
Ici l'astuce est effectivement que les parties actuelles du serpent seront définies ailleurs, nous permettant ainsi d'ajouter une ou plusieurs parties au serpent, leur donner la position désirée, et finalement d'avoir la méthode DrawSnake() qui fera le travail pour nous . Nous ferons ceci de manière à ce qu'il puisse être réutilisé pour bouger le serpent.
Faire bouger le Snake
Afin que la méthode DrawSnake() puisque fonctionner, nous devons alimenter la liste ssnakeParts. Cette liste nous sert constamment de base pour savoir où dessiner chaque élément du serpent. Nous l'utiliserons également pour créer le mouvement du serpent. Le processus de mouvement du serpent consiste simplement à ajouter un nouvel élément dans la direction actuelle du serpent, et de supprimer la dernière partie du serpent. Cela donnera l'impression que chaque élément avance, mais en réalité, nous ajoutons juste un nouvel élément et supprimons le plus anciens.
Nous allons donc avoir besoin d'une méthode MoveSnake(), que je vous montrerai dans une petite minute, mais d'abord, nous devons ajouter quelques briques à notre définition de classe Window:
public partial class SnakeWPFSample : Window
{
const int SnakeSquareSize = 20;
private SolidColorBrush snakeBodyBrush = Brushes.Green;
private SolidColorBrush snakeHeadBrush = Brushes.YellowGreen;
private List<SnakePart> snakeParts = new List<SnakePart>();
public enum SnakeDirection { Left, Right, Up, Down };
private SnakeDirection snakeDirection = SnakeDirection.Right;
private int snakeLength;
......
Nous avons ajouté une nouvelle énumération, nommée SnakeDirection, qui correspond aux différentes directions que le serpent peut prendre. Nous avons donc un champ privé pour garder la direction courante (snakeDirection), puis nous avons un entier qui conserve la longueur désirée du serpent (snakeLength). Avec tout ceci, nous sommes prêts à implémenter la méthode MoveSnake(). Elle est un peu longue, donc j'ai ajouté des commentaires "inline" aux moments importants:
private void MoveSnake()
{
// Remove the last part of the snake, in preparation of the new part added below
while(snakeParts.Count >= snakeLength)
{
GameArea.Children.Remove(snakeParts[0].UiElement);
snakeParts.RemoveAt(0);
}
// Next up, we'll add a new element to the snake, which will be the (new) head
// Therefore, we mark all existing parts as non-head (body) elements and then
// we make sure that they use the body brush
foreach(SnakePart snakePart in snakeParts)
{
(snakePart.UiElement as Rectangle).Fill = snakeBodyBrush;
snakePart.IsHead = false;
}
// Determine in which direction to expand the snake, based on the current direction
SnakePart snakeHead = snakeParts[snakeParts.Count - 1];
double nextX = snakeHead.Position.X;
double nextY = snakeHead.Position.Y;
switch(snakeDirection)
{
case SnakeDirection.Left:
nextX -= SnakeSquareSize;
break;
case SnakeDirection.Right:
nextX += SnakeSquareSize;
break;
case SnakeDirection.Up:
nextY -= SnakeSquareSize;
break;
case SnakeDirection.Down:
nextY += SnakeSquareSize;
break;
}
// Now add the new head part to our list of snake parts...
snakeParts.Add(new SnakePart()
{
Position = new Point(nextX, nextY),
IsHead = true
});
//... and then have it drawn!
DrawSnake();
// We'll get to this later...
//DoCollisionCheck();
}
Une fois cela en place, nous avons toute la logique requise pour créer le mouvement du serpent. Notez comment nous utilisons constamment la constante SnakeSquareSize dans tous les aspects du jeu, depuis le dessin de la zone de jeu jusqu'à la création et l'ajout d'éléments au serpent.
Résumé
Dans le premier article, nous avons créé le fond et depuis l'article courant, nous avons en plus le code pour dessiner et faire bouger le serpent. Mais même avec cette logique en place, il n'y a toujours aucun mouvement (et même aucun serpent sur notre zone de jeu), car nous n'avons pas encore fait appel à nos nouvelles méthodes.
L'appel aux méthodes de mouvement du serpent doit provenir d'une source qui se répète, car le serpent devrait être constamment en mouvement tant que le jeu tourne - en WPF, nous avons la classe DispatcherTimer qui est justement faite pour ça. Le mouvement continu du serpent en utilisant le timer fera l'objet du prochain article.