JavaScript贪吃蛇游戏:如何避免重复按键导致蛇加速
解决贪吃蛇狂按按键导致异常加速的问题
问题根源
你当前的代码每次按下W/A/S/D时,都会调用对应方向的xxxLoop()函数,而每个xxxLoop()内部会递归创建新的setTimeout定时器。狂按按键会同时启动多个独立的定时器,这些定时器都会触发蛇的移动逻辑,最终导致移动速度叠加,出现异常加速的情况。
最优解决方案:单主循环+方向状态控制
改用单个游戏主循环控制蛇的移动,按键仅修改当前方向状态,不直接启动新的定时器。这种方式从根源上避免了多个定时器叠加的问题,同时还能限制蛇不能反向移动(防止直接撞自己)。
修改后的完整代码
HTML(无需修改)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./style.css"> <script src="./game.js" defer></script> <title>Snake Game</title> </head> <body> <div id="game-board"> <div class="snake" id="snake"></div> <div class="apple" id="apple"></div> </div> </body> </html>
CSS(无需修改)
body{ height:100vh; width:100vw; display: flex; justify-content: center; align-items: center; margin:0; background-color: darkgreen; } #game-board{ background-color: green; width:500px; height:500px; display: grid; grid-template-rows: repeat(20, 1fr); grid-template-columns: repeat(20, 1fr); } .snake{ background-color: rgb(65, 65, 255); border: 2px solid black; border-radius: 5px; z-index: 99999; } .apple{ background-color: rgb(248, 24, 24); border: 2px solid black; border-radius: 5px; }
JavaScript(重构核心逻辑)
let apple = document.getElementById('apple'); let snake = document.getElementById('snake'); let snakeColumnStart = 11; let snakeRowStart = 11; let currentDirection = null; // 记录当前移动方向:'up'/'right'/'left'/'down' let gameOver = false; // 生成苹果随机位置 const getApplePos = (min, max) => { return Math.floor(Math.random() * (max - min + 1) + min); } // 设置苹果位置 const setApplePos = () => { const gridColumnStart = getApplePos(1, 20); const gridColumnEnd = gridColumnStart + 1; const gridRowStart = getApplePos(1, 20); const gridRowEnd = gridRowStart + 1; apple.style.gridArea = `${gridRowStart} / ${gridColumnStart} / ${gridRowEnd} / ${gridColumnEnd}`; } // 设置蛇的位置 const setSnakePos = () => { let snakeColumnEnd = snakeColumnStart + 1; let snakeRowEnd = snakeRowStart + 1; snake.style.gridArea = `${snakeRowStart} / ${snakeColumnStart} / ${snakeRowEnd} / ${snakeColumnEnd}`; } // 游戏主循环:统一控制移动逻辑 const gameLoop = () => { setTimeout(() => { if (gameOver) return; // 根据当前方向更新蛇的位置 switch(currentDirection) { case 'up': snakeRowStart -= 1; break; case 'right': snakeColumnStart += 1; break; case 'left': snakeColumnStart -= 1; break; case 'down': snakeRowStart += 1; break; } // 边界检测,触碰到边界则游戏结束 if (snakeRowStart < 1 || snakeRowStart > 20 || snakeColumnStart < 1 || snakeColumnStart > 20) { console.log("game over"); gameOver = true; return; } setSnakePos(); gameLoop(); // 递归调用,维持循环 }, 1000); // 移动间隔,可调整速度 } // 键盘事件监听:仅修改方向状态,禁止反向切换 window.addEventListener('keydown', (e) => { switch(e.key) { case 'w': // 当前不是向下时,才能切换为向上 if (currentDirection !== 'down') currentDirection = 'up'; break; case 'd': // 当前不是向左时,才能切换为向右 if (currentDirection !== 'left') currentDirection = 'right'; break; case 'a': // 当前不是向右时,才能切换为向左 if (currentDirection !== 'right') currentDirection = 'left'; break; case 's': // 当前不是向上时,才能切换为向下 if (currentDirection !== 'up') currentDirection = 'down'; break; } }); // 初始化游戏 setSnakePos(); setApplePos(); gameLoop(); // 启动主循环
代码说明
- 单主循环:用
gameLoop()统一控制蛇的移动,只有一个定时器在运行,彻底避免多定时器叠加的问题。 - 方向状态管理:用
currentDirection记录当前方向,按键仅修改这个状态,不触发新的定时器。 - 禁止反向移动:添加判断逻辑,防止蛇直接反向移动(比如向上时不能直接按向下),符合贪吃蛇的常规规则。
内容的提问来源于stack exchange,提问作者user19559860
相关产品推荐
相关产品推荐

