Posted May 16, 2025 by SteelCycloneStudios
#Air Hockey #arcade #game development #canvas #javascript
Hey everyone! ๐
Welcome to the very first devlog for Velocity Puck, a fast-paced, physics-driven vertical air hockey-inspired game built for the web browser. In this post, I want to share how the project began, what the early development process looked like, and how the code powering the first version actually works under the hood.
Velocity Puck started out as a 2D experiment. I wanted to recreate that satisfying feeling of smacking a puck around on an air hockey table — but with a twist: vertical gameplay, cool particle effects, and a future vision of multiplayer and power-ups.
To keep things light and accessible, I built the first version entirely with HTML, CSS, and JavaScript, no frameworks or engines. Just good old canvas and some physics logic to get things moving.
This foundation was key. Before adding arenas, themes, or complex mechanics, I needed a smooth, responsive core — something that felt good to play right away.
All of this lives in a single index.html
file. Let me break it down:
html <canvas id="gameCanvas" width="800" height="600"></canvas>
The game runs inside a single HTML5 <canvas>
.
I chose a vertical layout to differentiate it from traditional horizontal air hockey.
This approach gave direct control over rendering without relying on external libraries like Pixi.js or Three.js and keeps performance snappy on mobile and desktop.
Within the <script>
tags, youโll find:
a. Game Loop Setup
The game loop uses requestAnimationFrame
to keep things smooth at 60 FPS:
js function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); } gameLoop();
b. Basic Objects: Puck and Paddles
js let puck = { x: 400, y: 300, vx: 3, vy: 3, radius: 10 }; let paddle1 = { x: 100, y: 300, radius: 20 }; let paddle2 = { x: 700, y: 300, radius: 20 };
Movements are handled by updating their positions based on velocity.
c. Collision & Bouncing
js if (puck.x - puck.radius < 0 || puck.x + puck.radius > canvas.width) puck.vx *= -1; if (puck.y - puck.radius < 0 || puck.y + puck.radius > canvas.height) puck.vy *= -1;
Basic collision with the canvas boundaries gives the puck bounce.
Puck bounces off walls and interacts with the paddles.
More advanced collision logic will evolve from here—especially paddle interactions, deflection angles, and realistic friction.
If the puck enters the goal zone, a point is scored and the puck resets.
I added explosions using particles when goals are scored, which adds some satisfying feedback.
d. Input Handling
js document.addEventListener('mousemove', function (e) { paddle1.y = e.clientY - canvas.getBoundingClientRect().top; });
Player moves their paddle using mouse or touch input.
AI paddle moves based on puck position with a bit of artificial delay to make things fair (but not too easy!).
This will eventually support keyboard/gamepad controls and two-player mechanics.
Players can choose their paddle color from a vertical menu of swatches on the left.
Each color option is a button with a specific data-color
value:
html <div id="colorMenu"> <div class="colorBtn" style="background:#e74c3c" data-color="#e74c3c"></div> <div class="colorBtn" style="background:#2ecc71" data-color="#2ecc71"></div> <!-- more colors --> </div>
Each button updates the paddle.color
when clicked:
js document.querySelectorAll('#colorMenu .colorBtn').forEach(btn => { btn.addEventListener('click', function () { // Remove 'selected' from all buttons document.querySelectorAll('#colorMenu .colorBtn').forEach(b => b.classList.remove('selected')); // Add 'selected' style to the clicked one this.classList.add('selected'); // Update the paddle color paddle.color = this.dataset.color; }); });
In the drawPaddle()
function, the selected color is used every frame:
js drawPaddle(paddle.x, paddle.y, paddle.radius, paddle.color);
Players can also change the background color of the hockey table (the field).
On the right, another vertical menu contains swatches for the field:
html <div id="fieldColorMenu"> <div class="colorBtn" style="background:#00366d" data-field-color="#00366d"></div> <!-- more colors --> </div>
Clicking one of these buttons updates the global fieldColor
variable:
js document.querySelectorAll('#fieldColorMenu .colorBtn').forEach(btn => { btn.addEventListener('click', function () { // Unselect previous document.querySelectorAll('#fieldColorMenu .colorBtn').forEach(b => b.classList.remove('selected')); // Highlight this one this.classList.add('selected'); // Change field color fieldColor = this.dataset.fieldColor; }); });
The background color is drawn every frame in drawTable()
:
js ctx.fillStyle = fieldColor; ctx.fillRect(0, 0, canvas.width, canvas.height);
.selected
Both color menus use this visual cue:
css .colorBtn.selected { transform: scale(1.2); box-shadow: 0 0 10px white; }
When a color is active, it grows slightly and glows, giving instant feedback.
You also added toggles to show/hide the color menus via the side-panel menu:
js case 1: // Colors document.getElementById('colorMenu').style.display = document.getElementById('colorMenu').style.display === 'none' ? 'flex' : 'none'; break; case 2: // Field Colors document.getElementById('fieldColorMenu').style.display = document.getElementById('fieldColorMenu').style.display === 'none' ? 'flex' : 'none'; break;
Customization | Variable | Menu ID | Event Logic | Draw Function |
---|---|---|---|---|
Paddle Color | paddle.color
| #colorMenu
| data-color
| drawPaddle()
|
Field Background | fieldColor
| #fieldColorMenu
| data-field-color
| drawTable()
|
These systems are super extensible—you can easily add avatar styles, puck skins, or particle trails later using the same pattern.
Starting with a single HTML file let me:
Test mechanics quickly
Debug easily without a build system or complex architecture
Avoid setup overhead and to establish a clear visual and interactive baseline
Keep the prototype fun, flexible and test fast without overengineering
Get something playable now instead of perfecting too early
This minimal setup makes iteration super easy and perfect for deploying to sites like itch.io, CrazyGames, or Facebook Instant Games later.
Hereโs what Iโm planning for future updates:
A Menu system
Smarter AI with different difficulty levels
More arena environments with unique themes
Power-ups and puck modifiers
Multiplayer support (eventually)
Custom game modes (tournament, party mode, etc.)
Iโm also considering expanding to a 3D version using Three.js later on — but only once the 2D mechanics are perfect.
If youโre reading this on itch.io, feel free to try the current build and let me know what you think! Your feedback means a lot and helps shape what comes next.
Thanks for checking out the first devlog — more updates soon!
— Jordon McClain
Steel Cyclone Studios LLC