当前位置:首页 > 云服务器 > 正文

html游戏demo怎么做?html5小游戏开发教程

HTML5 Canvas 游戏开发基础 Demo 详解

HTML5 的 <canvas> 元素为在网页上绘制图形提供了强大的支持,结合 JavaScript,我们可以轻松构建无需插件即可运行的 2D 游戏,以下是一个完整的、可运行的“躲避方块”游戏 Demo,包含详细的代码解析和逻辑说明。

核心架构设计

一个基础的 HTML5 游戏通常由以下三个核心部分组成:

  1. HTML 结构:定义画布容器。
  2. CSS 样式:设置画布外观和居中显示。
  3. JavaScript 逻辑:处理游戏循环、输入控制、碰撞检测和渲染。
  • 玩家控制:使用键盘左右箭头键移动玩家方块。
  • 敌人生成:随机从顶部生成下落方块。
  • 碰撞检测:检测玩家与敌人是否重叠。
  • 分数系统:存活时间越长,分数越高。
  • 游戏结束:发生碰撞后停止游戏并显示最终分数。

完整代码实现

你可以将以下代码保存为 game.html 文件,然后直接在浏览器中打开运行。

html游戏demo怎么做?html5小游戏开发教程 第1张

; { if (e.key === 'ArrowLeft') player.dx = -player.speed; if (e.key === 'ArrowRight') player.dx = player.speed; // 重启游戏 if (isGameOver && e.key === 'Enter') resetGame(); }); document.addEventListener('keyup', (e) => { if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') player.dx = 0; }); // 6. 碰撞检测函数 (AABB 算法) function checkCollision(rect1, rect2) { return ( rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y ); } // 7. 生成敌人 function spawnEnemy() { const size = Math.random() 30 + 20; // 随机大小 20-50 const x = Math.random() (canvas.width size); enemies.push({ x: x, y: -size, width: size, height: size, speed: Math.random() 2 + 2, // 随机速度 color: '#e74c3c' }); } // 8. 更新游戏逻辑 function update() { if (isGameOver) return; // 移动玩家 player.x += player.dx; // 边界检查 if (player.x < 0) player.x = 0; if (player.x + player.width > canvas.width) player.x = canvas.width player.width; // 生成敌人 frameCount++; if (frameCount % enemySpawnRate === 0) { spawnEnemy(); // 随着分数增加,生成速度加快 if (score > 0 && score % 10 === 0 && enemySpawnRate > 20) { enemySpawnRate -= 2; } } // 更新敌人位置并检测碰撞 for (let i = 0; i < enemies.length; i++) { let enemy = enemies[i]; enemy.y += enemy.speed; // 检测碰撞 if (checkCollision(player, enemy)) { isGameOver = true; cancelAnimationFrame(animationId); drawGameOver(); return; } // 移除超出屏幕的敌人并加分 if (enemy.y > canvas.height) { enemies.splice(i, 1); i--; score++; scoreElement.innerText = score; } } } // 9. 渲染画面 function draw() { // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制玩家 ctx.fillStyle = player.color; ctx.fillRect(player.x, player.y, player.width, player.height); // 绘制敌人 enemies.forEach(enemy => { ctx.fillStyle = enemy.color; ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); }); } // 10. 游戏结束画面 function drawGameOver() { ctx.fillSt

yle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.font = '30px Arial'; ctx.textAlign = 'center'; ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 20); ctx.font = '20px Arial'; ctx.fillText('最终分数: ' + score, canvas.width / 2, canvas.height / 2 + 20); ctx.fillText('按 Enter 键重新开始', canvas.width / 2, canvas.height / 2 + 60); } // 11. 游戏主循环 function gameLoop() { update(); draw(); if (!isGameOver) { animationId = requestAnimationFrame(gameLoop); } } // 12. 重置游戏 function resetGame() { score = 0; scoreElement.innerText = '0'; enemies = []; frameCount = 0; enemySpawnRate = 60; isGameOver = false; player.x = canvas.width / 2 20; gameLoop(); } // 启动游戏 gameLoop(); </script> </body> </html>

关键逻辑解析

为了让你更好地理解上述代码,以下是核心模块的详细拆解:

html游戏demo怎么做?html5小游戏开发教程 第2张

1 游戏循环 (Game Loop)

游戏的核心是 requestAnimationFrame,它告诉浏览器在下一次重绘之前执行指定的回调函数,这比传统的 setInterval 更高效,能确保动画流畅(通常为 60fps)。

步骤 函数/操作 作用
1 update() 计算逻辑:移动物体、生成敌人、检测碰撞、更新分数。
2 draw() 渲染画面:清空画布,重新绘制所有物体到最新位置。
3 requestAnimationFrame 调度下一帧,形成无限循环。

2 碰撞检测 (Collision Detection)

本 Demo 使用了 AABB (Axis-Aligned Bounding Box) 算法,即轴对齐包围盒,这是最简单且高效的矩形碰撞检测方法。

逻辑公式:

如果两个矩形在 X 轴和 Y 轴上都重叠,则发生碰撞。

html游戏demo怎么做?html5小游戏开发教程 第3张

  • rect1.x < rect2.x + rect2.width (左边界)
  • rect1.x + rect1.width > rect2.x (右边界)
  • rect1.y < rect2.y + rect2.height (上边界)
  • rect1.y + rect1.height > rect2.y (下边界)

3 状态管理

  • isGameOver: 布尔值,用于控制游戏循环是否继续,一旦设为 true,update 函数将提前返回,停止逻辑更新,但 draw 函数仍会执行以显示“游戏结束”画面。
  • score: 整数,每当敌人离开屏幕底部时增加。

扩展与优化建议

如果你想将这个 Demo 扩展为更复杂的游戏,可以考虑以下方向:

  1. 资源加载:将 fillRect 替换为 drawImage,加载真实的图片精灵(Sprites)代替色块。
  2. 音效系统:使用 HTML5 <audio> 标签或 Web Audio API 添加背景音乐和碰撞音效。
  3. 粒子系统:在碰撞发生时,生成多个小粒子向四周飞散,增强视觉反馈。
  4. 移动端适配:添加触摸事件监听 (touchstart, touchmove),使游戏可以在手机浏览器上通过滑动屏幕控制。


相关问题与解答 (Q&A)

Q1: 为什么使用 requestAnimationFrame 而不是 setInterval 或 setTimeout 来制作游戏循环?

A:

主要区别在于性能同步性

  1. 屏幕刷新同步:requestAnimationFrame 会在浏览器下一次重绘之前调用回调函数,通常与显示器的刷新率(如 60Hz)同步,这能避免画面撕裂,并确保动画流畅。
  2. 后台标签页优化:当用户切换到其他标签页时,浏览器会自动暂停 requestAnimationFrame 的调用,从而节省 CPU 和电池电量,而 setInterval 即使页面不可见也会继续运行,造成资源浪费。
  3. 动态帧率调整:如果当前页面负载过高导致帧率下降,requestAnimationFrame 会自动降低调用频率以适应性能,而 setInterval 会保持固定间隔,可能导致逻辑更新过快或过慢。

Q2: 在这个 Demo 中,如果玩家移动速度非常快,可能会穿过敌人(隧道效应),该如何解决?

A:

这种现象称为“隧道效应”(Tunneling),是因为在单帧时间内,物体移动的距离超过了其自身的尺寸,导致直接跳过了碰撞检测的位置,解决方法有以下几种:

  1. 连续碰撞检测 (CCD)

    不要只检测当前帧的位置,而是检测物体在上一帧当前帧之间形成的“ swept volume ”(扫掠体积),如果这个体积与敌人重叠,则判定为碰撞。

  2. 减小时间步长 (Fixed Time Step)

    将游戏逻辑更新与渲染分离,使用一个固定的、较小的时间步长(1/120 秒)来更新物理位置,即使渲染频率只有 60fps,这样每一帧的移动距离变小,碰撞检测更精确。

  3. 限制最大速度

    在代码中限制玩家或敌人的最大速度,确保其在一帧内移动的距离不超过其自身宽度的一半,这是最简单但效果有限的方案。

在本 Demo 中,由于速度较慢且物体较大,简单的离散检测即可满足需求,但在高速射击或赛车游戏中,必须实现 CCD 或固定时间步长。

0