Created
March 26, 2025 05:53
-
-
Save adhishthite/4553af43793d340b40bf51c1e65c6bea to your computer and use it in GitHub Desktop.
Dino Game: Using p5.js, developed by Gemini 2.5 Pro!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 🎮 Game vibe-coded by Adhish Thite <adhish.thite@gmail.com> | |
| // 🤖 Entirely generated using Gemini 2.5 (Experimental) on March 26, 2025 | |
| // 🚀 Paste this code into https://editor.p5js.org to play the game and enjoy the vibe! | |
| // ================================== | |
| // == GAME CONFIGURATION == | |
| // ================================== | |
| const INITIAL_GAME_SPEED = 6; | |
| const GAME_SPEED_INCREMENT = 0.003; | |
| const GRAVITY = 0.7; | |
| const JUMP_FORCE = -16; | |
| const SCORE_DIVISOR = 5; // Divide raw score by this for display | |
| // Spawning | |
| const MIN_OBSTACLE_DISTANCE = 90; // Min horizontal pixels between obstacles | |
| const BASE_OBSTACLE_SPAWN_RATE = 0.015; // Base chance per frame (increases with speed) | |
| const CLOUD_SPAWN_RATE = 0.005; | |
| const GROUND_DETAIL_SPAWN_RATE = 0.1; | |
| const NUM_STARS = 100; // Number of stars in the night sky | |
| // Day/Night Cycle | |
| const CYCLE_SCORE_LENGTH = 1000; // Internal score points for one phase (day or night) | |
| const TRANSITION_SCORE_DURATION = 200; // Internal score points for transition | |
| // Dino Visuals | |
| const DINO_X_POS = 80; | |
| const DINO_WIDTH = 44; | |
| const DINO_HEIGHT = 47; | |
| const DINO_ANIM_SPEED = 5; // Lower is faster animation frame rate | |
| // Colors - Day | |
| const DAY_SKY_COLOR = [135, 206, 250]; | |
| const DAY_GROUND_COLOR = [210, 180, 140]; | |
| const DAY_SUN_COLOR = [255, 255, 0]; | |
| const DAY_TEXT_COLOR = [50]; | |
| const DAY_CLOUD_COLOR = [255, 255, 255, 200]; | |
| const DAY_DETAIL_COLOR = [160, 120, 90, 150]; | |
| // Colors - Night | |
| const NIGHT_SKY_COLOR = [25, 25, 112]; | |
| const NIGHT_GROUND_COLOR = [88, 62, 40]; | |
| const NIGHT_MOON_COLOR = [240, 240, 240]; | |
| const NIGHT_TEXT_COLOR = [230]; | |
| const NIGHT_CLOUD_COLOR = [180, 180, 180, 150]; | |
| const NIGHT_DETAIL_COLOR = [60, 40, 20, 150]; | |
| const STAR_COLOR = [255, 255, 255, 180]; | |
| // Other Visuals | |
| const SUN_MOON_SIZE = 50; | |
| // ================================== | |
| // == GLOBAL VARIABLES == | |
| // ================================== | |
| let dino; | |
| let obstacles = []; | |
| let clouds = []; | |
| let groundDetails = []; | |
| let stars = []; | |
| let score = 0; | |
| let gameSpeed; // Initialized in resetGame | |
| let groundY; | |
| let gameState = 'initial'; // 'initial', 'running', 'paused', 'gameOver' | |
| // Day/Night Cycle State | |
| let isDay; | |
| let isTransitioning; | |
| let transitionProgress; | |
| let targetIsDay; | |
| // Current Dynamic Colors | |
| let currentSkyColor, currentGroundColor, currentSunMoonColor, currentTextColor, currentCloudColor, currentDetailColor; | |
| // Sun/Moon Position | |
| let sunMoonX, sunMoonY; | |
| // Screen Shake | |
| let shakeDuration = 0; | |
| let shakeIntensity = 3; | |
| // ================================== | |
| // == P5.JS FUNCTIONS == | |
| // ================================== | |
| function setup() { | |
| // Use 90% of window width, fixed height | |
| createCanvas(windowWidth * 0.9, 350); | |
| // Calculate ground level based on canvas height | |
| groundY = height - 50; | |
| // Set text defaults | |
| textAlign(LEFT); | |
| textSize(18); | |
| textFont('monospace'); | |
| // Use CORNER mode for easier positioning with top-left coordinates | |
| rectMode(CORNER); | |
| // Initial game setup | |
| resetGame(); // Call resetGame to initialize all states and objects | |
| noLoop(); // Start paused, waiting for user input | |
| } | |
| function draw() { | |
| // --- Update Day/Night Cycle colors if running --- | |
| if (gameState === 'running') { | |
| updateCycle(); | |
| } | |
| // --- Apply Screen Shake (if active) --- | |
| if (shakeDuration > 0) { | |
| translate(random(-shakeIntensity, shakeIntensity), random(-shakeIntensity, shakeIntensity)); | |
| shakeDuration--; | |
| } | |
| // --- Draw Background --- | |
| background(currentSkyColor); | |
| // --- Draw Stars (if appropriate) --- | |
| // Draw if fully night, or during any transition phase (alpha handles visibility) | |
| if (!isDay || isTransitioning) { | |
| drawStars(); | |
| } | |
| // --- Draw Sun/Moon --- | |
| fill(currentSunMoonColor); | |
| noStroke(); | |
| ellipse(sunMoonX, sunMoonY, SUN_MOON_SIZE, SUN_MOON_SIZE); | |
| // --- Draw Clouds --- | |
| if (gameState === 'running' || gameState === 'paused' || gameState === 'gameOver') { | |
| trySpawnCloud(); // Continue spawning clouds even if paused/game over for visual effect | |
| } | |
| for (let i = clouds.length - 1; i >= 0; i--) { | |
| // Clouds keep moving slowly even if paused for parallax effect | |
| let cloudSpeed = (gameState === 'running') ? gameSpeed : INITIAL_GAME_SPEED * 0.5; // Slower when paused | |
| clouds[i].move(cloudSpeed); | |
| clouds[i].show(currentCloudColor); | |
| if (clouds[i].isOffscreen()) { | |
| clouds.splice(i, 1); | |
| } | |
| } | |
| // --- Draw Ground --- | |
| fill(currentGroundColor); | |
| noStroke(); | |
| rect(0, groundY, width, height - groundY); | |
| // --- Draw Ground Details --- | |
| if (gameState === 'running' || gameState === 'paused' || gameState === 'gameOver') { | |
| trySpawnGroundDetail(); // Keep spawning details | |
| } | |
| for (let i = groundDetails.length - 1; i >= 0; i--) { | |
| // Ground details keep moving even if paused | |
| let detailSpeed = (gameState === 'running') ? gameSpeed : INITIAL_GAME_SPEED; | |
| groundDetails[i].move(detailSpeed); | |
| groundDetails[i].show(currentDetailColor); | |
| if (groundDetails[i].isOffscreen()) { | |
| groundDetails.splice(i, 1); | |
| } | |
| } | |
| // --- Game Logic --- | |
| if (gameState === 'running') { | |
| // Update score & speed | |
| score++; | |
| gameSpeed += GAME_SPEED_INCREMENT; | |
| let currentObstacleSpawnRate = BASE_OBSTACLE_SPAWN_RATE * (gameSpeed / INITIAL_GAME_SPEED); | |
| // Handle Dino | |
| dino.applyGravity(); | |
| dino.update(); | |
| dino.show(); | |
| // Handle Obstacles | |
| trySpawnObstacle(currentObstacleSpawnRate); | |
| for (let i = obstacles.length - 1; i >= 0; i--) { | |
| obstacles[i].move(gameSpeed); | |
| obstacles[i].show(); | |
| // Check Collision | |
| if (dino.collides(obstacles[i])) { | |
| gameOver(); // Trigger game over sequence | |
| break; // Stop checking collisions after the first one | |
| } | |
| // Remove offscreen obstacles | |
| if (obstacles[i].isOffscreen()) { | |
| obstacles.splice(i, 1); | |
| } | |
| } | |
| // Display Score | |
| drawScore(); | |
| } else { | |
| // Draw Dino/Obstacles in their last position if paused or game over | |
| if (dino) dino.show(); | |
| obstacles.forEach(obs => obs.show()); | |
| // Handle specific overlays/text for non-running states | |
| if (gameState === 'initial') { | |
| drawOverlayMessage("Press SPACE or Click/Tap to Start"); | |
| } else if (gameState === 'paused') { | |
| drawOverlayMessage("PAUSED", "Press P to Resume"); | |
| drawScore(); // Still show score when paused | |
| } else if (gameState === 'gameOver') { | |
| drawOverlayMessage("GAME OVER", `Final Score: ${floor(score / SCORE_DIVISOR)}\nPress SPACE or Click/Tap to Restart`); | |
| } | |
| } | |
| } | |
| // ================================== | |
| // == INPUT HANDLING & STATE == | |
| // ================================== | |
| function keyPressed() { | |
| // --- Pause/Resume --- | |
| if ((key === 'p' || key === 'P') && (gameState === 'running' || gameState === 'paused')) { | |
| togglePause(); | |
| return false; // Prevent default behavior | |
| } | |
| // --- Start / Jump / Restart --- | |
| if (key === ' ') { | |
| handlePrimaryAction(); | |
| return false; // Prevent space bar scrolling page | |
| } | |
| } | |
| function mousePressed() { | |
| handlePrimaryAction(); | |
| } | |
| // Handle touch events for mobile | |
| function touchStarted() { | |
| handlePrimaryAction(); | |
| return false; // Prevent touch causing clicks/scrolling etc. | |
| } | |
| function handlePrimaryAction() { | |
| if (gameState === 'initial') { | |
| startGame(); | |
| } else if (gameState === 'running') { | |
| if (dino.isOnGround()) { | |
| dino.jump(); | |
| // Optional: Add jump sound effect here | |
| } | |
| } else if (gameState === 'gameOver') { | |
| resetGame(); | |
| startGame(); // Directly start after reset | |
| } | |
| // No action if paused (except unpausing with 'P') | |
| } | |
| function togglePause() { | |
| if (gameState === 'running') { | |
| gameState = 'paused'; | |
| noLoop(); // Stop draw loop updates (except for manual redraws if needed) | |
| redraw(); // Redraw once to show pause message | |
| } else if (gameState === 'paused') { | |
| gameState = 'running'; | |
| loop(); // Resume draw loop | |
| } | |
| } | |
| function startGame() { | |
| if(gameState === 'initial' || gameState === 'gameOver') { | |
| gameState = 'running'; | |
| loop(); // Start the draw loop if it wasn't running | |
| } | |
| } | |
| function gameOver() { | |
| gameState = 'gameOver'; | |
| shakeDuration = 15; // Activate screen shake for 15 frames | |
| // Optional: Play game over sound | |
| noLoop(); // Stop updates | |
| redraw(); // Draw the final game over screen | |
| } | |
| // ================================== | |
| // == GAME RESET == | |
| // ================================== | |
| function resetGame() { | |
| // Reset scores and speed | |
| score = 0; | |
| gameSpeed = INITIAL_GAME_SPEED; | |
| // Clear dynamic objects | |
| obstacles = []; | |
| clouds = []; | |
| groundDetails = []; | |
| // Reset Day/Night cycle to initial Day state | |
| isDay = true; | |
| targetIsDay = true; | |
| isTransitioning = false; | |
| transitionProgress = 0; | |
| currentSkyColor = color(...DAY_SKY_COLOR); | |
| currentGroundColor = color(...DAY_GROUND_COLOR); | |
| currentSunMoonColor = color(...DAY_SUN_COLOR); | |
| currentTextColor = color(...DAY_TEXT_COLOR); | |
| currentCloudColor = color(...DAY_CLOUD_COLOR); | |
| currentDetailColor = color(...DAY_DETAIL_COLOR); | |
| // Sun/Moon position | |
| sunMoonX = width - 80; | |
| sunMoonY = 70; | |
| // Create/Reset Dino | |
| dino = new Dino(DINO_X_POS, groundY - DINO_HEIGHT, DINO_WIDTH, DINO_HEIGHT); | |
| // Initial spawn for visual elements | |
| stars = []; generateStars(NUM_STARS); | |
| for (let i = 0; i < 5; i++) { trySpawnCloud(); } | |
| for (let i = 0; i < 10; i++) { trySpawnGroundDetail(); } | |
| // Reset shake effect | |
| shakeDuration = 0; | |
| // Set initial state (will wait for user input) | |
| gameState = 'initial'; | |
| // Ensure p5 loop is stopped until user starts | |
| noLoop(); | |
| // Draw the initial screen once | |
| redraw(); | |
| } | |
| // ================================== | |
| // == DRAWING HELPER FUNCTIONS == | |
| // ================================== | |
| function drawScore() { | |
| fill(currentTextColor); | |
| noStroke(); | |
| textSize(18); | |
| textAlign(LEFT); | |
| text(`Score: ${floor(score / SCORE_DIVISOR)}`, 20, 30); | |
| } | |
| function drawOverlayMessage(line1, line2 = "") { | |
| // Semi-transparent overlay - adjust alpha based on day/night | |
| let overlayAlpha = isDay ? 100 : 80; | |
| fill(0, 0, 0, overlayAlpha); | |
| rect(0,0,width, height); | |
| // Draw text lines | |
| fill(currentTextColor); | |
| stroke(isDay ? 0 : 255); // Contrast stroke | |
| strokeWeight(1); | |
| textAlign(CENTER, CENTER); | |
| textSize(30); // Larger primary message | |
| text(line1, width / 2, height / 2 - (line2 ? 20 : 0)); // Adjust position if two lines | |
| if (line2) { | |
| textSize(16); // Smaller secondary message | |
| noStroke(); | |
| text(line2, width / 2, height / 2 + 20); | |
| } | |
| textAlign(LEFT); // Reset alignment | |
| } | |
| function drawStars() { | |
| push(); | |
| let baseAlpha = STAR_COLOR[3] || 255; // Get alpha from definition or default to 255 | |
| let currentAlpha = baseAlpha; | |
| if (isTransitioning) { | |
| // Fade stars based on transition direction and progress | |
| currentAlpha = targetIsDay ? baseAlpha * (1 - transitionProgress) : baseAlpha * transitionProgress; | |
| } else if (isDay) { | |
| currentAlpha = 0; // Explicitly no stars during full day | |
| } | |
| // Only draw if alpha is significant | |
| if (currentAlpha > 5) { | |
| fill(STAR_COLOR[0], STAR_COLOR[1], STAR_COLOR[2], currentAlpha); | |
| noStroke(); | |
| for (let star of stars) { | |
| ellipse(star.x, star.y, star.size, star.size); | |
| } | |
| } | |
| pop(); | |
| } | |
| // ================================== | |
| // == DAY/NIGHT CYCLE LOGIC == | |
| // ================================== | |
| function updateCycle() { | |
| let phase = score % (CYCLE_SCORE_LENGTH * 2); | |
| targetIsDay = (phase < CYCLE_SCORE_LENGTH); | |
| if (!isTransitioning && targetIsDay !== isDay) { | |
| isTransitioning = true; | |
| transitionProgress = 0; | |
| } | |
| if (isTransitioning) { | |
| // Increment progress - somewhat tied to score speed but capped | |
| // Use a small fixed increment for smoother visual transition regardless of game speed jumps | |
| transitionProgress += (1 / (TRANSITION_SCORE_DURATION * 0.1)); // Adjust denominator for transition speed | |
| transitionProgress = min(transitionProgress, 1); // Clamp progress to 1 | |
| let fromSky, toSky, fromGround, toGround, fromSunMoon, toSunMoon, fromText, toText, fromCloud, toCloud, fromDetail, toDetail; | |
| if (targetIsDay) { // To Day | |
| fromSky = color(...NIGHT_SKY_COLOR); toSky = color(...DAY_SKY_COLOR); | |
| fromGround = color(...NIGHT_GROUND_COLOR); toGround = color(...DAY_GROUND_COLOR); | |
| fromSunMoon = color(...NIGHT_MOON_COLOR); toSunMoon = color(...DAY_SUN_COLOR); | |
| fromText = color(...NIGHT_TEXT_COLOR); toText = color(...DAY_TEXT_COLOR); | |
| fromCloud = color(...NIGHT_CLOUD_COLOR); toCloud = color(...DAY_CLOUD_COLOR); | |
| fromDetail = color(...NIGHT_DETAIL_COLOR); toDetail = color(...DAY_DETAIL_COLOR); | |
| } else { // To Night | |
| fromSky = color(...DAY_SKY_COLOR); toSky = color(...NIGHT_SKY_COLOR); | |
| fromGround = color(...DAY_GROUND_COLOR); toGround = color(...NIGHT_GROUND_COLOR); | |
| fromSunMoon = color(...DAY_SUN_COLOR); toSunMoon = color(...NIGHT_MOON_COLOR); | |
| fromText = color(...DAY_TEXT_COLOR); toText = color(...NIGHT_TEXT_COLOR); | |
| fromCloud = color(...DAY_CLOUD_COLOR); toCloud = color(...NIGHT_CLOUD_COLOR); | |
| fromDetail = color(...DAY_DETAIL_COLOR); toDetail = color(...NIGHT_DETAIL_COLOR); | |
| } | |
| // Lerp all colors | |
| currentSkyColor = lerpColor(fromSky, toSky, transitionProgress); | |
| currentGroundColor = lerpColor(fromGround, toGround, transitionProgress); | |
| currentSunMoonColor = lerpColor(fromSunMoon, toSunMoon, transitionProgress); | |
| currentTextColor = lerpColor(fromText, toText, transitionProgress); | |
| currentCloudColor = lerpColor(fromCloud, toCloud, transitionProgress); | |
| currentDetailColor = lerpColor(fromDetail, toDetail, transitionProgress); | |
| // Check if transition ended | |
| if (transitionProgress === 1) { | |
| isTransitioning = false; | |
| isDay = targetIsDay; | |
| // Snap to final colors to avoid floating point inaccuracies | |
| currentSkyColor = isDay ? color(...DAY_SKY_COLOR) : color(...NIGHT_SKY_COLOR); | |
| currentGroundColor = isDay ? color(...DAY_GROUND_COLOR) : color(...NIGHT_GROUND_COLOR); | |
| currentSunMoonColor = isDay ? color(...DAY_SUN_COLOR) : color(...NIGHT_MOON_COLOR); | |
| currentTextColor = isDay ? color(...DAY_TEXT_COLOR) : color(...NIGHT_TEXT_COLOR); | |
| currentCloudColor = isDay ? color(...DAY_CLOUD_COLOR) : color(...NIGHT_CLOUD_COLOR); | |
| currentDetailColor = isDay ? color(...DAY_DETAIL_COLOR) : color(...NIGHT_DETAIL_COLOR); | |
| } | |
| } | |
| // If not transitioning, colors are already correct (set at end of last transition or in reset) | |
| } | |
| function generateStars(numStars) { | |
| stars = []; | |
| for (let i = 0; i < numStars; i++) { | |
| stars.push({ | |
| x: random(width), | |
| y: random(groundY * 0.7), // Keep stars above ground level | |
| size: random(1, 2.5) // Smaller size variation | |
| }); | |
| } | |
| } | |
| // ================================== | |
| // == SPAWNING FUNCTIONS == | |
| // ================================== | |
| // (These remain largely the same, but ensure they only run when appropriate) | |
| function trySpawnObstacle(spawnRate) { | |
| // Only spawn if running | |
| if (gameState !== 'running') return; | |
| if (random(1) < spawnRate) { | |
| let lastObstacle = obstacles[obstacles.length - 1]; | |
| // Dynamic required distance based on game speed slightly | |
| let speedFactor = map(gameSpeed, INITIAL_GAME_SPEED, INITIAL_GAME_SPEED * 3, 1, 1.5, true); // Increase spacing slightly at higher speeds | |
| let requiredDist = (MIN_OBSTACLE_DISTANCE + random(0, width * 0.3)) * speedFactor + (lastObstacle ? lastObstacle.w : 0); | |
| if (!lastObstacle || (width - lastObstacle.x > requiredDist)) { | |
| obstacles.push(new Obstacle()); | |
| } | |
| } | |
| } | |
| function trySpawnCloud() { | |
| // Clouds can spawn visually even if paused/game over | |
| if (random(1) < CLOUD_SPAWN_RATE) { | |
| let lastCloud = clouds[clouds.length - 1]; | |
| if (!lastCloud || (width - lastCloud.x > 150 + random(0, 100))) { // Slightly more random cloud spacing | |
| clouds.push(new Cloud()); | |
| } | |
| } | |
| } | |
| function trySpawnGroundDetail() { | |
| // Details can spawn visually even if paused/game over | |
| if (random(1) < GROUND_DETAIL_SPAWN_RATE) { | |
| let lastDetail = groundDetails[groundDetails.length - 1]; | |
| if (!lastDetail || (width - lastDetail.x > 5 + random(0,25))) { // Slightly more random detail spacing | |
| groundDetails.push(new GroundDetail()); | |
| } | |
| } | |
| } | |
| // ================================== | |
| // == GAME CLASSES == | |
| // ================================== | |
| // (Classes remain largely the same, ensure methods use constants) | |
| class Dino { | |
| constructor(x, y, w, h) { | |
| this.baseY = y; this.x = x; this.y = y; this.w = w; this.h = h; | |
| this.velocityY = 0; this.legToggle = false; this.animFrameCounter = 0; | |
| } | |
| jump() { this.velocityY = JUMP_FORCE; } | |
| applyGravity() { | |
| this.velocityY += GRAVITY; this.y += this.velocityY; | |
| if (this.y > this.baseY) { this.y = this.baseY; this.velocityY = 0; } | |
| } | |
| isOnGround() { return this.y === this.baseY; } | |
| update() { | |
| if (this.isOnGround()) { | |
| this.animFrameCounter++; | |
| if (this.animFrameCounter >= DINO_ANIM_SPEED) { this.legToggle = !this.legToggle; this.animFrameCounter = 0; } | |
| } else { this.legToggle = false; } // Keep legs consistent mid-air | |
| } | |
| collides(obstacle) { // Simple AABB collision | |
| let dinoHitbox = { x: this.x + 5, y: this.y, w: this.w - 15, h: this.h - 5 }; | |
| let obsHitbox = obstacle.getHitbox(); // Use a method to get obstacle hitbox | |
| return ( dinoHitbox.x < obsHitbox.x + obsHitbox.w && dinoHitbox.x + dinoHitbox.w > obsHitbox.x && dinoHitbox.y < obsHitbox.y + obsHitbox.h && dinoHitbox.y + dinoHitbox.h > obsHitbox.y ); | |
| } | |
| reset() { this.y = this.baseY; this.velocityY = 0; this.legToggle = false; this.animFrameCounter = 0;} | |
| show() { // Pixelated dino drawing | |
| push(); translate(this.x, this.y); fill(80); noStroke(); | |
| rect(10, 0, 24, 30); rect(0, 8, 10, 12); rect(28, -7, 16, 18); rect(24, 5, 8, 8); | |
| fill(255); rect(36, -2, 3, 3); // Eye | |
| fill(80); | |
| if (this.legToggle && this.isOnGround()) { rect(12, 30, 8, 12); rect(24, 30, 8, 8); } // Running legs | |
| else { rect(12, 30, 8, 17); rect(24, 30, 8, 17); } // Standing/Jumping legs | |
| pop(); | |
| } | |
| } | |
| class Obstacle { | |
| constructor() { // Tree obstacle | |
| this.type = random(1) > 0.6 ? 'tall' : 'bushy'; | |
| if (this.type === 'tall') { this.trunkW = random(15, 25); this.trunkH = random(40, 80); this.foliageH = random(20, 40); this.foliageW = this.trunkW * random(1.8, 2.5); } | |
| else { this.trunkW = random(25, 40); this.trunkH = random(20, 40); this.foliageH = random(30, 50); this.foliageW = this.trunkW * random(1.2, 1.8); } | |
| this.h = this.trunkH + this.foliageH / 2; this.w = this.foliageW; | |
| this.x = width; this.y = groundY - this.trunkH; // Place trunk base on ground | |
| } | |
| move(speed) { this.x -= speed; } | |
| isOffscreen() { return this.x < -this.foliageW; } // Check against foliage width | |
| getHitbox() { // Provide hitbox relative to current position | |
| let xOffset = 0, yOffset = 0, boxW = 0, boxH = 0; | |
| yOffset = (this.type === 'tall' ? -this.foliageH / 1.5 : -this.foliageH / 2); | |
| boxW = this.trunkW; boxH = this.trunkH + this.foliageH; | |
| if (this.foliageW > this.trunkW) { boxW = this.foliageW * 0.8; xOffset = -(this.foliageW - this.trunkW) / 2;} | |
| return { x: this.x + xOffset, y: this.y + yOffset, w: boxW, h: boxH }; | |
| } | |
| show() { // Draws the tree | |
| push(); translate(this.x, this.y); noStroke(); | |
| fill(139, 69, 19); rect(0, 0, this.trunkW, this.trunkH); // Trunk | |
| fill(34, 139, 34); // Foliage | |
| if (this.type === 'tall') { ellipse(this.trunkW / 2, -this.foliageH / 2, this.foliageW, this.foliageH); } | |
| else { ellipse(this.trunkW / 2, -this.foliageH / 3, this.foliageW, this.foliageH * 0.8); ellipse(this.trunkW / 2, 0, this.foliageW * 0.8, this.foliageH * 0.6); } | |
| pop(); | |
| } | |
| } | |
| class Cloud { | |
| constructor() { this.w = random(50, 100); this.h = random(20, 40); this.x = width + random(0, width * 0.5); this.y = random(height * 0.1, height * 0.4); this.speedMultiplier = random(0.2, 0.6); } | |
| move(baseSpeed) { this.x -= baseSpeed * this.speedMultiplier; } // Slower parallax speed | |
| isOffscreen() { return this.x < -this.w; } | |
| show(cloudCol) { // Draws cloud using overlapping ellipses | |
| fill(cloudCol); noStroke(); | |
| ellipse(this.x + this.w / 2, this.y + this.h / 2, this.w, this.h); | |
| ellipse(this.x + this.w * 0.2, this.y + this.h * 0.7, this.w * 0.6, this.h * 0.8); | |
| ellipse(this.x + this.w * 0.8, this.y + this.h * 0.8, this.w * 0.7, this.h * 0.6); | |
| } | |
| } | |
| class GroundDetail { | |
| constructor() { this.x = width + random(0, 50); this.y = groundY + random(5, 15); this.w = random(10, 30); this.h = random(1, 3); } | |
| move(speed) { this.x -= speed; } | |
| isOffscreen() { return this.x < -this.w; } | |
| show(detailCol) { // Draws small ground detail rectangle | |
| fill(detailCol); noStroke(); rect(this.x, this.y, this.w, this.h); | |
| } | |
| } | |
| // ================================== | |
| // == WINDOW RESIZE == | |
| // ================================== | |
| function windowResized() { | |
| print("Window resized. Resetting game."); // Log to console | |
| resizeCanvas(windowWidth * 0.9, 350); | |
| groundY = height - 50; | |
| resetGame(); // Force reset to correctly scale/position elements relative to new size | |
| // Note: This means resizing during gameplay restarts the game. | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment