Canvas乒乓球游戏击球25次后故障,求技术解决方案
乒乓球游戏故障排查:击球25次后球穿过球网消失并误判得分
故障现象:当击球次数达到25次时,球正常飞向对方区域后穿过中间球网消失,且错误判定我方得分。
问题代码
HTML
<audio id="background-music-for-video-short-blog-era-of-cyborgs-54-second-199951.mp3" loop> <source src="background-music-for-video-short-blog-era-of-cyborgs-54-second-199951.mp3" type="audio/mp3"> Your browser does not support the audio element. </audio> <p>Click the buttons to play or pause the audio.</p> <button onclick="playAudio()" type="button">Play Audio</button> <button onclick="pauseAudio()" type="button">Pause Audio</button> <canvas id="pong" width="600" height="400"></canvas>
CSS
body { background-color: dimgray; } #pong { border: 2px solid #FFF; position: absolute; margin: auto; top: 0; right: 0; left: 0; bottom: 0; }
JavaScript
var x = document.getElementById("background-music-for-video-short-blog-era-of-cyborgs-54-second-199951.mp3"); function playAudio() { x.play(); } function pauseAudio() { x.pause(); } const canvas = document.getElementById("pong"); const ctx = canvas.getContext('2d'); let hit = new Audio(); let wall = new Audio(); let userScore = new Audio(); let comScore = new Audio(); hit.src = "sounds/hit.mp3"; wall.src = "sounds/wall.mp3"; comScore.src = "sounds/comScore.mp3"; userScore.src = "sounds/userScore.mp3"; const ball = { x: canvas.width / 2, y: canvas.height / 2, radius: 10, velocityX: 5, velocityY: 5, speed: 7, color: "WHITE" } const user = { x: 0, y: (canvas.height - 100) / 2, width: 10, height: 100, score: 0, color: "WHITE" } const com = { x: canvas.width - 10, y: (canvas.height - 100) / 2, width: 10, height: 100, score: 0, color: "WHITE" } const net = { x: (canvas.width - 2) / 2, y: 0, height: 10, width: 2, color: "WHITE" } function drawRect(x, y, w, h, color) { ctx.fillStyle = color; ctx.fillRect(x, y, w, h); } function drawArc(x, y, r, color) { ctx.fillStyle = color; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2, true); ctx.closePath(); ctx.fill(); } canvas.addEventListener("mousemove", getMousePos); function getMousePos(evt) { let rect = canvas.getBoundingClientRect(); user.y = evt.clientY - rect.top - user.height / 2; } function resetBall() { ball.x = canvas.width / 2; ball.y = canvas.height / 2; ball.velocityX = -ball.velocityX; ball.speed = 7; } function drawNet() { for (let i = 0; i <= canvas.height; i += 15) { drawRect(net.x, net.y + i, net.width, net.height, net.color); } } function drawText(text, x, y) { ctx.fillStyle = "#FFF"; ctx.font = "75px fantasy"; ctx.fillText(text, x, y); } function collision(b, p) { p.top = p.y; p.bottom = p.y + p.height; p.left = p.x; p.right = p.x + p.width; b.top = b.y - b.radius; b.bottom = b.y + b.radius; b.left = b.x - b.radius; b.right = b.x + b.radius; return p.left < b.right && p.top < b.bottom && p.right > b.left && p.bottom > b.top; } function update() { if (ball.x - ball.radius < 0) { com.score++; comScore.play(); resetBall(); } else if (ball.x + ball.radius > canvas.width) { user.score++; userScore.play(); resetBall(); } ball.x += ball.velocityX; ball.y += ball.velocityY; let computerLevel = 1; com.y += (ball.y - (com.y + com.height / 2)) * computerLevel; if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) { ball.velocityY = -ball.velocityY; wall.play(); } let player = (ball.x + ball.radius < canvas.width / 2) ? user : com; if (collision(ball, player)) { hit.play(); let collidePoint = (ball.y - (player.y + player.height / 2)); collidePoint = collidePoint / (player.height / 2); let angleRad = (Math.PI / 4) * collidePoint; let direction = (ball.x + ball.radius < canvas.width / 2) ? 1 : -1; ball.velocityX = direction * ball.speed * Math.cos(angleRad); ball.velocityY = ball.speed * Math.sin(angleRad); ball.speed += 0.1; } } function render() { drawRect(0, 0, canvas.width, canvas.height, "#000"); drawText(user.score, canvas.width / 4, canvas.height / 5); drawText(com.score, 3 * canvas.width / 4, canvas.height / 5); drawNet(); drawRect(user.x, user.y, user.width, user.height, user.color); drawRect(com.x, com.y, com.width, com.height, com.color); drawArc(ball.x, ball.y, ball.radius, ball.color); } function game() { update(); render(); } let framePerSecond = 50; let loop = setInterval(game, 1000 / framePerSecond);
问题根源
- 代码执行顺序错误:得分判定逻辑在球位置更新之前,当球速达到一定值时,球会在一帧内直接从球网附近跳转到画布外,触发得分判定后重置,导致视觉上球消失。
- 球速无上限:每次击球后
ball.speed += 0.1,25次击球后球速达到9.5,高速下球可能直接穿过对方 paddle,无法触发碰撞检测,直接飞向边界。
修复方案
修改后的update函数
function update() { // 先更新球位置,再判定得分 ball.x += ball.velocityX; ball.y += ball.velocityY; if (ball.x - ball.radius < 0) { com.score++; comScore.play(); resetBall(); } else if (ball.x + ball.radius > canvas.width) { user.score++; userScore.play(); resetBall(); } let computerLevel = 1; com.y += (ball.y - (com.y + com.height / 2)) * computerLevel; if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) { ball.velocityY = -ball.velocityY; wall.play(); } let player = (ball.x + ball.radius < canvas.width / 2) ? user : com; if (collision(ball, player)) { hit.play(); let collidePoint = (ball.y - (player.y + player.height / 2)); collidePoint = collidePoint / (player.height / 2); let angleRad = (Math.PI / 4) * collidePoint; let direction = (ball.x + ball.radius < canvas.width / 2) ? 1 : -1; ball.velocityX = direction * ball.speed * Math.cos(angleRad); ball.velocityY = ball.speed * Math.sin(angleRad); // 限制球速上限,避免速度过快导致异常 if (ball.speed < 12) { ball.speed += 0.1; } } }
修复说明
- 调整代码顺序,先更新球位置再执行得分判定,确保逻辑基于最新的球坐标。
- 增加球速上限,防止球速无限增长导致碰撞检测失效或视觉异常,同时保留游戏的挑战性。
内容的提问来源于stack exchange,提问作者gio
相关产品推荐
相关产品推荐

