Pong in TypeScript · Teil 2 von 2 der Serie
Teil 2: Ball, Kollisionen und Punkte
Füge den Ball hinzu, erkenne Kollisionen mit achsenparallelen Bounding Boxes, lass die Spielenden mit dem Schläger zielen und zähle die Punkte.

Quest-Ziele
- Einen Ball mit einem Geschwindigkeitsvektor bewegen
- Kollisionen zwischen zwei Rechtecken erkennen
- Den Abprallwinkel von der Trefferposition abhängig machen
- Den Punktestand zählen und zeichnen
Du hast eine Game Loop und zwei Schläger, aber noch nichts zum Schlagen. In diesem Teil kommt der Ball dazu, und mit ihm alles, was dazugehört. Der gesamte Code landet in derselben src/main.ts aus Teil 1.
Der Ball
Der Ball hat eine Position und eine Geschwindigkeit. Füge das unter deinen Schlägern ein:
const BALL_SIZE = 12;
const BALL_SPEED = 360;
const MAX_BALL_SPEED = 900;
const ball = { x: 0, y: 0, vx: 0, vy: 0 };
const score = { left: 0, right: 0 };
function resetBall(direction: 1 | -1): void {
ball.x = canvas.width / 2 - BALL_SIZE / 2;
ball.y = canvas.height / 2 - BALL_SIZE / 2;
// Up to 30 degrees up or down, so no two serves are the same.
const angle = (Math.random() - 0.5) * (Math.PI / 3);
ball.vx = Math.cos(angle) * BALL_SPEED * direction;
ball.vy = Math.sin(angle) * BALL_SPEED;
}
resetBall(1);Die Geschwindigkeit ergibt sich aus einem Winkel: cos liefert den horizontalen Anteil, sin den vertikalen. So bleibt die Gesamtgeschwindigkeit des Balls gleich, ganz egal, welcher Winkel herauskommt.
Kollisionen
Zwei Rechtecke überlappen sich, wenn sie sich auf beiden Achsen überlappen. Diese Prüfung heißt AABB (Axis-Aligned Bounding Box), und du findest sie in fast jedem 2D-Spiel.
Ein einfaches Abprallen wäre vorhersehbar, deshalb kommt der klassische Pong-Trick ins Spiel: Wo der Ball den Schläger trifft, bestimmt, wohin er fliegt. Ein Treffer nahe der Kante schickt ihn in einem steilen Winkel von bis zu 60 Grad los. So können die Spielenden zielen.
function hits(paddle: Paddle): boolean {
return (
ball.x < paddle.x + PADDLE_WIDTH &&
ball.x + BALL_SIZE > paddle.x &&
ball.y < paddle.y + PADDLE_HEIGHT &&
ball.y + BALL_SIZE > paddle.y
);
}
function bounce(paddle: Paddle, direction: 1 | -1): void {
const paddleCenter = paddle.y + PADDLE_HEIGHT / 2;
const ballCenter = ball.y + BALL_SIZE / 2;
// -1 at the top edge of the paddle, 1 at the bottom edge.
const offset = (ballCenter - paddleCenter) / (PADDLE_HEIGHT / 2);
const angle = clamp(offset, -1, 1) * (Math.PI / 3);
const speed = Math.min(Math.hypot(ball.vx, ball.vy) * 1.05, MAX_BALL_SPEED);
ball.vx = Math.cos(angle) * speed * direction;
ball.vy = Math.sin(angle) * speed;
}
function updateBall(dt: number): void {
ball.x += ball.vx * dt;
ball.y += ball.vy * dt;
const bottom = canvas.height - BALL_SIZE;
if (ball.y < 0) {
ball.y = 0;
ball.vy = Math.abs(ball.vy);
} else if (ball.y > bottom) {
ball.y = bottom;
ball.vy = -Math.abs(ball.vy);
}
if (ball.vx < 0 && hits(left)) bounce(left, 1);
if (ball.vx > 0 && hits(right)) bounce(right, -1);
if (ball.x + BALL_SIZE < 0) {
score.right += 1;
resetBall(1);
} else if (ball.x > canvas.width) {
score.left += 1;
resetBall(-1);
}
}Alles zusammenführen
Ersetze zum Schluss update() und draw(), damit sie den Ball, eine Mittellinie und den Punktestand einbeziehen:
function update(dt: number): void {
movePaddle(left, 'KeyW', 'KeyS', dt);
movePaddle(right, 'ArrowUp', 'ArrowDown', dt);
updateBall(dt);
}
function draw(): void {
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#ffffff55';
ctx.setLineDash([10, 14]);
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(canvas.width / 2, 0);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.stroke();
ctx.fillStyle = '#ffffff';
for (const paddle of [left, right]) {
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH, PADDLE_HEIGHT);
}
ctx.fillRect(ball.x, ball.y, BALL_SIZE, BALL_SIZE);
ctx.font = '40px monospace';
ctx.textAlign = 'center';
ctx.fillText(String(score.left), canvas.width / 2 - 60, 56);
ctx.fillText(String(score.right), canvas.width / 2 + 60, 56);
}Das war's, du hast ein vollständiges Pong. Schnapp dir jemanden und spiele eine Runde.
Wie es weitergeht
Baue einen einfachen Computergegner: Bewege den rechten Schläger mit begrenzter Geschwindigkeit in Richtung
ball.y.Beende das Spiel, sobald jemand 10 Punkte erreicht.
Ergänze Soundeffekte mit der Web Audio API.