为什么乒乓球会卡在底部

Why does the ball in pong get stuck at the bottom?

本文关键字:底部 乒乓球 为什么      更新时间:2023-09-26

我最近制作了一款JS Pong游戏。它工作得很好,但球很少卡在底部或顶部。它看起来像是穿过了墙的一半,并且不断地弹跳。问题发生的视频。你可以在这里试试这个游戏。我不知道为什么会发生这个问题,因为逻辑似乎是正确的,90%的时间都是正确的。以下是我的程序的两个主要功能:

function moveAll() {
  if (showingWinScreen) {
    return;
  }
  computerMovement();
  ballX += ballSpeedX;
  ballY += ballSpeedY;
  if (ballY <= 10) {
    ballSpeedY = -ballSpeedY;
  } else if (ballY >= HEIGHT - 10) {
    ballSpeedY = -ballSpeedY;
  }
  if (ballX >= WIDTH - 10) {
    if ((ballY > paddleY) && (ballY < paddleY + 100)) {
      ballSpeedX = -ballSpeedX;
      var deltaY = ballY - paddleY - 50;
      ballSpeedY = deltaY / 5;
    } else {
      player1Score++;
      ballReset();
    }
  } else if (ballX <= 10) {
    if ((ballY > mouseY - 50) && (ballY < mouseY + 50)) {
      ballSpeedX = -ballSpeedX;
      deltaY = ballY - mouseY;
      ballSpeedY = deltaY / 6;
    } else {
      player2Score++;
      ballReset();
    }
  }
}
function drawAll() {
  if (showingWinScreen) {
    colorRect(0, 0, WIDTH, HEIGHT, "black");
    canvas.fillStyle = "yellow";
    canvas.fillText("Click to continue!", 300, 300);
    if (player1Score == WINNING_SCORE) {
      canvas.fillText("You won!", 360, 500);
    } else if (player2Score == WINNING_SCORE) {
      canvas.fillText("The computer beat you!", 280, 500);
    }
    return;
  }
  colorRect(0, 0, WIDTH, HEIGHT, "black");
  drawNet();
  makeCircle(ballX, ballY, 10, 0, Math.PI * 2, "red");
  colorRect(790, paddleY, 10, 100, "cyan");
  colorRect(0, mouseY - 50, 10, 100, "yellow");
  canvas.fillStyle = "white";
  canvas.fillText(player1Score + "    " + player2Score, 360, 100);
}

谢谢你的帮助!

我认为只有一种情况会发生这种情况:当在碰撞帧中降低速度时。

当速度保持不变时,无论怎样,你的球都会弹回到前一帧的位置:

var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
  Ball(50, 50, 0, 5, 5, "red"),
  Ball(100, 50, 0, 5, 10, "blue"),
  Ball(150, 50, 0, 5, 15, "green"),
  Ball(200, 50, 0, 5, 20, "yellow")
];
var next = () => {
  updateFrame(balls);
  drawFrame(balls);
}
var loop = () => {
  requestAnimationFrame(() => {
    next();
    loop();
  });
}
next();
function Ball(x, y, vx, vy, r, color) {
  return {
    x: x,
    y: y,
    vx: vx,
    vy: vy,
    r: r,
    color: color
  }
};
function updateBall(b) {
  b.x += b.vx;
  b.y += b.vy;
  
  if (b.y <= b.r ||
      b.y >= cvs.height - b.r) {
     b.vy *= -1; 
  }
};
function drawBall(b) {
  ctx.beginPath();
  ctx.fillStyle = b.color;
  ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
  ctx.fill();
}
function updateFrame(balls) {
   balls.forEach(updateBall); 
}
function drawFrame(balls) {
  ctx.clearRect(0, 0, cvs.width, cvs.height);
  balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>

但当速度变化时,事情就会陷入困境:

var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
  Ball(50, 50, 0, 10, 5, "red"),
  Ball(100, 50, 0, 10, 10, "blue"),
  Ball(150, 50, 0, 10, 15, "green"),
  Ball(200, 50, 0, 10, 20, "yellow")
];
var next = () => {
  updateFrame(balls);
  drawFrame(balls);
}
var loop = () => {
  requestAnimationFrame(() => {
    next();
    loop();
  });
}
next();
function Ball(x, y, vx, vy, r, color) {
  return {
    x: x,
    y: y,
    vx: vx,
    vy: vy,
    r: r,
    color: color
  }
};
function updateBall(b) {
  b.x += b.vx;
  b.y += b.vy;
  
  if (b.y <= b.r ||
      b.y >= cvs.height - b.r) {
     b.vy *= -0.5; 
  }
};
function drawBall(b) {
  ctx.beginPath();
  ctx.fillStyle = b.color;
  ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
  ctx.fill();
}
function updateFrame(balls) {
   balls.forEach(updateBall); 
}
function drawFrame(balls) {
  ctx.clearRect(0, 0, cvs.width, cvs.height);
  balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>

在你的情况下,我认为只有当同时发生桨叶碰撞和墙壁碰撞时,才会发生这种情况

一个快速实现的解决方案是在平移球位置之前检查新位置是否有效。如果不需要精确的位置,可以将球放置在碰撞点。请注意,这将产生一个稍微偏离的框架。

例如:

var newY = ballY + ballSpeedY;
// Top wall
if(newY <= 10) {
  ballY = 10;
  ballSpeedY = -ballSpeedY;  
}
// Bottom wall
else if(newY >= HEIGHT-10){
  ballY = HEIGHT - 10;
  ballSpeedY = -ballSpeedY;   
}
// No collision
else {
  ballY = newY;
}

更新:对可能发生的情况进行更详细的描述

假设你的球与画布的顶部边界碰撞,与同一帧中的球拍碰撞

首先,将球移动到碰撞位置:ballY += ballSpeedY;假设ballY为4,ballSpeedY为-5,则将球定位到墙内的-1

如果这是唯一的碰撞,你应该没事。你翻转速度(ballSpeedY = -ballSpeedY(,所以在下一帧中,你的球应该回到-1 + 5 = 4,所以ballY将再次成为4,你的球将在下一个帧中向4 + 5 = 9移动。

现在出现了一个问题,当在-1定位的框架中时,您也会与桨板碰撞!当球拍击球时,可以修改球速:ballSpeedY = deltaY / 5;。如果这是< 1,你的球将无法在下一帧中出墙。例如,您的球将移动到:-1 + 0.5 = -0.5,而不是-1 + 5 = 4

现在,你的球将无法回到比赛中,因为下一帧将再次计算碰撞并翻转速度当球被卡住时,会产生弹性、颤抖的效果

一个天真但相当不错的解决方案是,只将球的位置更新到有效的位置。也就是说:永远不要碰撞坐标。

var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
        window.setTimeout(callback, 1000 / 60)
    };
var canvas = document.createElement("canvas");
var width = 400;
var height = 600;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(200, 300);
var keysDown = {};
var render = function () {
    context.fillStyle = "#FF00FF";
    context.fillRect(0, 0, width, height);
    player.render();
    computer.render();
    ball.render();
};
var update = function () {
    player.update();
    computer.update(ball);
    ball.update(player.paddle, computer.paddle);
};
var step = function () {
    update();
    render();
    animate(step);
};
function Paddle(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.x_speed = 0;
    this.y_speed = 0;
}
Paddle.prototype.render = function () {
    context.fillStyle = "#0000FF";
    context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function (x, y) {
    this.x += x;
    this.y += y;
    this.x_speed = x;
    this.y_speed = y;
    if (this.x < 0) {
        this.x = 0;
        this.x_speed = 0;
    } else if (this.x + this.width > 400) {
        this.x = 400 - this.width;
        this.x_speed = 0;
    }
};
function Computer() {
    this.paddle = new Paddle(175, 10, 50, 10);
}
Computer.prototype.render = function () {
    this.paddle.render();
};
Computer.prototype.update = function (ball) {
    var x_pos = ball.x;
    var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
    if (diff < 0 && diff < -4) {
        diff = -5;
    } else if (diff > 0 && diff > 4) {
        diff = 5;
    }
    this.paddle.move(diff, 0);
    if (this.paddle.x < 0) {
        this.paddle.x = 0;
    } else if (this.paddle.x + this.paddle.width > 400) {
        this.paddle.x = 400 - this.paddle.width;
    }
};
function Player() {
    this.paddle = new Paddle(175, 580, 50, 10);
}
Player.prototype.render = function () {
    this.paddle.render();
};
Player.prototype.update = function () {
    for (var key in keysDown) {
        var value = Number(key);
        if (value == 37) {
            this.paddle.move(-4, 0);
        } else if (value == 39) {
            this.paddle.move(4, 0);
        } else {
            this.paddle.move(0, 0);
        }
    }
};
function Ball(x, y) {
    this.x = x;
    this.y = y;
    this.x_speed = 0;
    this.y_speed = 3;
}
Ball.prototype.render = function () {
    context.beginPath();
    context.arc(this.x, this.y, 5, 2 * Math.PI, false);
    context.fillStyle = "#000000";
    context.fill();
};
Ball.prototype.update = function (paddle1, paddle2) {
    this.x += this.x_speed;
    this.y += this.y_speed;
    var top_x = this.x - 5;
    var top_y = this.y - 5;
    var bottom_x = this.x + 5;
    var bottom_y = this.y + 5;
    if (this.x - 5 < 0) {
        this.x = 5;
        this.x_speed = -this.x_speed;
    } else if (this.x + 5 > 400) {
        this.x = 395;
        this.x_speed = -this.x_speed;
    }
    if (this.y < 0 || this.y > 600) {
        this.x_speed = 0;
        this.y_speed = 3;
        this.x = 200;
        this.y = 300;
    }
    if (top_y > 300) {
        if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
            this.y_speed = -3;
            this.x_speed += (paddle1.x_speed / 2);
            this.y += this.y_speed;
        }
    } else {
        if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
            this.y_speed = 3;
            this.x_speed += (paddle2.x_speed / 2);
            this.y += this.y_speed;
        }
    }
};
document.body.appendChild(canvas);
animate(step);
window.addEventListener("keydown", function (event) {
    keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
    delete keysDown[event.keyCode];
});

http://jsfiddle.net/kHJr6/2/