TOC

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

ゲームを作る: スネークWPF:
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!

ヘビを作って動かす

最後の記事では、Snake WPFで、動き回るヘビの実装を行います。ヘビを作成して、領域内を動かせるようにします。再度、WPF Rectangleクラスを使って、任意の長さを持ったヘビを作ります。その際は、背景の正方形と幅・高さのサイズにするため、SnakeSquareSizeの定数を利用します。

ヘビを作る

DrawSnake()メソッドを使って、ヘビを描画します。これは中身は単純なメソッドなのですが、実現するためにいくつかのものが必要になっています。SnakePartという新しいクラスとWindowクラスのいくつかのフィールドです。SnakePartクラスから始めます、通常は新しいファイル(SnakePart.cs)に定義して進めます。

using System.Windows;

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

public Point Position { get; set; }

public bool IsHead { get; set; }
    }
}

このクラスの中に、ヘビの各パーツの情報が入っています:ゲームエリアの中のどこにエレメントがあるか(Position)、どのUIElement(スネークWPFでは Rectangle)がパーツになるか、ヘビの頭部分かどうか(IsHead)、です。これらを後で使うわけですが、まずはWindowクラスの中で、DrawSnake()メソッドで使ういくつかのフィールドを定義しておきましょう(のちのち他のメソッドでも使います)。

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

二つのSolidColorBrushを定義します。一つはヘビのからだ用、もう一つは頭用です。また、List<SnakePart> を使って、ヘビの各パーツを保持します。では、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);
}
    }
}

ご覧の通り、このメソッドは特に難しくありません。snakeParts のリストをループして、その各パーツごとに、UIElement がパーツに指定されているかをチェックしています。もし指定されていなければ、作成し(Rectangle として)、それをゲームエリアに追加します。そして、UiElementの参照をSnakePartのインスタンスのプロパティに保存します。ここで、SnakePartインスタンスのPositionプロパティを使って、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.

ヘビを動かす

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.

サマリー

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!