TOC

This article has been localized into German by the community.

Erstellen eines Spiels: SnakeWPF:
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!

Erstellung der Schlange und ihrer Bewegungen

Im letzten Abschnitt haben wir eine schöne Umgebung für die Schlange in unserer SnakeWPF Implementation gebaut, in der sie sich bewegen kann. Da das erledigt ist, wird es Zeit, die eigentliche Schlange zu erstellen und diese sich durch ihr Areal bewegen zu lassen. Und ein weiteres Mal verwenden wir die WPF Rectangle-Klasse, diesmal, um eine Schlange mit einer bestimmten Länge zu erstellen. Jedes Segment soll dabei die selbe Breite und Höhe haben, wie die Quadrate im Hintergrund. Wie wir es nennen: Die SnakeSquareSize-Konstante!

Erstellung der Schlange

Wir werden die Schlange mit einer Methode namens DrawSnake() zeichnen. Diese Methode ist eigentlich ganz einfach, benötigt aber eine handvoll zusätzliche Handgriffe, darunter eine neue Klasse namens "SnakePart", sowie ein paar zusätzliche Felder in der Window-Klasse. Lass uns mit der SnakePart-Klasse anfangen, die normalerweise in einer neuen Datei definiert wird (z. B. 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; }
    }
}

Diese einfache Klasse wird Informationen über jeden Teil der Schlange enthalten: Wo in unserem Spielbereich ist dieses Segment positioniert, durch welches "UIElement" wird es dargestellt (in unserem Fall ein Rectangle-Objekt) und ist das Segment der Kopf oder nicht? Das werden wir später alles noch verwenden, zuerst aber müssen wir in der Window-Klasse ein paar Felder zur Verwendung in unserer DrawSnake()-Methode definieren (weitere Methoden werden diesem Beispiel folgen).

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

Für Kopf und Körper definieren wir jeweils eine SolidColorBrush. Außerdem definieren wir eine List<SnakePart>, welche die Referenzen auf jedes einzelne Körpersegment enthalten wird. Ist das getan, können wir endlich unsere DrawSnake()-Methode implementieren:

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

Wie man sehen kann ist diese Methode nicht sonderlich kompliziert: Wir gehen die snakeParts-Liste immer wieder durch und jedes Mal, wird überprüft, ob ein UIElement für dieses Segment festgelegt wurde - wenn nicht, so erstellen wir es (im Form eines Rectangle-Objekts) und fügen es dem Spielbereich hinzu. Dabei wird eine Referenz auf der UiElement-Eigenschaft der SnakePart-Instanz gespeichert. Anzumerken ist, wie wir die Position-Eigenschaft der SnakePart-Instanz nutzen um das tatsächliche Element auf dem GameArea Canvas-Objekt zu positionieren.

Der Trick an der Sache ist natürlich, dass die Teile letztendlich woanders definiert werden, was uns das hinzufügen eines oder mehrerer Schlangensegmente an der gewünschten Position erlaubt während die DrawSnake()-Methode dann die eigentliche Arbeit übernimmt. Das wird ein Teil des Prozesses, den wir auch zur Bewegung der Schlange verwenden.

Die Schlange bewegen

Um etwas an die DrawSnake()-Methode zu übergeben, muss die snakeParts-Liste befüllt werden. Diese Liste wird jederzeit als Basis dienen, jedes Segment der Schlange an den richtigen Ort zu zeichnen, deshalb werden wir es auch nutzen um die Bewegungen der Schlange zu erzeugen. Dieser Prozess der Bewegung der Schlange besteht im wesentlichen daraus, ein neues Segment am vorderen Ende hinzuzufügen (die Richtung, in die sich die Schlange gerade bewegt) und das letzte Segment der Schlange zu löschen. Dadurch sieht es so aus, als ob wir alle Elemente verschieben würden, wobei wir eigentlich ständig neue erzeugen und alte löschen.

Also brauchen wir jetzt eine MoveSnake()-Methode, die ich euch in Kürze zeigen werde. Doch zuerst müssen wir der Definition der Window-Klasse noch etwas hinzufügen:

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

Wir haben zusätzlich einen Aufzählungswert namens SnakeDirection hinzugefügt, der recht selbsterklärend sein dürfte. Dazu nutzen wir ein "private" Feld für die aktuelle Ausrichtung (snakeDirection) und eine zusätzliche Integer-Variable um die gewünschte Länge der Schlange zu speichern (snakeLength). Ist das geschafft, sind wir so weit, die MoveSnake()-Methode zu implementieren. Da sie ein wenig länger geworden ist, habe ich die wichtigsten Teile mit Kommentaren versehen.

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

Damit haben wir jetzt die gesamte Logik zum Bewegen der Schlange erstellt. Beachte, dass wir in allen Bereichen des Spiels die SnakeSquareSize-Konstante verwenden. Vom Zeichnen des Hintergrundes bis zur Erzeugung und Verlängern der Schlange.

Zusammenfassung

Aus dem ersten Artikel haben wir jetzt einen Hintergrund und aus diesem Artikel haben wir den Code um die Schlange zu zeichnen und zu bewegen. Aber selbst mit der eingebauten Logik bewegt sich erstmal noch nichts und eine Schlange gibt es im Spielbereich auch noch nicht, da wir bisher noch keine dieser Methoden aufgerufen haben.

Der Call-to-action für die Bewegung der Schlange muss von einer sich wiederholenden Quelle herrühren, da die Schlange sich ja unaufhörlich bewegen soll, während das Spiel läuft. In WPF gibt es dafür die DispatcherTimer-Klasse, für solche Fälle. Diese ständige Bewegung der Schlange mit Hilfe eines Timers wird Thema des nächsten Artikels sein.


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!