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

JavaScript开发2048游戏时快速连续键盘输入异常问题排查

2048游戏快速按键输入异常问题

我正在学习JavaScript,首个项目是2048游戏。目前功能已实现,但快速连续按下键盘按键时会出现异常,我认为这与键盘输入检测逻辑有关。以下是核心处理函数:

processing = false;

document.addEventListener('keydown', function(event) {
    if (processing) {
        return;
    }

    processing = true;

    let direction;
    if (event.code === 'ArrowRight' || event.code === 'KeyD') {
        direction = "right";
    } else if (event.code == "ArrowLeft" || event.code === 'KeyA') {
        direction = "left";
    } else if (event.code =="ArrowUp" || event.code === 'KeyW') {
        direction = "up";
    } else if (event.code == "ArrowDown" || event.code === 'KeyS') {
        direction = "down";
    } else {
        processing = false;
        return;
    }
    moveAll(direction)
        .then(changed => {
            if (changed) {
                spawnTile()
            }
        })
        .then(() => {
            return checkForMoves();
        })
        .then(moveIsPossible => {
            if (!moveIsPossible) {
                doGameOver();
            }
        })
        .finally(() => {
            processing = false
        });
});

我的设计思路是:触发输入时将processing设为true,完成所有更新后才接收新输入。但仅在快速连续输入时出现问题,这与运行的动画相关(若需要可提供动画代码)。

我推测问题是两个输入同时通过了最初的processing检查,进而引发异常。请问该推测是否合理?对应的解决方法是什么?

此前我使用transitionend事件监听动画,快速输入时会因该事件未触发导致游戏冻结;改用timeout处理动画后,冻结问题解决但仍存在卡顿。


问题分析与解决方案

你的推测是否合理?

不完全准确。JavaScript是单线程执行的,理论上不可能有两个keydown事件的回调同时执行,也就不会出现两个输入同时通过processing检查的情况。问题的根源大概率出在动画与异步流程的时序不匹配:

  • 如果moveAll内部依赖timeout处理动画,但timeout的时间设置过短,实际动画还没完成就把processing设回了false,此时快速输入会触发新的移动逻辑,和未完成的动画操作冲突,导致卡顿。
  • 若spawnTile或其他后续操作也包含异步逻辑,但没有正确串联进Promise链,会导致processing提前解锁,新输入介入时前一轮操作还没完全结束。

解决方法

  • 严格对齐异步流程与动画时长
    确保moveAll返回的Promise只有在动画完全结束后才resolve。如果用timeout,要保证timeout的时长和CSS动画的transition-duration完全一致,比如动画是300ms,就用setTimeout(resolve, 300),不能随意缩短。
    另外检查spawnTile是否是异步操作,如果是,必须把它的Promise加入主流程链(当前代码里spawnTile()没有return,导致Promise链不会等待它完成):

    .then(changed => {
        if (changed) {
            return spawnTile() // 这里要return,让链等待它完成
        }
    })
    
  • 添加输入节流(可选但推荐)
    对于短时间内的重复输入(比如连续按同一个方向),可以加节流逻辑过滤无效输入:

    let lastPressTime = 0;
    document.addEventListener('keydown', function(event) {
        const now = Date.now();
        if (now - lastPressTime < 200) { // 200ms内忽略重复输入
            return;
        }
        lastPressTime = now;
        // 原有的processing检查和逻辑
    });
    
  • 修复transitionend的冻结问题(可选)
    之前用transitionend冻结,是因为快速输入会中断当前动画,导致事件不触发。可以给transitionend添加超时兜底:

    function waitForTransition(element) {
        return new Promise((resolve) => {
            const timeout = setTimeout(resolve, 300); // 兜底超时,和动画时长一致
            element.addEventListener('transitionend', function handler() {
                clearTimeout(timeout);
                element.removeEventListener('transitionend', handler);
                resolve();
            });
        });
    }
    

    这样即使动画被中断,超时也会触发resolve,避免processing一直处于true导致冻结。

  • 检查processing的作用域
    确保processing是局部变量(比如用let processing = false;代替全局变量),避免意外被其他代码修改导致状态混乱。


内容的提问来源于stack exchange,提问作者bep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:50:10