Đình Anh
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 tựa game tuổi thơ rắn săn mồi. Lần này, chúng ta sẽ cùng nhau làm lại nó 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 có 2 function mà bạn chắc chắn sẽ sử dụng đó là:
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.draw()
: Function này sẽ chạy ngay sau functionsetup()
ở 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ủ.
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()
và 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 là thành quả của chúng ta
3. Kết
Vậy là chúng ta đã cùng nhau làm một game rắn săn mồi đơn giản bằng 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