The community is working on translating this tutorial into Indonesian, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".
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!
Creating & moving the Snake
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!
Creating the Snake
We'll draw the snake with a method called DrawSnake() - the method is actually quite simple, but it does require quite a bit of extra stuff, including a new class called SnakePart, as well as some extra fields on the Window class. Let's start with the SnakePart class, which you would normally define in a new file (SnakePart.cs, for instance):
using System.Windows;
namespace WpfTutorialSamples.Games
{
public class SnakePart
{
public UIElement UiElement { get; set; }
public Point Position { get; set; }
public bool IsHead { get; set; }
}
}
This simple class will contain information about each part of the snake: Where is the element positioned in our game area, which UIElement (a Rectangle, in our case) represents the part, and is this the head-part of the snake or not? We'll use all of it later, but first, inside our Window class, we need to define a couple of fields to be used in our DrawSnake() method (and later in other methods as well):
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 define two SolidColorBrush'es, one for the body and one for the head. We also define a List<SnakePart>, which will keep a reference to all parts of the snake. With that in place, we can now implement our DrawSnake() method:
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);
}
}
}
As you can see, this method is not particularly complicated: We loop over the snakeParts List, and for each part, we check if a UIElement has been specified for this part - if not, we create it (represented by a Rectangle) and add it to the game area, while saving a reference to it on the UiElement property of the SnakePart instance. Notice how we use the Position property of the SnakePart instance to position the actual element inside the GameArea Canvas.
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.
Moving the Snake
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.
Summary
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.