JS贪吃蛇游戏中SnakeBody类oldDirection始终为undefined问题
SnakeBody类oldDirection始终为undefined的问题
我在JavaScript贪吃蛇游戏中尝试将oldDirection设置为当前direction的值,用来实现蛇身U型转弯的精灵动画,但目前this.direction能获取到新值,而oldDirection始终是undefined,导致无法根据方向切换蛇身精灵。
SnakeBody类代码
class SnakeBody { constructor(x, y, ctx) { this.x = x this.y = y this.oldX this.oldY this.ctx = ctx this.direction this.oldDirection this.sprite = new Image() } draw(headDirection) { this.direction = headDirection this.sprite.onload = () => { this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20) } } update(newX, newY, newDirection) { this.oldDirection = this.direction this.direction = newDirection this.ctx.clearRect(this.x, this.y, 20, 20) console.log('This.Direction:', this.direction, 'This.oldDirection:', this.oldDirection); this.oldX = this.x this.oldY = this.y this.x = newX; this.y = newY; this.sprite.onload = () => { this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20) } } setDirection(nDirection) { this.oldDirection = this.direction this.direction = nDirection } getDirection() { return this.direction } setSnakeSpriteDirection(newDirection) { switch(true) { case this.direction == 'DOWN' && newDirection == 'LEFT': this.sprite.src = 'src/assets/img/snakeBody_rotateDownLeft.png' break case this.direction == 'UP' || this.direction == 'UP' && newDirection == 'DOWN' || this.direction == 'DOWN': this.sprite.src = 'src/assets/img/snakeBody_down.png' break; case this.direction == 'RIGHT' || this.direction == 'RIGHT' && newDirection == 'LEFT' || this.direction == 'LEFT': this.sprite.src = 'src/assets/img/snakeBody_left.png' break; default: this.sprite.src = 'src/assets/img/snakeBody_left.png' break; } this.draw() } }
Player类代码
class Player { constructor(x, y, name, ctx) { this.x = x; this.y = y; this.oldX; this.oldY; this.name = name; this.ctx = ctx; this.direction this.oldDirection this.body = [] this.sprite = new Image() this.counter = 0; } draw() { switch (this.getDirection()) { case 'UP': this.sprite.src = 'src/assets/img/snakeHead.png' break; case 'DOWN': this.sprite.src = 'src/assets/img/snakeHead_down.png' break; case 'RIGHT': this.sprite.src = 'src/assets/img/snakeHead_right.png' break; case 'LEFT': this.sprite.src = 'src/assets/img/snakeHead_left.png' break; default: this.sprite.src = 'src/assets/img/snakeHead.png' break; } this.sprite.onload = () => { this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20) } } update(newX, newY) { this.ctx.clearRect(this.x, this.y, 20, 20) this.x = newX; this.y = newY; } updateBody() { if (this.body.length > 0) { this.body.forEach((bodyPart, index) => { if( index == 0 ) { bodyPart.update(this.x, this.y, this.oldDirection) bodyPart.setSnakeSpriteDirection(this.direction) } else { bodyPart.update( this.body[index - 1].oldX, this.body[index - 1].oldY, this.body[index - 1].oldDirection ) bodyPart.setSnakeSpriteDirection(this.body[index - 1].getDirection()) } }) } } movement() { this.updateBody() this.oldY = this.y this.oldX = this.x switch (this.direction) { case 'DOWN': this.y != 380 ? this.update(this.x, this.y + 20) : this.update(this.x, 0) break; case 'UP': this.y == 0 ? this.update(this.x, 380) : this.update(this.x, this.y - 20) break; case 'LEFT': this.x == 0 ? this.update(380, this.y) : this.update(this.x - 20, this.y) break; case 'RIGHT': this.x != 380 ? this.update(this.x + 20, this.y) : this.update(0, this.y) break; default: break; } this.oldDirection = this.direction } setDirection(direction) { this.oldDirection = this.direction this.direction = direction } getDirection() { return this.direction } checkCollision(block) { if ( this.x < block.x + 20 && this.x + 20 > block.x && this.y < block.y + 20 && this.y + 20 > block.y ) { let newBody = new SnakeBody(this.x, this.y, this.ctx) newBody.setDirection() this.body.push(newBody) } } }
游戏主逻辑代码
document.addEventListener('DOMContentLoaded', (e) => { const canvasGame = document.querySelector('canvas') const ctx = canvasGame.getContext('2d') const cHeight = canvasGame.height = 400, cWidth = canvasGame.width = 400; let cells = 20; const drawGrid = () => { ctx.lineWidth = 1.1 ctx.strokeStyle = '#232332' ctx.shadowBlur = 0 for (let i = 0; i < cells; i++) { let f = (cHeight / cells) * i ctx.beginPath() ctx.moveTo(f, 0); ctx.lineTo(f, cHeight) ctx.stroke() ctx.beginPath(); ctx.moveTo(0, f) ctx.lineTo(cHeight, f) ctx.stroke() ctx.closePath() } } const drawPlayer = () => { let playerX = Math.floor(Math.random() * 19 - 0 + 1) * 20 let foodX = Math.floor(Math.random() * 19 - 0 + 1) * 20 let foodY = Math.floor(Math.random() * 19 - 0 + 1) * 20 let playerY = Math.floor(Math.random() * 19 - 0 + 1) * 20 let food = new Food(foodX, foodY, 'red', ctx) let player = new Player(playerX, playerY, 'Moises', ctx) player.draw() food.draw() document.addEventListener('keydown', (e) => { switch (e.keyCode) { case 37: player.setDirection('LEFT') break; case 39: player.setDirection('RIGHT') break case 38: player.setDirection('UP') break case 40: player.setDirection('DOWN') break; } }) setInterval(() => { player.movement() player.checkCollision(food) food.collision(player) player.draw() food.draw() drawGrid() }, 450) } drawGrid() drawPlayer() })
Food类代码
class Food { constructor(x, y, color, ctx) { this.x = x this.y = y this.color = color this.ctx = ctx } draw() { let img = new Image() img.src = 'src/assets/img/apple.png' img.onload = () => { this.ctx.drawImage(img, this.x, this.y, 20, 20); // this.ctx.fillStyle = this.color // this.ctx.fillRect(this.x, this.y, 20, 20) } } update(newX, newY) { this.ctx.clearRect(this.x, this.y, 20, 20) this.x = newX; this.y = newY; // this.ctx.fillRect(newX, newY, 20, 20) } collision(player) { let newX = (Math.floor(Math.random() * 19 - 0 + 1)) * 20; let newY = (Math.floor(Math.random() * 19 - 0 + 1)) * 20; if ( player.x < this.x + 20 && player.x + 20 > this.x && player.y < this.y + 20 && player.y + 20 > this.y ) { this.update(newX, newY) } } }
问题原因分析
- 未初始化默认方向:
SnakeBody和Player类的direction、oldDirection在构造函数中仅声明未赋值,初始值为undefined,第一次赋值时oldDirection会继承这个空值。 - 新身体块初始化错误:
Player的checkCollision方法中,创建新SnakeBody时调用newBody.setDirection()但未传参,导致新身体块的direction始终为undefined。 movement方法赋值顺序错误:movement中先执行updateBody再设置this.oldDirection = this.direction,第一次移动时oldDirection还未赋值。draw方法覆盖方向:SnakeBody的draw方法直接将direction设置为传入的headDirection,会覆盖之前的方向值,导致oldDirection无法保存上一次状态。
修复方案
1. 初始化默认方向
在构造函数中给方向属性设置初始值:
// Player构造函数 this.direction = 'RIGHT'; this.oldDirection = 'RIGHT'; // SnakeBody构造函数,新增初始方向参数 constructor(x, y, ctx, initialDirection) { this.x = x; this.y = y; this.oldX = x; this.oldY = y; this.ctx = ctx; this.direction = initialDirection; this.oldDirection = initialDirection; this.sprite = new Image(); }
2. 修复新身体块初始化
修改Player的checkCollision方法,创建身体块时传入当前头部方向:
checkCollision(block) { if ( this.x < block.x + 20 && this.x + 20 > block.x && this.y < block.y + 20 && this.y + 20 > block.y ) { let newBody = new SnakeBody(this.x, this.y, this.ctx, this.direction); this.body.push(newBody); } }
3. 调整movement方法赋值顺序
先保存旧方向,再执行移动和身体更新:
movement() { this.oldDirection = this.direction; this.oldY = this.y; this.oldX = this.x; // 移动逻辑 switch (this.direction) { case 'DOWN': this.y != 380 ? this.update(this.x, this.y + 20) : this.update(this.x, 0) break; // 其他方向逻辑... } this.updateBody(); }
4. 修改draw方法避免覆盖方向
移除draw方法中对this.direction的赋值:
draw() { this.sprite.onload = () => { this.ctx.drawImage(this.sprite, this.x, this.y, 20, 20); } }
5. 优化方向判断逻辑
修复setSnakeSpriteDirection中的条件错误,补充完整转弯场景:
setSnakeSpriteDirection(newDirection) { switch(true) { case this.direction === 'DOWN' && newDirection === 'LEFT': this.sprite.src = 'src/assets/img/snakeBody_rotateDownLeft.png'; break; case this.direction === 'UP' && newDirection === 'DOWN': this.sprite.src = 'src/assets/img/snakeBody_down.png'; break; case this.direction === 'RIGHT' && newDirection === 'LEFT': this.sprite.src = 'src/assets/img/snakeBody_left.png'; break; case this.direction === 'LEFT' && newDirection === 'RIGHT': this.sprite.src = 'src/assets/img/snakeBody_right.png'; break; case this.direction === 'DOWN' && newDirection === 'UP': this.sprite.src = 'src/assets/img/snakeBody_up.png'; break; case ['UP', 'DOWN'].includes(this.direction): this.sprite.src = 'src/assets/img/snakeBody_down.png'; break; case ['LEFT', 'RIGHT'].includes(this.direction): this.sprite.src = 'src/assets/img/snakeBody_left.png'; break; default: this.sprite.src = 'src/assets/img/snakeBody_left.png'; break; } this.draw(); }
内容的提问来源于stack exchange,提问作者gfmois
相关产品推荐
相关产品推荐

