You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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!") 
}

问题根源

  1. 重复键盘事件监听:多个独立的keydown监听导致逻辑冲突,碰撞检测事件会在玩家位置更新前触发,判断的是旧位置。
  2. 食物刷新逻辑错误:draw函数每次执行都会调用drawbleach,导致食物被频繁刷新,出现"提前消失"的问题。
  3. 方向控制逻辑颠倒:键盘按键的判断条件完全错误,比如左箭头(37)被当成返回条件,导致移动方向混乱。
  4. 随机位置计算错误:食物位置生成公式会产生负数(取绝对值后仅在左上角区域),且范围超出画布边界。
  5. 图片加载逻辑混乱: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:44:52