TOC

This article is currently in the process of being translated into Portuguese (~99% done).

Criando um jogo: 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!

Collision Detection

Agora que implementamos a área do jogo, o alimento e a cobra, bem como o movimento contínuo da cobra, precisamos apenas de uma última coisa para fazer com que isto pareça e se comporte como um verdadeiro jogo: Detecção de colisões. O conceito se desenvolve a partir de testar constantemente se nossa Cobra acabou de bater em alguma coisa e precisamos disso por dois motivos: Verificar se a Cobra acabou de comer algo ou se bateu em um obstáculo (a parede ou a própria cauda).

O Método DoCollisionCheck()

A detecção de colisões será realizada em um método chamado DoCollisionCheck(), então precisamos implementá-lo. Aqui está como deveria ser:

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

Como prometido, fazemos duas verificações: Primeiro vemos se a posição atual da cabeça da cobra é igual à posição do pedaço de comida atual. Se sim, chamamos o método EatSnakeFood() (mais sobre isso depois). Depois verificamos se a posição da cabeça da cobra ultrapassa os limites da GameArea (área do jogo), para ver se a cobra está a ponto de sair por um dos lados. Se estiver, chamamos o método EndGame(). Finalmente, verificamos se a cabeça da cobra é igual à posição de uma das partes do corpo - se sim, a cobra acaba de bater na própria cauda, o que fará com que o jogo também acabe, com uma chamada a EndGame().

O método EatSnakeFood()

O método EatSnakeFood() é responsável por fazer um par de coisas, porque tão logo a cobra coma o pedaço de alimento atual, precisamos adicionar um novo pedaço, em uma nova posição, bem como atualizar o placar, o comprimento da cobra e a velocidade atual do jogo. Para o placar, precisamos delcarar uma nova variável local chamada currentScore:

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

Com isso no lugar, adicione o método EatSnakeFood():

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

Como mencionado, várias coisas acontecem aqui:

  • Incrementamos as variáveis snakeLength e currentScore em uma unidade para refletir o fato de que a cobra acaba de pegar um pedaço de alimento.
  • Ajustamos o Interval de gameTickTimer, usando a seguinte regra: currentScore é multiplicado por 2 e depois subtraído do intervalo atual (velocidade). Isto fará com que a velocidade aumente exponencialmente junto com o comprimento da cobra, fazendo com que jogo fique cada vez mais difícil. Havíamos definido anteriormente um limite inferior para a velocidade, com a constante SnakeSpeedThreshold, garantindo que a velocidade do jogo nunca seja inferior a um intervalo de 100 ms.
  • Depois removemos o pedaço de alimento que acaba de ser comido pela cobra e então chamamos o método DrawSnakeFood() que adicionará um novo pedaço de alimento em uma nova posição.
  • Finalmente, chamamos o método UpdateGameStatus(), que é semelhante a isto:
private void UpdateGameStatus()
{
    this.Title = "SnakeWPF - Score: " + currentScore + " - Game speed: " + gameTickTimer.Interval.TotalMilliseconds;
}

Este método simplesmente atualizará a propriedade Title da Window para mostrar o placar atual e a velocidade do jogo. Esta é uma maneira fácil de mostrar o estado atual, e que pode ser expandida mais tarde caso desejado.

O método EndGame()

Também precisamos de uma pequena quantidade de código para executar quando o jogo deva acabar. Faremos isso a partir do método EndGame(), que é atualmente chamado pelo método DoCollisionCheck(). Como você pode ver, é mesmo bem simples:

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

Além de mostrar uma mensagem para o usuário sobre o infeliz falecimento de nossa amada cobra, simplesmente paramos o gameTickTimer. Já que este temporizador é o que faz todas as coisas acontecerem no jogo, tão logo ele para, todo o movimento e desenho também param.

Ajustes finais

Agora estamos quase prontos com uma primeira versão completamente funcional do jogo da Cobra - de fato, precisamos apenas fazer dois pequenos ajustes. Primeiro, precisamos garantir que DoCollisionCheck() é chamado - isto deve acontecer como a última ação realizada no método MoveSnake(), que implementamos anteriormente:

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

Agora a detecção de colisões é feita tão logo a cobra se move! Agora, você lembra que eu disse que implementamos uma versão simples do método StartNewGame()? Precisamos melhorá-lo um pouco, para garantir que reiniciamos o placar toda vez que o jogo (re)começa, bem como tratar de um par de outras coisas. Então, substitua o método StartNewGame() por esta versão levemente expandida:

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

Quando um novo jogo começa, agora as seguintes coisas acontecem:

  • Já que este pode não ser o primeiro jogo, precisamos garantir de que quaisquer restos de um jogo anterior sejam removidos: Isto inclui todas as partes existentes da cobra, bem como resto de alimento.
  • Também precisamos restaurar algumas das variáveis para os seus valores iniciais, entre elas o placar, o comprimento, a direção e a velocidade do temporizador. Também precisamos adicionar a cabeça da cobra (que será automaticamente expandida pelo método MoveSnake() ).
  • Então chamamos os métodos DrawSnake() e DrawSnakeFood()para mostrar visualmente que um novo jogo começou.
  • Logo após chamamos o método UpdateGameStatus().
  • E finalmente estamos prontos para iniciar o gameTickTimer - imediatamente começará a marcar tempo, basicamente colocando o jogo em movimento.

Resumo

Se você conseguiu passar por toda esta série de artigos: parabéns - você acaba de construir seu primeiro jogo WPF!. Aproveite todo o trabalho duro que fez executando o projeto, acionando a tecla Espaço e comece a jogar - até nesta simples implementação, o jogo da Cobra é divertido e viciante!


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!