Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
A browser-based voxel building sandbox (Minecraft-creative-mode-style) built with Three.js and vanilla JavaScript. Runs entirely client-side — no backend, no build-time asset pipeline beyond Vite. Works on desktop (mouse + keyboard, pointer lock) and mobile (touch joystick, drag-to-look, on-screen action buttons).查看完整 README ↗A browser-based voxel building sandbox (Minecraft-creative-mode-style) built with Three.js and vanilla JavaScript. Runs entirely client-side — no backend, no build-time asset pipeline beyond Vite. Works on desktop (mouse + keyboard, pointer lock) and mobile (touch joystick, drag-to-look, on-screen action buttons).
npm install
npm run dev # dev server
npm run build # production build to dist/
npm run preview # serve the production build locally
On touch devices the left thumb drives a virtual joystick, the right side of the screen drags to look, and on-screen buttons handle break/place/fly/jump/down/symmetry.
src/world/ — chunked voxel data (World.js), terrain generation (TerrainGenerator.js), and an instanced-mesh renderer with exposed-face culling (ChunkMesher.js). No gamification or UI code lives here.src/player/ — first-person controller: WASD/mouse-look/touch input, gravity, AABB collision, fly mode.src/interaction/ — a 3D DDA voxel raycaster used for break/place targeting.src/tools/ — undo/redo stack, box selection + copy/paste, symmetry/mirror mode.src/gamification/GamificationEngine.js — XP/levels, achievements, daily challenges, streaks, unlockable blocks, and the end-of-session build score. Entirely decoupled from rendering; it only reads the world through its public query API.src/config/ — blocks.js, achievements.js, challenges.js: plain arrays of config objects. Add a new block/achievement/challenge by adding an entry, not by touching engine logic.src/storage/SaveManager.js — localStorage persistence (RLE-compressed chunk data) with named saves + autosave.src/ui/UIManager.js — hotbar, XP bar, toasts, stats/achievements panel, save/load menu, and touch controls, all DOM-based and driven by an event bus.src/render/Models.js — the animals', people's and vehicles' models: .glb files in public/models/, built in Blender by tools/mobs/ (see art/mobs/sheets/). Loaded at start; each view draws its old boxes until they're in.src/Game.js — wires everything together: scene/camera/renderer, the game loop, and the block-edit pipeline (apply → undo stack → chunk remesh → gamification hooks).Three tiers, deliberately, in increasing order of commitment:
localStorage. No
account, no network. This is the default and it always works.Export world writes a .voxworld.json carrying the blocks,
your progression, economy and every saved design — enough to restore the
world exactly, on any device. Export .vox writes MagicaVoxel format, which
opens in MagicaVoxel and Blender. Exports are the real backup: localStorage
is per-browser and a cleared cache takes it with it.A browser cannot safely talk to Postgres without a verifiable identity. Row level security has to key off something, and a device id the client makes up is not a something: anyone who guessed or copied one would read another player's worlds, with no way to revoke it and no way to recover it when the device dies. So sync is gated on signing in, and anonymous play stays entirely local. That is a deliberate trade, not an oversight.
authenticated role can reach
only rows that resolve to its own players row; the anonymous role has no
grants on any game table at all. Verified: querying as authenticated
without a matching identity returns zero rows from every table.SyncEngine hashes each chunk and uploads only the ones that changed, so
save cost tracks what you edited rather than how big the world is.VITE_NEON_URL in .env is the only setting, and Neon derives both the auth
service and the Data API from it. It is public by design — a URL grants nothing
on its own. On Vercel, set the same variable under Settings → Environment
Variables.
Note on history: this was first built against Neon's legacy Stack Auth integration, which turned out to be closed to new projects and shipped with email/password sign-in disabled and no way to enable it for a Neon-owned project. Managed Better Auth is the supported path, enables password sign-in by default, and needs one setting instead of three.
Deleting a world from the cloud soft-deletes the row (a mistaken tap is recoverable) and drops its chunks immediately, since those are the bulk of it. Your local copy is never touched by a cloud delete.
The box snaps to a grid of its own size on all three axes. That means the block under your crosshair is always inside the box, the box does not slide around as your aim drifts within a cell, and captured regions tile against each other and against chunks — which is what lets achievements reason about a template's dimensions at all. Its edges are drawn with depth testing off so the cage stays legible from inside a build, and the blocks it contains get a skin over their exposed faces, so what you see highlighted is exactly what a save would capture.