如何实现点击按钮时在Canvas生成随机大小的水平移动黑色方块?
Block Avoider 游戏实现方案
嘿,我来帮你梳理下这款Block Avoider游戏的实现思路和可运行的代码示例吧!咱们一步步拆解需求,把功能落地:
一、基础结构搭建
首先需要一个Canvas用来渲染游戏元素,再加个按钮触发方块生成。HTML结构很简单:
<!DOCTYPE html> <html> <head> <title>Block Avoider</title> <style> canvas { border: 1px solid #333; } button { margin: 10px 0; padding: 8px 16px; font-size: 16px; } </style> </head> <body> <button id="spawnBtn">生成黑色方块</button> <canvas id="gameCanvas" width="800" height="600"></canvas> </body> </html>
二、核心JS逻辑实现
接下来是JavaScript部分,咱们分模块处理:
1. 初始化变量与画布
先获取画布上下文,定义玩家、敌方方块的基础属性:
const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const spawnBtn = document.getElementById('spawnBtn'); // 玩家蓝色方块属性 const player = { x: 50, y: canvas.height / 2 - 40, width: 40, height: 80, color: 'blue', speed: 5, upPressed: false, downPressed: false, leftPressed: false, rightPressed: false }; // 存储所有黑色方块的数组 const enemyBlocks = [];
2. 玩家移动控制
监听方向键的按下/松开事件,记录状态,然后在循环里更新玩家位置(还要限制不能移出画布边界):
// 监听按键事件 document.addEventListener('keydown', (e) => { switch(e.key) { case 'ArrowUp': player.upPressed = true; break; case 'ArrowDown': player.downPressed = true; break; case 'ArrowLeft': player.leftPressed = true; break; case 'ArrowRight': player.rightPressed = true; break; } }); document.addEventListener('keyup', (e) => { switch(e.key) { case 'ArrowUp': player.upPressed = false; break; case 'ArrowDown': player.downPressed = false; break; case 'ArrowLeft': player.leftPressed = false; break; case 'ArrowRight': player.rightPressed = false; break; } }); // 更新玩家位置的函数 function updatePlayer() { if (player.upPressed && player.y > 0) { player.y -= player.speed; } if (player.downPressed && player.y + player.height < canvas.height) { player.y += player.speed; } if (player.leftPressed && player.x > 0) { player.x -= player.speed; } if (player.rightPressed && player.x + player.width < canvas.width) { player.x += player.speed; } }
3. 生成随机黑色方块
给按钮绑定点击事件,每次生成一个随机大小、随机y轴位置的黑色方块,初始在画布右侧外:
spawnBtn.addEventListener('click', () => { const blockWidth = Math.random() * 60 + 20; // 20-80px宽 const blockHeight = Math.random() * 60 + 20; // 20-80px高 enemyBlocks.push({ x: canvas.width, // 初始在画布右侧 y: Math.random() * (canvas.height - blockHeight), // 随机y位置,不超出画布 width: blockWidth, height: blockHeight, color: 'black', speed: Math.random() * 2 + 1 // 1-3px的移动速度,增加随机性 }); });
4. 更新与渲染敌方方块
在游戏循环里让方块向左移动,并且移除已经移出左侧的方块(避免内存浪费):
function updateEnemies() { for (let i = enemyBlocks.length - 1; i >= 0; i--) { const block = enemyBlocks[i]; block.x -= block.speed; // 移除超出画布左侧的方块 if (block.x + block.width < 0) { enemyBlocks.splice(i, 1); } } }
5. 核心碰撞检测
用**轴对齐 bounding box(AABB)**的方法检测玩家与敌方方块的碰撞,这是2D游戏里最常用的碰撞检测方式:
function checkCollision() { for (const block of enemyBlocks) { if ( player.x < block.x + block.width && player.x + player.width > block.x && player.y < block.y + block.height && player.y + player.height > block.y ) { // 碰撞触发逻辑,这里示例是弹出提示并重置游戏 alert('碰撞啦!游戏结束!'); resetGame(); } } } // 重置游戏的函数 function resetGame() { // 重置玩家位置 player.x = 50; player.y = canvas.height / 2 - 40; // 清空所有敌方方块 enemyBlocks.length = 0; }
6. 游戏主循环
用requestAnimationFrame实现持续的更新与渲染:
function gameLoop() { // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新所有元素状态 updatePlayer(); updateEnemies(); checkCollision(); // 渲染玩家方块 ctx.fillStyle = player.color; ctx.fillRect(player.x, player.y, player.width, player.height); // 渲染所有敌方方块 for (const block of enemyBlocks) { ctx.fillStyle = block.color; ctx.fillRect(block.x, block.y, block.width, block.height); } // 循环调用 requestAnimationFrame(gameLoop); } // 启动游戏 gameLoop();
额外优化建议
- 可以给敌方方块添加不同的移动速度,增加游戏难度梯度
- 加入分数系统,比如每躲避一个方块加1分,碰撞后显示最终得分
- 可以设置自动生成方块的定时器,不用手动点击按钮,提升游戏流畅度
- 添加游戏开始界面和结束界面,替换简单的alert提示,提升整体体验
内容的提问来源于stack exchange,提问作者powelc91
相关产品推荐
相关产品推荐

