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

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);

问题根源

  1. 代码执行顺序错误:得分判定逻辑在球位置更新之前,当球速达到一定值时,球会在一帧内直接从球网附近跳转到画布外,触发得分判定后重置,导致视觉上球消失。
  2. 球速无上限:每次击球后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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 13:55:54