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
A little endless runner: a lop-eared rabbit and fourteen other animals hop over cacti, duck under branches and eat to keep going. Its music adapts to the run.
GamesJavaScript · HTMLGitHub @ahackelOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Lop Hop: a little endless runner with adaptive chiptune music (Stardrift). Play: https://andreashackel.de/lop-hop/
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A little endless runner: a lop-eared rabbit hops over bushes, cacti, rocks and logs, ducks under branches and crows, and eats to keep its energy up. Every bump costs energy; with none left, it is knocked out (birds circle its head).Read the full README ↗A little endless runner: a lop-eared rabbit hops over bushes, cacti, rocks and logs, ducks under branches and crows, and eats to keep its energy up. Every bump costs energy; with none left, it is knocked out (birds circle its head). A run is a day and a night (DAY and NIGHT in big letters as they come), each animal in a land of its own, with obstacles of its own (the meadow: bushes and sunflowers; the desert: cacti and tumbleweeds; the forest: toadstools, pines and stumps; the mountains: rocks and boulders; the canyon: red rock spires; the snow: snowmen, snowy pines and ice; then round again), and it gets harder as it goes: faster (up to 200 art pixels a second, about as fast as the eye follows on a screen), closer obstacles, more crows, branches and packs, less food, more tiring. An animal's course is the same every run (what comes and where), and every obstacle can be got past: there is always room to land and jump, duck or stand up in time. A jump pressed just before landing jumps as the animal lands; one pressed while ducking does nothing (let go of the duck first). Food eaten one after another is worth more (25, 50, 75, 100), until a bump or a missed one. Food eaten with the energy almost full (more than there is room for) is a treat too (an animal has at most 5): it flies into the bag by the energy bar, is brought home at dawn (a knock-out loses them all), and on the title the picked animal's treats are over it, with their count (it begs for them now and then), to be dragged to it: it reaches for one in tries, standing up on its hind legs for one held high (the elephant reaches with its trunk), bending down for one held low; let go at its mouth it is eaten, elsewhere it drops (caught as it passes the mouth, or the animal walks to where it lies: near its place it eats it there, further off it picks it up and carries it home in its mouth to eat it there), drawn behind the animals; eaten, the animal chews it (its head nodding, its eyes shut happily, crumbs of it flying off), and once it is eaten up hearts float up. At night the next animal chases it, closer with every bump; a second bump while it is close and it catches the animal. Toward the night's end the sky pales and the sun comes up, and at dawn the run ends, home (HOME!, worth 100): the world comes to a stop and the animal runs off to the right, back to the title, and the first time the chaser, unlocked, runs up to the middle of the screen and stays there (on the title, where the animals stand in a row from right to left, the first on the right, it runs in from the left and stops behind the others): twenty-one in all, the rabbit the easy start, each after it a little harder (faster, hungrier, bumps cost more, crows sooner, obstacles closer and in packs, and a head start on the run's hardness) and worth more points (×1 to ×4). On the title, the animal that would chase the one picked, not unlocked yet, stands behind it as a shadow, once it has chased it in a run (till then, a surprise). The first three animals are free; in the iOS app the others, unlocked by playing all the same, have a padlock over them on the title till the animal pack (an in-app purchase) is bought; on the web every animal is free. A tap on a padlock opens the shop behind a parental gate (a sum for a grown-up, its numbers in words, answered on a row of keys): the pack's animals running by, BUY with its price (the App Store's own sheet takes the payment) and RESTORE PURCHASES; which packs are the player's, the App Store says at each start. A padlocked one can be picked, its song heard, petted, not run with, fed or moved, and it stays at the end of the row (the padlock over it once it stands in its place). One unlocked and not played yet twinkles (from the dawn it joins on, once it stands in the middle). Now and then golden food floats high over an obstacle: catching it gives a super power for 8 seconds, each animal its own. Every animal has a song of its own: the game's song in its key, mode and pace, with its own voices and a motif of its own (the same three recordings). One table of ten high scores, kept in the browser: the animal, name, score (the picked animal runs over beside it, and back to its place after; in from the right after a run that got home). Every run is written down, for balancing the game (see the balance page below). Everything, the buttons and the high scores too, is drawn in the game's own low-res pixels. In English or German (the device's language).
| Animal | Food | Power |
|---|---|---|
| rabbit (×1) | carrot | super hop: higher, and once more in the air |
| guinea pig (×1.1) | cucumber | popcorn: hops by itself over everything (and for joy), ducks under what hangs low |
| cat (×1.21) | fish | nine lives: bounces off what it hits |
| dog (×1.43) | bone | zoomies: faster, bowls everything over |
| pig (×1.53) | truffle | truffle snout: what is just ahead is dug up as truffles, to eat on the way |
| cheetah ★ (×1.64) | drumstick | sprint: very fast, and untouchable |
| fox (×1.86) | grapes | sly fox: food comes to it, the chaser loses its trail |
| hedgehog (×2.07) | apple | spike ball: rolls through everything |
| squirrel (×2.29) | acorn | glide: falls slowly, jumps again in the air |
| otter (×2.5) | shell | belly slide: under what hangs low, through the rest |
| skunk (×2.71) | beetle | stink: obstacles fade (it runs through them), crows and the chaser flee |
| gorilla ★ (×2.93) | fig | chest drum: ducking drums instead, and the next obstacle is knocked over |
| wolf (×3.14) | sausage | howl: what is just ahead is blown away |
| boar (×3.21) | mushroom | tusk charge: faster, smashes everything (worth double) |
| bear (×3.29) | blueberries | berry rush: double points, food fills it twice as much |
| yak (×3.36) | hay | stampede: a herd runs by again and again, trampling what is ahead; the chaser flees |
| ostrich (×3.46) | melon | fly: keeps flying, its wings beating, while the jump is held |
| elephant ★ (×3.57) | peanut | splash: sprays water from its raised trunk, what is just ahead is washed away |
| dromedary (×3.68) | date | spit: knocks crows and branches away |
| rhino (×3.79) | leaf | quake: stomps, everything on the screen flies off |
| dino ★ (×4) |
Play: https://andreashackel.de/lop-hop/ — on a computer, or on a phone held sideways. Add it to the home screen (Share → Add to Home Screen on an iPhone, Install app on Android) and it runs full screen, offline too (online, it always loads the latest version).
| Keyboard | Touch | |
|---|---|---|
| jump (hold to jump higher) | Space / ↑ | tap the right half |
| duck | ↓ | hold the left half |
| pick an animal | ← → on the title | tap it on the title |
| pause (the animal sleeps), go on | P in a run (Space goes on too), or the pause button | the pause button at the top left (or the phone turned upright); tap to go on |
| sound, high scores, credits, full screen | M, H, C, F | the buttons at the top left of the game |
The music is Stardrift, a procedural chiptune engine for games: the song (song.zip: the song and the recordings it plays, as the editor's Export for a game writes it) has a mood for each state of play and stingers for its events, and the page shows the calls as the game makes them (with ?music, on a computer; ?auto lets the animal run by itself, to hear the moods come and go):
| Game | Music |
|---|---|
| title | setMood('menu') |
| high scores | setMood('highscore') |
| knocked out, home | setMood('relaxed') |
| the first stretch, crows from 300, fast from 700 | exploring, tension, action |
| night falls (after the day's 1000) | wonder |
| the next animal gives chase (through the night) | danger |
| a super power (golden food, 8 s) | power, with sting('power') and sting('powerdown') |
| a run starts, a jump, food, a bump, a smash | sting('go'), jump, reward, bump, smash |
| night falls, and the chaser comes | sting('dusk'), chased |
home, at dawn (the chaser unlocked, the first time: discovery) | sting('escape') |
| past the best score so far | sting('record') |
| knocked out (into the high scores) | sting('alert') (fanfare; home: the same) |
Plain files, no build step. GitHub Pages publishes only the game, at each push to main (.github/workflows/pages.yml, Settings → Pages → Source: GitHub Actions): node tools/site.mjs copies the files the game plays from (those sw.js keeps, and the engine's) into _site/, without the tools (the editor, balance, font) and with ?cheats turned off.
npm run dev # http://localhost:8323 (the audio needs http://, not file://)
npm test # the song plays every mood and stinger; every animal is tall enough to hit a branch and ducks under it;
# every course is the same every run and can be got past (played through, six days); it gets harder
# every day and with every animal; nothing newer than Safari 15 without a fallback
node tools/course.mjs # the courses, day by day: how fast, how close, food against tiredness, where energy runs out
npm run engine # copies the engine from ../stardrift-engine into engine/ (or: node tools/engine.mjs <path>)
npm run icons # draws icons/ (and the iOS app's icon) from the game's own pixel art
npm run rigs # writes every animal's build the way the editor saves it
Once per checkout, git config core.hooksPath tools/hooks: at each commit the hook writes version.js (when the build
was made, and which engine it plays); the credits screen shows when it was made.
In the address: ?cheats (not on the published game) puts orange buttons at the bottom of the screen to get somewhere quickly (in a run WIN: home at dawn at once, POWER: the animal's super power, ENERGY: the energy full again; on the title UNLOCK: the next animal, BUY: every animal pack, as if bought (no padlocks), FOOD: all the treats the picked one can have; RESET: as ?reset, the cheats still on; TOOLS: the editor), its runs not written down, ?reset starts the game over as the first time (nothing unlocked, no high scores, no treats; the runs written down for balancing kept, ?reset=all: them too), ?lang=de (or =en) plays it in that language (otherwise: the device's, if there is a file for it, else English), ?music shows the music's moods and calls under the game, ?all opens every animal for a visit, ?top=250 never lets the world go faster than that (art pixels a second; from three quarters of it on ever more slowly, as the game's 150 → 200) for a visit, to try another top speed (its runs not written down), ?shop plays it as the iOS app does (the padlocks, the gate, the shop, but no App Store: BUY says the shop is closed; with ?cheats it gets the pack at once), ?treats gives every animal all the treats it can have (5) for a visit (?treats=2: 2), ?auto runs by itself, ?fps shows the frames a second and the
work of a frame (on average and at most, in ms: a frame has 16.7 at 60) and the canvas's size, and since the start the
frames that took other than a 60th of a second's steps (UNEVEN) and those over 50 ms while running (LONG), ?scale=3
draws the game 3 screen pixels an art pixel (fewer than the screen has: the page scales it up), ?hz=120 draws up to 120
frames a second where the browser gives that many (not in Safari on an iPhone or iPad, nor in the iOS app: 60 there),
?log writes each frame that came late or early, or took long, to the console with what happened just before it (in the
iOS app: Xcode's console; a debugger attached holds up a frame now and then, so smoothness is judged with the app opened
from the home screen).
The animals are edited in the editor: with npm run dev running, open http://localhost:8323/editor. It has three
modes: Setup (the build), Animate (the moves) and Play, where the animal is handled as on the home screen, by the game's
own code (picked up, carried, dropped, petted, fed), with three feel sliders (heavy, lively, cuddly: each moves several
of the numbers of how it is handled, handling in its file) and, behind each ⚙, those numbers and its ears' and tails'
physics (?mode=play opens it there). The inspector is laid out as Figma's: a shape's place as X, Y, W, H and its turn
whatever it is (its own numbers behind ⚙), each number in a box with its unit (px, °, %, s), a sum typed into one
worked out (*2, +1: of what it is), the arrow keys a step (⇧ ten), its name dragged to scrub it; what can be added
(a swing) a + by its section's title; every setting's tooltip says what it is for. A swing's stiffness, damping and
weight are one number, how floppy it is; a gait's main settings are shown (the rest behind ⚙), or one like another
animal's. The stage's View menu: the onion skin, the bones, the swings, the screen's pixels, which handles. In Setup the
bones and the shapes are two tabs, the one a pick is in shown. The timeline's frames are dragged across to go through
them. Changes are saved by themselves a moment after each (Revert: back to as the editor found it). The face's frames (its eye open, blinking,
hurt, knocked out, happy; a mouth, if it has one) are drawn pixel by pixel in Setup (pick the face), and a clip's keys
pick which one shows. An animal's skins (the wolf is the fox's: its colors swapped) are picked beside it, a new one
made there, as is a new animal started from the one shown (in the editor until the game is given it: its place in
assets/animals.js); the library (⌘O) shows the game's animals in the order they unlock in, dragged to another place
in it (saved at once: the animal takes that place's difficulty, its own jump and food go with it); a leg or a pointed ear without one gets its far side made with a click (as the cat's: in every move, to tune).
Colors come from the game's one palette (assets/palette.js: named for how they look, orange, darkGreen; a few with a job,
outline, ink, the sky's): a color is picked from it in a popup, where a new one can be added. The settings page (/settings, in the
tools' bar) edits it: a swatch each; a color is set by day, worked out by night (by a rule, set there too), a role (the
sky, the words, the outline) set by night too. Shading needs no colors of its own: each color's
outline, underside and far legs are worked out from it, always darker. The settings page also sets the numbers every
animal shares (speed, days and nights, obstacles, energy and food, bumps, golden food: into level.js, game.js,
tools/course.mjs); how they play out, animal by animal, is tried on the balance page.
Drag the handles on the joints, shapes, legs, tails and ears, pick colors and layers in the list, watch the animal run in
the game's scene, and save: it writes the animal's build back into its file. Its moves are on a timeline, with the frames
before and after faded: a jump, hurt or knocked-out pose can be set by hand (drag the joints, paws, head, tail and ears of
each keyframe), a run or a crawl tuned by its gait's settings. In the game, an animal eases from one move into the next
and squashes as it lands.
The balance is tuned on the balance page (http://localhost:8323/balance, with npm run dev): the animals in the order
they unlock in (assets/animals.js: drag one along the ladder to another place), each place's difficulty (one slider:
where its dials, speed, drain, meals, …, are between the first place's and the last's; the dials themselves behind ⚙),
each animal's feel (its jump's height and time in the air), and the numbers all the animals share (the main ones; the
rest behind ⚙), each with a slider, and what they come to,
worked out by the game's own code: how hard it is to get the next animal (how often a player can run into something
and still get home at dawn, away from it, half the time; and the chance, missing 1 in 33, 17 or 8), how many days
a player who never misses lasts (eating all the food, or 4 in 5), the score that reaches, how fast it goes, how much
warning an obstacle gives. The ladder shows all twenty-one in a row, each
meant to be harder than the one before (one that is not is marked), and what was there before behind it (shown as their
difficulty, a bar dragged up or down sets it). The changes are kept in the browser till Save writes them into the files
(assets/animals.js, level.js, game.js). Under the ladder, your runs: for each animal how many, how long,
how often it got home, the tries it took to get the next one, how often a jump or a duck
was missed, how much food was eaten, how runs ended. The game writes every run down; played from the dev server (on this computer, or
an iPad in the house at http://.local:8323) they come in by themselves (data/runs.jsonl), from the
app they are shared from the credits (SHARE: AirDrop, Mail, Files…) and dropped onto the balance page.
How many play on the web is counted at GoatCounter (the counter in game.js): no
cookies, nothing kept in the browser for it, no address, only the sums. Only the game at andreashackel.de (not the iOS
app, the dev server, nor ?cheats, ?auto, ?all, ?treats, ?shop): the visit (where from, the screen) and the
events back (played in this browser before), home-screen (opened from it), run, first-run (the first in this
browser) and unlocked/<animal> (unlocked/dog: the three free animals played through). ?nocount once leaves a
browser out for good (your own; ?nocount=off counts it again).
| Path | |
|---|---|
index.html | the page: only the game, on every screen (?music on a computer: the music's moods and calls under it) |
game.js | the game: input, the run, energy, its buttons, the high scores, the calls to the music |
music.js | a song for every animal: the song in its key, mode, pace and swing, with its own voices and a motif (the rabbit's: the song as it is) |
level.js | the course: what comes where (the same every run, for an animal), how fast, how it gets harder, why it is fair |
art.js | the pixel art, drawn from shapes: the rigs that draw the animals, obstacles, food |
font.js | the pixel font: capitals 3×5 in a cell of 7 rows (a row above and below: Ä Ö Ü's dots, the $'s line, the comma's and Q's tails, brackets and / the whole height; room for accents), text() and textWidth() |
icons.js | the buttons' icons (7×7) and the pads' (9×9), as rows of pixels |
lang.js, lang/ | what the game writes, in English and German: a file per language, a line each, id = words, written as usual (the game writes them in capitals); npm test checks every language has every id, the same {name} holes, only letters the font has, and lines that fit. A new language: a file with the ids of en.txt, its code in LANGS (lang.js), the file in sw.js, and the language in the Xcode project (knownRegions, Info.plist CFBundleLocalizations: an app reports only the languages it declares) |
asset.js, assets/ | an object as an asset: one file of data each (assets/<animal>.js, format 4: its bones (only what moves), its shapes in groups drawn back to front, each on a joint or a bone (all it looks like), its face, the physics of its chains, how it is handled on the home screen, its moves: gaits' settings and clips' keys), what the editor saves and the game draws from; and the game's palette (assets/palette.js); the animals in the order they unlock in, each its name, food and jump, and each place's dials (assets/animals.js); asset.js says what is in one |
MIT (see LICENSE), the engine copy in engine/ too. The recordings in song.zip are CC0 (public domain), by Versilian Studios.
| roast leg |
| giant dino: twice as big, it tramples everything in its way, for double points |
editor/the editor, at /editor (through npm run dev, which saves for it: into the animal's file in assets/) |
tools/nav.js | the bar at the top of the editor, balance, settings and font: a link to each, and to the game (with ?cheats) |
settings/ | the settings page (its code tools/settings.js; the shared numbers' list tools/shared.js): the game's palette; the numbers every animal shares (each a number as the editor's inspector has it, editor/num.js) |
balance/ | the balance page (its code in tools/): the animals' order, each place's difficulty, each animal's feel, and the shared numbers, how hard each animal is (balance-worker.js works it out with course.mjs), and your runs |
font/ | every letter of the font (font.js), any size (with its pixel grid and the cell's rows), a line to try (in capitals, as the game writes it; letters the font lacks named) |
data/runs.jsonl | the runs played from the dev server, a line each (not in git) |
version.js | when the build was made, and its engine (written at each commit, tools/version.mjs) |
song.zip | the music: the song and its recordings, loaded as it is (music.loadZip); a new version comes from the editor's Export for a game, saved over it |
engine/ | a copy of the Stardrift engine's code (engine/VERSION says which commit; its recordings come in song.zip); npm run engine refreshes it |
sw.js, manifest.webmanifest, icons/ | the installable app: offline files, name, icons, landscape |
app/ | the iOS app (Capacitor): npm install and npm run sync there copy the game's files into www/ and the Xcode project (ios/), npm run open opens it in Xcode, npm run testflight (-- patch, minor or major to raise the version, nothing for another build of it) tests, archives and uploads a build to TestFlight, then commits and tags the new numbers (ios-1.0.1-3); it wants nothing uncommitted and the App Store Connect app (de.andreashackel.lophop) made. The App Store: ios/App/App/StorePlugin.swift (StoreKit 2, the game's store calls it), registered in SceneDelegate.swift; Run in Xcode buys from Products.storekit (the shared scheme's StoreKit configuration: no App Store Connect needed, Debug → StoreKit → Manage Transactions to undo a purchase), a TestFlight build from the sandbox |