TOC

This article is currently in the process of being translated into Russian (~98% 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!

Adding food for the Snake

На этом этапе серии статей о SnakeWPF у нас есть фон шахматной доски в качестве игровой области, а также симпатичная зеленая змея, движущаяся по ней. Однако, как уже упоминалось во введении, цель игры состоит в том, чтобы змея съела немного еды - в нашей версии это будут красные яблоки!

Теперь пришло время добавить немного еды в игровую зону. Мы сделаем это путем случайного добавления красного круга где-нибудь в границах GameArea Canvas, но нам нужно убедиться, что мы не размещаем его в одном из квадратов, уже занятых постоянно растущей змеей. Другими словами, одним из наиболее важных аспектов размещения яблока в игровой зоне является код, который определяет следующую позицию. Вот код, который мы будем использовать для этого:

private Point GetNextFoodPosition()
{
    int maxX = (int)(GameArea.ActualWidth / SnakeSquareSize);
    int maxY = (int)(GameArea.ActualHeight / SnakeSquareSize);
    int foodX = rnd.Next(0, maxX) * SnakeSquareSize;
    int foodY = rnd.Next(0, maxY) * SnakeSquareSize;

    foreach(SnakePart snakePart in snakeParts)
    {
if((snakePart.Position.X == foodX) && (snakePart.Position.Y == foodY))
    return GetNextFoodPosition();
    }

    return new Point(foodX, foodY);
}

Не забудьте также добавить эту строку в верхней части объявления класса Window вместе с остальными полями / константами:

public partial class SnakeWPFSample : Window
{
    private Random rnd = new Random();
    ......

Итак, чтобы быстро объяснить код: мы снова используем константу SnakeSquareSize, чтобы помочь нам вычислить следующую позицию для нашей еды в сочетании с классом Random, который даст нам случайную позицию X и Y. Получив его, мы проходим через все текущие части змеи и проверяем, соответствует ли их положение координатам X и Y, которые мы только что создали - если они это делают, это означает, что мы попали в область, в настоящее время занятую змеей, после чего запрашивем новую позицию, просто вызывая метод снова (делая это рекурсивным методом).

Это также означает, что этот метод может вызывать себя неограниченное количество раз и, теоретически, приводит к бесконечному циклу. Мы могли бы проверить это, но в этом нет необходимости, поскольку для этого потребуется, чтобы змея была настолько длинной, что не останется пустого места - ставлю на то, что игра закончится до того, как это произойдет.

Сделав всё это, мы готовы добавить код, который будет добавлять еду в только что рассчитанную позицию - мы будем делать это из метода DrawSnakeFood(). Благодаря всей работе, уже выполненной GetNextFoodPosition(), это довольно просто, но сначала обязательно объявите поле, используемое для сохранения ссылки на еду, а также SolidColorBrush, используемый для рисования яблока, вместе с другими объявлениями полей / констант:

public partial class SnakeWPFSample : Window  
{  
    private UIElement snakeFood = null;  
    private SolidColorBrush foodBrush = Brushes.Red;
    ......

Вот реализация метода:

private void DrawSnakeFood()
{
    Point foodPosition = GetNextFoodPosition();
    snakeFood = new Ellipse()
    {
Width = SnakeSquareSize,
Height = SnakeSquareSize,
Fill = foodBrush
    };
    GameArea.Children.Add(snakeFood);
    Canvas.SetTop(snakeFood, foodPosition.Y);
    Canvas.SetLeft(snakeFood, foodPosition.X);
}

Как и было обещано, все очень просто - как только у нас есть позиция, мы просто создаем новый экземпляр Ellipse и снова используем константу SnakeSquareSize, чтобы убедиться, что она имеет тот же размер, что и фоновые плитки, а также каждая часть змеи. Мы сохраняем ссылку на экземпляр Ellipse в поле snakeFood, потому что она понадобится нам позже.

Зная это, нам просто нужно вызвать метод DrawSnakeFood (), чтобы увидеть результат нашей работы. Это будет сделано в двух ситуациях: в начале игры и когда змея «ест» пищу (подробнее об этом позже). А сейчас давайте добавим к нему вызов в нашем методе StartNewGame():

private void StartNewGame()
{
    snakeLength = SnakeStartLength;
    snakeDirection = SnakeDirection.Right;
    snakeParts.Add(new SnakePart() { Position = new Point(SnakeSquareSize * 5, SnakeSquareSize * 5) });
    gameTickTimer.Interval = TimeSpan.FromMilliseconds(SnakeStartSpeed);

    // Draw the snake and the snake food
    DrawSnake();
    DrawSnakeFood();

    // Go!    
    gameTickTimer.IsEnabled = true;
}

Готово! Если вы запустите игру, вы должны увидеть, что у змеи наконец-то есть какая-то пища:

Не так уж и много труда требуется, чтобы разместить красную точку на экране, верно?

Подведём итоги

В этой статье мы наконец добавили немного еды на стол, чтобы змея могла ее поймать, но предстоит еще проделать большую работу: нам нужно научиться контролировать змею, а также знать, когда она обо что то ударится (стена, свой хвост или еда). Подробнее об этом в следующей статье.

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!