JavaScript游戏Bug:收集目标方块后碰撞触发异常,重启按钮不显示
问题根源与修复方案
核心问题分析
- 重复的碰撞检测定时器:代码中为
playerCollision创建了两个独立定时器,playerHide仅清理了playerCollisionInterval,另一个定时器未被清理,导致碰撞检测持续触发gameOver函数。 objects数组未同步更新:绿色障碍物到期移除时,仅从DOM中删除,但未从objects数组中移除对应元素,导致碰撞检测时访问已不存在的DOM节点,引发报错中断gameOver流程。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
相关产品推荐
相关产品推荐

