TOC

This article has been localized into Vietnamese by the community.

Tạo trò chơi: 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!

Phát hiện va chạm

Bây giờ chúng tôi đã triển khai khu vực trò chơi, thức ăn và con rắn, cũng như chuyển động liên tục của con rắn, chúng tôi chỉ cần một điều cuối cùng để làm cho giao diện này hoạt động giống như một trò chơi thực tế: Phát hiện va chạm(Collision detection). Khái niệm này phát triển xung quanh việc liên tục kiểm tra xem Rắn của chúng ta có đánh thứ gì không và hiện tại chúng ta cần nó cho hai mục đích: Để xem Rắn chỉ ăn một số thức ăn hay nếu nó va vào chướng ngại vật (tường hoặc đuôi của chính nó).

Phương thức DoCollisionCheck()

Việc phát hiện va chạm sẽ được thực hiện trong một phương thức gọi là DoCollisionCheck(), vì vậy chúng ta cần tạo nó. Đây là giao diện hiện tại:

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

Như đã hứa, chúng tôi thực hiện hai kiểm tra: Đầu tiên chúng tôi xem vị trí hiện tại của đầu rắn có khớp với vị trí của miếng thức ăn hiện tại không. Nếu có, chúng ta gọi phương thức EatSnakeFood()(sẽ có về sau). Sau đó, chúng tôi sẽ kiểm tra xem vị trí của đầu rắn có vượt quá ranh giới của GameArea hay không, để xem con rắn có đang trên đường ra khỏi một trong các biên giới hay không. Nếu có, chúng ta gọi phương thức EndGame(). Cuối cùng, chúng tôi kiểm tra xem đầu của con rắn có khớp với một trong các vị trí của bộ phận cơ thể hay không - nếu có, con rắn chỉ va chạm với đuôi của chính nó, điều này cũng sẽ kết thúc trò chơi, bằng một lời kêu gọi EndGame().

Phương thức EatSnakeFood()

Phương thức EatSnakeFood() chịu trách nhiệm thực hiện một số điều, bởi vì ngay khi con rắn ăn miếng thức ăn hiện tại, chúng ta cần thêm một cái mới, ở một vị trí mới, cũng như điều chỉnh điểm số, độ dài của con rắn và tốc độ trò chơi hiện tại. Để ghi điểm, chúng ta cần khai báo một biến cục bộ mới gọi là currentScore:

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

Với vị trí đó, hãy thêm phương thức 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();
}

Như đã đề cập, một số điều xảy ra ở đây:

  • Chúng tôi tăng các biến số snakeLength và biến số currentScore lên một để phản ánh thực tế rằng con rắn vừa bắt được một miếng thức ăn.
  • Chúng tôi điều chỉnh Interval củagameTickTimer, sử dụng quy tắc sau: CurrentScore được nhân với 2 và sau đó trừ đi khoảng thời gian hiện tại (tốc độ). Điều này sẽ khiến tốc độ tăng theo cấp số nhân cùng với chiều dài của con rắn, khiến trò chơi ngày càng khó khăn. Trước đây chúng tôi đã xác định ranh giới thấp hơn cho tốc độ, với hằng số SnakeSpeedThreshold, có nghĩa là tốc độ trò chơi không bao giờ giảm xuống dưới một khoảng 100 ms.
  • Sau đó, chúng tôi loại bỏ miếng thức ăn vừa ăn của con rắn và sau đó chúng tôi gọi phương thức DrawSnakeFood() sẽ thêm một miếng thức ăn mới vào một vị trí mới.
  • Cuối cùng, chúng ta gọi phương thức UpdateGameStatus(), trông giống như sau:
private void UpdateGameStatus()
{
    this.Title = "SnakeWPF - Score: " + currentScore + " - Game speed: " + gameTickTimer.Interval.TotalMilliseconds;
}

Phương pháp này sẽ chỉ cập nhật Title của Window để phản ánh điểm số và tốc độ trò chơi hiện tại. Đây là một cách dễ dàng để hiển thị trạng thái hiện tại, có thể dễ dàng được mở rộng sau này nếu muốn.

Phương thức EndGame()

Chúng ta cũng cần một chút mã để thực thi khi trò chơi kết thúc. Chúng tôi sẽ làm điều này từ phương thức EndGame(), hiện được gọi từ phương thức DoCollisionCheck(). Như bạn có thể thấy, hiện tại nó rất đơn giản:

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

Bên cạnh việc hiển thị một thông báo cho người dùng về sự ra đi đáng tiếc của con rắn yêu quý của chúng tôi, chúng tôi chỉ cần dừng trò gameTickTimer. Vì timer này là nguyên nhân khiến tất cả mọi thứ xảy ra trong trò chơi, ngay khi nó dừng lại, mọi chuyển động và vẽ cũng dừng lại.

Điều chỉnh cuối cùng

Bây giờ chúng ta gần như đã sẵn sàng với bản nháp đầu tiên của trò chơi Snake đầy đủ chức năng - thực tế, chúng ta chỉ cần thực hiện hai điều chỉnh nhỏ. Trước tiên, chúng ta cần đảm bảo rằng DoCollisionCheck() được gọi - điều này sẽ xảy ra khi hành động cuối cùng được thực hiện trong phương thức MoveSnake() mà chúng ta đã thực hiện trước đó:

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

Bây giờ phát hiện va chạm được thực hiện ngay khi con rắn di chuyển! Bây giờ hãy nhớ làm thế nào tôi nói với bạn rằng chúng tôi đã triển khai một biến thể đơn giản của phương thức StartNewGame()? Chúng tôi cần mở rộng nó một chút, để đảm bảo rằng chúng tôi đặt lại điểm số mỗi khi trò chơi bắt đầu. Vì vậy, thay thế phương thức StartNewGame() bằng phiên bản hơi mở rộng này:

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

Khi một trò chơi mới bắt đầu, những điều sau đây sẽ xảy ra:

  • Vì đây có thể không phải là trò chơi đầu tiên, chúng tôi cần đảm bảo rằng mọi phần thức ăn thừa tiềm năng từ trò chơi trước đã được loại bỏ: Điều này bao gồm tất cả các bộ phận hiện có của con rắn, cũng như thức ăn thừa.
  • Chúng ta cũng cần đặt lại một số biến về cài đặt ban đầu của chúng, như score, length, directionspeed của bộ đếm thời gian. Chúng tôi cũng thêm đầu rắn ban đầu (sẽ được tự động mở rộng bằng phương thức MoveSnake()).
  • Sau đó, chúng tôi gọi các phương thức DrawSnake()DrawSnakeFood() để phản ánh trực quan rằng một trò chơi mới đã được bắt đầu.
  • Chúng tôi gọi phương thức UpdateGameStatus().
  • Và cuối cùng, chúng tôi đã sẵn sàng để bắt đầu trò gameTickTimer - nó sẽ ngay lập tức bắt đầu tích tắc, về cơ bản khiến trò chơi chuyển động.

Tổng kết

Nếu bạn đã thực hiện tất cả các cách thông qua loạt bài viết này: xin chúc mừng - bạn vừa xây dựng trò chơi WPF đầu tiên của mình! Tận hưởng tất cả công sức lao động của bạn bằng cách chạy dự án của bạn, nhấn phím Space và bắt đầu chơi - ngay cả trong cách thực hiện rất đơn giản này, Snake là một trò chơi thú vị và gây nghiện!


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!