Tank Arena Game — HTML Canvas Top-Down Shooter in JS
Tank Arena Game · Games · Plain HTML, CSS & JS · Live preview
For supporters
Source Code
Want the source code?
Play the game for free. Become a WebDevPuneet supporter to unlock the complete HTML, CSS and JavaScript source code, the exports (HTML, React, Vue, Angular, Tailwind) and Fork.
Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Tank Arena — A Top-Down Tank Shooter with Ricochets, Enemy Pathfinding, Power-Ups and Twin-Stick Controls

Top-down tank battles are a classic because the rules are instantly readable: drive, aim, shoot, use cover. This snippet builds a complete, polished one on a single HTML5 <canvas>: a destructible arena, free 360° aiming, shells that ricochet, four enemy types that navigate around walls and lead their shots, power-ups, a boss every fifth wave, combo scoring, synthesized sound and mouse, keyboard or twin-stick touch input.
Drive, aim and fire independently
The tank has a hull and a turret that rotate separately, like a real twin-stick game. WASD or the arrow keys (or the left stick) drive the hull, which turns smoothly toward the direction of travel with angDiff, a shortest-angle helper that avoids spinning the long way round. The turret eases toward the mouse position, the right stick, or the hull direction if no aim input exists, so the game is playable with a keyboard alone. A dashed laser sight is raycast from the barrel to the first wall, which shows exactly where a ricochet will start.
A tile arena that is fair and destructible
The map is a 16×16 grid with steel (indestructible), brick (three hits, with cracks that grow as it takes damage) and crates (one hit, 60% chance of a power-up). Every wave generates a new layout on the left half and mirrors it, so neither side has an advantage, then a flood fill checks that every enemy spawn can still reach the player and regenerates the map if steel has walled anything off. Movement collision is circle-versus-rectangle using the nearest point on each tile, resolved one axis at a time so tanks slide along walls instead of sticking.
Shells that ricochet, with sub-stepping
Shells move in sub-steps of about 4 px so a fast bullet can never tunnel through a wall. When a shell enters a solid tile the code compares the previous and current tile coordinates to decide which axis to flip, so it bounces off steel and the arena edge exactly once; hitting a brick damages it and ends the shell. Opposing shells cancel each other on contact, and friendly fire is off. Each shell draws a short fading trail and a glow.
Enemies that find their way
Scouts are fast and fragile, gunners are balanced, heavies carry five hits and fire two-shell bursts, and the boss fires a five-shell fan. Every tank runs the same loop. If it has a clear line (a raycast through the grid) it holds a preferred range, closing in when far, backing off when near and strafing in between. If the player is hidden it follows a breadth-first flow field, a distance map from the player's tile that is rebuilt every 0.35 seconds or when a wall breaks, always stepping to the neighbour with the lowest distance. Shots lead the target using distance / bulletSpeed and the player's velocity, fire only when the barrel is within about 20° of the target, and a small stuck detector nudges a tank that stops making progress.
Waves, power-ups and combos
Waves are generated from a formula (more gunners, then scouts, then heavies, and a boss on every fifth wave) and enemies are released through a spawn queue with a short warning ring, capped at six alive at a time. Power-ups drop from some kills and crates: repair, rapid fire, shield and triple spread. Kills within three seconds of each other build a combo multiplier up to x4.
Juice, sound and mobile support
Tracks, scorch marks and brick rubble are painted onto a persistent off-screen ground canvas instead of being redrawn, so they cost almost nothing. Muzzle flashes, sparks, smoke, shockwave rings, screen shake and a red hit flash sell every impact. Sound effects are synthesized with the Web Audio API, with a saved mute toggle, and the game pauses on blur or a hidden tab. Touch players get two on-screen sticks built from Pointer Events, with the aim stick firing automatically while it is pushed. The canvas is scaled by devicePixelRatio for sharp lines, and prefers-reduced-motion disables screen shake. For more canvas games see the Dodge the falling blocks game and the Asteroids Blaster game.
Step by step
How to Use
- 1DeployClick "Deploy" (or press Enter or Space). The title screen shows tanks idling in a freshly generated arena. Wave 1 starts with a banner and a short spawn warning for each enemy.
- 2Drive and aimDrive with WASD or the arrow keys and aim with the mouse. The turret follows the crosshair and a dashed laser shows where your shell will go. On a phone use the left stick to drive and the right stick to aim.
- 3Fire and use ricochetsHold the mouse button or Space (the right stick fires automatically). Shells bounce once off steel and the arena edge, so you can hit tanks hiding behind cover. Enemy shells ricochet too.
- 4Break cover and collect power-upsBricks crack after three hits and crates break in one, sometimes dropping a power-up: repair, rapid fire, shield or triple spread. Drive over them to collect them.
- 5Chain killsDestroy tanks within three seconds of each other to raise the combo multiplier up to x4. Heavy tanks need five hits, and every fifth wave brings a boss.
- 6Pause, mute and beat your bestP or Esc pauses and M mutes. You have five armour pips. Your best score is saved in localStorage under tank-arena-best.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
A breadth-first search starts from the player's tile and gives every reachable tile its distance from the player. Each hidden enemy steps toward the neighbouring tile with the smallest distance. The map is rebuilt every 0.35 seconds, or immediately when a brick breaks, so enemies route around walls and through new gaps.
Shells move in sub-steps of about 4 px. When a step lands inside a steel tile, the code compares the previous and current tile coordinates: if the column changed it flips the horizontal speed, if the row changed it flips the vertical speed. A shell may bounce once, and bricks absorb it instead.
The generator builds one half and mirrors it, then runs a flood fill from the player's start to every enemy spawn with only steel counted as blocking. If anything is sealed off it throws the layout away and tries again, up to 30 times.
They estimate the shell's travel time as distance divided by shell speed, add the player's current velocity multiplied by that time to the player's position, and aim at that point. Each shot also gets a small random error, larger for scouts.
Each stick is an element with a knob. On pointerdown it captures the pointer, and on every move it converts the offset from the centre into a vector clamped to the stick radius. The left stick feeds the driving vector, and the right stick sets the aim angle and fires while pushed past 30%.
Drawing them every frame would cost more as they accumulate. Painting each mark once onto an off-screen canvas and copying that canvas under the scene each frame keeps the cost flat, and the layer is cleared when a new wave builds a new map.
Yes. The best score is stored in localStorage under tank-arena-best and read on load, wrapped in try/catch so the game still runs where storage is blocked.
Render the canvas and overlays, create the context in a mount effect and run the requestAnimationFrame loop there, keeping game state in refs. Cancel the loop and remove the key, pointer and visibility listeners in the cleanup. Use the export buttons on this page for the markup and styles.