如何让JavaScript代码逐行慢执行?类似draw()函数的实现方案
解决角色动作逐行慢速执行的问题
问题描述
我正在开发一款“自定义冒险”类游戏,已搭建模块可通过character.move()等方法控制角色移动,但存在以下问题:当连续调用多个方法时,例如:
character.move() character.move() character.turnLeft() character.move()
代码会瞬间执行完毕,角色直接跳到最终位置,我希望能让代码逐行、慢速执行。
使用setInterval(() => {character.move()}, 400)能实现类似效果,但添加其他操作时仍会瞬间执行。
我想了解是否存在类似draw()函数的机制,可实现代码逐行执行,或有其他可行的解决方案。
可行解决方案
方案1:异步队列调度器
核心思路是将所有要执行的动作存入队列,通过异步方法按固定间隔逐个执行,确保动作按顺序分步完成。
在bob.initiate()下方添加以下代码:
// 动作调度器:按间隔执行动作队列 async function runActions(actions, interval = 400) { for (const action of actions) { await new Promise(resolve => setTimeout(resolve, interval)); action(); } } // 定义需要执行的动作序列 const actions = [ () => bob.move(), () => bob.move(), () => bob.turnLeft(), () => bob.move() ]; // 启动调度 runActions(actions);
该方案灵活性高,支持任意动作组合,完全按顺序逐行执行,每个动作间的间隔可自定义。
方案2:生成器函数分步执行
利用JavaScript生成器的暂停/恢复特性,配合定时器模拟逐行执行的效果:
// 定义动作生成器 function* actionGenerator() { bob.move(); yield new Promise(resolve => setTimeout(resolve, 400)); bob.move(); yield new Promise(resolve => setTimeout(resolve, 400)); bob.turnLeft(); yield new Promise(resolve => setTimeout(resolve, 400)); bob.move(); } // 执行生成器 function runGenerator(gen) { const nextStep = gen.next(); if (!nextStep.done) { if (nextStep.value instanceof Promise) { nextStep.value.then(() => runGenerator(gen)); } else { setTimeout(() => runGenerator(gen), 400); } } } // 启动执行 runGenerator(actionGenerator());
这种方式更贴近“逐行编写、逐行执行”的直观逻辑,每个yield对应一次等待间隔。
方案3:异步包装角色方法
将角色的动作方法包装为异步方法,调用时通过await实现自动等待,写法接近原同步代码:
// 包装方法,添加延迟逻辑 function wrapWithDelay(method, delay = 400) { return async function() { await new Promise(resolve => setTimeout(resolve, delay)); method.call(this); }; } // 替换bob的原有方法 bob.move = wrapWithDelay(bob.move); bob.turnLeft = wrapWithDelay(bob.turnLeft); // 按顺序执行动作 async function runSequence() { await bob.move(); await bob.move(); await bob.turnLeft(); await bob.move(); } runSequence();
此方案无需修改原有动作序列的编写逻辑,只需通过await关键字控制执行顺序。
完整示例代码(以方案1为例)
class HTMLGrid { constructor(w, h) { this.w = w; this.h = h; this.gridArray = this.create(); this.painterPosition = null; this.obstacleArray = []; } create() { let tempArr = [] for (let i = 0; i < this.w; i++) { for (let j = 0; j < this.h; j++) { tempArr.push([i, j]); } } return tempArr; } initiate() { for (let i = 0; i < this.gridArray.length; i++) { if (!document.getElementById(this.gridArray[i][0])) { let row = document.createElement('tr'); row.id = 'row' + this.gridArray[i][0]; document.getElementById('holder').appendChild(row); } let cell = document.createElement('td'); cell.id = 'cell' + this.gridArray[i]; document.getElementById('row' + this.gridArray[i][0]).appendChild(cell); } } changeColorAtIndex(x, y, color) { document.getElementById('cell' + [x, y]).style.backgroundColor = color; if (color == 'black') { document.getElementById('cell' + [x, y]).style.color = 'white'; } else document.getElementById('cell' + [x, y]).style.color = 'black'; } returnColorIndex(x, y) { return document.getElementById('cell' + [x, y]).style.backgroundColor; } placePainter(x, y, sprite) { if (!document.getElementById('cell' + [x, y])) { console.log('Painter Cannot Move Out of Bounds'); } else if (this.painterPosition === null) { document.getElementById('cell' + [x, y]).innerHTML = sprite; this.painterPosition = [x, y]; } else { document.getElementById('cell' + this.painterPosition).innerText = ''; document.getElementById('cell' + [x, y]).innerHTML = sprite; this.painterPosition = [x, y]; } } returnPainterPosition() { return this.painterPosition; } changeSprite(x, y, sprite) { document.getElementById('cell' + [x, y]).innerHTML = sprite; } isValidSquare(x, y) { if (this.obstacleArray.length === 0) return this.gridArray.flat().includes(x, y) else { if (this.gridArray.join('|').indexOf([x, y] + '|') == -1) { return this.gridArray.join('|').indexOf([x, y] + '|') !== -1 } else { return !this.obstacleArray.flat().includes(x, y) } } } createObstacle(x, y) { document.getElementById('cell' + [x, y]).innerHTML = '⃤'; this.obstacleArray.push([x, y]) } } class Painter { constructor(gridToPlaceOn) { this.x = 0; this.y = 0; this.grid = gridToPlaceOn; this.direction = null; this.sprite = '→'; this.spriteArr = { 0: '↑', 1: '→', 2: '↓', 3: '←' }; } initiate() { this.grid.placePainter(this.x, this.y, this.sprite); this.direction = 1; this.sprite = '→'; } directionToMove() { let tempX = this.x, tempY = this.y; if (this.direction == 0) tempX -= 1; else if (this.direction == 1) tempY += 1; else if (this.direction == 2) tempX += 1; else tempY -= 1; return [tempX, tempY] } move() { let moveTo = this.directionToMove(); this.grid.placePainter(moveTo[0], moveTo[1], this.sprite); this.x = moveTo[0]; this.y = moveTo[1]; } canMove() { let direction = this.directionToMove() return this.grid.isValidSquare(direction[0], direction[1]) } turnRight() { this.direction += 1; if (this.direction == 4) this.direction = 0; this.sprite = this.spriteArr[this.direction]; this.grid.changeSprite(this.x, this.y, this.sprite); } turnLeft() { this.turnRight(); this.turnRight(); this.turnRight(); } turnAround() { this.turnRight(); this.turnRight(); } paint(color) { this.grid.changeColorAtIndex(this.x, this.y, color); } scrapePaint() { this.grid.changeColorAtIndex(this.x, this.y, '#808080'); } isOnPaint() { return this.grid.returnColorIndex(this.x, this.y) != '#808080'; } getColor() { return this.grid.returnColorIndex(this.x, this.y) == '' ? '#808080' : this.grid.returnColorIndex(this.x, this.y); } getX() { return this.x; } getY() { return this.y; } isFacingNorth() { return this.direction == 0; } isFacingSouth() { return this.direction == 2; } isFacingEast() { return this.direction == 1; } isFacingWest() { return this.direction == 3; } getDirection() { if (this.isFacingNorth()) return 'north'; else if (this.isFacingSouth()) return 'south'; else if (this.isFacingEast()) return 'east'; else return 'west'; } } let grid = new HTMLGrid(8, 8); let bob = new Painter(grid); grid.initiate(); bob.initiate(); // 新增的动作调度器代码 async function runActions(actions, interval = 400) { for (const action of actions) { await new Promise(resolve => setTimeout(resolve, interval)); action(); } } const actions = [ () => bob.move(), () => bob.move(), () => bob.turnLeft(), () => bob.move() ]; runActions(actions);
table { border-collapse: collapse; } td { height: 40px; width: 40px; text-align: center; border: 1px solid white; border-radius: 5px; border-color: white; background-color: #808080; }
<table id='holder'></table>
内容的提问来源于stack exchange,提问作者Zain Wilson - WCH Student
相关产品推荐
相关产品推荐

