Best for
Developers learning canvas game loops in React, or anyone who needs a working browser game for a portfolio or demo
Stack
A ready-made Flappy Bird UI you can fork, run, and customize with the prompt pack below.
What's actually inside
The honest engineer's breakdown — what the Flappy Birdtemplate does, how it's wired, and where it's opinionated.
The Flappy Bird template implements a complete game loop inside a single React component using HTML5 Canvas. The GameCanvas is a useRef'd canvas element that GameLoop animates via requestAnimationFrame — physics (gravity, velocity) are applied each frame to the BirdSprite's vertical position and rotation. PipeGenerator creates pipe pairs at random heights on a timer and scrolls them left each frame. CollisionDetector runs an axis-aligned bounding box (AABB) check between the bird rect and each pipe rect every frame. The ScoreCounter renders current and high score via canvas fillText. Three overlays (idle, playing, dead) are handled by GameStateManager using a simple useState string union. Keyboard spacebar and touchstart both trigger a flap via KeyboardTouchHandler.
The architecture is intentionally self-contained: everything lives in one canvas draw loop, with no external game engine dependency. This makes it an excellent study template for how requestAnimationFrame works with React's lifecycle — but it also means any feature you add (sprite sheets, leaderboards, power-ups) requires understanding the draw-loop pattern.
The honest caveat: the game loop state is entirely ephemeral. High scores reset on page refresh unless you add localStorage persistence (covered in the prompt pack). The canvas also renders at 1:1 pixel density by default, which looks blurry on Retina/MacBook screens — that fix is in the gotchas section.
Key UI components
GameCanvasuseRef'd HTML5 Canvas element rendering the bird, pipes, background, and score each frame
GameLoopuseEffect + requestAnimationFrame loop updating gravity and velocity each frame
BirdSpritecanvas drawImage or fillRect representing the bird with vertical position and rotation
PipeGeneratorGenerates pipe pairs at random heights on a timer and scrolls them left each frame
CollisionDetectorAABB collision check between the bird rect and each pipe rect
ScoreCountercanvas fillText drawing current and high score each frame
GameStateManageruseState tracking 'idle' | 'playing' | 'dead' states, shown as overlay screens
KeyboardTouchHandlerSpacebar keydown and touchstart event listeners for flap input
Libraries it leans on
HTML5 Canvas 2D APICore rendering — all game graphics drawn via ctx.fillRect, drawImage, and fillText
React useRef / useEffectCanvas ref access and game loop setup/teardown lifecycle
Tailwind CSSPage layout and overlay screen styling outside the canvas element
Fork it and get it running
Forking takes about 5 minutes. Because the entire game runs on the client in a canvas loop, you can publish immediately and play the live version — no backend or env vars required.
Fork the template from V0 community
Open https://v0.dev/chat/community/H6d9DNE50jO and click 'Fork' to copy the template into your V0 workspace. You'll land in the V0 editor with the game loaded.
Tip: Sign in to V0 before forking — unauthenticated clicks redirect to login.
You should see: You land in the V0 editor with the game canvas visible in the Preview tab.
Play the game in Preview to verify the loop
In the V0 Preview tab, press Space or click the canvas to start. Confirm the bird flaps, pipes scroll left, collision ends the game with a 'dead' overlay, and the score increments when passing pipes. Note that the game loop runs on the client only — animation renders in the active V0 sandbox but won't show in a static screenshot.
You should see: The bird flaps on Space/click, pipes scroll, collision triggers game over, and score counts up.
Adjust game constants in the Code tab
Open the V0 Code tab and find the game constants (gravity, pipe speed, pipe gap, flap force). These are typically defined as named constants near the top of the game file. Adjust them to your preferred difficulty — the prompt pack includes an easy-difficulty prompt if you want V0 to make the changes for you.
Tip: Changing constants is the safest first edit — it doesn't affect the draw loop architecture.
You should see: Modified constants apply immediately in the Preview tab after V0 saves the file.
Publish to Vercel
Click Share → Publish tab → 'Publish to Production'. Vercel builds and deploys in 30-60 seconds. You'll receive a .vercel.app URL where the game runs in any browser.
You should see: A live Vercel URL is shown. The game is fully playable on the public URL.
Test on mobile (touch input)
Open the Vercel URL on an iPhone or Android browser and tap the canvas. The touchstart handler should trigger a flap on each tap. If tapping doesn't work on iOS Safari, see the gotchas section — touchstart on canvas requires cursor: pointer and a passive: false event listener.
You should see: Tapping the canvas on mobile makes the bird flap. The game is playable on touchscreen devices.
Connect GitHub to pull and modify game code locally
Click the Git panel in the V0 editor and connect your GitHub account. V0 creates a branch (v0/main-{hash}) tracking your edits. Pull the canvas code locally via Cursor or VS Code to modify game constants, add sprite sheets, or implement a leaderboard backend without consuming V0 credits.
You should see: The Git panel shows a connected repo. You can clone and extend the game locally.
The prompt pack
Copy-paste these straight into v0's chat to customize the Flappy Birdtemplate. Each one names this template's own components — no generic filler.
Tune difficulty with named constants
Reduces difficulty and extracts game tuning constants into a single named object for easy future edits.
Make the game easier for beginners: reduce the PipeGenerator scroll speed from the current value to 2px per frame, increase the pipe gap height by 40px, and reduce the gravity constant in GameLoop from the current value to 0.3. Add a DIFFICULTY constant object at the top of the file grouping these three values (PIPE_SPEED, PIPE_GAP, GRAVITY) so they're easy to find and change.
Add localStorage high score persistence
Persists the high score across page refreshes using localStorage, displayed each frame by the canvas draw loop.
Add localStorage persistence for the high score in GameCanvas. On game over, if ScoreCounter's current score exceeds the stored high score, update localStorage with the key 'flappy-highscore'. On GameCanvas mount, read the stored high score inside a useEffect (not during render to avoid SSR errors). Display it as 'Best: X' in the top-right corner of the canvas via canvas fillText.
Replace bird rect with sprite sheet animation
Replaces the placeholder bird rectangle with an animated sprite cycling through 3 wing-flap frames.
Replace the static BirdSprite rectangle with a sprite sheet animation. Add a bird spritesheet image to /public/bird-sprites.png with 3 frames (wings up, middle, down). In GameLoop, cycle through frames every 100ms using a frame counter ref. Use canvas drawImage with sx, sy, sWidth, sHeight to clip the correct frame from the spritesheet. Keep the existing rotation and vertical position logic.
Add post-game leaderboard with Supabase
Adds a Supabase-backed leaderboard shown after each game over, with real-time top 10 display.
Add a post-game leaderboard. When GameStateManager transitions to 'dead', show a shadcn/ui Dialog with a name input pre-focused. On submit, call a Server Action that inserts {name: string, score: number} into a Supabase table named 'leaderboard' (id uuid, name TEXT, score INT, created_at TIMESTAMPTZ). Re-query and display the top 10 scores sorted by score DESC inside the Dialog. Add RLS policy: anon users can insert and select. Use NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY from the Vars panel.Add a shield power-up that spawns between pipe pairs
Adds a collectable shield power-up that grants 3-second invincibility with a visual pulse effect.
Add a shield power-up to the game. In PipeGenerator, randomly spawn a power-up object between every 3rd-5th pipe pair, positioned horizontally between two consecutive pipe pairs. Render the power-up as a golden star using canvas arc and fillStyle '#FFD700' on GameCanvas. In GameLoop, check if the bird's bounding rect overlaps the power-up rect. On overlap: mark it collected, activate a 3-second shield state stored in a ref, and during the shield, make CollisionDetector skip pipe collision checks. Draw a pulsing blue border ring around BirdSprite while the shield is active (use a sin wave on the frame counter for the pulse).
Gotchas when you extend it
The failures people actually hit when they push this template past its defaults — and the exact fix for each.
`Attempted import error: 'phaser' does not contain a default export` (if upgrading to Phaser)Why: Phaser doesn't work with V0's esm.sh preview sandbox — the module fails to load entirely. npm-imported Phaser also conflicts with V0's module resolution.
Fix: Keep the game on native HTML5 Canvas (which this template already does) or switch to CDN script loading via next/script with strategy='beforeInteractive'.
Don't import Phaser via npm in V0. Instead, load it via CDN using Next.js Script component with strategy='beforeInteractive' and src='https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.min.js'. Access Phaser via window.Phaser.
GameLoop continues running after the component unmounts — memory leak warning in consoleWhy: requestAnimationFrame holds a reference to the callback. Without a cleanup function in useEffect, the loop keeps firing after the user navigates away, causing 'Warning: Can't perform state update on unmounted component'.
Fix: Return a cleanup function from the GameLoop useEffect that calls cancelAnimationFrame on the stored animation frame id.
The GameLoop causes 'Warning: Can't perform state update on unmounted component'. Return a cleanup from the useEffect: store the requestAnimationFrame id in a ref and call cancelAnimationFrame(animIdRef.current) in the return function.
Canvas renders blurry on Retina / MacBook screensWhy: The canvas width and height in CSS pixels don't account for devicePixelRatio (2x on Retina screens), so the canvas resolution is half the display resolution, making sprites appear blurry.
Fix: Set canvas.width = canvas.offsetWidth * window.devicePixelRatio, canvas.height = canvas.offsetHeight * window.devicePixelRatio, then call ctx.scale(devicePixelRatio, devicePixelRatio) before drawing.
The game looks blurry on Retina screens. In GameCanvas, set canvas.width = canvas.offsetWidth * window.devicePixelRatio, canvas.height = canvas.offsetHeight * window.devicePixelRatio, then call ctx.scale(devicePixelRatio, devicePixelRatio) once after getting the 2D context.
Touch input (tap to flap) doesn't work on iOS SafariWhy: touchstart events on canvas elements require the element to have cursor: pointer CSS and the event listener must use { passive: false } to allow preventDefault(). Without these, Safari doesn't register the touch as a tap.
Fix: Add style={{ cursor: 'pointer' }} to the GameCanvas element. Update the touchstart addEventListener in KeyboardTouchHandler to use { passive: false } and call e.preventDefault() to prevent scroll interference.
Tap-to-flap doesn't work on iPhone. Add style={{ cursor: 'pointer' }} to the GameCanvas element. Update the touchstart addEventListener to use { passive: false } and call e.preventDefault() to prevent scroll interference.Template vs. custom — the honest call
A forked template gets you far, fast. Here's where it holds up, and where you'll outgrow it.
The template is enough when
- You want a browser game for a portfolio demo or hackathon submission
- You're learning canvas-based game loops and want working code to study and modify
- You need a fun interactive element on a landing page or internal tool
- You want to customize difficulty, colors, and sprites without a game engine
Go custom when
- You need multiple levels, saved game state across sessions, or in-app purchases
- You want to build on a real game engine like Phaser or Babylon.js — V0 can scaffold code but can't run Phaser in preview
- You need multiplayer or real-time gameplay (requires WebSockets beyond what this template provides)
- You want to publish to mobile app stores (requires React Native or native code)
RapidDev extends V0 game prototypes into shareable experiences — leaderboards, social sharing, and mobile-optimized deployment.
Frequently asked questions
Is this Flappy Bird template free to use?
Yes. The template is a free V0 community template — forking is free. Subsequent AI chat edits consume V0 credits from your monthly allowance.
Can I use this template commercially?
You can use the code for commercial projects — the underlying stack (Next.js, React, TypeScript) is MIT-licensed. Note that 'Flappy Bird' is an intellectual property — if you're publishing for commercial gain, rename the game and change the visual assets to avoid trademark issues with the original.
Why does the game look blurry on my MacBook?
The canvas renders at 1x pixel density by default, which looks blurry on Retina (2x) screens. Fix it by multiplying canvas.width and canvas.height by window.devicePixelRatio and calling ctx.scale(devicePixelRatio, devicePixelRatio). The full fix prompt is in the gotchas section above.
Why does my fork show a blank canvas in the V0 preview?
The game loop runs on the client using requestAnimationFrame. V0's Preview tab is an iframe sandbox — animation should render when the tab is active, but if you see a blank canvas, try clicking inside the preview to focus it. The game renders reliably after deploying to Vercel.
How do I save the high score between sessions?
The template stores the high score in component state only — it resets on refresh. Use the 'Add localStorage high score persistence' prompt on this page to persist the score to localStorage. Read the value in a useEffect (not during render) to avoid Next.js SSR errors.
Can I add a Phaser game engine to this template?
Not via npm in V0 preview — Phaser fails in V0's esm.sh sandbox with 'Attempted import error: phaser does not contain a default export'. If you want Phaser, load it via a CDN script tag using Next.js Script with strategy='beforeInteractive'. See the gotchas section for the exact fix prompt.
Can RapidDev extend this game template?
Yes. RapidDev takes V0 game starters into production — adding leaderboards, social sharing, mobile-optimized canvas rendering, and Vercel edge deployments for fast global load times.
Does this template work on mobile?
Yes, with a caveat. The KeyboardTouchHandler includes a touchstart listener, so tap-to-flap works on Android Chrome. On iOS Safari, you may need to add cursor: pointer to the canvas element and use { passive: false } on the touchstart listener — see the gotchas section for the exact fix.
Outgrowing the template?
RapidDev turns v0 prototypes into production apps — real auth, database, and payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.