TOC

This article has been localized into Chinese by the community.

创建一个游戏: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!

创建&移动贪吃蛇

在上一篇文章中,我们在WPF贪吃蛇实现中制造一个很好的给蛇移动的区域。有了这些,现在去创造实际的贪吃蛇,然后让其在这区域移动。再一次,我们使用WPFRectangle 类去构成一定长度的蛇,每个元素的宽和高与正方形背景相同,或者我们称之为: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; }
    }
}

这个简单的类将会包含一些关于贪吃蛇每个部分的信息:元素位于游戏区域的哪里,UIElement(在我们案例是一个Rectangle) 代表着这部分,然后这是蛇的头部还是没有?我们之后全部会用到,但是首先,在我们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);
}
    }
}

如你所见,这个方法不是特别地复杂:我们循环snakePartsList,对于每个部分,我们检查是否对该部分指定了一个 UIElement(表现为一个Rectangle),如果没有,我们创建一个然后添加进游戏区域,同时在SnakePart 的实例的 UiElement的属性保存对它的引用。注意我们如何用SnakePart 实例的Position属性去定位在GameArea Canvas中实际的元素

这里的诀窍是贪吃蛇实际的部分会被声明在各个地方,允许我们去添加一个或多个部分到蛇,给他们想要的方位,然后有DrawSnake()方法为我们做实际的工作。我们将会把它作为移动蛇过程的一部分。

移动贪吃蛇

要想向DrawSnake() 方法填充一些内容,我们需要填充snakePartsList集合。这个list不断地作为我们画贪吃蛇每个元素的基础,所以我们也将会用它去创建贪吃蛇的移动。移动贪吃蛇的过程在它移动的方向上添加一个新的元素,然后删除蛇的末尾部分。这将会让它看起来是我们在实际移动每个元素,但事实上,我们只是添加一个新的同事哦删除一个旧的。

因此,我们将需要一个MoveSnake() 方法,一会儿我将会展示给你,但是首先,我们需要添加一些更多在我们的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;  
    ......

我们已经添加了一个新的枚举,叫SnakeDirection,那个应该很容易解释。我们有一个私有字段去保存实际正确的方向(snakeLength),然后我们有一个Int变量去保存蛇的长度snake (snakeLength),有了这个,我们已经准备去实现MoveSnake()方法。它有点长,所以我添加了内嵌注释在各个重要的部分:

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

有了这个,我们现在有全部逻辑了,需要去创建贪吃蛇的移动.注意我们如何不断地使用SnakeSquareSize 常量在游戏的各个方面,从绘制棋盘背景图案到创建和添加贪吃蛇。

小结

从第一篇文章我们现在已经有一个背景,从这篇文章,我们有代码去绘制和移动贪吃蛇。但是尽管有了逻辑,这里依然没有实际的移动或者一个实际的蛇在游戏区域,因为我们没有调用这些方法中的任何一个。

蛇的移动的调用行为必须来自一个重复的源,因为在游戏运行的时候贪吃蛇应该不断地移动——在WPF中,我们有DispatcherTimer类将能够帮助我们做到这一点,贪吃蛇持续的移动,用一个定时器,将会是下一篇的主题。


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!