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!

Kollisions Erkennung

Nachdem wir das Spielfeld, das Futter und die Schlange sowie die kontinuierliche Bewegung der Schlange implementiert haben, brauchen wir nur noch eine letzte Sache, um dieses Erscheinungsbild und Verhalten wie ein tatsächliches Spiel zu gestalten: Kollisionserkennung. Das Konzept basiert darauf, ständig zu überprüfen, ob unsere Schlange gerade etwas getroffen hat und wird derzeit für zwei Zwecke benötigt: Um zu sehen, ob die Schlange gerade etwas gegessen hat oder ob sie auf ein Hindernis (die Wand oder ihren eigenen Schwanz) gestoßen ist.

Die DoCollisionCheck() Methode

Die Kollisionserkennung wird in einer Methode mit dem Namen DoCollisionCheck() durchgeführt. Daher müssen wir dies implementieren. So sollte es aktuell aussehen:

private void DoCollisionCheck()
{
    SnakePart snakeHead = snakeParts[snakeParts.Count - 1];
   
    if((snakeHead.Position.X == Canvas.GetLeft(snakeFood)) && (snakeHead.Position.Y == Canvas.GetTop(snakeFood)))
    {        
EatSnakeFood();
return;
    }

    if((snakeHead.Position.Y < 0) || (snakeHead.Position.Y >= GameArea.ActualHeight) ||
(snakeHead.Position.X < 0) || (snakeHead.Position.X >= GameArea.ActualWidth))
    {
EndGame();
    }

    foreach(SnakePart snakeBodyPart in snakeParts.Take(snakeParts.Count - 1))
    {
if((snakeHead.Position.X == snakeBodyPart.Position.X) && (snakeHead.Position.Y == snakeBodyPart.Position.Y))
    EndGame();
    }
}

Wie versprochen führen wir zwei Überprüfungen durch: Zuerst prüfen wir, ob die aktuelle Position des Schlangenkopfs mit der Position des aktuellen Nahrungsstücks übereinstimmt. In diesem Fall rufen wir die EatSnakeFood() -Methode auf (dazu später mehr). Wir prüfen dann, ob die Position des Schlangenkopfes die Grenzen der GameArea überschreitet, um festzustellen, ob sich die Schlange auf dem Weg aus einer der Grenzen befindet. Wenn ja, rufen wir die EndGame() -Methode auf. Schließlich prüfen wir, ob der Kopf der Schlange mit einer der Körperteilpositionen übereinstimmt. Wenn dies der Fall ist, kollidiert die Schlange nur mit ihrem eigenen Schwanz, wodurch auch das Spiel mit einem Aufruf von EndGame() endet.

Die EatSnakeFood() Methode

Die EatSnakeFood() -Methode ist für einige Dinge verantwortlich, denn sobald die Schlange das aktuelle Futterstück isst, müssen wir an einem neuen Ort ein neues hinzufügen sowie den Score, die Länge und die aktuelle Spielgeschwindigkeit der Schlange anpassen . Für den Score müssen wir eine neue lokale Variable mit dem Namen currentScore deklarieren:

public partial class SnakeWPFSample : Window  
{  
    ....  
    private int snakeLength;  
    private int currentScore = 0;  
    ....

Fügen Sie dazu die EatSnakeFood() -Methode hinzu:

private void EatSnakeFood()
{
    snakeLength++;
    currentScore++;
    int timerInterval = Math.Max(SnakeSpeedThreshold, (int)gameTickTimer.Interval.TotalMilliseconds - (currentScore * 2));
    gameTickTimer.Interval = TimeSpan.FromMilliseconds(timerInterval);    
    GameArea.Children.Remove(snakeFood);
    DrawSnakeFood();
    UpdateGameStatus();
}

Wie bereits erwähnt, geschieht hier Folgendes:

  • Wir erhöhen die Variablen snakeLength und currentScore um eins, um die Tatsache widerzuspiegeln, dass die Schlange gerade ein Stück Futter gefangen hat.
  • Wir passen das Intervall von gameTickTimer nach folgender Regel an: Der aktuelle Wert wird mit 2 multipliziert und dann vom aktuellen Intervall (Geschwindigkeit) abgezogen. Dadurch steigt die Geschwindigkeit mit der Länge der Schlange exponentiell an, was das Spiel zunehmend schwieriger macht. Wir haben zuvor mit der SnakeSpeedThreshold-Konstante eine untere Grenze für die Geschwindigkeit definiert, was bedeutet, dass die Spielgeschwindigkeit niemals unter ein Intervall von 100 ms fällt.
  • Wir entfernen dann das gerade von der Schlange konsumierte Futterstück und rufen dann die DrawSnakeFood() -Methode auf, mit der ein neues Futterstück an einem neuen Ort hinzugefügt wird.
  • Schließlich rufen wir die UpdateGameStatus() -Methode auf, die so aussieht:
private void UpdateGameStatus()
{
    this.Title = "SnakeWPF - Score: " + currentScore + " - Game speed: " + gameTickTimer.Interval.TotalMilliseconds;
}

Diese Methode aktualisiert einfach die Title -Eigenschaft des Fensters, um den aktuellen Punktestand und die aktuelle Spielgeschwindigkeit widerzuspiegeln. Dies ist eine einfache Möglichkeit, den aktuellen Status anzuzeigen, der später bei Bedarf problemlos erweitert werden kann.

Die EndGame() Methode

Wir brauchen auch ein bisschen Code, um herauszufinden, wann das Spiel enden soll. Dies machen wir mit der EndGame() -Methode, die derzeit von der DoCollisionCheck() -Methode aufgerufen wird. Wie Sie sehen, ist es derzeit sehr einfach:

private void EndGame()
{
    gameTickTimer.IsEnabled = false;
    MessageBox.Show("Oooops, you died!\n\nTo start a new game, just press the Space bar...", "SnakeWPF");
}

Wir zeigen dem Benutzer nicht nur eine Nachricht über das unglückliche Ableben unserer geliebten Schlange, sondern stoppen auch einfach den gameTickTimer. Da dieser Timer alles veranlasst, was im Spiel passiert, stoppen auch alle Bewegungen und das Zeichnen, sobald er gestoppt wird.

Letzte Einstellungen

Wir sind jetzt fast fertig mit dem ersten Entwurf eines voll funktionsfähigen Snake-Spiels - in der Tat müssen wir nur zwei geringfügige Anpassungen vornehmen. Zuerst müssen wir sicherstellen, dass DoCollisionCheck() aufgerufen wird. Dies sollte als letzte Aktion in der zuvor implementierten MoveSnake() -Methode geschehen:

private void MoveSnake()
{
    .....
   
    //... and then have it drawn!
    DrawSnake();
    // Finally: Check if it just hit something!
    DoCollisionCheck();    
}

Sobald sich die Schlange bewegt hat, wird nun eine Kollisionserkennung durchgeführt! Erinnern Sie sich, wie ich Ihnen sagte, dass wir eine einfache Variante der StartNewGame() -Methode implementiert haben? Wir müssen es ein wenig erweitern, um sicherzustellen, dass wir die Punktzahl jedes Mal, wenn das Spiel (neu) gestartet wird, zurücksetzen, wie wir es aus ein paar anderen Gründen tun. Ersetzen Sie daher die Methode StartNewGame() durch diese leicht erweiterte Version:

private void StartNewGame()
{
    // Remove potential dead snake parts and leftover food...
    foreach(SnakePart snakeBodyPart in snakeParts)
    {
if(snakeBodyPart.UiElement != null)
    GameArea.Children.Remove(snakeBodyPart.UiElement);
    }
    snakeParts.Clear();
    if(snakeFood != null)
GameArea.Children.Remove(snakeFood);

    // Reset stuff
    currentScore = 0;
    snakeLength = SnakeStartLength;
    snakeDirection = SnakeDirection.Right;
    snakeParts.Add(new SnakePart() { Position = new Point(SnakeSquareSize * 5, SnakeSquareSize * 5) });
    gameTickTimer.Interval = TimeSpan.FromMilliseconds(SnakeStartSpeed);

    // Draw the snake again and some new food...
    DrawSnake();
    DrawSnakeFood();

    // Update status
    UpdateGameStatus();

    // Go!    
    gameTickTimer.IsEnabled = true;
}

Wenn ein neues Spiel gestartet wird, geschieht Folgendes:

  • Da dies möglicherweise nicht das erste Spiel ist, müssen wir sicherstellen, dass alle potenziellen Reste eines vorherigen Spiels entfernt werden: Dazu gehören alle vorhandenen Teile der Schlange sowie Reste von Futter.
  • Wir müssen auch einige der Variablen auf ihre ursprünglichen Einstellungen zurücksetzen, z.B.: die Punktzahl, die Länge, die Richtung und die Geschwindigkeit des Timers Wir fügen auch den anfänglichen Schlangenkopf hinzu (der automatisch durch die MoveSnake() - Methode erweitert wird).
  • Anschließend rufen wir die Methoden DrawSnake() und DrawSnakeFood() auf, um visuell darzustellen, dass ein neues Spiel gestartet wurde.
  • Das nennen wir die UpdateGameStatus() Methode.
  • Und zum Schluss können wir den gameTickTimer starten - er beginnt sofort zu ticken und setzt das Spiel im Grunde genommen in Gang.

Zusammenfassung

Wenn Sie diese Artikelserie vollständig durchgearbeitet haben: Glückwunsch - Sie haben gerade Ihr erstes WPF-Spiel erstellt! Genießen Sie all Ihre harte Arbeit, indem Sie Ihr Projekt ausführen, die Leertaste drücken und mit dem Spielen beginnen - auch bei dieser sehr einfachen Implementierung ist Snake ein unterhaltsames und süchtig machendes Spiel!


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!