Canvas游戏食物误消失Bug求助(400×400画布/16×16对象)
Canvas小游戏碰撞检测Bug修复
我正在开发一款基于Canvas的小游戏,画布尺寸400×400像素,玩家角色与食物对象均为16×16像素。预期逻辑是玩家吃到食物后,食物消失并刷新,同时计分增加。但当前存在Bug:
- 玩家经过或移动到食物正上方位置时,食物会提前消失
- 仍需移动到原食物位置才能触发计分
以下是我的代码:
const stage = document.getElementById("stage"); //Gets the canvas const ctx = stage.getContext("2d"); //Context var scoreval = 0 //Score var bleachx = 0 //food x var bleachy = 0 //food y const playerimg = new Image(); // Create Player Image const bleachimg = new Image(); // Create Food Image bleachimg.src = "../IMG/bleach.png"; //Food Image file var playerx = 0 //PlayerX var playery = 0 //PlayerY function draw() { playerimg.addEventListener("load", () => { ctx.drawImage(playerimg,playerx,playery); console.log("Image Drawn"); }); playerimg.src = "../IMG/bob.png"; // Set source path ctx.fillText('Score: ' + scoreval, 5, (stage.height - 5)); drawbleach() //Loads Bleach } //Draws player //up window.addEventListener("keydown", (event) => { if (event.isComposing || event.keyCode === 37) { return; } ctx.clearRect(playerx,playery,playerimg.height,playerimg.width); playerx = playerx + 16; ctx.drawImage(playerimg,playerx,playery); }); //right window.addEventListener("keydown", (event) => { if (event.isComposing || event.keyCode === 38) { return; } ctx.clearRect(playerx,playery,playerimg.height,playerimg.width); playery = playery + 16; ctx.drawImage(playerimg,playerx,playery); }); //up window.addEventListener("keydown", (event) => { if (event.isComposing || event.keyCode === 39) { return; } ctx.clearRect(playerx,playery,playerimg.height,playerimg.width); playerx = playerx - 16; ctx.drawImage(playerimg,playerx,playery); }); //down window.addEventListener("keydown", (event) => { if (event.isComposing || event.keyCode === 40) { return; } ctx.clearRect(playerx,playery,playerimg.height,playerimg.width); playery = playery - 16; ctx.drawImage(playerimg,playerx,playery); console.log(event.keyCode); }); function drawbleach () { bleachx = Math.abs(Math.round(Math.random() * 25 - 25 ))*16 bleachy = Math.abs(Math.round(Math.random() * 25 - 25 ))*16 ctx.drawImage(bleachimg,bleachx,bleachy) } window.addEventListener("keydown", (event) => { if (playerx == bleachx && playery == bleachy) { scoreval = scoreval+1 ctx.fillText('Score: ' + scoreval, 5, (stage.height - 5)); drawbleach() } }); //checks if touching bleach when key pressed function notify() { //doesn't matter alert("Hi!") }
问题根源
- 重复键盘事件监听:多个独立的
keydown监听导致逻辑冲突,碰撞检测事件会在玩家位置更新前触发,判断的是旧位置。 - 食物刷新逻辑错误:
draw函数每次执行都会调用drawbleach,导致食物被频繁刷新,出现"提前消失"的问题。 - 方向控制逻辑颠倒:键盘按键的判断条件完全错误,比如左箭头(37)被当成返回条件,导致移动方向混乱。
- 随机位置计算错误:食物位置生成公式会产生负数(取绝对值后仅在左上角区域),且范围超出画布边界。
- 图片加载逻辑混乱:
draw函数每次都重新绑定playerimg的load事件并设置src,导致重复加载和绘制异常。
修复后的代码
const stage = document.getElementById("stage"); const ctx = stage.getContext("2d"); let scoreval = 0; let bleachx = 0; let bleachy = 0; const playerimg = new Image(); const bleachimg = new Image(); // 预加载图片 playerimg.src = "../IMG/bob.png"; bleachimg.src = "../IMG/bleach.png"; let playerx = 0; let playery = 0; const tileSize = 16; const maxIndex = (stage.width / tileSize) - 1; // 初始化游戏 function init() { stage.width = 400; stage.height = 400; spawnFood(); requestAnimationFrame(drawLoop); } // 全局渲染循环 function drawLoop() { // 清除整个画布,避免残留绘制内容 ctx.clearRect(0, 0, stage.width, stage.height); // 绘制玩家(确保图片加载完成) if (playerimg.complete) { ctx.drawImage(playerimg, playerx, playery, tileSize, tileSize); } // 绘制食物(确保图片加载完成) if (bleachimg.complete) { ctx.drawImage(bleachimg, bleachx, bleachy, tileSize, tileSize); } // 绘制分数 ctx.fillText(`Score: ${scoreval}`, 5, stage.height - 5); // 检测碰撞 checkCollision(); requestAnimationFrame(drawLoop); } // 生成合法食物位置 function spawnFood() { bleachx = Math.floor(Math.random() * (maxIndex + 1)) * tileSize; bleachy = Math.floor(Math.random() * (maxIndex + 1)) * tileSize; } // 碰撞检测 function checkCollision() { if (playerx === bleachx && playery === bleachy) { scoreval++; spawnFood(); } } // 统一键盘控制逻辑 window.addEventListener("keydown", (event) => { if (event.isComposing) return; const step = tileSize; // 根据按键更新玩家位置,限制在画布内 switch(event.keyCode) { case 37: // 左 playerx = Math.max(0, playerx - step); break; case 38: // 上 playery = Math.max(0, playery - step); break; case 39: // 右 playerx = Math.min(stage.width - tileSize, playerx + step); break; case 40: // 下 playery = Math.min(stage.height - tileSize, playery + step); break; default: return; } }); // 页面加载完成后启动游戏 window.addEventListener('load', init);
修复说明
- 统一键盘事件:将所有方向控制合并到一个
keydown监听中,避免逻辑冲突。 - 全局渲染循环:使用
requestAnimationFrame实现持续渲染,每次渲染前清除整个画布,确保画面干净。 - 修正食物生成逻辑:计算合法的随机位置,确保食物在画布内且为16像素的倍数。
- 碰撞检测时机调整:在每次渲染时检测碰撞,确保使用最新的玩家位置。
- 预加载图片:提前设置图片路径,避免重复绑定
load事件,保证绘制稳定。 - 方向控制修正:正确对应按键与移动方向,并限制玩家移动范围,防止超出画布。
内容的提问来源于stack exchange,提问作者Goofy Gunton
相关产品推荐
相关产品推荐

