如何减慢Canvas贪吃蛇游戏中食物的setInterval重生速度?
解决贪吃蛇食物重生过快的问题
问题根源
你当前的实现里,food.draw()方法每次执行都会创建一个新的setInterval定时器,而游戏主循环play()每100ms就会调用一次games.draw()——相当于每100ms就多一个每秒生成食物的定时器。随着时间推移,定时器数量越来越多,食物生成速度自然会变得越来越快。另外食物的坐标生成逻辑也有问题,会出现固定在左上角的无效食物。
修复方案
方案1:重构食物生成逻辑,只初始化一次定时器
把定时器移到food类的构造函数中,确保整个游戏周期只创建一个定时器,同时修正食物坐标的生成逻辑,让食物落在网格的有效位置:
class food { constructor(game) { this.game = game this.width = 20 this.height = 20 // 存储当前食物的位置 this.foodPositions = [] // 在构造函数里初始化一次定时器,每秒生成食物 setInterval(() => { // 控制每次生成的食物数量,比如保持最多2个 if (this.foodPositions.length < 2) { // 生成符合网格的坐标(20px一格,画布400px宽高,所以x/y范围是0-19*20) const x = Math.floor(Math.random() * 20) * 20 const y = Math.floor(Math.random() * 20) * 20 this.foodPositions.push({x, y}) } }, 1000) } draw(ctx) { // 只负责绘制当前存在的食物,不再创建定时器 ctx.fillStyle = 'white' this.foodPositions.forEach(pos => { ctx.fillRect(pos.x, pos.y, this.width, this.height) }) } // 新增方法:当蛇吃到食物时,移除对应的食物 removeFood(pos) { this.foodPositions = this.foodPositions.filter(p => !(p.x === pos.x && p.y === pos.y)) } }
方案2:贴合游戏主循环,不用setInterval(更推荐)
游戏主循环本身就是定时执行的,我们可以在主循环里通过记录时间差来控制食物生成的间隔,这样和游戏的更新节奏更统一:
class food { constructor(game) { this.game = game this.width = 20 this.height = 20 this.foodPositions = [] // 记录上次生成食物的时间 this.lastSpawnTime = Date.now() // 食物生成间隔(毫秒),可自行调整 this.spawnInterval = 1000 } update() { const now = Date.now() // 检查是否到了生成食物的时间 if (now - this.lastSpawnTime >= this.spawnInterval) { if (this.foodPositions.length < 2) { const x = Math.floor(Math.random() * 20) * 20 const y = Math.floor(Math.random() * 20) * 20 this.foodPositions.push({x, y}) } this.lastSpawnTime = now } } draw(ctx) { ctx.fillStyle = 'white' this.foodPositions.forEach(pos => { ctx.fillRect(pos.x, pos.y, this.width, this.height) }) } removeFood(pos) { this.foodPositions = this.foodPositions.filter(p => !(p.x === pos.x && p.y === pos.y)) } }
然后需要在游戏的update方法里调用food的update:
class game { // ... 其他代码 update(ctx) { this.snake.update(ctx) this.food.update() // 新增这一行 } // ... 其他代码 }
额外优化点
- 蛇的键盘事件监听不要放在
update方法里,每次update都会新增一个监听,会导致按键响应异常,应该移到window.load的回调里:
window.addEventListener("load", () => { // ... 类定义 const games = new game // 把键盘监听移到这里 window.addEventListener("keypress", (e) => { switch(e.key) { case "w": games.snake.sy = -20 games.snake.sx = 0 break case "s": games.snake.sy = 20 games.snake.sx = 0 break case "a": games.snake.sx = -20 games.snake.sy = 0 break case "d": games.snake.sx = 20 games.snake.sy = 0 } }) // ... play函数 })
- 可以添加蛇吃食物的碰撞检测逻辑,在蛇的update里检查是否吃到食物,如果吃到就不要pop蛇尾,同时调用food的removeFood方法。
内容的提问来源于stack exchange,提问作者KurniawanRizky
相关产品推荐
相关产品推荐

