Why Build Pong for Learning Game Development
Pong represents an ideal entry point into game development for several compelling reasons. The game's core mechanics involve moving two paddles, bouncing a ball, and detecting collisions--these fundamental concepts appear in virtually every game ever created.
By building Pong, you learn how to:
- Render graphics using scalable vector elements
- Handle user input from keyboard interactions
- Detect collisions between objects in 2D space
- Track game state including scores and positions
- Implement AI opponents with adjustable difficulty
SVG.js simplifies this approach by providing an intuitive API for creating, manipulating, and animating SVG elements. The library has no external dependencies, making it lightweight and straightforward to integrate into any project. If you're interested in exploring how SVG powers interactive web experiences, our web development services team can help you build similar interactive applications for your business.
Setting Up SVG.js and Your Development Environment
Installing and Including SVG.js
SVG.js offers multiple installation options to suit different development workflows. For npm-based projects, install the library using your preferred package manager:
npm install @svgdotjs/svg.js
For projects without a build step, include SVG.js directly from a CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.2.0/svg.min.js"></script>
Initialize the SVG canvas by selecting an HTML container and specifying dimensions:
const width = 450;
const height = 300;
const draw = SVG('pong').size(width, height);
The SVG() function accepts either a DOM element ID or a direct reference to an HTML element.
Drawing Game Elements with SVG.js
Creating the Court and Background
Every game needs a proper playing surface. The court establishes boundaries and provides visual context for gameplay:
// Create background covering the entire game area
const background = draw.rect(width, height).fill('#E3E8E6');
// Draw center dividing line with dashed styling
const centerLine = draw.line(width / 2, 0, width / 2, height);
centerLine.stroke({
width: 5,
color: '#ffffff',
dasharray: '5, 5'
});
Creating Paddles and the Ball
Game pieces require precise positioning and distinct colors for each player:
const paddleWidth = 20;
const paddleHeight = 100;
// Create left paddle with green fill
const paddleLeft = draw.rect(paddleWidth, paddleHeight);
paddleLeft.x(0).cy(height / 2).fill('#00ff99');
// Create right paddle by cloning
const paddleRight = paddleLeft.clone();
paddleRight.x(width - paddleWidth).fill('#ff0066');
// Create ball element
const ball = draw.circle(ballSize);
ball.center(width / 2, height / 2).fill('#7f7f7f');
Implementing Game Logic and the Game Loop
Understanding the Animation Loop
Games require continuous updates to reflect changing state. The requestAnimationFrame API provides smooth, efficient animation:
let lastTime = null;
function gameLoop(timestamp) {
const dt = lastTime ? (timestamp - lastTime) / 1000 : 0;
lastTime = timestamp;
update(dt);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
The dt (delta time) value enables frame-rate-independent movement.
Ball Movement and Wall Collisions
let vx = 150;
let vy = 100;
function update(dt) {
ball.dmove(vx * dt, vy * dt);
const cx = ball.cx();
const cy = ball.cy();
// Bounce off top and bottom walls
if ((vy < 0 && cy <= 0) || (vy > 0 && cy >= height)) {
vy = -vy;
}
// Bounce off left and right walls
if ((vx < 0 && cx <= 0) || (vx > 0 && cx >= width)) {
vx = -vx;
}
}
Paddle Collision Detection
if ((vx < 0 && cx <= paddleWidth && cy > paddleLeftY && cy < paddleLeftY + paddleHeight) ||
(vx > 0 && cx >= width - paddleWidth && cy > paddleRightY && cy < paddleRightY + paddleHeight)) {
const hitPosition = cy - ((vx < 0 ? paddleLeftY : paddleRightY) + paddleHeight / 2);
vy = hitPosition * 7;
vx = -vx * 1.05;
}
Adding Player Controls and AI Opponent
Keyboard Input for Player Control
let paddleDirection = 0;
const paddleSpeed = 300;
SVG.on(document, 'keydown', function(e) {
if (e.keyCode === 38) paddleDirection = -1;
if (e.keyCode === 40) paddleDirection = 1;
});
SVG.on(document, 'keyup', function(e) {
if (e.keyCode === 38 || e.keyCode === 40) paddleDirection = 0;
});
Implementing the AI Opponent
const difficulty = 3;
function updateAI(dt) {
const ballCY = ball.cy();
const paddleCY = paddleLeft.cy();
const dy = Math.min(difficulty, Math.abs(ballCY - paddleCY));
paddleLeft.cy(cy > paddleCY ? paddleCY + dy : paddleCY - dy);
// Constrain to court boundaries
paddleLeft.cy(
Math.max(paddleHeight / 2, Math.min(height - paddleHeight / 2, paddleLeft.cy()))
);
}
The difficulty variable limits AI movement speed--higher values create harder opponents. For more advanced AI implementations, explore how AI automation services can enhance your applications with intelligent features.
Polishing with Visual Effects
Color Morphing for the Ball
const ballColor = new SVG.Color('#ff0066');
ballColor.morph('#00ff99');
function updateBallColor() {
const progress = ball.x() / width;
ball.fill(ballColor.at(progress));
}
Creating Explosion Effects on Scoring
function boom() {
const winningPaddle = vx > width / 2 ? paddleLeft : paddleRight;
const gradient = draw.gradient('radial', function(stop) {
stop.at(0, winningPaddle.attr('fill'), 1);
stop.at(1, winningPaddle.attr('fill'), 0);
});
const blast = draw.circle(300);
blast.center(ball.cx(), ball.cy()).fill(gradient);
blast.animate(1000, '>').opacity(0).after(function() {
blast.remove();
});
}
Complete Working Example
Here's a complete, playable Pong game combining all elements:
<!DOCTYPE html>
<html>
<head>
<title>Pong with SVG.js</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.2.0/svg.min.js"></script>
<style>
body { display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #222; }
#pong { border: 2px solid #333; }
</style>
</head>
<body>
<div id="pong"></div>
<script>
const width = 450, height = 300;
const paddleWidth = 20, paddleHeight = 100;
const ballSize = 20;
const draw = SVG('pong').size(width, height);
const background = draw.rect(width, height).fill('#1a1a1a');
const centerLine = draw.line(width/2, 0, width/2, height);
centerLine.stroke({ width: 5, color: '#333', dasharray: '5, 5' });
const paddleLeft = draw.rect(paddleWidth, paddleHeight).x(0).cy(height/2).fill('#00ff99');
const paddleRight = draw.rect(paddleWidth, paddleHeight).x(width-paddleWidth).cy(height/2).fill('#ff0066');
const ball = draw.circle(ballSize).center(width/2, height/2).fill('#7f7f7f');
let playerLeft = 0, playerRight = 0;
const scoreLeft = draw.text('0').font({ size: 32, family: 'Menlo', anchor: 'end', fill: '#fff' }).move(width/2-10, 10);
const scoreRight = scoreLeft.clone().text('0').font('anchor', 'start').x(width/2+10);
let vx = 0, vy = 0, paddleDirection = 0;
const paddleSpeed = 300, difficulty = 3;
SVG.on(document, 'keydown', e => { if(e.keyCode===38) paddleDirection=-1; if(e.keyCode===40) paddleDirection=1; });
SVG.on(document, 'keyup', e => { if(e.keyCode===38||e.keyCode===40) paddleDirection=0; });
draw.on('click', () => { if(vx===0 && vy===0) { vx = Math.random()*400-200; vy = Math.random()*400-200; }});
function reset() {
vx = 0; vy = 0;
ball.animate(100).center(width/2, height/2);
paddleLeft.animate(100).cy(height/2);
paddleRight.animate(100).cy(height/2);
}
function update(dt) {
ball.dmove(vx*dt, vy*dt);
const cx = ball.cx(), cy = ball.cy();
if((vy<0 && cy<=0) || (vy>0 && cy>=height)) vy = -vy;
const pLeftY = paddleLeft.y(), pRightY = paddleRight.y();
if((vx<0 && cx<=paddleWidth && cy>pLeftY && cy<pLeftY+paddleHeight) ||
(vx>0 && cx>=width-paddleWidth && cy>pRightY && cy<pRightY+paddleHeight)) {
vy = (cy - ((vx<0 ? pLeftY : pRightY) + paddleHeight/2)) * 7;
vx = -vx * 1.05;
}
let py = paddleRight.y();
if(paddleDirection===-1 && py>0) paddleRight.y(py - paddleSpeed*dt);
else if(paddleDirection===1 && py<height-paddleHeight) paddleRight.y(py + paddleSpeed*dt);
const aiCY = paddleLeft.cy();
const dy = Math.min(difficulty, Math.abs(cy - aiCY));
paddleLeft.cy(cy > aiCY ? aiCY + dy : aiCY - dy);
if((vx<0 && cx<=0) || (vx>0 && cx>=width)) {
if(vx<0) { playerRight++; scoreRight.text(playerRight); }
else { playerLeft++; scoreLeft.text(playerLeft); }
reset();
}
}
let lastTime = null;
function loop(ts) {
if(lastTime) update((ts-lastTime)/1000);
lastTime = ts;
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>
Performance Considerations and Best Practices
Optimizing SVG Rendering
- Use
transformattributes for movement rather than changingxorydirectly - Group related elements in
<g>containers to minimize DOM updates - Add
will-change: transformin CSS for frequently animated elements
Memory Management
Always remove temporary visual effects after animation completes:
blast.animate(1000, '>').opacity(0).after(function() {
blast.remove(); // Clean up DOM element
});
Frame Rate Independence
Always multiply movement by delta time:
// Correct: Frame-independent movement
ball.dmove(vx * dt, vy * dt);
This ensures consistent speed across different frame rates. Performance optimization is crucial for any web application you build.
Extending Your Pong Game
Once the core game works, consider adding these enhancements:
| Feature | Description | Difficulty |
|---|---|---|
| Sound Effects | Add audio feedback for hits and scoring | Easy |
| Difficulty Levels | Menu to select AI challenge level | Easy |
| Power-ups | Random items that change gameplay | Medium |
| Two-Player Mode | Local multiplayer on same keyboard | Easy |
| Network Multiplayer | Online competitive play | Advanced |
Sound Effects Example
const hitSound = new Audio('paddle-hit.mp3');
function playHitSound() {
hitSound.currentTime = 0;
hitSound.play().catch(() => {});
}
Conclusion
Building Pong with SVG.js demonstrates how modern web technologies enable interactive experiences without heavy game engines. The SVG approach offers advantages in crisp vector graphics, accessibility through DOM integration, and straightforward debugging.
This project covered essential game development concepts that transfer directly to more complex games and interactive applications.
Ready to build more interactive web experiences? Our web development services team creates custom applications using modern technologies like React, Next.js, and SVG. From interactive games to complex business applications, we bring your ideas to life with clean, performant code.
Frequently Asked Questions
What is SVG.js?
SVG.js is a lightweight JavaScript library for manipulating and animating SVG (Scalable Vector Graphics). It provides an intuitive API for creating, modifying, and animating vector graphics with no external dependencies.
Why use SVG instead of Canvas for games?
SVG offers resolution-independent graphics that remain sharp at any zoom level. Elements are part of the DOM, making them accessible and easy to debug with browser developer tools. SVG.js handles cross-browser compatibility.
How do I make the AI harder?
Increase the `difficulty` variable value. Higher values allow the AI paddle to move faster and track the ball more effectively. A difficulty of 1-2 is easy, 3-5 is medium, and 6+ is challenging.
Can I use this code in production?
Yes, SVG.js is MIT licensed and suitable for production use. The complete Pong implementation provided can be used as a starting point for web-based games and interactive content.
Sources
- SVG.js Official Documentation - Library for manipulating and animating SVG with no dependencies
- CSS-Tricks: Pong with SVG.js - Step-by-step implementation guide
- HTML5 Game Development: Pong Game with SVG.js - Overview of SVG.js capabilities and features
- CodePen: Fully Functional Pong Game with Effects - Working example of complete Pong game