Làm Game Rắn Săn Mồi Bằng Javascript

Làm Game Rắn Săn Mồi Bằng Javascript

Chắc hẳn mọi người cũng đã quá quen thuộc với game rắn săn mồi. Bài viết hướng dẫn mọi người làm game này bằng Javascript và thư viện p5,js. Qua đó, bạn sẽ nắm vững hơn một số khái niệm trong Javascript cũng như biết cách dùng p5.js để làm đồ họa, animation trên web.

1. Giới thiệu thư viện p5.js

p5.js là một thư viện Javascript được dựa trên nền tảng Processing. Thư viện này giúp xử lý đồ họa, tương tác trên trang web dễ hơn, p5.js cung cấp đầy đủ các chức năng để vẽ animation lên trang web và một số thư viện để tương tác với các đối tượng trong HTML5 như text, input, video, webcam và âm thanh.

Để bắt đầu với p5.js thì bạn chỉ cần thêm thư viện vào thông qua thẻ <script>, một file html mẫu sẽ như sau:

<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.js"></script>
    <script src="game.js"></script>
  </head>
  <body>
  </body>
</html>

Trong p5.js thì có 2 function mà bạn chắc chắn sẽ sử dụng như sau:

  1. setup()Đây là function sẽ chạy ngay lập tức khi mở chương trình. Thường thì hay dùng để config cho chương trình.
  2. draw(): Function này sẽ chạy ngay sau thằng setup() ở trên. Đây là function chính của p5.js và sẽ được lặp đi lặp lại đến khi kết thúc chương trình.

Bạn có thể tìm hiểu sâu hơn về p5.js tại trang chủ này.

2. Làm game

2.1 Thiết kế giao diện và Hiển thị rắn

Phần giao diện khá đơn giản, sẽ gồm một lưới các ô vuông. Mỗi ô vuông sẽ hiển thị một phần thân con rắn, hoặc là mồi. Chúng ta tạo ra 4 file:

1. index.html

Đây là file html chứa giao diện của game:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Snake game</title>
</head>
<body>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.js"></script>
   <script src="config.js" charset="utf-8"></script>
   <script src="snake.js" charset="utf-8"></script>
   <script src="food.js" charset="utf-8"></script>
   <script src="game.js" charset="utf-8"></script>
</body>
</html>

2. config.js

Đây là file chứa các hằng số trong game (đội rộng, cao của màn hình, ...)

const GRID_SIZE = 30;

const WITDH = 600;
const HEIGHT = 600;

3. snake.js

Đây là Class chứa toàn bộ code để điều khiển con rắn.

class Snake {
   constructor(){
      this.head = createVector(0,0);
   }
   show() {
      noStroke();
      // Draw snake head
      fill(255);
      rect(this.head.x, this.head.y, GRID_SIZE, GRID_SIZE);
   }
}

Đầu tiên, ta tạo ra một con rắn với phần đầu ở tọa độ (0,0) trên màn hình.

Class này sẽ có một hàm show(), giúp hiển thị con rắn lên màn hình.

4. game.js

Đây là file chứa code của toàn bộ game.

let snake;

function setup() {
   createCanvas(WITDH, HEIGHT);
   newGame();
}

function draw() {
   background(0);
   drawSnake();
}

function drawSnake() {
   snake.show();
}

function newGame() {
   snake = new Snake();
}

Sau khi chạy code, bạn sẽ được một màn hình kết quả: Bạn sẽ thấy một ô vuông màu trắng được vẽ ở tọa độ (0,0), đó chính là đầu của con rắn, sang đến phần sau chúng ta sẽ xử lý đến phần chuyển động.

    2.2 Tạo chuyển động cho rắn

    Để xử lý chuyển động, ta sẽ tăng hoặc giảm tọa độ x,y của phần đầu rắn. Vậy ta sẽ có 4 trường hợp như sau:

    • Đi lên: y += 1.
    • Đi xuống: y -= 1.
    • Sang trái: x -= 1.
    • Sang phải: x += 1.

    Nhưng như vậy sẽ phải xử lý 4 trường hợp rất dài dòng. Vậy nên ta sẽ dùng một Vector khác, gọi là Vector vel dùng để xác định phương hướng đang đi.

    • Đi lên: vel = (0, 1);
    • Đi xuống: vel = (0, -1);
    • Sang trái: vel = (-1, 0);
    • Sang phải: vel = (-1, 0);

    Để thay đổi giá trị cho vel mỗi khi người chơi nhấn các phím mũi tên, ta sẽ viết thêm 1 hàm là keyPressed() trong file game.js

    function keyPressed() {
       if (keyCode == UP_ARROW && snake.vel.y != 1) {
          snake.vel.y = -1;
          snake.vel.x = 0;
       } else if (keyCode == DOWN_ARROW && snake.vel.y != -1) {
          snake.vel.y = 1;
          snake.vel.x = 0;
       }  else if (keyCode == LEFT_ARROW && snake.vel.x != 1) {
          snake.vel.y = 0;
          snake.vel.x = -1;
       } else if (keyCode == RIGHT_ARROW && snake.vel.x != -1) {
          snake.vel.y = 0;
          snake.vel.x = 1;
       }
    }

    Ta cũng sẽ viết thêm một hàm vào class Snake là update() để cập nhật lại vị trí của con rắn

       update(){
          this.head.x += this.vel.x * GRID_SIZE;
          this.head.y += this.vel.y * GRID_SIZE;
    
          this.head.x = (this.head.x + WITDH) % WITDH;
          this.head.y = (this.head.y + HEIGHT) % HEIGHT;
       }

    Hàm drawSnake() cũng sẽ được viết thêm để cập nhật lại vị trí

    function drawSnake() {
       // update every SNAKE_SPEED frame
       if(frameCount % SNAKE_SPEED == 0)
       {
          snake.update();
       }
       snake.show();
    }

    Chạy lại code và dùng các phím mũi tên để điều khiển, ta sẽ được kết quả như sau:

    2.3 Hiển thị thức ăn

    Đến phần này, ta sẽ hiển thị ra thức ăn để rắn có thể ăn. Tạo thêm một file food.js nữa để xử lý phần này

    class Food{
       constructor () {
          this.newFood();
       }
       newFood(){
          this.x = Math.floor(random(width));
          this.y = Math.floor(random(height));
    
          this.x = Math.floor(this.x / GRID_SIZE) * GRID_SIZE;
          this.y = Math.floor(this.y / GRID_SIZE) * GRID_SIZE;
       }
       show(){
          fill(255, 40, 0);
          rect(this.x, this.y, GRID_SIZE, GRID_SIZE);
       }
    }

    Sẽ có hàm newFood() để tạo lại tọa độ của thức ăn một cách ngẫu nhiên và một hàm show() để hiển thi lên màn hình game. Sau đó trong hàm drawSnake() chỉ cần gọi thêm 

    food.show();

    là đã hiển thị được thức ăn.

    2.4 Xử lý khi rắn ăn thức ăn

    Để biết được khi nào rắn đã ăn mồi, ta chỉ cần kiểm tra xem tọa độ của phần head có trùng với tọa độ của food không là được, đồng thời cũng tạo thêm 1 biến length ở bên snake - đây sẽ là chiều dài của con rắn, mỗi khi ăn mồi sẽ tăng thêm 1. Ta viết thêm vào file game.js như sau:

    function drawSnake() {
       // update every SNAKE_SPEED frame
       if(frameCount % SNAKE_SPEED == 0)
       {
          snake.update();
       }
    
       snake.show();
       food.show();
    
       // Handle when snake eat food
       if(snake.head.x == food.x && snake.head.y == food.y){
          eatFood();
       }
    }
    
    function eatFood() {
       snake.length++;
       food.newFood();
    }

    Ta sẽ có kết quả như sau:

    Như vậy là ta đã hoàn thành phần ăn thức ăn. Tiếp theo sẽ đến việc xử lý phần thân của con rắn, làm sao để mỗi khi ăn thức ăn thì nó sẽ dài ra.

    Chúng ta sẽ thêm đoạn code sau vào hàm update()show() của con rắn, đoạn code này sẽ update lại vị trí của phần thân rắn, dựa theo biến length.

       update(){
          this.body.push(createVector(this.head.x, this.head.y));
    
          this.head.x += this.vel.x * GRID_SIZE;
          this.head.y += this.vel.y * GRID_SIZE;
    
          this.head.x = (this.head.x + WITDH) % WITDH;
          this.head.y = (this.head.y + HEIGHT) % HEIGHT;
    
          if(this.length < this.body.length)
          {
             this.body.shift();
          }
       }
       show() {
          noStroke();
          // Draw snake head
          fill(255);
          rect(this.head.x, this.head.y, GRID_SIZE, GRID_SIZE);
    
          // Draw snake body
          fill(155);
          for(let vector of this.body)
          {
             rect(vector.x, vector.y, GRID_SIZE, GRID_SIZE);
          }
       }

    Sau khi chạy, ta được kết quả sau:

    2.5 Xử lý khi kết thúc game

    Trò chơi kết thúc khi con rắn cắn vào thân của nó, tương tự như phần ăn thức ăn, ta chỉ cần kiểm tra xem có phần thần nào trùng tọa độ với head hay không là được. Ta tạo thêm 1 biến isDead tượng trưng cho trạng thái của con rắn và thêm đoạn code sau vào hàm update()

          for(let vector of this.body)
          {
             if(vector.x == this.head.x && vector.y == this.head.y)
             {
                this.isDead = true;
             }
          }

    Và hàm draw() cũng sẽ được chỉnh lại, để mỗi khi isDead == true thì sẽ tạo lại 1 game mới.

    function draw() {
       background(0);
       if(!snake.isDead){
          drawSnake();
       } else {
          newGame()
       }
    }

    Và cuối cùng, đây sẽ là game hoàn chỉnh :))

    3. Kết

    Vậy là mình đã hướng dẫn các bạn làm một game rắn săn mồi đơn giản bằng Ngôn ngữ lập trình Javascript. Hy vọng sau bài viết thì bạn sẽ cảm thấy hứng thú với Javascript hơn, cũng như với p5.js :)) Toàn bộ code, các bạn có thể tham khảo ở repo này: snake game

    Mong mọi người nhận xét , thảo luận để bài viết tốt hơn. Nếu thấy hay thì hãy vote 5 sao giúp mình nhé :)) Cảm ơn mọi người đã đọc.