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를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A browser-based 3D flight simulator that combines real-time physics, aerodynamic visualization, and a responsive heads-up display. Fly with a keyboard or gamepad while switching between sensor views to see the forces around the aircraft.README 전문 보기 ↗A browser-based 3D flight simulator that combines real-time physics, aerodynamic visualization, and a responsive heads-up display. Fly with a keyboard or gamepad while switching between sensor views to see the forces around the aircraft.
The main branch deploys to GitHub Pages on every push, so the playable build
is always live at the repository's Pages URL — no checkout, no build step.
Enable it once under Settings → Pages → Source: GitHub Actions; the workflow
in .github/workflows/pages.yml does the rest. Assets are built with a relative
base, so the site works from a project subpath (user.github.io/bird/) as well
as from a domain root.
Install the pinned dependencies and start Vite:
npm ci
npm run dev
Vite prints the local URL to open in a modern browser.
index.htmlneeds the build step. Three.js, Cannon and Tailwind are bundled fromnode_modulesrather than fetched from CDNs, so openingindex.htmlfrom disk or serving it as a plain static file no longer works — the bare module imports do not resolve, and the page comes up unstyled with no simulator. It says so on the page if that happens. After pulling, runnpm installand restart any dev server that was already running. The simulator remains a static application and the production output can be generated withnpm run build.
Three.js and Cannon are pinned in package.json and bundled by Vite. They are
passed into FlightSimulator from src/main.js, so every module still receives
them by injection and stays testable with stand-ins.
Tailwind is compiled at build time by @tailwindcss/vite from style.css,
which points the scanner at index.html and src — the HUD assembles some
class strings in JavaScript, and those files have to be scanned for the
utilities to be emitted.
The page makes no external requests, so the simulator runs offline.
Run all formatting, lint, unit-test, and production-build checks with:
npm run check
Browser smoke tests use Playwright and run separately with npm run test:e2e.
The simulator opens on a title screen with the controls, over a live view of the aircraft parked above the city while the camera sweeps around it. Nothing is simulated until TAKE OFF is pressed — no physics steps, no input — so the flight always begins from the same state however long the screen is left up.
A page with no #start-screen element, such as an embedded or test document,
starts flying immediately.
Pick one on the start screen; the preview swaps to it straight away. The choice is fixed once the flight begins, because rebuilding mid-air would teleport you. The SR-71 starts higher and faster than the fighters.
| Character | |
|---|---|
| F-22 Raptor | Heavy, thrust-rich, twin canted fins. |
| F-16 Fighting Falcon | Light and single-engined. Rolls fastest, stalls soonest. |
| SR-71 Blackbird | Enormous and fast. Wide turns, thin margins, lights the burner early. |
Adding an aircraft means adding a file under src/rendering/airframes/, listing
it in airframes/index.js, and adding a button carrying its data-airframe id
to the start screen.
| Input | Action |
|---|---|
W / S | Pitch down / up |
A / D | Roll left / right |
Q / E | Yaw left / right |
Shift / Control | Increase / decrease throttle |
G | Toggle landing gear |
P | Pause / resume |
R | Reset the aircraft |
H | Show / hide the HUD panel |
| Input | Action |
|---|---|
| Left stick | Pitch and roll |
| Right stick | Orbit the camera |
| Left / right bumper | Yaw |
| Left / right trigger | Decrease / increase throttle |
| A / Cross | Toggle landing gear |
| B / Circle or Start | Reset the aircraft |
Resetting — whether from R, the gamepad, or a crash — flashes a message
across the HUD, so a building strike reads as a crash rather than an unexplained
teleport back to the start.
Keyboard and gamepad are read together every frame, so a connected controller sitting at rest never takes the keyboard out of the loop.
The HUD controls carry their own state for assistive technology: the HUD button
reports aria-expanded, the sensor buttons report aria-pressed, the gamepad
line is a live region, and the on-screen reticle is hidden from screen readers
because it repeats the panel readouts. Keyboard focus draws a cyan ring, and the
UI transitions collapse under prefers-reduced-motion.
Use the HUD button to open flight data and visualization controls. Choose a sensor view or adjust wind opacity while you fly.
Past the throttle detent in AircraftConfig.js (85% by default) the engines
light two exhaust plumes, each a bright core inside a longer outer cone with
drifting shock diamonds. The plume brightens and stretches with throttle and
spools rather than snapping on. It is purely visual: thrust is unchanged.
The plume colours are linear RGB triples rather than hex literals because Three.js colour management converts hex through sRGB, which would darken them before they reach the shader.
The runway is laid out along the spawn heading, starting just past the northern
edge of the city, so flying straight ahead from a reset brings it into view. It
is scenery rather than its own collision surface — the ground body already spans
the world — and RUNWAY_CONFIG in Runway.js holds its size and markings.
Buildings are kept off the paving and out of the approach corridor leading up to
the near threshold: buildCity rerolls any site the runway reports as
obstructing, which keeps the city deterministic because the generator is seeded.
Touching down with the gear up, or with the gear down above 12 m/s of closing speed or banked past about 37 degrees, still counts as a crash. Below that the aircraft rolls out.
The gear wheels turn at ground speed while they are on the paving and free-wheel down once airborne. Their materials are lifted clear of the ground plane and carry a polygon offset as well: at kilometre range the depth buffer resolves in metres, and a lift small enough to be invisible is not enough on its own to keep the paving in front of the ground.
Each aircraft lives under src/rendering/airframes/ as a build function for
the shapes that are its own, plus data for the parts every aircraft has —
stabilator and rudder hinges, gear positions, nozzle exits, collision shapes —
and its flight config. AircraftModel supplies the machinery around that:
hinging the control surfaces, animating them, building the physics body, and
running the flight model. Adding an aircraft means adding a file and listing it
in airframes/index.js.
The physics model uses SI units internally: metres (m), seconds (s), kilograms
(kg), newtons (N), pascals (Pa), and radians. HUD conversions are isolated in
UnitConversions.js; the deliberately rounded prototype display uses 2 knots
per m/s, 3 feet per metre, and 180 feet/minute per m/s.
Velocity is transformed into aircraft-local coordinates where Z is forward, Y
is up, and X completes the right-handed frame by pointing out the left wing
(Three.js and Cannon.js are right-handed, so a nose along +z with +y up puts
the right wing on -x). Angle of attack and sideslip are derived from that
velocity; positive sideslip means the airflow is pushing the aircraft toward its
left wing. Dynamic pressure is q = 1/2 rho V², with density following an
exponential atmosphere (rho = 1.225 exp(-altitude / 8500)). Mach uses a
constant 343 m/s speed of sound.
The skyline is generated from a seeded generator in Random.js, so the city —
and every collision with it — is the same on every load. Pass a random
function or a city override to Environment to vary it.
Each block draws its own height between minBlockHeight and maxBlockHeight,
biased towards the bottom of the range by heightBias so the towers stay rare
enough to read as towers. The blocks are still one instanced mesh and one draw
call: the geometry is a box of unit height that each instance stretches, and
each block gets a collision box matching the height it was drawn at. The top of
the range stays below the 150 m spawn altitude, because nothing keeps a block
off the origin the aircraft appears over.
Lift and profile drag coefficients are linearly interpolated from the tables in
AircraftConfig.js. Lift falls after the configured 15-degree stall angle;
induced drag is proportional to lift coefficient squared. Deployed gear adds
0.08 to drag coefficient. Aerodynamic forces act along the local airflow/lift
axes, sideslip produces a restoring side force, and control moments scale with
dynamic pressure. The fixed-step physics loop, fixed coefficient tables, and
absence of random inputs make a repeated initial state and input sequence
deterministic.