This article has been localized into Ukrainian by the community.
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!
Створення та переміщення Змії
У попередній статті ми створили гарну область для переміщення змії в нашій реалізації SnakeWPF. Після цього настав час створити саму змію та змусити її рухатися по цій області. Знову ж таки, ми використаємо клас 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; }
}
}Цей простий клас міститиме інформацію про кожну частину змії: де розташований елемент у нашій ігровій області, який UIElement (у нашому випадку прямокутник) представляє цю частину, і чи є це головою змії, чи ні? Ми використаємо все це пізніше, але спочатку, всередині нашого класу 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 для цієї частини - якщо ні, ми створюємо його (представленого прямокутником) і додаємо до ігрової області, зберігаючи посилання на нього у властивості UiElement екземпляра SnakePart. Зверніть увагу, як ми використовуємо властивість Position екземпляра SnakePart для позиціонування фактичного елемента всередині полотна GameArea.
Хитрість тут, звичайно, в тому, що фактичні частини змії будуть визначені в іншому місці, що дозволить нам додати одну або кілька частин до змії, надати їм потрібне положення, а потім використати метод DrawSnake() для виконання фактичної роботи за нас. Ми зробимо це як частину того ж процесу, який використовується для переміщення змії.
Переміщення змії
Щоб передати дані методу DrawSnake(), нам потрібно заповнити список snakeParts. Цей список постійно служить основою для того, де малювати кожен елемент змії, тому ми також використовуватимемо його для створення руху змії. Процес переміщення змії по суті полягає в додаванні до неї нового елемента в напрямку, в якому змія наразі рухається, а потім видаленні останньої частини змії. Це створить враження, що ми насправді переміщуємо кожен елемент, але насправді ми просто додаємо нові, видаляючи старі.
Отже, нам знадобиться метод 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, яке має бути досить зрозумілим. Для цього у нас є приватне поле для зберігання фактичного поточного напрямку (snakeDirection), а потім у нас є цілочисельна змінна для зберігання бажаної довжини змійки (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, який чудово підходить для такого завдання. Безперервний рух змійки за допомогою таймера буде розглянуто в наступній статті.