|
|
||
|---|---|---|
| .forgejo/workflows | ||
| app | ||
| auto-triage | ||
| .dockerignore | ||
| .gitignore | ||
| .quickfix-run.log | ||
| docker-compose.yml | ||
| Dockerfile | ||
| lefthook.yml | ||
| nginx.conf | ||
| package.json | ||
| plan.md | ||
| README.md | ||
Marble Rush: Arena
A physics-based tilt marble maze game built with React, TypeScript, and a custom Canvas 2D engine.
Navigate a marble through intricate maze-like tracks, tilting the playing field to roll, jump, and boost your way past obstacles. Collect coins and gems, grab power-ups, race against the clock, and conquer three handcrafted worlds.
🎮 Game Overview
Marble Rush: Arena is a web-based marble racing game that combines classic labyrinth-tilt mechanics with modern game design. The game features:
- Physics-driven marble movement with acceleration, friction, wall bouncing, and ice surfaces
- Device tilt controls for mobile play — physically tilt your phone to steer
- Touch joystick with on-screen jump and boost buttons
- Keyboard controls (WASD / Arrow keys, Space, Shift)
- 3 themed levels: Jungle Temple, Crystal Cave, Volcano Core
- Multiple obstacle types including spinners, boulders, pendulums, collapsing platforms, spikes, magnetic fields, gravity wells, and speed gates
- 5 power-ups: Speed Boost, Shield, Magnet, Freeze, Wall Jump
- Collectibles: Coins (score), Gems (bonus score), Time Bonuses (reduce timer)
- Checkpoint system with respawn on death
- Star ratings based on par time performance (1–3 stars)
- Energy boost mechanic — expend energy for bursts of speed
- Particle effects, screen shake, marble trail, mini-map, and full HUD
🚀 Quick Start
Prerequisites
- Node.js ≥ 20
- npm ≥ 9
Install & Run
# Navigate to the app directory
cd app
# Install dependencies
npm install
# Start the dev server (opens on http://localhost:3000)
npm run dev
Build for Production
npm run build
The output is written to app/dist/ and can be served by any static file server.
Preview Production Build
npm run preview
Other Scripts
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server (port 3000) |
npm run build |
TypeScript check + production build |
npm run preview |
Preview the production build locally |
npm run typecheck |
TypeScript type-check (no emit) |
npm run lint |
Run ESLint |
npm run test |
Run test suite (placeholder) |
🎯 Controls
Keyboard
| Key | Action |
|---|---|
W / Arrow Up |
Tilt forward / up |
A / Arrow Left |
Tilt left |
S / Arrow Down |
Tilt backward / down |
D / Arrow Right |
Tilt right |
Space |
Jump / Wall Jump |
Shift |
Boost (uses energy) |
Touch (Mobile)
| Area | Action |
|---|---|
| Left side of screen | Joystick (tilt) |
| Right side — upper half | Jump button |
| Right side — lower half | Boost button |
Tap and drag the joystick to steer. The jump and boost buttons are static touch zones on the right side of the screen.
Device Tilt (Mobile)
On supported devices, the game uses the device orientation API. Tilt your phone to steer the marble. Tilt sensitivity and inversion can be configured in settings. iOS requires explicit permission via the DeviceOrientationEvent.requestPermission() API.
🧩 Game Mechanics
Marble Physics
- The marble accelerates based on input tilt direction
- Friction slows the marble when no input is applied
- Colliding with walls produces a bounce with energy damping
- The marble slides along walls with friction
- Ice tiles drastically reduce friction, causing the marble to slide
- Boost pads launch the marble in its current velocity direction
- Airborne gravity applies after 2 frames of no ground contact
Obstacles
| Type | Description |
|---|---|
| Spinner | Rotating red disc with blades — contact kills |
| Boulder | Heavy rolling ball that patrols a path |
| Pendulum | Swinging spiked ball on a chain |
| Collapsing Platform | Platform that breaks after being stepped on (respawns) |
| Spikes | Periodically retracting floor spikes |
| Speed Gate | Reduces your timer by 2 seconds on contact |
| Magnetic Field | Pulls the marble toward its center |
| Gravity Well | Stronger pull — can crush the marble at close range |
| Moving Platform | Oscillating platform the marble can ride |
Power-ups
| Power-up | Color | Duration | Effect |
|---|---|---|---|
| Speed | Green | ~3s | Instant velocity burst + speed trail |
| Shield | Cyan | ~5s | Temporary invincibility (cyan glow) |
| Magnet | Gold | ~4s | Pulls nearby coins and gems toward the marble |
| Freeze | Silver | ~3s | Slows the marble by 50% (useful for precision sections) |
| Wall Jump | Orange | ~4s | Enables wall jumping off vertical surfaces |
Collectibles
| Type | Score | Description |
|---|---|---|
| Coin | 10 | Gold spinning disc |
| Gem | 50 | Blue diamond — rarer, higher value |
| Time Bonus | -2s | Green timer reduction orb |
Scoring & Stars
- Score is accumulated from coins and gems
- Death penalty: −50 points per death
- Stars are awarded at level completion based on par time:
- ⭐⭐⭐ ≤ 80% of par time
- ⭐⭐⭐ ≤ 100% of par time
- ⭐⭐ ≤ 130% of par time
- ⭐ > 130% of par time
Energy System
- Energy bar (100 max) is consumed when holding Boost
- Energy regenerates slowly over time
- Boost increases max speed and reduces friction
🌍 Levels
The game currently features 3 handcrafted levels:
| # | Name | Theme | Par Time | Description |
|---|---|---|---|---|
| 1 | Jungle Temple | Ancient ruins | 45s | Introductory maze with basic obstacles |
| 2 | Crystal Cave | Icy caverns | 55s | Ice tiles, pendulums, magnetic fields |
| 3 | Volcano Core | Lava tunnels | 60s | Dense obstacles, spikes, gravity wells |
Each level includes:
- A winding maze-like wall layout
- Multiple obstacle types
- Coins, gems, and at least one time bonus
- 3 checkpoints for respawn
- Hidden collectible routes
- Boost pads for shortcuts
🗂️ Project Structure
tilt-marble-maze/
├── app/ # Application root
│ ├── public/ # Static assets
│ │ ├── marble-diffuse.jpg # Marble texture (diffuse)
│ │ ├── marble-normal.jpg # Marble texture (normal map)
│ │ ├── hero-bg-void.jpg # Home page background
│ │ └── track-glow-overlay.png
│ ├── src/
│ │ ├── main.tsx # App entry point (HashRouter)
│ │ ├── App.tsx # Route definitions
│ │ ├── index.css # Tailwind + CSS variables
│ │ ├── lib/utils.ts # shadcn utility
│ │ ├── types/
│ │ │ └── game.ts # All TypeScript game type definitions
│ │ ├── game/ # Custom Canvas 2D game engine
│ │ │ ├── engine.ts # Core game loop, state machine, update logic
│ │ │ ├── physics.ts # Marble physics, collision detection, vector math
│ │ │ ├── levels.ts # Level definitions (3 levels)
│ │ │ ├── obstacles.ts # Obstacle factory functions
│ │ │ ├── controls.ts # Keyboard, touch, and tilt input handling
│ │ │ ├── renderer.ts # Canvas 2D rendering (walls, obstacles, effects)
│ │ │ ├── hud.ts # HUD overlay (timer, speed, score, mini-map)
│ │ │ ├── particles.ts # Particle system (trails, explosions, confetti)
│ │ │ └── powerups.ts # Power-up pickup logic, effects, and rendering
│ │ ├── components/ # React UI components
│ │ │ ├── Layout.tsx # Page layout shell
│ │ │ ├── Navbar.tsx # Top navigation
│ │ │ ├── Footer.tsx # Page footer
│ │ │ └── ui/ # shadcn/ui primitives
│ │ └── pages/ # Route pages
│ │ ├── Home.tsx # Landing page (Canvas marble animation + GSAP)
│ │ ├── Play.tsx # Main game canvas + game state UI
│ │ ├── Modes.tsx # Game mode selection page
│ │ ├── Leaderboard.tsx # 3D leaderboard (Three.js / R3F)
│ │ └── HowToPlay.tsx # Controls and mechanics guide
│ ├── dist/ # Production build output
│ ├── package.json
│ ├── vite.config.ts
│ ├── tailwind.config.js
│ ├── tsconfig.json / tsconfig.app.json
│ ├── components.json # shadcn/ui config
│ └── index.html # HTML entry point
├── .forgejo/workflows/ci.yml # CI pipeline (type-check, lint, build, test)
├── .gitignore
└── README.md # You are here
🏗️ Architecture
Game Engine
The game uses a custom 2D Canvas-based engine (no game framework). The core loop runs via requestAnimationFrame:
- Input →
Controls.getInput()returns the currentInputState(tilt direction, jump/boost flags) - Update →
updateGame()advances the physics, processes collisions, collects items, updates particles, and manages game state - Render →
renderGame()draws the level, marble, particles, and obstacles onto a<canvas>element - HUD →
drawHUD()overlays timer, speed meter, score, energy bar, power-up slots, mini-map, and touch controls
State Machine
The game follows a simple state machine:
start → countdown → playing ↔ paused → levelcomplete / gameover
- start: Initial state, waiting for player input
- countdown: 3-2-1-GO countdown animation
- playing: Active gameplay
- paused: Game paused (overlay menu)
- levelcomplete: Level finished — show star rating and score
- gameover: Currently functions as respawn from last checkpoint
Tech Stack
| Technology | Purpose |
|---|---|
| React 19 | UI framework |
| TypeScript | Type-safe development |
| Vite 7 | Build tool and dev server |
| Canvas 2D | Game rendering (custom engine) |
| Tailwind CSS | Styling and design system |
| shadcn/ui | UI component primitives |
| Framer Motion | Page/UI animations |
| GSAP | Landing page scroll animations |
| Three.js / R3F | 3D backgrounds (leaderboard, how-to-play) |
| React Router | Client-side routing (HashRouter) |
🧪 CI/CD
The project includes a Forgejo CI workflow (.forgejo/workflows/ci.yml) that runs on every push/PR:
- Install —
npm ciwith Node.js 20 - TypeScript check —
tsc --noEmit - Lint —
eslint(continue on error) - Build —
vite build - Test — placeholder test runner
🧭 Current Status
- ✅ Core gameplay loop working with physics, collisions, rendering
- ✅ 3 playable levels with distinct themes
- ✅ Keyboard, touch, and tilt input support
- ✅ Full HUD, particles, screen shake, mini-map
- ✅ Power-up and collectible system
- ✅ Level-complete screen with star ratings
- ✅ Landing page, how-to-play guide, modes page, leaderboard UI
- 🚧 Game mode switching (Time Attack/Adventure/Endurance/Boss Rush) — UI exists, game logic scoped to Time Attack
- 🚧 Leaderboard persistence — UI renders but data is static/mock
- 🚧 Daily Challenge — UI concept, not implemented
- 🚧 Tests — placeholder only; no unit/integration tests yet
🔧 Development
Adding a New Level
- Define a new
LevelThemeinapp/src/game/levels.ts - Create a level function (e.g.,
level4()) returning aLevelDataobject - Add the case to
getLevel()and incrementgetLevelCount() - Design the maze using
walls[], placeobstacles[],collectibles[],powerups[], andcheckpoints[]
Adding a New Obstacle
- Add the type string to
Obstacle.typeinapp/src/types/game.ts - Create a factory function in
app/src/game/obstacles.ts - Add update logic in the
updateObstacles()switch inapp/src/game/physics.ts - Add collision handling in
checkObstacleCollision()→ engine.ts's obstacle switch - Add rendering in the
drawObstacles()switch inapp/src/game/renderer.ts
📄 License
This project is provided for educational and personal use. See the repository owner for licensing details.