You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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 = '&#8420;';
    this.obstacleArray.push([x, y])
  }
}

class Painter {
  constructor(gridToPlaceOn) {
    this.x = 0;
    this.y = 0;
    this.grid = gridToPlaceOn;
    this.direction = null;
    this.sprite = '&rarr;';
    this.spriteArr = {
      0: '&uarr;',
      1: '&rarr;',
      2: '&darr;',
      3: '&larr;'
    };
  }

  initiate() {
    this.grid.placePainter(this.x, this.y, this.sprite);
    this.direction = 1;
    this.sprite = '&rarr;';
  }

  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 10:30:56