Midterm 3 Progress: ROOFTOP RUSH
The Concept
ROOFTOP RUSH is a side-scrolling parkour runner built in p5.js. The player controls a free-runner crossing a city skyline at dusk. The city scrolls to the right at increasing speed. The player must jump between rooftops, avoid obstacles, and collect coins. Each run generates a different sequence of buildings, gaps, and obstacles, so no two runs are the same.
The central idea is one core mechanic: the more points you earn, the farther your jumps carry you. Score is not just a number in the corner. It directly changes how far the player can jump. Early in a run, jumps are short and the player must plan each crossing carefully. As the score grows, the jumps grow with it. The player gains the ability to clear gaps that were not possible at the start. At the same time, the world speeds up. The game becomes harder and more powerful at once. The tension between those two forces is what makes each run feel urgent.
The planned interactive features are:
- Grapple Hook (G key): A crane will spawn automatically over any gap that is too wide to jump. Pressing G will lock onto the crane and swing the player across.
- Wall-Run (Up key on a wall): Touching a wall will trigger a wall-slide. Holding Up will convert it into a wall-run, carrying the player upward before launching off.
- Trick System (F for flip, R for spin): Performing tricks in mid-air will award bonus points. Chaining multiple tricks in one jump will multiply the reward.
- Slide Kick: Sliding into certain obstacles will destroy them and award points instead of dealing damage. This turns a defensive move into an offensive one.
- Upgrade Shop: Coins will carry over between runs. The player will spend them on permanent upgrades such as stronger jumps, longer dashes, or a larger coin magnet range.
- Day and Night Cycle: The sky will shift from sunset to night over time. Stars will appear and a helicopter with a spotlight will patrol the skyline after dark.
The Riskiest Part: The Jump Curve
The most uncertain part of this project is the score-to-jump-force progression curve. This mechanic is the entire point of the game. If the curve is wrong, nothing else works. If it is too flat, the player will not notice the progression. If it is too steep, the player will overshoot buildings and the game will break.
The challenge is not technical. It is perceptual. Jump force is measured in pixels per frame. That number has no intuitive meaning to a player. The curve needs to satisfy three conditions:
- The change must be noticeable early. A player who earns 500 points should feel a real difference in jump distance.
- It must plateau at high scores. The growth must slow down so the game stays controllable.
- The maximum jump height must stay within the bounds of the level. Buildings differ in height by at most 90px. The widest gap will be 180px.
I plan to use a logarithmic curve. Logarithms grow fast near zero and flatten at large values. This matches both requirements. The formula will be:
jumpForce = max( BASE_JUMP - K * ln(1 + score) , MAX_JUMP )
Planned constants: BASE_JUMP = -11.0, K = 0.0004, MAX_JUMP = -18.5. The negative sign follows the p5.js convention where upward velocity is negative.
To test this before building the game, I wrote a standalone sketch. It plots jump height in pixels against score so I can read the curve visually and check the numbers at key milestones.
// Risk-reduction test sketch
// Paste into p5.js editor to visualize the jump progression curve
// before writing any game logic
const BASE_JUMP = -11.0;
const MAX_JUMP = -18.5;
const K = 0.0004;
const GRAVITY = 0.62;
function getJumpForce(score) {
return max(BASE_JUMP - K * log(1 + score), MAX_JUMP);
}
// Physics: h = v^2 / (2 * gravity)
function jumpHeight(score) {
let v = abs(getJumpForce(score));
return (v * v) / (2 * GRAVITY);
}
function setup() {
createCanvas(700, 400);
}
function draw() {
background(20, 20, 30);
// axis labels
fill(180); noStroke(); textSize(12);
text("Score ->", 600, 390);
text("^ Jump Height (px)", 10, 20);
// reference lines
stroke(60, 60, 80);
for (let h = 50; h <= 300; h += 50) {
let y = map(h, 0, 300, height - 40, 20);
line(40, y, width - 20, y);
fill(100); noStroke(); text(h + "px", 2, y + 4);
stroke(60, 60, 80);
}
// curve
stroke(255, 160, 40);
strokeWeight(2.5);
noFill();
beginShape();
for (let score = 0; score <= 10000; score += 50) {
let x = map(score, 0, 10000, 40, width - 20);
let y = map(jumpHeight(score), 0, 300, height - 40, 20);
vertex(x, y);
}
endShape();
// milestone markers
let milestones = [0, 500, 1000, 2500, 5000, 10000];
for (let s of milestones) {
let x = map(s, 0, 10000, 40, width - 20);
let h = jumpHeight(s);
let y = map(h, 0, 300, height - 40, 20);
stroke(255, 80, 80); fill(255, 80, 80); ellipse(x, y, 7);
noStroke(); fill(220);
text("s=" + s + "\n" + nf(h, 0, 1) + "px", x - 10, y - 16);
}
noLoop();
}
The sketch produces the following numbers:
| Score | Jump height | What the player will feel |
|---|---|---|
| 0 | 97 px | Short. The player must judge each gap carefully. |
| 500 | 116 px | Noticeably higher. The reward is felt immediately. |
| 1,000 | 128 px | Confident. Medium gaps are now comfortable. |
| 2,500 | 147 px | Strong. Most gaps are within reach. |
| 5,000 | 163 px | Powerful. Wide gaps feel manageable. |
| 10,000 | 177 px | Near the ceiling. The curve has flattened. |
The hard cap at MAX_JUMP = -18.5 gives a maximum jump height of 277px. That is just under half the canvas height and within the maximum building height of 360px. A player at any score will never jump off screen. The widest gap in the level will always be crossable. These numbers confirm the curve is safe to use before writing a single line of game logic.
The second risk is procedural level generation. A bad sequence could produce an impossible gap or a long boring flat stretch. To address this, I will clamp the height difference between adjacent buildings to 90px. I will also write a query function that automatically places a crane anchor over any gap wider than 110px. The grapple hook will always be reachable from that gap, so no run will ever be blocked by the level generator.
Next Steps
The concept and design are clear. The riskiest algorithm has been tested and validated. The next step is to build the full game system: the player state machine, the building generator, the collision detection, the scoring logic, and the upgrade shop.
p5.js v1.9.0 · February 2026 · Intro to IM, NYUAD