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

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

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

一、了解游戏规则

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

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

二、建立 HTML 结构

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

<!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 (index.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.mzph.cn/web/66302.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每日进步一点点(网安)

今日练习题目是PHP反序列化&#xff0c;也学习一下说明是序列化和反序列化 1.PHP序列化 序列化是指将数据结构或对象转换为可传输或可储存的格式的过程。这通常需要将数据转换为字节流或者其他编码格式&#xff0c;以便在不同系统和应用程序之间进行传输或存储 在PHP中&…

【专题一 递归】21. 合并两个有序链表

1.题目解析 2.讲解算法原理 解法:递归-> 重复的子问题 重复子问题 ->函数头的设计 合并两个有序链表--->Node dfs(l1&#xff0c;l2) 只关心某一个子问题在做什么事情 ->函数体的设计 比大小l1→next dfs( l1.next, l2)return l1 递归的出口 if(l1null)return l2…

OpenCV基础:获取子矩阵的几种方式

目录 相关阅读 方法一&#xff1a;使用切片操作 方法二&#xff1a;使用高级索引 方法三&#xff1a;使用条件筛选 方法四&#xff1a;使用 numpy 的 take 函数 相关阅读 OpenCV基础&#xff1a;矩阵的创建、检索与赋值-CSDN博客 OpenCV基础&#xff1a;图像运算-CSDN博客…

Visual Studio Community 2022(VS2022)安装方法

废话不多说直接上图&#xff1a; 直接上步骤&#xff1a; 1&#xff0c;首先可以下载安装一个Visual Studio安装器&#xff0c;叫做Visual Studio installer。这个安装文件很小&#xff0c;很快就安装完成了。 2&#xff0c;打开Visual Studio installer 小软件 3&#xff0c…

学习threejs,使用OrbitControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.OrbitControls 相机控…

【2024年华为OD机试】 (B卷,100分)- 流水线(Java JS PythonC/C++)

一、问题描述 题目描述 一个工厂有 m 条流水线&#xff0c;来并行完成 n 个独立的作业&#xff0c;该工厂设置了一个调度系统&#xff0c;在安排作业时&#xff0c;总是优先执行处理时间最短的作业。 现给定流水线个数 m&#xff0c;需要完成的作业数 n&#xff0c;每个作业…

opencv图像基础学习

2.3图像的加密解密 源码如下&#xff1a; import cv2 import numpy as np import matplotlib.pyplot as plt def passImg():imgcv2.imread(./image/cat.jpg,0)h,wimg.shape#生成一个密码&#xff0c;加密key_imgnp.random.randint(0,256,size(h,w),dtypenp.uint8)img_addmcv2…

【机器学习】零售行业的智慧升级:机器学习驱动的精准营销与库存管理

我的个人主页 我的领域&#xff1a;人工智能篇&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;&#x1f44d;点赞 收藏❤ 在当今数字化浪潮汹涌澎湃的时代&#xff0c;零售行业正站在转型升级的十字路口。市场竞争的白热化使得企业必须另辟蹊径&#xff0…

Navicat Premium 原生支持阿里云 PolarDB 数据库

近日&#xff0c;我司旗下的 Navicat Premium 软件通过了阿里云 PolarDB 数据库产品生态集成认证&#xff0c;这标志着 Navicat 通过原生技术全面实现了对秒级弹性、高性价比、稳定可靠的PolarDB 数据库三大引擎&#xff08;PolarDB MySQL版、PolarDB PostgreSQL版和 PolarDB f…

【SpringBoot应用篇】SpringBoot+MDC+自定义Filter操作traceId实现日志链路追踪

【SpringBoot应用篇】SpringBootMDC自定义Filter操作traceId实现日志链路追踪 解决的问题解决方案MDC具体逻辑ymllogback-spring.xmlTraceIdUtil操作工具类TraceIdFilter自定义过滤器GlobalExceptionHandler全局异常处理类TraceIdAspect切面UserController测试验证 多线程处理M…

python如何解析word文件格式(.docx)

python如何解析word文件格式&#xff08;.docx&#xff09; .docx文件遵从开源的“Office Open XML标准”&#xff0c;这意味着我们能用python的文本操作对它进行操作&#xff08;实际上PPT和Excel也是&#xff09;。而且这并不是重复造轮子&#xff0c;因为市面上操作.docx的…

python爬虫报错日记

python爬虫报错日记 类未定义 原因&#xff1a;代码检查没有问题**&#xff0c;位置错了**&#xff0c;测试代码包含在类里…… UnicodedecodeError错误 原因&#xff1a;字符没有自动转换成utf-8格式 KeyError&#xff1a;“href” 原因&#xff1a;前面运行正常&#x…

第34天:Web开发-PHP应用鉴别修复AI算法流量检测PHP.INI通用过滤内置函数

#知识点 1、安全开发-原生PHP-PHP.INI安全 2、安全开发-原生PHP-全局文件&单函数 3、安全开发-原生PHP-流量检测&AI算法 一、通用-PHP.INI设置 参考&#xff1a; https://www.yisu.com/ask/28100386.html https://blog.csdn.net/u014265398/article/details/109700309 …

基于PHP的校园新闻发布管理

摘要 近年来&#xff0c;随着互联网技术的迅速发展&#xff0c;人们获取新闻的渠道也变得越来越多样化&#xff0c;已经不再拘束于传统的报纸、期刊、杂志等纸质化的方式&#xff0c;而是通过网络满足了人们获得第一手新闻的愿望&#xff0c;这样更加有助于实现新闻的规范化管…

从玩具到工业控制--51单片机的跨界传奇【3】

在科技的浩瀚宇宙中&#xff0c;51 单片机就像一颗独特的星辰&#xff0c;散发着神秘而迷人的光芒。对于无数电子爱好者而言&#xff0c;点亮 51 单片机上的第一颗 LED 灯&#xff0c;不仅仅是一次简单的操作&#xff0c;更像是开启了一扇通往新世界的大门。这小小的 LED 灯&am…

boss直聘 __zp_stoken__ 逆向分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我删…

【日志篇】(7.6) ❀ 01. 在macOS下刷新FortiAnalyzer固件 ❀ FortiAnalyzer 日志分析

【简介】FortiAnalyzer 是 Fortinet Security Fabric 安全架构的基础&#xff0c;提供集中日志记录和分析&#xff0c;以及端到端可见性。因此&#xff0c;分析师可以更有效地管理安全状态&#xff0c;将安全流程自动化&#xff0c;并快速响应威胁。具有分析和自动化功能的集成…

Linux 内核自旋锁spinlock(一)

文章目录 前言一、自旋锁1.1 简介1.2 API1.2.1 spin_lock/spin_unlock1.2.2 spin_lock_irq/spin_unlock_irq1.2.3 spin_lock_irqsave/spin_unlock_irqstore1.2.4 spin_lock_bh/spin_unlock_bh1.2.5 补充 二、自选锁原理三、自旋锁在内核的使用3.1 struct file3.2 struct dentry…

【太古新篇,智驭未来】 SFA系统成功上线

经过双方团队的不懈努力与紧密合作&#xff0c;eBest成功帮助香港太古可乐完成了SFA系统的全面上线&#xff01; 太古可乐&#xff0c;作为饮料行业的佼佼者&#xff0c;一直以来以其卓越的品质和深入人心的品牌形象深受消费者喜爱。然而&#xff0c;在快速变化的市场环境中&am…

Web安全|渗透测试|网络安全

基础入门(P1-P5) p1概念名词 1.1域名 什么是域名&#xff1f; 域名&#xff1a;是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。 什么是二级域名多级域名…