基于 HTML5 Canvas 制作一个精美的 2048 小游戏--day 1

devtools/2025/1/22 11:36:24/

基于 HTML5 Canvas 制作一个精美的 2048 小游戏

在这个快节奏的生活中,简单而富有挑战性的游戏总能给我们带来乐趣。2048 是一款受欢迎的益智游戏,不仅考验智力,还能让人回味无穷。今天,我带领大家将一起学习如何使用 HTML5 Canvas 来制作一个精美的 2048 小游戏。

一、了解游戏规则

在深入代码之前,我们需要了解游戏的基本规则:

  1. 目标:通过合并相同的数字块,最终达到2048。
  2. 操作:玩家可以通过上下左右的箭头键控制数字块的移动。
  3. 生成新块:每次成功移动后,会随机生成一个“2”或“4”的数字块。
  4. 游戏结束:当所有方块被填满且无法进行任何合并时,游戏结束。

二、建立 HTML 结构

首先,我们需要搭建游戏的基础 HTML 结构。在 HTML 文件中,引入 Canvas 元素以绘制游戏界面。

html"><!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>2048 游戏</title><link rel="stylesheet" href="style.css">
</head>
<body><div class="container"><canvas id="gameCanvas"></canvas></div><script src="script.js"></script>
</body>
</html>

三、样式设计

接下来,我们需要为游戏添加一些样式,使其更具吸引力。我们将在 CSS 文件中设置按钮和画布的样式。

body {display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #faf8ef;font-family: 'Arial', sans-serif;
}.container {position: relative;
}canvas {border: 2px solid #bbada0;background-color: #eee4da;
}

四、游戏逻辑实现

在脚本文件中,我们将编写游戏的核心逻辑,包括初始化游戏、绘制方块、移动操作和合并方块等。

4.1 初始化

首先,我们需要创建一个类似于二维数组的数字格子,并用随机数填充初始状态。

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');const gridSize = 4;
const tileSize = 100;
canvas.width = gridSize * tileSize;
canvas.height = gridSize * tileSize;let board = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));function initBoard() {addRandomTile();addRandomTile();drawBoard();
}function drawBoard() {ctx.clearRect(0, 0, canvas.width, canvas.height);for (let r = 0; r < gridSize; r++) {for (let c = 0; c < gridSize; c++) {drawTile(r, c);}}
}function drawTile(r, c) {const value = board[r][c];ctx.fillStyle = value !== 0 ? getTileColor(value) : '#ccc0b3';ctx.fillRect(c * tileSize, r * tileSize, tileSize - 10, tileSize - 10);if (value !== 0) {ctx.fillStyle = '#776e65';ctx.font = 'bold 45px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText(value, c * tileSize + tileSize / 2 - 5, r * tileSize + tileSize / 2);}
}function getTileColor(value) {switch (value) {case 2: return '#eee4da';case 4: return '#ede0c8';case 8: return '#f2b179';case 16: return '#f59563';case 32: return '#f67c5f';case 64: return '#f67c5f';case 128: return '#edcf72';case 256: return '#edcc61';case 512: return '#edc850';case 1024: return '#edc53f';case 2048: return '#edc22e';default: return '#ccc0b3';}
}function addRandomTile() {let emptyCells = [];for (let r = 0; r < gridSize; r++) {for (let c = 0; c < gridSize; c++) {if (board[r][c] === 0) {emptyCells.push({ r, c });}}}if (emptyCells.length) {const { r, c } = emptyCells[Math.floor(Math.random() * emptyCells.length)];board[r][c] = Math.random() < 0.9 ? 2 : 4;}
}

4.2 移动与合并方块

通过键盘事件监听来实现方块的移动和合并,我们定义方向常量,结合用户输入的方向实现移动和合并的逻辑。

document.addEventListener('keydown', (event) => {switch (event.key) {case 'ArrowUp':moveUp();break;case 'ArrowDown':moveDown();break;case 'ArrowLeft':moveLeft();break;case 'ArrowRight':moveRight();break;}drawBoard();
});function canMergeTiles(r1, c1, r2, c2) {return board[r1][c1] !== 0 && board[r1][c1] === board[r2][c2];
}function moveUp() {for (let c = 0; c < gridSize; c++) {for (let r = 1; r < gridSize; r++) {if (board[r][c] !== 0) {let targetRow = r;while (targetRow > 0 && board[targetRow - 1][c] === 0) {// 向上移动board[targetRow - 1][c] = board[targetRow][c];board[targetRow][c] = 0;targetRow--;}if (targetRow > 0 && canMergeTiles(targetRow - 1, c, targetRow, c)) {// 合并方块board[targetRow - 1][c] *= 2;board[targetRow][c] = 0;}}}}
}function moveDown() {for (let c = 0; c < gridSize; c++) {for (let r = gridSize - 2; r >= 0; r--) {if (board[r][c] !== 0) {let targetRow = r;while (targetRow < gridSize - 1 && board[targetRow + 1][c] === 0) {// 向下移动board[targetRow + 1][c] = board[targetRow][c];board[targetRow][c] = 0;targetRow++;}if (targetRow < gridSize - 1 && canMergeTiles(targetRow + 1, c, targetRow, c)) {// 合并方块board[targetRow + 1][c] *= 2;board[targetRow][c] = 0;}}}}
}function moveLeft() {for (let r = 0; r < gridSize; r++) {for (let c = 1; c < gridSize; c++) {if (board[r][c] !== 0) {let targetCol = c;while (targetCol > 0 && board[r][targetCol - 1] === 0) {// 向左移动board[r][targetCol - 1] = board[r][targetCol];board[r][targetCol] = 0;targetCol--;}if (targetCol > 0 && canMergeTiles(r, targetCol - 1, r, targetCol)) {// 合并方块board[r][targetCol - 1] *= 2;board[r][targetCol] = 0;}}}}
}function moveRight() {for (let r = 0; r < gridSize; r++) {for (let c = gridSize - 2; c >= 0; c--) {if (board[r][c] !== 0) {let targetCol = c;while (targetCol < gridSize - 1 && board[r][targetCol + 1] === 0) {// 向右移动board[r][targetCol + 1] = board[r][targetCol];board[r][targetCol] = 0;targetCol++;}if (targetCol < gridSize - 1 && canMergeTiles(r, targetCol + 1, r, targetCol)) {// 合并方块board[r][targetCol + 1] *= 2;board[r][targetCol] = 0;}}}}
}

五、完善游戏体验

在游戏逻辑实现后,我们需要添加分数计算、胜利和失败的提示,以及重新开始游戏的功能。这些都将进一步提升游戏体验。

5.1 分数系统

我们为游戏添加分数系统,每次合并方块时更新分数。

let score = 0;function mergeTiles(r1, c1, r2, c2) {if (board[r1][c1] === board[r2][c2]) {board[r1][c1] *= 2;score += board[r1][c1];board[r2][c2] = 0;}
}

5.2 结束提示

当玩家没有可移动的方块时,可以弹出提示框告知游戏结束。

function checkGameOver() {for (let r = 0; r < gridSize; r++) {for (let c = 0; c < gridSize; c++) {if (board[r][c] === 0) {return false; // 还有空格}if (c < gridSize - 1 && canMergeTiles(r, c, r, c + 1)) {return false; // 可以合并}if (r < gridSize - 1 && canMergeTiles(r, c, r + 1, c)) {return false; // 可以合并}}}return true; // 游戏结束
}function showGameOver() {alert('游戏结束!您的得分是:' + score);
}

结论

现在,您应该对如何使用 HTML5 Canvas 制作一个 2048 小游戏有了更详细的了解。从简单的 UI 设计到移动和合并方块的逻辑实现,每一步都至关重要。虽然本文未能详细说明所有代码,但希望您能根据提供的思路和示例进行深入探索和实现。创建游戏是一项有趣且富有成就感的工作,快去尝试制作您自己的 2048 小游戏吧!

完整代码

html_308">HTML (index.html)

html"><!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>2048 游戏</title><link rel="stylesheet" href="style.css">
</head>
<body><div class="container"><canvas id="gameCanvas"></canvas></div><script src="script.js"></script>
</body>
</html>

CSS (style.css)

body {display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #faf8ef;font-family: 'Arial', sans-serif;
}.container {position: relative;
}canvas {border: 2px solid #bbada0;background-color: #eee4da;
}

JavaScript (script.js)

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');const gridSize = 4;
const tileSize = 100;
canvas.width = gridSize * tileSize;
canvas.height = gridSize * tileSize;let board = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
let score = 0;initBoard();function initBoard() {addRandomTile();addRandomTile();drawBoard();
}function drawBoard() {ctx.clearRect(0, 0, canvas.width, canvas.height);for (let r = 0; r < gridSize; r++) {for (let c = 0; c < gridSize; c++) {drawTile(r, c);}}// 显示分数ctx.fillStyle = '#776e65';ctx.font = 'bold 20px Arial';ctx.textAlign = 'center';ctx.fillText('Score: ' + score, canvas.width / 2, canvas.height - 20);
}function drawTile(r, c) {const value = board[r][c];ctx.fillStyle = value !== 0 ? getTileColor(value) : '#ccc0b3';ctx.fillRect(c * tileSize, r * tileSize, tileSize - 10, tileSize - 10);if (value !== 0) {ctx.fillStyle = '#776e65';ctx.font = 'bold 45px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText(value, c * tileSize + tileSize / 2 - 5, r * tileSize + tileSize / 2);}
}function getTileColor(value) {switch (value) {case 2: return '#eee4da';case 4: return '#ede0c8';case 8: return '#f2b179';case 16: return '#f59563';case 32: return '#f67c5f';case 64: return '#f67c5f';case 128: return '#edcf72';case 256: return '#edcc61';case 512: return '#edc850';case 1024: return '#edc53f';case 2048: return '#edc22e';default: return '#ccc0b3';}
}function addRandomTile() {let emptyCells = [];for (let r = 0; r < gridSize; r++) {for (let c = 0; c < gridSize; c++) {if (board[r][c] === 0) {emptyCells.push({ r, c });}}}if (emptyCells.length) {const { r, c } = emptyCells[Math.floor(Math.random() * emptyCells.length)];board[r][c] = Math.random() < 0.9 ? 2 : 4;}
}document.addEventListener('keydown', (event) => {let moved = false;switch (event.key) {case 'ArrowUp':moved = moveUp();break;case 'ArrowDown':moved = moveDown();break;case 'ArrowLeft':moved = moveLeft();break;case 'ArrowRight':moved = moveRight();break;}if (moved) {addRandomTile();drawBoard();if (checkGameOver()) {showGameOver();}}
});function canMergeTiles(r1, c1, r2, c2) {return board[r1][c1] !== 0 && board[r1][c1] === board[r2][c2];
}function moveUp() {let moved = false;for (let c = 0; c < gridSize; c++) {for (let r = 1; r < gridSize; r++) {if (board[r][c] !== 0) {let targetRow = r;while (targetRow > 0 && board[targetRow - 1][c] === 0) {board[targetRow - 1][c] = board[targetRow][c];board[targetRow][c] = 0;targetRow--;moved = true;}if (targetRow > 0 && canMergeTiles(targetRow - 1, c, targetRow, c)) {board[targetRow - 1][c] *= 2;score += board[targetRow - 1][c];board[targetRow][c] = 0;moved = true;}}}}return moved;
}function moveDown() {let moved = false;for (let c = 0; c < gridSize; c++) {for (let r = gridSize - 2; r >= 0; r--) {if (board[r][c] !== 0) {let targetRow = r;while (targetRow < gridSize - 1 && board[targetRow + 1][c] === 0) {board[targetRow + 1][c] = board[targetRow][c];board[targetRow][c] = 0;targetRow++;moved = true}if (targetRow < gridSize - 1 && canMergeTiles(targetRow + 1, c, targetRow, c)) {board[targetRow + 1][c] *= 2;score += board[targetRow + 1][c];board[targetRow][c] = 0;moved = true;}}}}return moved;
}function moveLeft() {let moved = false;for (let r = 0; r < gridSize; r++) {for (let c = 1; c < gridSize; c++) {if (board[r][c] !== 0) {let targetCol = c;while (targetCol > 0 && board[r][targetCol - 1] === 0) {board[r][targetCol - 1] = board[r][targetCol];board[r][targetCol] = 0;targetCol--;moved = true;}if (targetCol > 0 && canMergeTiles(r, targetCol - 1, r, targetCol)) {board[r][targetCol - 1] *= 2;score += board[r][targetCol - 1];board[r][targetCol] = 0;moved = true;}}}}return moved;
}function moveRight() {let moved = false;for (let r = 0; r < gridSize; r++) {for (let c = gridSize - 2; c >= 0; c--) {if (board[r][c] !== 0) {let targetCol = c;while (targetCol < gridSize - 1 && board[r][targetCol + 1] === 0) {board[r][targetCol + 1] = board[r][targetCol];board[r][targetCol] = 0;targetCol++;moved = true;}if (targetCol < gridSize - 1 && canMergeTiles(r, targetCol + 1, r, targetCol)) {board[r][targetCol + 1] *= 2;score += board[r][targetCol + 1];board[r][targetCol] = 0;moved = true;}}}}return moved;
}function checkGameOver() {for (let r = 0; r < gridSize; r++) {for (let c = 0; c < gridSize; c++) {if (board[r][c] === 0) {return false; // 还有空格}if (c < gridSize - 1 && canMergeTiles(r, c, r, c + 1)) {return false; // 可以合并}if (r < gridSize - 1 && canMergeTiles(r, c, r + 1, c)) {return false; // 可以合并}}}return true; // 游戏结束
}function showGameOver() {alert('游戏结束!您的得分是:' + score);
}

http://www.ppmy.cn/devtools/152588.html

相关文章

OSI5GWIFI自组网协议层次对比

目录 5G网络5G与其他协议栈各层映射 5G网络 物理层 (PHY) 是 5G 基站协议架构的最底层&#xff0c;负责将数字数据转换为适合无线传输的信号&#xff0c;并将接收到的无线信号转换为数字数据。实现数据的编码、调制、多天线处理、资源映射等操作。涉及使用新的频段&#xff08…

【C++学习篇】滑动窗口--结合例题讲解思路

目录 1. 例题&#xff1a;最小覆盖子串 1.1 解题思路 2. 方法一代码实现&#xff1a;用kinds来记录t中有效元素的种类&#xff0c;count来记录s中的有效元素种类 3. 方法二&#xff1a;count来记录s中有效元素的个数 ok&#xff0c;这是最后一期关于滑动窗口的学习章节了…

Graylog采集MySQL慢日志实战

文章目录 前言一、MySQL慢日志0. 慢查询相关语句1. 检查MySQL是否开启慢日志及慢查询保存位置2. 检查慢查询阈值3. 未使用索引是否开启记录慢查询日志4. 查看mysql.slow_log表结构及字段含义5. 慢查询记录两种情况示例 二、graylog采集慢查询日志1. 采集思路2. 创建Sidecar配置…

【Day23 LeetCode】贪心算法题

一、贪心算法 贪心没有套路&#xff0c;只有碰运气&#xff08;bushi&#xff09;&#xff0c;举反例看看是否可行&#xff0c;&#xff08;运气好&#xff09;刚好贪心策略的局部最优就是全局最优。 1、分发饼干 455 思路&#xff1a;按照孩子的胃口从小到大的顺序依次满足…

C# 以管理员方式启动程序全解析

引言 在 Windows 应用程序开发的领域中&#xff0c;C# 语言凭借其强大的功能和广泛的适用性&#xff0c;被众多开发者所青睐。然而&#xff0c;在实际的开发过程里&#xff0c;我们常常会遭遇这样的情况&#xff1a;程序需要访问特定的系统资源&#xff0c;像是系统文件夹、注…

springboot基于微信小程序的健康管理系统

Spring Boot 基于微信小程序的健康管理系统 在现代快节奏生活中&#xff0c;人们愈发关注自身健康&#xff0c;Spring Boot 基于微信小程序的健康管理系统应运而生&#xff0c;它将便捷的移动端体验与强大的后端技术相结合&#xff0c;为用户打造了个性化、全方位的健康管理助手…

MySql字段的值是以逗号隔开的另一个表的主键关联查询

查询sql SELECT s.student_id, s.name, c.name as course_name FROM student s INNER JOIN course c ON FIND_IN_SET(c.course_id, s.course_id) > 0 WHERE 1 1;相似sql -- 翻译&#xff08;需要带条件&#xff0c;可用于字典翻译&#xff0c;但条件需要注意唯一性&#…

`Port: Direct Attach Copper` 和 `Port: Twisted Pair`

目录标题 这些端口类型的来源结论1. **Intel Network Interface Cards (NICs)**2. **Broadcom / Avago Technologies**3. **Mellanox Technologies (现为 NVIDIA)**4. **Chelsio Communications**5. **Realtek**6. **Netgear / TP-Link / ASUS**总结 你提到的 Port: Direct Att…