Platformer race template (SKYLEDGE)
Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under @netforge with source at github.com/netnetdev/net-forge.
SKYLEDGE is a complete multiplayer platformer race: up to eight climbers race along cliffside courses to the flag, with tuned jump feel (coyote time, jump buffering, variable jump height, an air dash), springs, one-way ledges, moving platforms and thorns. A round is one course; seat bots fill empty places so one person still has a race.
npm create netforge@latest my-game -- --template platformer --yes
cd my-game
npm run playKeys: A D or arrows to run, Space W or Up to jump (hold for height), Shift to dash, Down plus Jump to drop through a ledge, R or Enter to ready the next course.
What you get
| Part | What it does | Where |
|---|---|---|
| Courses | Blocks, one-way ledges, thorns, springs, moving platforms, coins and checkpoints, in metres | levels in game({...}) |
| Motor | Run speed, jump height, time to apex, fall gravity, jump cut, coyote and buffer windows, air jumps, dash | motor in game({...}) |
| Race rounds | Countdown, race, results, next course; places in order of finishing | the engine's race runner |
| Route planner | Checks every course can be finished, and drives the seat bots | runs in the build and in npm test |
| Netcode | 60 Hz deterministic platform physics on a 1/1024 m grid; your climber is predicted bit for bit | the template package |
| Playtest bots | Four climbers plus seat bots for four minutes: rounds, finishes, falls, earn | npm test |
Measured on an Apple M2 Pro, Node 22: four playtest climbers plus seat bots over four minutes (14,400 ticks at 60 Hz) ran 7 courses, all four finished every one, nobody fell, and the server tick stayed under 0.02 ms at p99.
1. Build a course
Coordinates are metres in side view: x runs right from the start to the flag and y runs up. blocks are [x, y, width, height] from the bottom-left corner. A full running jump clears about 6 m and about 8.5 m with the dash.
import { game } from "@netforge/engine";
const ground = (x0: number, x1: number, y = 0): [number, number, number, number] => [x0, y - 4, x1 - x0, 4];
export default game({
template: "platformer",
style: "lowpoly",
tickRate: 60,
places: { cliffs: { chunks: "places/cliffs", spawn: [0, 0, 0] } },
players: { prefab: "style.lowpoly.rogue", max: 8, bots: 3 },
round: { seconds: 180 },
levels: [
{
name: "First Steps",
start: [0, 0],
flag: [70, 2.4],
blocks: [ground(-8, 24), [14, 0, 3, 1.2], ground(28, 50), [50, -4, 24, 6.4]],
ledges: [[38, 2.6, 4]],
hazards: [[33, 0, 2, 0.5]],
springs: [[45, 0, 21]],
coins: [[4, 1], [5.2, 1], [6.4, 1]],
checkpoints: [[30, 0]],
},
],
});Steps up of 1 to 2.5 m and gaps of 2 to 5 m play well, with a checkpoint every 30 to 50 m. If the route planner finds no way to the flag, the build fails and names the course.
2. Tune the jump feel
Add motor: { runSpeed: 8, jumpHeight: 3.6, airJumps: 1 } to game({...}). The ranges are runSpeed 3 to 14, jumpHeight 1 to 8, timeToApex 0.2 to 0.7, fallGravity 1 to 3, jumpCut 0.2 to 1, coyoteSeconds and bufferSeconds 0 to 0.25, airJumps 0 to 2 and dashSpeed 6 to 30. Change a few at a time; the planner re-checks every course with the new numbers.
3. Reward a clean climb
import { earn, system } from "@netforge/engine";
system("clean_climb", ({ on, emit }) => {
const falls = new Map<number, number>();
on("player.fell", (ids) => {
for (const id of ids) falls.set(id, (falls.get(id) ?? 0) + 1);
});
on("player.finished", (ids) => {
const clean = ids.filter((id) => !falls.get(id));
if (clean.length) emit("climb.clean", clean);
});
on("run.started", (ids) => {
for (const id of ids) falls.delete(id);
});
});
earn.rule("clean_climb", { when: "climb.clean", n: 1, pay: 12, oncePer: "run" });race.won and race.podium count only when another person was in the round, and seat bots never earn.
4. Check it with bots
npm testHow it works
The simulation is @netforge/engine-sim's platform mode: plain TypeScript swept box movement on a 1/1024 m grid at 60 Hz, so the browser replays its own climber exactly the way the server does. Climbers pass through each other and each collects their own coins, so nothing another player does can make your prediction wrong. That is why a race feels instant even on a slow connection. See Netcode and prediction.
The template draws the side view with three.js in every art style. The full guide is Make a 3D platformer for the web.
Alternatives
- Phaser has arcade physics and tilemaps and is a common choice for 2D platformers in the browser. Pick it for a single-player platformer with pixel art and hand-made levels.
- Godot has a 2D physics engine, a character body node and a level editor. Pick it when you want to build levels visually and export to desktop too.
- Net Forge fits when you want an eight-player race with server-checked finishes, prediction that never mispredicts, and courses an agent can write as data.
FAQ
Does the platformer have coyote time and jump buffering?
Yes: 0.1 s of coyote time and a 0.13 s jump buffer by default, plus variable jump height, a lighter apex while Jump is held, a heavier fall and ceiling corner nudges.
Can players collide with each other?
No. Climbers are ghosts to each other in this template, which is what lets each browser predict its own climber exactly.
How do the bots know the course?
A route planner finds a path to the flag on every course, and the seat bots run those routes. The same planner is the build's softlock check.
How many players can race?
Up to eight per room, with seat bots filling places up to players.bots.
Is it 2D or 3D?
The game plays in side view, and the template renders it as a 3D three.js scene in any of the six art styles.