Tech Details

Eiffel Tower Parachute - Day 15

Canvas + Physics

Table of Contents

01. World Coordinate System

The game uses a world coordinate system measured in meters, which is then transformed to screen coordinates. This makes physics calculations intuitive (the Eiffel Tower is 330m tall).

World vs Screen Coordinates

// World constants (in meters) const TOWER_HEIGHT = 330; // Eiffel Tower height const GROUND_LEVEL = 0; // Scale calculation worldScale = canvasHeight / (TOWER_HEIGHT + 50); // Transform world coordinates to screen pixels function worldToScreen(worldX, worldY) { return { // Center horizontally x: canvasWidth / 2 + worldX * worldScale, // Y from bottom (world Y=0 is ground) y: canvasHeight - (worldY * worldScale) - 30 }; }

Why Use World Coordinates?

Using real-world units makes the game logic more intuitive:

Coordinate Transformation

World (meters) * Scale Factor = Screen (pixels)

02. Physics Simulation

The player follows basic physics with gravity, velocity, and air resistance. The simulation runs every frame.

Player State Object

let player = { x: 0, // Horizontal position (world units) y: 330, // Vertical position (starts at tower top) vx: 0, // Horizontal velocity vy: 0, // Vertical velocity (negative = falling) parachuteDeployed: false, landed: false }; // Physics constants const GRAVITY = 0.15; const AIR_RESISTANCE = 0.01; const MAX_FALL_SPEED = 8; const HORIZONTAL_SPEED = 0.8;

Physics Update Loop

function update() { // Apply gravity (accelerates downward) player.vy -= GRAVITY; // Player input for horizontal movement if (keys.left) { player.vx -= HORIZONTAL_SPEED * 0.1; } if (keys.right) { player.vx += HORIZONTAL_SPEED * 0.1; } // Air resistance slows horizontal movement player.vx *= (1 - AIR_RESISTANCE); // Terminal velocity - can't fall faster than this if (player.vy < -MAX_FALL_SPEED) { player.vy = -MAX_FALL_SPEED; } // Update position player.x += player.vx; player.y += player.vy; // Check for landing if (player.y <= GROUND_LEVEL) { player.y = GROUND_LEVEL; player.landed = true; } }
Physics Tip

Negative velocity means falling (y decreases). Gravity subtracts from vy each frame, making the player accelerate downward.

03. Wind Mechanics

Wind adds challenge by pushing the player horizontally. It's randomized each jump with varying strength and direction.

Wind Generation

let wind = { strength: 0, // 1-5 scale direction: 1 // 1 = right, -1 = left }; function generateWind() { // Random strength between 1 and 5 wind.strength = 1 + Math.random() * 4; // Random direction wind.direction = Math.random() > 0.5 ? 1 : -1; updateWindDisplay(); }

Applying Wind Force

function update() { // Wind pushes player horizontally player.vx += wind.direction * wind.strength * 0.01; // Wind is constant force, player must compensate // Strong wind (5) = significant drift // Weak wind (1) = minor adjustment needed }

Visual Wind Indicator

function updateWindDisplay() { const arrow = document.getElementById('windArrow'); // Arrow direction arrow.textContent = wind.direction > 0 ? '→' : '←'; // Scale arrow based on wind strength arrow.style.transform = `scaleX(${wind.strength / 3})`; } // Wind particles for visual feedback function drawWindParticles() { // Spawn dashes moving in wind direction if (Math.random() < 0.3) { windParticles.push({ x: wind.direction > 0 ? -10 : canvasWidth + 10, y: Math.random() * canvasHeight * 0.8, speed: wind.strength * 2 }); } }

04. Parachute Deployment

Deploying the parachute dramatically changes the physics - increasing drag and slowing the fall.

Parachute Physics

const PARACHUTE_DRAG = 0.85; // Multiplier each frame const MAX_PARACHUTE_FALL = 2; // Much slower terminal velocity function update() { if (player.parachuteDeployed) { // High drag - velocity decays quickly player.vy *= PARACHUTE_DRAG; player.vx *= 0.95; // New, much lower terminal velocity if (player.vy < -MAX_PARACHUTE_FALL) { player.vy = -MAX_PARACHUTE_FALL; } } else { // Normal freefall physics player.vx *= (1 - AIR_RESISTANCE); if (player.vy < -MAX_FALL_SPEED) { player.vy = -MAX_FALL_SPEED; } } }

Deployment Trigger

function onKeyDown(e) { if (e.key === ' ') { // Can only deploy once, while falling, not landed if (!player.parachuteDeployed && gameRunning && !player.landed) { player.parachuteDeployed = true; } e.preventDefault(); } }

Velocity Comparison

Freefall: -8 m/s vs Parachute: -2 m/s
Game Balance

Players who deploy the parachute get a 20% score bonus for controlled landings, encouraging skillful timing.

05. Target Collision System

Targets are circles on the ground. We check if the player lands within a target's radius and award points based on accuracy.

Target Generation

function generateTargets() { targets = []; // 3-5 targets per jump const numTargets = 3 + Math.floor(Math.random() * 3); const spacing = 300 / numTargets; for (let i = 0; i < numTargets; i++) { // Spread horizontally with some randomness const baseX = (i - numTargets/2 + 0.5) * spacing; const x = baseX + (Math.random() - 0.5) * 50; // Target type determines size and points const type = Math.random(); let radius, points, color; if (type < 0.2) { // Bullseye: small, 100 points radius = 15; points = 100; color = '#ffd700'; } else if (type < 0.5) { // Medium: 50 points radius = 30; points = 50; color = '#44ff44'; } else { // Large: 25 points radius = 50; points = 25; color = '#44aaff'; } targets.push({ x, y: GROUND_LEVEL, radius, points, color }); } }

Landing Detection

function handleLanding() { let landedOnTarget = null; let minDist = Infinity; // Check each target for (const target of targets) { // Distance from player to target center const dist = Math.abs(player.x - target.x); // Within radius? And closer than previous matches? if (dist < target.radius && dist < minDist) { minDist = dist; landedOnTarget = target; } } if (landedOnTarget) { // Accuracy bonus: closer to center = more points const accuracy = 1 - (minDist / landedOnTarget.radius); let points = Math.floor(landedOnTarget.points * (1 + accuracy)); // Parachute bonus if (player.parachuteDeployed) { points = Math.floor(points * 1.2); } score += points; } else { // Missed all targets - consolation points score += 5; } }
Collision Note

We use 1D collision (horizontal distance only) since targets are on the ground. A full 2D check would compare distance to radius in both dimensions.

06. Drawing the Eiffel Tower

The Eiffel Tower is drawn using Canvas path operations, creating its iconic silhouette with layered detail.

Tower Silhouette

function drawEiffelTower() { const baseY = canvasHeight - 30; const topPos = worldToScreen(0, TOWER_HEIGHT); const towerWidth = 80; ctx.fillStyle = '#5D4837'; ctx.strokeStyle = '#4A3728'; // Draw iconic tapered shape ctx.beginPath(); // Left leg - curves inward as we go up ctx.moveTo(canvasWidth/2 - towerWidth, baseY); ctx.lineTo(canvasWidth/2 - towerWidth * 0.7, baseY - canvasHeight * 0.3); ctx.lineTo(canvasWidth/2 - towerWidth * 0.4, baseY - canvasHeight * 0.55); ctx.lineTo(canvasWidth/2 - towerWidth * 0.15, baseY - canvasHeight * 0.75); ctx.lineTo(canvasWidth/2, topPos.y); // Right leg - mirror of left ctx.lineTo(canvasWidth/2 + towerWidth * 0.15, baseY - canvasHeight * 0.75); ctx.lineTo(canvasWidth/2 + towerWidth * 0.4, baseY - canvasHeight * 0.55); ctx.lineTo(canvasWidth/2 + towerWidth * 0.7, baseY - canvasHeight * 0.3); ctx.lineTo(canvasWidth/2 + towerWidth, baseY); ctx.closePath(); ctx.fill(); ctx.stroke(); }

Adding Platforms and Details

// Horizontal viewing platforms ctx.fillStyle = '#6B5344'; // First platform (bottom observation deck) const p1Y = baseY - canvasHeight * 0.18; ctx.fillRect(canvasWidth/2 - towerWidth * 0.85, p1Y, towerWidth * 1.7, 8); // Second platform (middle) const p2Y = baseY - canvasHeight * 0.45; ctx.fillRect(canvasWidth/2 - towerWidth * 0.5, p2Y, towerWidth, 6); // Top platform const p3Y = baseY - canvasHeight * 0.72; ctx.fillRect(canvasWidth/2 - towerWidth * 0.2, p3Y, towerWidth * 0.4, 4); // Lattice effect with horizontal lines ctx.strokeStyle = '#3D2817'; ctx.lineWidth = 1; for (let i = 0; i < 6; i++) { const y = baseY - canvasHeight * (0.1 + i * 0.1); const spread = towerWidth * (1 - i * 0.12); ctx.beginPath(); ctx.moveTo(canvasWidth/2 - spread, y); ctx.lineTo(canvasWidth/2 + spread, y); ctx.stroke(); }
Design Tip

The tower uses percentages of canvas height rather than world coordinates, ensuring it always fits the screen properly regardless of scale.