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

JavaScript游戏Bug:收集目标方块后碰撞触发异常,重启按钮不显示

问题根源与修复方案

核心问题分析

  1. 重复的碰撞检测定时器:代码中为playerCollision创建了两个独立定时器,playerHide仅清理了playerCollisionInterval,另一个定时器未被清理,导致碰撞检测持续触发gameOver函数。
  2. objects数组未同步更新:绿色障碍物到期移除时,仅从DOM中删除,但未从objects数组中移除对应元素,导致碰撞检测时访问已不存在的DOM节点,引发报错中断gameOver流程。
  3. gameOver未做防重入处理:未判断dead状态,导致多次调用gameOver,重复添加restartButton到DOM引发错误,后续代码无法执行。

修复后的完整代码

document.body.style.overflow = 'hidden';
var dead = false;

/* 创建游戏容器 */
var game = document.createElement('div');
game.style.width = '100%';
game.style.height = '100%';
game.style.backgroundColor = '#000';
game.style.color = '#fff';
game.style.fontFamily = 'monospace';
game.style.fontSize = '20px';
game.style.textAlign = 'center';
game.style.paddingTop = '50px';
game.innerHTML = '<div id="game-title">Dodge the Particles! </div><p style="font-size: small;">(But eat the pink ones!)</p><div id="game-content"></div>';
document.body.appendChild(game);

/* 创建玩家角色 */
var player = document.createElement('div');
player.style.width = '20px';
player.style.height = '20px';
player.style.backgroundColor = '#00f';
player.style.position = 'absolute';
player.style.left = '50%';
player.style.top = '50%';
player.style.borderRadius = '10px';
player.style.marginLeft = '-10px';
player.style.marginTop = '-10px';
document.getElementById('game-content').appendChild(player);

/* 鼠标跟随逻辑 */
var mouseX = 0;
var mouseY = 0;
document.onmousemove = function(e) {
  mouseX = e.pageX;
  mouseY = e.pageY;
};
var playerX = window.innerWidth / 2;
var playerY = window.innerHeight / 2;
var playerSpeed = 3;
var playerMove = function() {
  var dx = mouseX - playerX;
  var dy = mouseY - playerY;
  var dist = Math.sqrt(dx * dx + dy * dy);
  if (dist > playerSpeed) {
    dx *= playerSpeed / dist;
    dy *= playerSpeed / dist;
  }
  playerX += dx;
  playerY += dy;
  player.style.left = playerX + 'px';
  player.style.top = playerY + 'px';
};

/* 绿色障碍物生成逻辑 */
var objects = [];
var objectSpeed = 5;
var objectSize = 10;
var objectLifetime = 6000;
var objectSpawn = function() {
  var object = document.createElement('div');
  object.style.width = objectSize + 'px';
  object.style.height = objectSize + 'px';
  object.style.backgroundColor = '#0f0';
  object.style.position = 'absolute';
  object.style.left = '50%';
  object.style.top = '50%';
  object.style.marginLeft = '-' + (objectSize / 2) + 'px';
  object.style.marginTop = '-' + (objectSize / 2) + 'px';
  document.getElementById('game-content').appendChild(object);
  
  var side = Math.floor(Math.random() * 4);
  var x = 0;
  var y = 0;
  if (side === 0) {
    x = Math.random() * window.innerWidth;
    y = 0;
  } else if (side === 1) {
    x = window.innerWidth;
    y = Math.random() * window.innerHeight;
  } else if (side === 2) {
    x = Math.random() * window.innerWidth;
    y = window.innerHeight;
  } else if (side === 3) {
    x = 0;
    y = Math.random() * window.innerHeight;
  }
  
  var dx = playerX - x;
  var dy = playerY - y;
  var dist = Math.sqrt(dx * dx + dy * dy);
  dx *= objectSpeed / dist;
  dy *= objectSpeed / dist;
  
  var objectMove = function() {
    x += dx;
    y += dy;
    object.style.left = x + 'px';
    object.style.top = y + 'px';
  };
  var objectRemove = function() {
    if (object.isConnected) {
      document.getElementById('game-content').removeChild(object);
    }
    clearInterval(objectMoveInterval);
    // 同步更新objects数组
    var index = objects.indexOf(object);
    if (index !== -1) {
      objects.splice(index, 1);
    }
  };
  var objectMoveInterval = setInterval(objectMove, 10);
  setTimeout(objectRemove, objectLifetime);
  objects.push(object);
};
var objectSpawnInterval = setInterval(objectSpawn, 500);

/* 玩家碰撞检测 */
var playerSize = 20;
var playerCollision = function() {
  if (dead) return;
  for (var i = 0; i < objects.length; i++) {
    var object = objects[i];
    if (!object.isConnected) continue;
    var x = parseInt(object.style.left);
    var y = parseInt(object.style.top);
    var dx = playerX - x;
    var dy = playerY - y;
    var dist = Math.sqrt(dx * dx + dy * dy);
    if (dist < playerSize / 2 + objectSize / 2) {
      gameOver();
      break;
    }
  }
};

/* 粉色分数方块逻辑 */
var score = 0;
var scoreElement = document.createElement('div');
scoreElement.style.position = 'absolute';
scoreElement.style.left = '0px';
scoreElement.style.top = '20px';
scoreElement.style.width = '100%';
scoreElement.style.textAlign = 'center';
scoreElement.innerHTML = 'Score: ' + score;
document.getElementById('game-content').appendChild(scoreElement);

var scoreObject = document.createElement('div');
scoreObject.style.width = objectSize + 'px';
scoreObject.style.height = objectSize + 'px';
scoreObject.style.backgroundColor = '#f0f';
scoreObject.style.position = 'absolute';
scoreObject.style.left = '50%';
scoreObject.style.top = '50%';
scoreObject.style.marginLeft = '-' + (objectSize / 2) + 'px';
scoreObject.style.marginTop = '-' + (objectSize / 2) + 'px';
document.getElementById('game-content').appendChild(scoreObject);

var scoreObjectX = 0;
var scoreObjectY = 0;
var scoreObjectMove = function() {
  scoreObjectX = Math.random() * window.innerWidth;
  scoreObjectY = Math.random() * window.innerHeight;
  scoreObject.style.left = scoreObjectX + 'px';
  scoreObject.style.top = scoreObjectY + 'px';
};
scoreObjectMove();

var scoreObjectCollision = function() {
  if (dead) return;
  var dx = playerX - scoreObjectX;
  var dy = playerY - scoreObjectY;
  var dist = Math.sqrt(dx * dx + dy * dy);
  if (dist < playerSize / 2 + objectSize / 2) {
    score++;
    scoreElement.innerHTML = 'Score: ' + score;
    scoreObjectMove();
  }
};

/* 统一管理游戏定时器 */
var playerMoveInterval = setInterval(playerMove, 10);
var playerCollisionInterval = setInterval(playerCollision, 10);
var playerScoreInterval = setInterval(scoreObjectCollision, 10);

var playerHide = function() {
  player.style.display = 'none';
  clearInterval(playerMoveInterval);
  clearInterval(playerCollisionInterval);
  clearInterval(playerScoreInterval);
  dead = true;
};

/* 游戏结束逻辑 */
var gameOver = function() {
  if (dead) return;
  document.getElementById('game-title').innerHTML = 'Game Over';
  clearInterval(objectSpawnInterval);
  
  // 清理所有障碍物
  for (var i = 0; i < objects.length; i++) {
    var obj = objects[i];
    if (obj.isConnected) {
      document.getElementById('game-content').removeChild(obj);
    }
  }
  objects = [];
  
  playerHide();
  scoreObject.style.display = 'none';
  
  // 确保按钮未在DOM中时再添加
  if (!restartButton.isConnected) {
    document.getElementById('game-content').appendChild(restartButton);
  }
};

/* 重启按钮逻辑 */
var restartButton = document.createElement('div');
restartButton.style.width = '100px';
restartButton.style.height = '50px';
restartButton.style.backgroundColor = '#fff';
restartButton.style.color = '#000';
restartButton.style.position = 'absolute';
restartButton.style.zIndex = '1';
restartButton.style.left = '50%';
restartButton.style.top = '50%';
restartButton.style.marginLeft = '-50px';
restartButton.style.marginTop = '-25px';
restartButton.style.textAlign = 'center';
restartButton.style.lineHeight = '50px';
restartButton.style.cursor = 'pointer';
restartButton.innerHTML = 'Restart';

restartButton.onclick = function() {
  document.getElementById('game-title').innerHTML = 'Dodge the Particles! ';
  player.style.display = 'block';
  scoreObject.style.display = 'block';
  
  // 重置玩家位置
  playerX = window.innerWidth / 2;
  playerY = window.innerHeight / 2;
  player.style.left = playerX + 'px';
  player.style.top = playerY + 'px';
  
  // 重启定时器
  playerMoveInterval = setInterval(playerMove, 10);
  playerCollisionInterval = setInterval(playerCollision, 10);
  playerScoreInterval = setInterval(scoreObjectCollision, 10);
  
  // 重置分数与粉色方块位置
  score = 0;
  scoreElement.innerHTML = 'Score: ' + score;
  scoreObjectMove();
  
  // 重启障碍物生成
  objectSpawnInterval = setInterval(objectSpawn, 500);
  dead = false;
  
  document.getElementById('game-content').removeChild(restartButton);
};

关键修复点说明

  • 移除重复定时器:删除了playerCollision的冗余定时器,仅保留playerCollisionInterval管理的实例,确保死亡后能完全停止碰撞检测。
  • 同步objects数组:在障碍物移除函数中同步更新数组,避免碰撞检测时访问无效DOM节点。
  • 添加状态判断:在碰撞检测和游戏结束函数中加入dead状态校验,防止重复触发游戏结束流程。
  • 完善重启逻辑:重启时重置玩家位置、重新启动障碍物生成,确保游戏完全回到初始状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:40:35