Canvas渲染时游戏Tick变慢问题排查求助
我用JavaScript结合HTML Canvas开发了一款贪吃蛇游戏,游戏逻辑参考Java实现。核心逻辑在Game.js文件中,每个游戏类(Game.js、Snake.js、SnakeCell.js、Map.js、Food.js)都包含render方法,负责在index.html的Canvas上完成画面渲染。
目前遇到的问题是:游戏的每一个Tick(游戏节拍)运行速度会逐渐变慢。我做了两项测试:
- 测试渲染耗时:大部分场景下耗时不超过2ms
// rendering the game this.render(); var that = this; setTimeout(function(){ let now = Date.now(); that.tick() console.log(Date.now() - now); }, this.gameTick); - 测试蛇身数组操作耗时:蛇身存储在Snake对象的
body属性中,是SnakeCell对象组成的数组,操作耗时同样很短let daba = Date.now(); newBody.unshift(newHead) console.log(Date.now() - daba); if(removeTail){ newBody.pop() } console.log(daba)
我是JavaScript新手,希望能得到解决这个问题的具体建议。
换掉setTimeout,用requestAnimationFrame做游戏循环
你现在用setTimeout控制tick间隔,但这个API的延迟是"至少等待X毫秒",不是精确的固定间隔,每次调用的误差会累积,时间久了就会让游戏看起来越来越慢。requestAnimationFrame是浏览器专门为动画、游戏场景设计的,它会和浏览器的刷新频率同步(一般是60fps),能保证更稳定的帧率,从根源上避免时间偏移问题。
简单的循环写法:function gameLoop() { this.render(); this.tick(); requestAnimationFrame(gameLoop.bind(this)); } // 启动游戏循环 gameLoop.call(this);如果需要固定的tick间隔(比如不想和帧率绑定),可以在循环里记录时间差,当累计时间达到你的
gameTick阈值时再执行tick逻辑。优化蛇身数组的操作逻辑
你现在用unshift添加蛇头,这个操作需要把数组里所有元素向后移动一位,时间复杂度是O(n)——蛇越长,这个操作的耗时就会越高,单次看可能只有几毫秒,但累积起来就会拖慢整个tick。
可以换个思路:把蛇头存在数组的末尾,添加新头用push(O(1)操作,不需要移动其他元素);如果不需要增长蛇身,就移除数组最前面的元素(尾巴)。另外,尽量不要每次tick都新建newBody数组,直接操作原有的this.body数组,能减少不必要的内存分配和GC压力。检查render方法里的重复冗余操作
看看各个类的render方法有没有重复做没必要的事:比如每次render都重新获取Canvas上下文(document.getElementById('canvas').getContext('2d'))、每次都重复设置fillStyle/strokeStyle等样式、或者每次都清空整个Canvas后全量重绘。
比如样式可以只在初始化或需要改变时设置;如果只重绘变化的区域(蛇头新位置、消失的尾巴、新生成的食物),而不是全量重绘,能大幅减少渲染的工作量。排查内存泄漏问题
游戏越跑越慢,大概率和内存泄漏有关。打开Chrome DevTools的Memory面板,先拍一个初始状态的快照,运行游戏几分钟后再拍一个快照,对比两个快照,看看有没有大量的SnakeCell或其他游戏对象没有被回收。比如有没有全局变量在不断存储这些对象、或者事件监听没有正确移除,导致对象无法被垃圾回收,内存占用越来越高,浏览器自然会越来越卡。
内容的提问来源于stack exchange,提问作者ayoubachak

