TOC

This article is currently in the process of being translated into Dutch (~35% done).

Een Spel Creëren: SnakeWPF:
Chapter introduction:

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!

De Slang maken & bewegen

In the last article, we made a nice area for the snake in our SnakeWPF implementation to move around. With that in place, it's now time to create the actual snake and then make it move around the area. Once again, we'll use the WPF Rectangle class to form a snake of a certain length, with each element being the same width and height as the background squares, or as we call it: The SnakeSquareSize constant!

De Slang creëren

We zullen de slang tekenen via de methode DrawSnake(). Deze is feitelijk vrij simpel, maar vereist een handvol aan extra code, waaronder een nieuwe klasse genaamd SnakePart, en enkele extra fields in de Window-klasse. Laten we eerst de SnakePart klasse maken (die je normaal volgens conventie in een apart bestand definieert - SnakePart.cs, bijvoorbeeld):

using System.Windows;

namespace WpfTutorialSamples.Games
{
    public class SnakePart
    {
public UIElement UiElement { get; set; }

public Point Position { get; set; }

public bool IsHead { get; set; }
    }
}

Deze eenvoudige klasse zal informatie over elk onderdeel van de slang bijhouden: waar is het element gepositioneerd in ons spelgebied, door welk UIElement (in ons geval een Rectangle) wordt het onderdeel gevisualiseerd, en is dit de kop van de slang of niet? Deze informatie zal later allemaal geraadpleegd worden, maar laten we eerst een aantal fields initialiseren in de Window-klasse die nodig zijn voor onze DrawSnake() methode (en toekomstige methodes):

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>();  
    ......

We definiëren twee SolidColorBrush instanties; één voor het lichaam en één voor de kop van onze slang. Daarnaast initialiseren we een List<SnakePart> om referenties naar de verschilende slangonderdelen bij te houden. Nu deze fields klaarstaan om gebruikt te worden, kunnen we overgaan tot de implementatie van onze DrawSnake() methode:

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);
}
    }
}

Zoals je kan zien is deze methode vrij eenvoudig: we itereren over elk element in de snakeParts List en controleren of er voor het huidige element een UIElement bestaat. Is dit niet het geval, dan creëren we een nieuwe Rectangle en updaten de UIElement property van de SnakePart instantie om hier naar te refereren, waarna we het toevoegen aan het spelgebied. Stel vast dat we de Position property van het SnakePart object raadplegen om het element in het spelgebied (Canvas) te positioneren.

The trick here is of course that the actual parts of the snake will be defined elsewhere, allowing us to add one or several parts to the snake, give them the desired position, and then have the DrawSnake() method do the actual work for us. We'll do that as a part of the same process used to move the snake.

De Slang bewegen

To feed something to the DrawSnake() method, we need to populate the snakeParts list. This list constantly serves as the basis of where to draw each element of the snake, so we'll also be using it to create movement for the snake. The process of moving the snake basically consists of adding a new element to it, in the direction where the snake is currently moving, and then delete the last part of the snake. This will make it look like we're actually moving each element, but in fact, we're just adding new ones while deleting the old ones.

So, we'll need a MoveSnake() method, which I'll show you in just a minute, but first, we need to add some more to the top of our Window class definition:

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;  
    ......

We have added a new enumeration, called SnakeDirection, which should be pretty self-explanatory. For that, we have a private field to hold the actual, current direction (snakeDirection), and then we have an integer variable to hold the desired length of the snake (snakeLength). With that in place, we're ready to implement the MoveSnake() method. It's a bit long, so I've added inline-comments to each of the important parts of it:

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();      
}

With that in place, we now have all the logic needed to create movement for the snake. Notice how we constantly use the SnakeSquareSize constant in all aspects of the game, from drawing the background checkerboard pattern to creating and adding to the snake.

Samenvatting

From the first article, we now have a background and from this article, we have the code to draw and move the snake. But even with this logic in place, there's still no actual movement or even an actual snake on the game area, because we haven't called any of these methods yet.

The call-to-action for the movement of the snake must come from a repeating source, because the snake should be constantly moving as long as the game is running - in WPF, we have the DispatcherTimer class which will help us with just that. The continuous movement of the snake, using a timer, will be the subject for the next article.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!