JavaScript Canvas贪吃蛇游戏checkCollision方法调用报错求助
贪吃蛇游戏碰撞检测崩溃问题修复
问题描述
使用JavaScript Canvas开发贪吃蛇游戏,多数功能已实现,但蛇撞到墙体或自身时游戏崩溃,无法正常调用checkCollision方法。
报错信息
Uncaught TypeError: Cannot read properties of undefined (reading 'col') at Snake.checkCollision (snake.html:148:43) at core (snake.html:191:27) at snake.html:196:13 Uncaught TypeError: Cannot read properties of undefined (reading 'col') at Snake.checkCollision (snake.html:148:43) at core (snake.html:191:27) snake.html:129 Uncaught TypeError: gameOver is not a function at Snake.move (snake.html:129:21) at core (snake.html:186:23)
问题分析
- 无参调用
checkCollision:core函数中调用snake.checkCollision()时未传入head参数,而checkCollision方法依赖该参数访问col和row属性,导致head为undefined触发报错。 - 函数定义顺序错误:
gameOver函数定义在core()调用之后,当snake.move()中碰撞触发调用gameOver()时,该函数还未被定义,因此抛出gameOver is not a function错误。 - 苹果位置判断逻辑错误:
Apple.prototype.move中的randomCol !== this.segments判断无效,this指向苹果实例,不存在segments属性,无法避免苹果生成在蛇身体上。
修复方案
- 调整
gameOver函数位置,移至core()调用之前,确保函数提前声明。 - 删除
core函数中多余的snake.checkCollision()调用,因为snake.move()内部已经完成碰撞检测并处理游戏结束逻辑。 - 修复苹果生成时的位置判断,检查新生成的位置是否与蛇的身体重叠,若重叠则重新生成。
修复后的完整代码
<script> //Create canvas const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let width = canvas.width; let height = canvas.height; let blockSize = 10; let widthInBlocks = width / blockSize; let heightInBlocks = height / blockSize; let drawBorder = function () { ctx.fillStyle = 'Black'; ctx.fillRect(0, 0, width, blockSize); ctx.fillRect(0, height - blockSize, width, blockSize); ctx.fillRect(0, 0, blockSize, height); ctx.fillRect(width - blockSize, 0, blockSize, height); }; drawBorder(); //Create score var score = 0; let drawScore = function () { ctx.clearRect(10, 10, width - 20, 40); ctx.fillStyle = 'Black'; ctx.textBaseline = 'top'; ctx.textAlign = 'left'; ctx.font = '24px Arial'; ctx.fillText('Score : ' + score, 15, 45); }; //Block constructor const Block = function (col, row) { this.col = col; this.row = row; }; Block.prototype.drawSquare = function (color) { let x = this.col * blockSize; let y = this.row * blockSize; ctx.fillStyle = color; ctx.fillRect(x, y, blockSize, blockSize); }; //Create food const circle = function (x, y, radius, color, fill) { ctx.fillStyle = color; ctx.strokeStyle = color; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2, false); if (fill) { ctx.fill(); } else { ctx.stroke(); } }; Block.prototype.drawCircle = function (color) { let x = this.col * blockSize + blockSize / 2; let y = this.row * blockSize + blockSize / 2; ctx.fillStyle = color; circle(x, y, blockSize / 2, color, true); }; let Apple = function () { this.position = new Block(10, 10); }; Apple.prototype.draw = function () { this.position.drawCircle(colorList[Math.floor(Math.random() * 7)]); }; Apple.prototype.move = function () { let randomCol, randomRow; let isOnSnake; do { isOnSnake = false; randomCol = Math.floor(Math.random() * (widthInBlocks - 2)) + 1; randomRow = Math.floor(Math.random() * (heightInBlocks - 2)) + 1; // 检查是否在蛇身体上 for (let i = 0; i < snake.segments.length; i++) { if (randomCol === snake.segments[i].col && randomRow === snake.segments[i].row) { isOnSnake = true; break; } } } while (isOnSnake); this.position = new Block(randomCol, randomRow); }; //Setting keycode const directions = { 37:'left', 38:'up', 39:'right', 40:'down' }; $('body').keydown(function (event) { let newDirection = directions[event.keyCode]; if (newDirection !== undefined) { snake.setDirection(newDirection); } }); //Create snake var colorList = ['Blue','Green','Red','Gold','Silver','Purple','Cyan'] var Snake = function () { this.segments = [ new Block(7,5), new Block(6,5), new Block(5,5) ]; this.direction = 'right'; this.nextDirection = 'right'; }; Snake.prototype.draw = function () { for (let i = 0; i < this.segments.length; i ++) { this.segments[i].drawSquare(colorList[Math.floor(Math.random() * 7)]); }; }; //Setting moving directions Snake.prototype.move = function () { let head = this.segments[0]; let newHead; this.direction = this.nextDirection; if (this.direction === 'right'){ newHead = new Block(head.col + 1, head.row); } else if (this.direction === 'left') { newHead = new Block(head.col - 1, head.row); } else if (this.direction === 'up') { newHead = new Block(head.col, head.row - 1); } else if (this.direction === 'down') { newHead = new Block(head.col, head.row + 1) } if (this.checkCollision(newHead)) { gameOver(); return; } this.segments.unshift(newHead); if (newHead.equal(apple.position)) { score ++; apple.move(); aniTime -= 1; } else { this.segments.pop(); } }; //Define collision Block.prototype.equal = function (otherBlock) { return this.col === otherBlock.col && this.row === otherBlock.row; }; Snake.prototype.checkCollision = function (head) { var leftCollision = (head.col === 0); var topCollision = (head.row === 0); var rightCollision = (head.col === widthInBlocks - 1); var bottomCollision = (head.row === heightInBlocks - 1); var wallCollision = leftCollision || topCollision || rightCollision || bottomCollision; var selfCollision = false; for (let i = 0; i < this.segments.length; i ++) { if (head.equal(this.segments[i])) { selfCollision = true; } } return wallCollision || selfCollision }; Snake.prototype.setDirection = function (newDirection) { if (this.direction === 'up' && newDirection === 'down') { return; } else if (this.direction === 'right' && newDirection ==='left') { return; } else if (this.direction === 'down' && newDirection ==='up') { return; } else if (this.direction === 'left' && newDirection ==='right') { return; } this.nextDirection = newDirection; }; //Game over condition var gameOver = function () { clearTimeout(timeOutId); ctx.font = '60px Arial'; ctx.fillStyle = 'Black'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('Game Over', width / 2, height / 2); }; //run the game let snake = new Snake(); let apple = new Apple(); var aniTime = 100; var timeOutId; // 声明全局变量避免未定义 function core () { ctx.clearRect(0, 0, width, height); drawScore(); snake.move(); snake.draw(); apple.draw(); drawBorder(); timeOutId = setTimeout(core, aniTime); }; core(); </script>
额外说明
- 修复了
textBaseLine的拼写错误,改为正确的textBaseline,避免文本渲染异常。 - 声明了全局变量
timeOutId,避免gameOver中访问未定义变量。
内容的提问来源于stack exchange,提问作者Panda CHENG
相关产品推荐
相关产品推荐

