Xin chào các bạn, mình là 1 con mòe vui vẻ. Hôm nay rảnh rỗi nên mình ngồi vọc vạch code con game game khủng long chạy phiên bản pikachu với html5 canvas chỉ với... chưa đến 200 dòng code.
Dành cho bạn nào chưa biết thì canvas là một phần tử của HTML5, được đẻ ra để thực hiện kết xuất đồ họa trên trang web. Các bạn có thể tham khảo các canvas API trên trang https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
Thôi, không câu giờ nữa, bắt tay vào làm thôi nào!
1. Đặt vấn đề
Bài toán của chúng ta là tạo ra 1 game khủng long chạy dựa theo game dino trên chrome mà chúng ta hay chơi khi mất mạng đó. Gameplay sẽ như sau: Chú khủng long sẽ chạy trên đường, các vật cản sẽ lần lượt hiện ra, nhiệm vụ của người chơi là ấn nút space trên bàn phím để giúp chú khủng long nhảy lên tránh các chướng ngại vật. Điểm số sẽ tăng dần theo thời gian chơi. Phiên chơi sẽ kết thúc khi khủng long chạm vào chướng ngại vật.
2. Bắt tay vào làm
2.1 Tạo game canvas
Đầu tiên, mình sẽ tạo một khung html cơ bản, sau đó thêm vào một thẻ canvas với chiều dài 1280px, chiều rộng 720px để đặt game bên trong, sau đó style lại một chút cho đẹp.
2.2 Tạo các biến/hàm cơ bản
Sau khi tạo canvas cho game thì tiếp theo, mình sẽ khai báo các biến/hàm chung để sau này sử dụng trong game.
const canvas = document.querySelector('canvas'); // Lấy thẻ canvas const ctx = canvas.getContext('2d'); // Lấy context 2d const game = {}; // Object này để chứa dữ liệu game
Mình sẽ nhóm một số nhóm code theo chức năng thành các hàm để sau này chỉ việc lôi ra dùng thôi, đỡ phải viết đi viết lại nhiều. Vì mình lười lắm.
- Hàm createText để tạo text trên canvas với các tham số truyền vào là tọa độ x,y, style của text, căn chỉnh text, nội dung text
function createText(x, y, style, align, content) { ctx.textAlign = align; ctx.font = style; ctx.fillText(content, x, y); }
- Hàm createImg để tạo một html image mới:
function createImg(src) { const image = new Image(); image.src = src; return image; }
- Hàm resizeCanvas để căn chỉnh canvas cho phù hợp với các kích cỡ màn hình khác nhau:
function resizeCanvas() { if ((window.innerWidth / window.innerHeight) >= (1280 / 720)) { canvas.style.width = ""; canvas.style.height = "100%"; } else { canvas.style.width = "100%"; canvas.style.height = ""; } }
2.3 Tạo các đối tượng trong game:
Ok, xong các hàm cơ bản, tiếp theo chúng ta sẽ tạo các đối tượng trong game:
1. Player (chính là con khủng long đó):
function Player(img, x, y, w, h) { this.img = createImg(img); // Ảnh của vật thể this.x = x; // Tọa độ x this.y = y; // Tọa độ y this.w = w; // Chiều rộng this.h = h; // Chiều cao this.maxJump = 500; // Độ cao nhảy tối đa this.jumpStatus = "None"; //Trạng thái nhảy this.update = () => { // Nếu trạng thái nhảy là up thì tăng tọa độ y if (this.jumpStatus === "Up") { this.y += 10; if (this.y >= this.maxJump) { this.y = this.maxJump; this.jumpStatus = "Down"; } } // Nếu trạng thái nhảy là down thì giảm tọa độ y if (this.jumpStatus === "Down") { this.y -= 10; if (this.y <= 0) { this.y = 0 this.jumpStatus = "None"; } } // Vẽ ảnh trên canvas ctx.drawImage(this.img, this.x, 720 - this.y - this.h, this.w, this.h); } }
2. các chướng ngại vật:
function Obstacle(img, x, y, w, h) { this.img = createImg(img); this.x = x; this.y = y; this.w = w; this.h = h; this.active = true; this.update = () => { if (!this.active) return; // Chướng ngại vật sẽ di chuyển từ phải qua trái this.x -= 10; if (this.x <= -this.w) { this.active = false; } ctx.drawImage(this.img, this.x, 720 - this.y - this.h, this.w, this.h); } }
2.4 Game play:
- Khởi tạo game mới:
function initGame() { // Ẩn nút chơi lại document.getElementById('play-again').style.display = "none"; game.score = 0; game.startTime = new Date().getTime(); // Tạo Player game.pikachu = new Player('https://media.discordapp.net/attachments/600891241185411082/875985072942120960/pikachu.png', 100, 0, 200, 200); // Danh sách các chướng ngại vật game.obstacles = []; // Mốc thời gian tạo chướng ngại vật tiếp theo game.nextObstacleTmp = new Date().getTime() + Math.floor(Math.random() * 2000) + 1000; // Xử lý sự kiên khi ấn phím cách thì nhảy lên window.onkeyup = function (e) { if (e.keyCode == 32) { if (game.pikachu.jumpStatus == "None") game.pikachu.jumpStatus = "Up"; } } gameLoop(); } initGame();
- Game loop:
function gameLoop() { resizeCanvas(); // Xóa frame cũ ctx.clearRect(0, 0, 1280, 720); // Cập nhật điểm updateScore(); // Tạo chướng ngại vật mới genObstacle(); // Cập nhật vị trí khủng long game.pikachu.update(); // Cập nhật vị trí các chướng ngại vật for (let i = 0; i < game.obstacles.length; i++) { game.obstacles[i].update(); // Kiểm tra va chạm với khủng long, nếu va chạm thì game kết thúc if (checkCollision(game.obstacles[i], game.pikachu)) { createText(1280 / 2, 720 / 2, "40px Arial", "center", "GAME OVER"); document.getElementById('play-again').style.display = "inline-block"; return window.cancelAnimationFrame(gameLoop); } } window.requestAnimationFrame(gameLoop); }
Sinh chướng ngại vật mới:
function genObstacle() { // Nếu chưa đến thời gian tạo chướng ngại vật mới thì return luôn if (game.nextObstacleTmp > new Date().getTime()) return; // Tạo sỗ ngẫu nhiên 0 hoặc 1 const randomNum = Math.floor(Math.random() * 2); // Nếu số là 0 thì tạo pokeball if (randomNum == 0) { const newObstacles = new Obstacle('https://media.discordapp.net/attachments/600891241185411082/875985078428237874/pokeball.png', 1280, 0, 200, 200); game.obstacles.push(newObstacles); } else { // Nếu không thì tạo con Nyasu là Nyasu const newObstacles = new Obstacle('https://media.discordapp.net/attachments/600891241185411082/875985079883685918/nyasu.png', 1280, 0, 220, 275); game.obstacles.push(newObstacles); } // Cập nhật thời gian sinh chướng ngại vật tiếp theo game.nextObstacleTmp = new Date().getTime() + Math.floor(Math.random() * 2000) + 1000; }
- Cập nhật điểm:
function updateScore() { game.score = Math.floor((new Date().getTime() - game.startTime) / 100); createText(1280 - 50, 50, "28px Arial", "right", `Score: ${game.score}`); }
- Kiểm tra va chạm:
function checkCollision(obj1, obj2) { if (obj1.x > obj2.x + obj2.w || obj1.x + obj1.w < obj2.x || obj1.y > obj2.y + obj2.h || obj1.y + obj1.h < obj2.y) { return false; } else { return true; } }
3. Thành quả:
Vậy là chỉ với html/css/js thuần, chúng ta đã tạo ra một game Pikachu chạy vô vùng đơn giản. Các bạn có thể xem source code và demo tại đây để tham khảo và tự tạo cho mình các game khác. Chúc các bạn thành công.