Inspired by: https://www.youtube.com/watch?v=ccYrSACDNsw
This is a small Crossy-Road–style demo implemented with React and a React-friendly Three.js renderer. The project demonstrates how to build an interactive 3D scene in the browser using @react-three/fiber, manage simple game state in React, and animate objects with gsap. It was bootstrapped with Vite for a fast development experience.
The app places a player on a tile grid and procedurally generates rows (grass, forest with trees, and roads with moving vehicles). Your goal is to move the player forward across the rows while avoiding vehicles and trees.
- React 19 — UI and component model
- Vite — dev server and bundling
- three.js — 3D engine
- @react-three/fiber — React renderer for three.js
- @react-three/drei — useful helpers for Three + React
- Tailwind CSS — utility-first styling for UI overlays
- GSAP — animations and tweens for smooth motion
- gh-pages — optional deploy script (in
package.json)
All dependencies are declared in package.json.
- The React app mounts a
Canvasfrom@react-three/fiberwhich provides the WebGL context and camera. - Scene elements (player, rows, trees, vehicles) are React components that create three.js meshes.
- The player is parented to the camera so the camera follows the player.
- Input (W/A/S/D or arrow keys) is captured and published into a tiny player store;
usePlayerAnimationconsumes the move and runs agsaptimeline to animate the player across tiles. - Road rows spawn vehicles that run a looping
gsapanimation. Each vehicle also runs a per-frame bounding-box collision check against the player. - When a collision occurs the
gamecontext is set to theoverstate and the UI shows a Game Over overlay with a retry option. - Rows are generated procedurally on demand; moving forward close to the end of the generated rows triggers generation of more rows.
Prerequisites: Node.js (LTS recommended) and npm (or your package manager of choice).
-
Install dependencies:
npm install
-
Start the development server with HMR:
npm run dev- Open the URL printed by Vite (typically
http://localhost:5173).
-
Build for production:
npm run build
-
Preview the production build:
npm run preview
-
Deploy to GitHub Pages (optional):
npm run deploy
Note:npm run deployusesgh-pagesto publish thedistfolder. If you plan to use it, ensurehomepageis set correctly inpackage.json.
src/main.jsx— app bootstrap and root rendersrc/App.jsx— composition ofGameProvider,Scene,Player,Map, andInterfacesrc/components/Scene.jsx—Canvasand camera setupsrc/components/Player.jsx— player mesh, light, and camera parentingsrc/hooks/usePlayerAnimation.js— input handling, movement validation, and jump/rotation animation via GSAPsrc/hooks/useEventListeners.js— keyboard event listeners (W/A/S/D and arrows)src/stores/player.js— small player store (getMove,setMove,playerRef)src/stores/game.jsx— game context (rows, score, gameState,generateRows,reset)src/components/Map.jsx/src/components/Row.jsx— row mounting and row-type dispatch (grass,forest,road)src/components/*—Grass,Forest,Road,Tree,Vehicle,Car,Truck,Wheelsrc/hooks/useVehicleAnimation.js— GSAP-based looped vehicle motionsrc/hooks/useHitDetection.js— per-frame Box3 collision checks between vehicles and playersrc/constants.js—tileSize,minTileIndex,maxTileIndex, etc.
Below are the common edits you will make and where to find them.
-
Change the grid / tile scale
- File:
src/constants.js
EdittileSizeto scale spacing, movement distance and scene tiling. Larger values increase distance between tiles.
- File:
-
Tweak player behavior and animation
- Files:
src/hooks/usePlayerAnimation.js— movement rules, jump z-offset, animation durations and rotation are here. The hook readsgetMove()from the player store, checks for obstacles, updates score, triggersgenerateRows, and runs GSAP timelines for the actual motion.src/hooks/useEventListeners.js— keyboard bindings. Modify or extend this to add custom keys or touch controls.
- Files:
-
Adjust vehicle speed and density
- File:
src/stores/game.jsx— the row generator (generateRow) randomly createsroadrows and populatesrowData.vehicleswithtype,initialTileIndex, andcolor. It also setsrowData.speed. - Lower
rowData.speedto make vehicles traverse rows faster (duration in GSAP). Increase the number of generated vehicles to increase density.
- File:
-
Change collision detection
- File:
src/hooks/useHitDetection.js— usesBox3().setFromObject(...)to compute bounding boxes and checksintersectsBox. If you prefer circle-based collisions, replace this logic with distance checks between centers and a radius.
- File:
-
Add new vehicle or obstacle types
- Add a new component under
src/components/(for exampleBus.jsx).
- Add a new component under
Update src/components/Vehicle.jsx to include your new type in the switch/return logic.
- Update
generateRowinsidesrc/stores/game.jsxto randomly choose your new vehicle type.
- Movement is discrete and grid-based; the system avoids a heavy physics engine to keep the logic readable and deterministic.
- Animations use
gsapfor predictable timings and easy looping. Vehicle loops userepeat: -1. - For continuous motion or per-frame logic, prefer
useFramefrom@react-three/fiber. Keep heavy computations out of render functions to avoid frame jank. - The camera is parented to the player group (
Player.jsx) so it moves naturally with the player; change this if you need different camera behavior. useFrameand per-frame hooks run at animation frame rate — avoid expensive synchronous work inside them.
- Use the
Statspanel mounted insrc/components/Scene.jsxwhile developing to monitor render stats and performance. - If you need deterministic behavior for testing, temporarily make row generation deterministic by hard-coding row data or seeding the generator logic.
- Insert
console.logstatements inuseFramecallbacks or thegenerateRowpath to trace runtime behavior; remember to remove or gate them to avoid heavy logging. - Keep separate concerns: rendering/meshes in
components/, animation hooks inhooks/, and game state instores/.
