TECH DETAILS

Mario Like - Day 3

Advanced Level

Table of Contents

01 / Platformer Physics

Every frame, we apply gravity to the player's vertical velocity, then update position. This creates the "weight" feel.

const player = { x: 100, y: 300, width: 32, height: 48, vx: 0, // Horizontal velocity vy: 0, // Vertical velocity grounded: false, speed: 5, jumpForce: -12 }; const GRAVITY = 0.5; const MAX_FALL_SPEED = 15; function updatePhysics() { // Apply gravity player.vy += GRAVITY; // Clamp falling speed if (player.vy > MAX_FALL_SPEED) { player.vy = MAX_FALL_SPEED; } // Apply velocity to position player.x += player.vx; player.y += player.vy; }
Clamping Fall Speed

Without MAX_FALL_SPEED, the player would accelerate forever when falling, making precise landings impossible.

02 / Platform Collision

Platform collision is tricky - we need to detect from which direction the player is colliding and respond correctly.

function handlePlatformCollision() { player.grounded = false; for (const platform of platforms) { if (!isColliding(player, platform)) continue; // Calculate overlap on each axis const overlapTop = (player.y + player.height) - platform.y; const overlapBottom = (platform.y + platform.height) - player.y; const overlapLeft = (player.x + player.width) - platform.x; const overlapRight = (platform.x + platform.width) - player.x; // Find smallest overlap to determine collision side const minOverlap = Math.min(overlapTop, overlapBottom, overlapLeft, overlapRight); if (minOverlap === overlapTop && player.vy > 0) { // Landing on top player.y = platform.y - player.height; player.vy = 0; player.grounded = true; } else if (minOverlap === overlapBottom && player.vy < 0) { // Hitting from below player.y = platform.y + platform.height; player.vy = 0; } else if (minOverlap === overlapLeft) { // Hitting left side player.x = platform.x - player.width; } else if (minOverlap === overlapRight) { // Hitting right side player.x = platform.x + platform.width; } } }

03 / Jump Mechanics

A good jump feels responsive. We use variable jump height - hold the button longer to jump higher.

const JUMP_FORCE = -12; const JUMP_CUT_MULTIPLIER = 0.5; // When releasing early function handleJump() { // Start jump if (keys.jump && player.grounded) { player.vy = JUMP_FORCE; player.grounded = false; } // Variable jump height - cut jump short if button released if (!keys.jump && player.vy < 0) { player.vy *= JUMP_CUT_MULTIPLIER; } } // Coyote time - brief window to jump after leaving platform let coyoteTimer = 0; const COYOTE_TIME = 6; // frames function updateCoyoteTime() { if (player.grounded) { coyoteTimer = COYOTE_TIME; } else { coyoteTimer--; } } // Allow jump if grounded OR within coyote time const canJump = player.grounded || coyoteTimer > 0;
Coyote Time

Named after Wile E. Coyote running off cliffs - gives players a few frames grace period to jump after leaving a platform. Makes the game feel more forgiving.

04 / Enemy AI Patrol

Basic enemies patrol back and forth between boundaries. They reverse direction when hitting walls or platform edges.

const enemy = { x: 300, y: 350, width: 32, height: 32, speed: 1.5, direction: 1, // 1 = right, -1 = left patrolMin: 200, // Left boundary patrolMax: 500 // Right boundary }; function updateEnemy(enemy) { // Move in current direction enemy.x += enemy.speed * enemy.direction; // Reverse at patrol boundaries if (enemy.x <= enemy.patrolMin) { enemy.direction = 1; enemy.x = enemy.patrolMin; } else if (enemy.x + enemy.width >= enemy.patrolMax) { enemy.direction = -1; enemy.x = enemy.patrolMax - enemy.width; } } // Player stomps enemy if landing on top function checkEnemyCollision(player, enemy) { if (!isColliding(player, enemy)) return; const playerBottom = player.y + player.height; const enemyTop = enemy.y; if (player.vy > 0 && playerBottom < enemyTop + 10) { // Stomp! Player bounces, enemy dies enemy.alive = false; player.vy = -8; // Bounce up } else { // Hit from side - player takes damage damagePlayer(); } }

05 / Camera Scrolling

The camera follows the player with a "dead zone" - it only moves when the player reaches the edge of a central region.

const camera = { x: 0, y: 0, width: canvas.width, height: canvas.height }; // Dead zone - camera doesn't move if player is in this region const DEAD_ZONE = { left: canvas.width * 0.3, right: canvas.width * 0.7, top: canvas.height * 0.3, bottom: canvas.height * 0.7 }; function updateCamera() { // Player position relative to camera const playerScreenX = player.x - camera.x; const playerScreenY = player.y - camera.y; // Push camera if player exits dead zone if (playerScreenX < DEAD_ZONE.left) { camera.x = player.x - DEAD_ZONE.left; } else if (playerScreenX > DEAD_ZONE.right) { camera.x = player.x - DEAD_ZONE.right; } // Clamp camera to world bounds camera.x = Math.max(0, Math.min(WORLD_WIDTH - camera.width, camera.x)); } // When drawing, offset everything by camera position function draw() { ctx.save(); ctx.translate(-camera.x, -camera.y); // Draw world objects at their world positions drawPlatforms(); drawEnemies(); drawPlayer(); ctx.restore(); }

06 / Collectibles System

Coins float and spin. When collected, they play an animation before being removed.

const coins = [ { x: 150, y: 280, collected: false, animTimer: 0 }, // ... more coins ]; function updateCoins() { for (const coin of coins) { if (coin.collected) { // Collection animation - float up and fade coin.animTimer++; coin.y -= 2; if (coin.animTimer > 20) { coin.remove = true; } continue; } // Bob up and down coin.displayY = coin.y + Math.sin(Date.now() / 200) * 3; // Check collection if (isColliding(player, coin)) { coin.collected = true; score += 100; playSound('coin'); } } // Remove finished animations coins = coins.filter(c => !c.remove); } function drawCoin(coin) { ctx.save(); ctx.translate(coin.x + 8, coin.displayY + 8); // Spin effect using scale const scaleX = Math.cos(Date.now() / 150); ctx.scale(scaleX, 1); // Draw coin ctx.fillStyle = '#ffd700'; ctx.beginPath(); ctx.arc(0, 0, 8, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }