This article has been localized into Czech 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!
Vytvoření a přesun hada
V posledním článku jsme vytvořili pěknou oblast pro hada v naší implementaci SnakeWPF, aby se mohl pohybovat. Nyní je čas vytvořit samotného hada a pak jej nechat pohybovat se po oblasti. Opět použijeme třídu WPF Rectangle k vytvoření hada určité délky, přičemž každý prvek bude mít stejnou šířku a výšku jako čtverce na pozadí, nebo jak to nazýváme: konstantu SnakeSquareSize!
Vytvoření hada
Had bude nakreslen metodou nazvanou DrawSnake() - metoda je ve skutečnosti poměrně jednoduchá, ale vyžaduje si docela dost dalších věcí, včetně nové třídy nazvané SnakePart, jakož i některé další pole v třídě Window. Začněme třídou SnakePart, kterou byste obvykle definovali v novém souboru (například 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; }
}
}
Tato jednoduchá třída bude obsahovat informace o každé části hada: Kde je prvek umístěn v naší herní oblasti, který UIElement (v našem případě Rectangle) reprezentuje část a je tato část hlavou hada nebo ne? Všechno to později využijeme, ale nejprve, uvnitř naší třídy Window, potřebujeme definovat několik polí, která budou použita v naší metodě DrawSnake() (a později i v dalších metodách):
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>();
......
Definujeme dva SolidColorBrush, jeden pro tělo a jeden pro hlavu. Dále definujeme List<SnakePart>, který bude uchovávat odkaz na všechny části hada. S tím na místě nyní můžeme implementovat naši metodu 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);
}
}
}
Jak vidíte, tato metoda není zvlášť složitá: Projdeme seznam snakeParts a pro každou část zkontrolujeme, zda byl pro tuto část určen UIElement - pokud ne, vytvoříme jej (reprezentovaný obdélníkem) a přidáme jej do herní oblasti, zatímco si na vlastnosti UiElement instance SnakePart uložíme odkaz na něj. Všimněte si, jak používáme vlastnost Position instance SnakePart k umístění skutečného prvku uvnitř plátna GameArea.
Triky zde samozřejmě spočívá v tom, že skutečné části hada budou definovány jinde, což nám umožní přidat k hadovi jednu nebo více částí, dát jim požadovanou pozici a poté nechat metodu DrawSnake() udělat za nás skutečnou práci. Uděláme to jako součást stejného procesu používaného k pohybu hada.
Pohyb hada
Abychom mohli něco zadat metodě DrawSnake(), potřebujeme naplnit seznam snakeParts. Tento seznam neustále slouží jako základ, kde kreslit každý prvek hada, takže ho také budeme používat k vytvoření pohybu pro hada. Proces pohybu hada v zásadě spočívá v přidání nového prvku směrem, kterým se had aktuálně pohybuje, a poté odstranění poslední části hada. To vytvoří dojem, že skutečně pohybujeme každým prvkem, ale ve skutečnosti jen přidáváme nové, zatímco staré odstraňujeme.
Takže budeme potřebovat metodu MoveSnake(), kterou vám za chvíli ukážu, ale nejprve musíme přidat něco navíc na začátek definice naší třídy 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;
......
Přidali jsme novou enumeraci s názvem SnakeDirection, která by měla být poměrně samovysvětlující. Pro to máme soukromé pole pro uchování aktuálního směru (snakeDirection) a poté máme celočíselnou proměnnou pro uchování požadované délky hada (snakeLength). S tím na místě jsme připraveni implementovat metodu MoveSnake(). Je trochu dlouhá, takže jsem přidal komentáře uvnitř kódu k jednotlivým důležitým částem:
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();
}
S tímto na místě máme nyní veškerou logiku potřebnou k vytvoření pohybu hada. Všimněte si, jak neustále používáme konstantu SnakeSquareSize ve všech aspektech hry, od kreslení vzoru šachovnice na pozadí až po vytváření a přidávání k hadu.
Shrnutí
Od prvního článku máme nyní pozadí a z tohoto článku máme kód pro kreslení a pohyb hada. Ale i s touto logikou na místě, stále zde není žádný skutečný pohyb nebo dokonce skutečný had na herní ploše, protože jsme tyto metody ještě nevolali.
Výzva k akci pro pohyb hada musí pocházet z opakujícího se zdroje, protože had by měl být neustále v pohybu, dokud hra běží - ve WPF máme třídu DispatcherTimer, která nám s tím právě pomůže. Neustálý pohyb hada pomocí časovače bude předmětem dalšího článku.