当前位置: 首页 > article >正文

用HTML5 Canvas和原生JS手搓一个Emoji消消乐(附完整源码和算法解析)

用HTML5 Canvas和原生JS手搓一个Emoji消消乐附完整源码和算法解析在移动游戏风靡的今天消除类游戏因其简单易上手、又兼具策略性的特点始终占据着一席之地。作为前端开发者自己动手实现一个消除游戏不仅能巩固Canvas绘图和JavaScript基础更能深入理解游戏开发中的核心算法逻辑。本文将带你从零开始用纯前端技术打造一个Emoji主题的消消乐游戏并重点解析其中的匹配检测、重力模拟等关键算法。1. 游戏基础架构设计消消乐游戏的核心架构可以分解为三个主要部分游戏状态管理、用户交互处理和游戏逻辑更新。我们先从最基础的Canvas绘制开始。1.1 Canvas初始化与网格绘制首先需要设置游戏画布和基本参数const canvas document.getElementById(gameCanvas); const ctx canvas.getContext(2d); const GRID_SIZE 8; // 8x8网格 const CELL_SIZE canvas.width / GRID_SIZE; const EMOJIS [, , , , , ]; // 6种不同Emoji初始化游戏网格时我们需要确保不会一开始就出现可消除的组合function initGrid() { let grid Array(GRID_SIZE).fill().map(() Array(GRID_SIZE).fill(null)); // 确保初始网格没有可消除的组合 for (let y 0; y GRID_SIZE; y) { for (let x 0; x GRID_SIZE; x) { let validEmojis [...EMOJIS]; // 检查左侧两个格子 if (x 2 grid[x-1][y] grid[x-2][y]) { validEmojis validEmojis.filter(e e ! grid[x-1][y]); } // 检查上方两个格子 if (y 2 grid[x][y-1] grid[x][y-2]) { validEmojis validEmojis.filter(e e ! grid[x][y-1]); } grid[x][y] validEmojis[Math.floor(Math.random() * validEmojis.length)]; } } return grid; }提示这种初始化方式虽然复杂一些但能确保游戏开始时玩家就有可操作的空间而不是需要先随机交换才能开始游戏。1.2 游戏状态管理我们需要跟踪几个关键状态let gameState { grid: initGrid(), selectedCell: null, // 当前选中的格子 score: 0, isAnimating: false, // 是否正在执行动画 animationQueue: [] // 动画队列 };2. 核心游戏逻辑实现2.1 交换与匹配检测玩家交换两个相邻格子后需要立即检查是否形成了三个或以上相同的连续Emojifunction checkMatches(grid) { const matches new Set(); // 使用Set避免重复记录 // 水平检测 for (let y 0; y GRID_SIZE; y) { let streak 1; for (let x 1; x GRID_SIZE; x) { if (grid[x][y] grid[x][y] grid[x-1][y]) { streak; } else { if (streak 3) { for (let i x - streak; i x; i) { matches.add(${i},${y}); } } streak 1; } } // 行末检查 if (streak 3) { for (let i GRID_SIZE - streak; i GRID_SIZE; i) { matches.add(${i},${y}); } } } // 垂直检测类似逻辑 // ... return Array.from(matches).map(str { const [x, y] str.split(,).map(Number); return {x, y}; }); }2.2 消除与重力模拟消除匹配的格子后上方的格子需要下落填补空缺顶部生成新的格子function applyGravity(grid) { let moved false; for (let x 0; x GRID_SIZE; x) { // 从下往上检查 let emptyY GRID_SIZE - 1; for (let y GRID_SIZE - 1; y 0; y--) { if (grid[x][y]) { if (y ! emptyY) { grid[x][emptyY] grid[x][y]; grid[x][y] null; moved true; } emptyY--; } } // 填充顶部空缺 for (let y 0; y emptyY; y) { grid[x][y] EMOJIS[Math.floor(Math.random() * EMOJIS.length)]; moved true; } } return moved; }3. 性能优化与高级功能3.1 匹配检测算法优化原始的水平垂直双重扫描算法虽然直观但在大型网格上可能效率不高。我们可以优化为单次扫描function optimizedCheckMatches(grid) { const matches new Set(); const directions [ [1, 0], // 水平 [0, 1], // 垂直 [1, 1], // 对角线 [1, -1] // 反对角线 ]; for (let y 0; y GRID_SIZE; y) { for (let x 0; x GRID_SIZE; x) { if (!grid[x][y]) continue; for (const [dx, dy] of directions) { let streak 1; let nx x dx, ny y dy; const matchedCells [${x},${y}]; while (nx 0 nx GRID_SIZE ny 0 ny GRID_SIZE grid[nx][ny] grid[x][y]) { matchedCells.push(${nx},${ny}); streak; nx dx; ny dy; } if (streak 3) { matchedCells.forEach(cell matches.add(cell)); } } } } return Array.from(matches).map(str { const [x, y] str.split(,).map(Number); return {x, y}; }); }3.2 动画效果实现为了让游戏体验更流畅我们可以添加交换、消除和下落动画function animateSwap(cell1, cell2, callback) { const duration 200; // 毫秒 const startTime performance.now(); const originalPositions { cell1: {x: cell1.x * CELL_SIZE, y: cell1.y * CELL_SIZE}, cell2: {x: cell2.x * CELL_SIZE, y: cell2.y * CELL_SIZE} }; function step(currentTime) { const elapsed currentTime - startTime; const progress Math.min(elapsed / duration, 1); // 计算中间位置 const cell1X originalPositions.cell1.x (originalPositions.cell2.x - originalPositions.cell1.x) * progress; const cell1Y originalPositions.cell1.y (originalPositions.cell2.y - originalPositions.cell1.y) * progress; const cell2X originalPositions.cell2.x (originalPositions.cell1.x - originalPositions.cell2.x) * progress; const cell2Y originalPositions.cell2.y (originalPositions.cell1.y - originalPositions.cell2.y) * progress; // 重绘 drawGrid(); drawCell(cell1, cell1X, cell1Y); drawCell(cell2, cell2X, cell2Y); if (progress 1) { requestAnimationFrame(step); } else { callback(); } } requestAnimationFrame(step); }4. 完整游戏循环与用户体验优化4.1 游戏主循环将各个模块组合起来形成完整的游戏循环function gameLoop() { if (gameState.isAnimating) { requestAnimationFrame(gameLoop); return; } // 处理动画队列 if (gameState.animationQueue.length 0) { const animation gameState.animationQueue.shift(); gameState.isAnimating true; animation(() { gameState.isAnimating false; }); requestAnimationFrame(gameLoop); return; } // 检查是否有匹配 const matches checkMatches(gameState.grid); if (matches.length 0) { // 处理消除和下落 removeMatches(matches); updateScore(matches.length); gameState.animationQueue.push( cb animateRemove(matches, () { applyGravity(gameState.grid); cb(); }) ); } drawGrid(); requestAnimationFrame(gameLoop); }4.2 特殊效果与得分加成为了增加游戏深度可以实现连击系统和特殊效果function updateScore(matchCount, isCombo false) { let baseScore matchCount * 10; // 连击加成 if (isCombo) { baseScore * gameState.comboMultiplier; gameState.comboMultiplier 0.5; } else { gameState.comboMultiplier 1; } gameState.score Math.floor(baseScore); updateScoreDisplay(); } function createSpecialEffects(matches) { // 4连及以上创建炸弹效果 if (matches.length 4) { const center matches[Math.floor(matches.length / 2)]; return {type: bomb, x: center.x, y: center.y, radius: 2}; } return null; }实现一个完整的消消乐游戏需要考虑的细节远不止这些比如关卡设计、难度曲线、音效管理等。本文提供的代码已经实现了核心玩法你可以在此基础上继续扩展。在实际开发中建议使用TypeScript来获得更好的类型安全或者考虑使用游戏引擎如Phaser来简化开发流程。

相关文章:

用HTML5 Canvas和原生JS手搓一个Emoji消消乐(附完整源码和算法解析)

用HTML5 Canvas和原生JS手搓一个Emoji消消乐(附完整源码和算法解析) 在移动游戏风靡的今天,消除类游戏因其简单易上手、又兼具策略性的特点,始终占据着一席之地。作为前端开发者,自己动手实现一个消除游戏不仅能巩固Ca…...

Unity:Cinemachine Virtual Camera(虚拟摄像机)的智能追踪艺术

1. Cinemachine Virtual Camera的核心价值 第一次接触Cinemachine时,我完全被它的智能程度震惊了。记得当时在做一个篮球游戏demo,需要摄像机跟随球员运球突破。传统方法要写一堆代码处理镜头平滑移动、边界限制、动态缩放,而Cinemachine Vir…...

基于虚拟矢量与FOC控制算法的死区补偿仿真模型:m文件编写SVPWM与死区补偿算法研究与应用

死区补偿仿真模型 基于虚拟矢量角度死区补偿方法 (1)模型包含FOC控制算法 (2)用m文件编写svpwm算法和死区补偿算法 (3)包含转速环控制和死区模块 可用于永磁同步电机foc算法学习和死区补偿算法学习,模型搭建不易,谨慎联系,详细资料见图&#…...

FineReport报表JS实现动态参数传递与对话框报表交互

1. 动态参数传递的基础原理 在FineReport报表开发中,动态参数传递就像给快递员写送货单。当你点击主报表中的某个数据项(比如图书ID),需要把这个"包裹"准确无误地送到对话框报表里。这个过程涉及三个关键环节&#xff1…...

STM32串口下载全攻略:FlyMcu配置详解与一键下载电路设计

STM32串口下载全攻略:FlyMcu配置详解与一键下载电路设计 嵌入式开发中,程序烧录是每个工程师必须掌握的技能。对于STM32系列单片机而言,除了常见的ST-LINK调试器下载方式,串口下载因其成本低廉、操作简单而广受欢迎。本文将深入解…...

Hyper-V虚拟机安装Deepin避坑指南:从镜像选择到循环安装解决

Hyper-V虚拟机安装Deepin避坑实战手册 在Windows平台上通过Hyper-V运行Deepin系统,是许多开发者体验国产操作系统的首选方案。但实际操作中,从镜像下载到完成安装的每一步都可能暗藏玄机。本文将带你直击三大核心痛点:版本兼容性陷阱、IDE控制…...

1588v2协议实战:如何在工业自动化场景中实现纳秒级时间同步?

1588v2协议工业部署指南:从纳秒同步到故障排查全解析 工业自动化产线上,三台机械臂突然出现0.5毫米的位置偏差——这个发生在某汽车焊接车间的真实案例,最终被追溯到毫秒级的时间同步误差。当现代工业系统对协同精度要求进入纳秒时代&#xf…...

从SMS网格到FVCOM:.grd与.2dm文件结构解析与海洋建模实战

1. 从SMS网格到FVCOM模型的基础认知 第一次接触海洋数值模拟时,我被各种网格文件格式搞得晕头转向。直到在项目实践中反复使用SMS和FVCOM,才真正理解.grd和.2dm文件的价值。这两个看似简单的文本文件,实际上承载着整个海洋模型的空间骨架。 S…...

Ubuntu18.04虚拟机300GB配置全攻略:Vivado2019.2+Vitis+Petalinux一站式安装

Ubuntu 18.04虚拟机300GB配置全攻略:Vivado 2019.2VitisPetalinux一站式安装 对于FPGA开发者来说,搭建一个稳定高效的开发环境是项目成功的第一步。本文将带你从零开始,在Ubuntu 18.04虚拟机上配置300GB磁盘空间,并完整安装Xilinx…...

从西工大网安导论出发:构建网络空间安全的知识体系与实践视角

1. 网络空间安全的基础认知框架 第一次接触网络空间安全这个概念时,很多人会陷入一个误区——认为装个杀毒软件就是做好了安全防护。实际上,网络空间安全是一个庞大而精密的系统工程。西工大《网络空间安全导论》开篇就给出了一个精辟的定义:…...

AutoGen Manager-Broadcast机制详解:手把手教你配置多代理聊天组(含Python代码示例)

AutoGen Manager-Broadcast机制深度解析:构建高效多代理协作系统的实践指南 在当今AI技术快速发展的背景下,多代理协作系统正成为解决复杂问题的关键架构。微软推出的AutoGen框架为开发者提供了一套强大的工具集,其中Manager-Broadcast机制是…...

智慧无人机城市植被绿化巡检数据集 无人机树木分割 无人机草地识别 城郊植被覆盖度监测 生态环境评估 城市绿化规划 遥感影像语义解析 yolo分割数据集第10591期

埔里居住区-郊区 语义分割数据集文档数据集核心信息表项目内容类别数量3类中文类别树、草地、低植被图像数量260数据集格式YOLO核心应用价值城郊植被覆盖度监测、生态环境评估、城市绿化规划、遥感影像语义解析 数据集概述类别设计 聚焦城郊居住区植被场景,划分树、…...

基于全局守恒场算法的火箭箭体壳体原子级轻量化超强耐热材料全域设计方法

基于全局守恒场算法的火箭箭体壳体 原子级轻量化超强耐热材料全域设计方法 适用部门:中国航天科技集团、航天材料研究所、中科院金属所、航天材料工艺研究所 作者:华夏之光永存 标签:#华夏之光永存 #航天材料 #火箭外壳 #原子级设计 #轻量化 …...

基于全局守恒场算法的运载火箭回收姿态稳定与软着陆全域优化方法

基于全局守恒场算法的运载火箭回收姿态稳定与软着陆全域优化方法 适用部门:中国航天科技集团、中国航天科工集团、中国运载火箭技术研究院、航天动力学与控制研究所、航天软件与仿真中心 作者:华夏之光永存 标签:#华夏之光永存 #运载火箭 #火…...

Python自动化界面操作:从基础到实战全攻略

一、自动化界面操作概述 1.1 定义 Python自动化界面操作是指通过代码模拟人工的鼠标、键盘操作,或直接调用界面控件API,实现对桌面应用、Web页面、移动端APP等图形界面的自动化控制,无需人工干预即可完成重复性任务。 1.2 常见应用场景场景类…...

Pytest 核心特性与技术优势

Pytest 核心特性与技术优势 核心特性详解 语法极简设计 测试用例仅需以 test_ 前缀命名函数或方法,无需继承任何基类。例如: def test_addition():assert 1 1 2智能用例发现 自动扫描项目目录下匹配 test_*.py 或 *_test.py 模式的文件,支持…...

Windows NTFS硬链接技术深度解析:EternalBlaze如何实现磁盘空间零成本释放

在Windows操作系统中,NTFS文件系统提供了一项被大多数用户忽视的强大功能——硬链接(Hard Link)。 这项技术允许单个文件在文件系统中拥有多个路径引用,而所有引用均指向同一份物理数据块。 EternalBlaze正是基于这一底层机制开…...

crewAI 部署形态:本地、Docker、K8s 与 Serverless 化实践

crewAI 部署形态:本地、Docker、K8s 与 Serverless 化实践 本文基于 crewAI v1.11.0,全面覆盖从本地开发到企业级部署的完整实践方案。 一、部署复杂度的阶梯 crewAI 项目的部署需求随规模增长呈阶梯式上升: Stage 1:本地开发└…...

crewAI 可观测性体系:Langfuse/Phoenix 集成与执行链路追踪

crewAI 可观测性体系:Langfuse/Phoenix 集成与执行链路追踪 本文基于 crewAI v1.11.0,介绍如何为多智能体系统建立完整的可观测性基础设施。 一、为什么多智能体系统需要可观测性 一个 crewAI Crew 在生产环境中运行时,你关心哪些问题&#…...

crewAI CLI 与项目结构:从原型到生产的工程化规范

crewAI CLI 与项目结构:从原型到生产的工程化规范 本文基于 crewAI v1.11.0,系统介绍 crewAI 项目的标准工程结构、CLI 工具链和生产环境配置规范。 一、原型与生产的鸿沟 很多 crewAI 项目都死在从原型到生产的过渡阶段。原型阶段的代码通常是这样的&a…...

一、安装Redis(win11环境下)

1.windows安装Redis 1.1下载redis https://github.com/tporadowski/redis/releases 图1-1 网页Redis版本选择 1.2解压redis 图1-2 redis目录下 1.3启动redis 在解压成功后,进入到redis文件下(如上图1-2),右键打开命令窗口&am…...

Comsol声子晶体能带计算,包含六角晶格不同原胞的选取以及简约布里渊区高对称点选择

Comsol声子晶体能带计算,包含六角晶格不同原胞的选取以及简约布里渊区高对称点选择。 核心在于区分三角晶格和六角晶格区别最近在研究Comsol声子晶体的能带计算,发现六角晶格的原胞选取和简约布里渊区高对称点的选择真是个大坑。尤其是三角晶格和六角晶格…...

从Tacotron到智能语音:端到端语音合成的原理、应用与未来

从Tacotron到智能语音:端到端语音合成的原理、应用与未来 引言 你是否曾好奇智能音箱里流畅自然的语音是如何“凭空”产生的?这背后,端到端语音合成技术正扮演着核心角色。本文将深入解析这一领域的里程碑式模型——Tacotron。我们将从其颠覆…...

保姆级教程:手把手复现攻防世界shrine靶场(Flask+Jinja2 SSTI)

从零构建Flask SSTI靶场:绕过黑名单获取FLAG的实战指南 第一次接触CTF中的SSTI漏洞时,我完全被那些奇怪的{{}}符号和魔术方法搞晕了。直到亲手搭建环境复现漏洞,才真正理解模板注入的精妙之处。本文将带你从零开始,完整复现攻防世…...

Arduino Uno引脚全解析:从电源管理到PWM调光,新手必看的实战指南

Arduino Uno引脚深度实战:从电源配置到智能控制的全能指南 当你第一次拿起Arduino Uno开发板时,那些密密麻麻的金属引脚可能会让你感到困惑。这块小小的蓝色板子如何通过这些引脚与外部世界对话?本文将带你超越简单的引脚定义,深入…...

基于Docker和Orthanc构建高效医学影像存储系统的实践指南

1. 为什么选择DockerOrthanc搭建医学影像系统 在医院信息化建设中,医学影像存储一直是个让人头疼的问题。传统的PACS系统往往价格昂贵、部署复杂,而且扩展性差。我去年帮一家社区医院做系统升级时,他们原有的影像系统已经用了8年,…...

NXP i.MX8M Plus Cortex-M7多核通信与实时控制开发实战

1. 认识i.MX8M Plus的异构多核架构 第一次拿到NXP i.MX8M Plus开发板时,最让我惊讶的是它独特的"大小核"设计。这颗芯片内部其实藏着两个完全不同的世界:一边是四核Cortex-A53组成的"大脑",主频高达1.6GHz,能…...

计算机毕设 java基于微信小程序点餐系统的设计与实现 微信小程序智能点餐平台开发 基于 SpringBoot 的餐饮在线点餐系统设计

计算机毕设 java基于微信小程序点餐系统的设计与实现pmz399(配套有源码 程序 mysql 数据库 论文)本套源码可以先看具体功能演示视频领取,文末有联 xi 可分享随着移动互联网的普及和微信小程序的广泛应用,“互联网 餐饮” 成为行业…...

LeetCode 48 1886.矩阵旋转与判断

LeetCode 48 & 1886.矩阵旋转与判断 题目概览 [LeetCode 48] 旋转图像 给定一个 n n 的二维矩阵 matrix 表示一个图像,请你将图像顺时针旋转 90 度,必须原地旋转。 [LeetCode 1886] 判断矩阵经轮转后是否一致 给定两个 n n 的矩阵 mat 和 target&a…...

Comsol纳米摩擦发电机仿真计算模型探索

Comsol纳米摩擦发电机仿真计算模型,采用静电场对相反电极材料感应的表面电荷进行计算,可以得到不同电极距离下计算模型的电势、电场分布最近在研究Comsol纳米摩擦发电机仿真计算模型,感觉还挺有意思的,来和大家分享一下&#x1f6…...