Drawaria.online Bird Game

Adds a detailed parallax bird game to drawaria.online

// ==UserScript==
// @name         Drawaria.online Bird Game
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds a detailed parallax bird game to drawaria.online
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://drawaria.online/avatar/cache/86e33830-86ea-11ec-8553-bff27824cf71.jpg
// ==/UserScript==

(function() {
    'use strict';

    // Create a container for the game
    const gameContainer = document.createElement('div');
    gameContainer.id = 'gameContainer';
    gameContainer.style.position = 'fixed';
    gameContainer.style.top = '0';
    gameContainer.style.left = '0';
    gameContainer.style.width = '100vw';
    gameContainer.style.height = '100vh';
    gameContainer.style.zIndex = '1000';
    gameContainer.style.pointerEvents = 'auto';
    document.body.appendChild(gameContainer);

    // Create the canvas element
    const canvas = document.createElement('canvas');
    canvas.id = 'gameCanvas';
    canvas.width = 800;
    canvas.height = 600;
    gameContainer.appendChild(canvas);

    // Create the game over overlay
    const overlay = document.createElement('div');
    overlay.id = 'gameOverOverlay';
    overlay.style.position = 'absolute';
    overlay.style.top = '0';
    overlay.style.left = '0';
    overlay.style.width = '100%';
    overlay.style.height = '100%';
    overlay.style.background = 'rgba(0, 0, 0, 0.5)';
    overlay.style.color = '#fff';
    overlay.style.display = 'flex';
    overlay.style.flexDirection = 'column';
    overlay.style.justifyContent = 'center';
    overlay.style.alignItems = 'center';
    overlay.style.fontSize = '2rem';
    overlay.style.zIndex = '10';
    overlay.style.visibility = 'hidden';
    gameContainer.appendChild(overlay);

    const gameOverText = document.createElement('div');
    gameOverText.innerText = 'Game Over';
    overlay.appendChild(gameOverText);

    const restartBtn = document.createElement('button');
    restartBtn.id = 'restartBtn';
    restartBtn.innerText = 'Restart';
    restartBtn.style.marginTop = '20px';
    restartBtn.style.padding = '10px 20px';
    restartBtn.style.fontSize = '1rem';
    restartBtn.style.cursor = 'pointer';
    restartBtn.style.border = 'none';
    restartBtn.style.borderRadius = '5px';
    restartBtn.style.backgroundColor = '#ff5050';
    restartBtn.style.color = '#fff';
    overlay.appendChild(restartBtn);

    const ctx = canvas.getContext('2d');

    // Game Variables
    let bird, obstacles, frameCount, gameOver;
    let gravity, jumpForce, obstacleSpeed, obstacleGap, obstacleWidth, obstacleInterval;

    // Parallax background variables
    let mountainPoints = [];
    let hillsPoints = [];
    let mountainOffset = 0;
    let hillsOffset = 0;
    let clouds = [];

    // Speeds for parallax layers (relative multipliers)
    const mountainSpeedMultiplier = 0.2;
    const hillsSpeedMultiplier = 0.5;
    const cloudSpeedMultiplier = 0.3;

    // Utility to generate terrain points for mountains/hills
    function generateTerrain(totalWidth, step, base, variation) {
      const points = [];
      for (let x = -step; x <= totalWidth; x += step) {
        const y = base + Math.random() * variation - variation / 2;
        points.push({ x, y });
      }
      return points;
    }

    // Initialize clouds array
    function initClouds() {
      clouds = [];
      const cloudCount = 8;
      for (let i = 0; i < cloudCount; i++) {
        clouds.push({
          x: Math.random() * canvas.width,
          y: Math.random() * canvas.height * 0.3,
          scale: Math.random() * 0.5 + 0.5
        });
      }
    }

    function init() {
      // Bird properties
      bird = {
        x: 100,
        y: canvas.height / 2,
        width: 30,
        height: 30,
        velocity: 0
      };

      obstacles = [];
      frameCount = 0;
      gameOver = false;
      gravity = 0.5;
      jumpForce = -9;
      obstacleSpeed = 3;
      obstacleGap = 150;
      obstacleWidth = 60;
      obstacleInterval = 90; // frames between obstacles

      // Initialize parallax backgrounds
      mountainPoints = generateTerrain(canvas.width * 2, 50, canvas.height * 0.4, 80);
      hillsPoints = generateTerrain(canvas.width * 2, 50, canvas.height * 0.75, 40);
      mountainOffset = 0;
      hillsOffset = 0;
      initClouds();

      overlay.style.visibility = 'hidden';
    }

    function resetGame() {
      init();
      loop();
    }

    // Event listeners for controls
    document.addEventListener('keydown', function(e) {
      if (e.code === 'Space') {
        e.preventDefault();
        if (gameOver) {
          resetGame();
        } else {
          bird.velocity = jumpForce;
        }
      }
    });

    canvas.addEventListener('mousedown', function() {
      if (!gameOver) {
        bird.velocity = jumpForce;
      }
    });

    restartBtn.addEventListener('click', resetGame);

    // Spawn an obstacle (pipe pair)
    function spawnObstacle() {
      const minGapY = 50;
      const maxGapY = canvas.height - obstacleGap - 50;
      const gapY = Math.floor(Math.random() * (maxGapY - minGapY + 1)) + minGapY;
      obstacles.push({ x: canvas.width, gapY });
    }

    // Update game objects
    function update() {
      // Update bird
      bird.velocity += gravity;
      bird.y += bird.velocity;

      // Spawn obstacles at intervals
      if (frameCount % obstacleInterval === 0) {
        spawnObstacle();
      }

      // Update obstacles positions
      obstacles.forEach(obstacle => {
        obstacle.x -= obstacleSpeed;
      });
      obstacles = obstacles.filter(obstacle => obstacle.x + obstacleWidth > 0);

      // Check collision with top/bottom
      if (bird.y < 0 || bird.y + bird.height > canvas.height) {
        gameOver = true;
      }

      // Collision with obstacles
      obstacles.forEach(obstacle => {
        if (
          bird.x + bird.width > obstacle.x &&
          bird.x < obstacle.x + obstacleWidth
        ) {
          if (bird.y < obstacle.gapY || bird.y + bird.height > obstacle.gapY + obstacleGap) {
            gameOver = true;
          }
        }
      });

      // Update parallax backgrounds
      mountainOffset = (mountainOffset + obstacleSpeed * mountainSpeedMultiplier) % canvas.width;
      hillsOffset = (hillsOffset + obstacleSpeed * hillsSpeedMultiplier) % canvas.width;
      clouds.forEach(cloud => {
        cloud.x -= obstacleSpeed * cloudSpeedMultiplier;
        if (cloud.x + 100 * cloud.scale < 0) { // if cloud moves off the left edge
          cloud.x = canvas.width + Math.random() * canvas.width;
          cloud.y = Math.random() * canvas.height * 0.3;
        }
      });

      frameCount++;
    }

    // Draw sky background gradient on canvas
    function drawSky() {
      const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
      gradient.addColorStop(0, '#87CEEB');
      gradient.addColorStop(1, '#fff');
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }

    // Draw clouds with soft edges
    function drawCloud(x, y, scale) {
      ctx.save();
      ctx.translate(x, y);
      ctx.scale(scale, scale);
      ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
      ctx.beginPath();
      ctx.arc(0, 0, 20, Math.PI * 0.5, Math.PI * 1.5);
      ctx.arc(25, -10, 25, Math.PI * 1, Math.PI * 1.85);
      ctx.arc(55, 0, 20, Math.PI * 1.37, Math.PI * 1.91);
      ctx.arc(40, 10, 25, Math.PI * 1.5, Math.PI * 0.5);
      ctx.arc(15, 10, 20, Math.PI * 1.15, Math.PI * 1.9);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    }

    // Draw all clouds
    function drawClouds() {
      clouds.forEach(cloud => {
        drawCloud(cloud.x, cloud.y, cloud.scale);
      });
    }

    // Draw mountain range based on pre-generated points
    function drawMountains(offset) {
      ctx.save();
      ctx.fillStyle = '#708090'; // Slate gray for distant mountains
      ctx.beginPath();
      ctx.moveTo(-offset, canvas.height);
      mountainPoints.forEach(pt => {
        ctx.lineTo(pt.x - offset, pt.y);
      });
      ctx.lineTo(canvas.width + 100, canvas.height);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    }

    // Draw hills in the foreground
    function drawHills(offset) {
      ctx.save();
      ctx.fillStyle = '#228B22'; // Forest green for hills
      ctx.beginPath();
      ctx.moveTo(-offset, canvas.height);
      hillsPoints.forEach(pt => {
        ctx.lineTo(pt.x - offset, pt.y);
      });
      ctx.lineTo(canvas.width + 100, canvas.height);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    }

    // Draw obstacles (pipes)
    function drawObstacles() {
      ctx.fillStyle = '#006400'; // Dark green for pipes
      obstacles.forEach(obstacle => {
        // Upper pipe
        ctx.fillRect(obstacle.x, 0, obstacleWidth, obstacle.gapY);
        // Lower pipe
        ctx.fillRect(obstacle.x, obstacle.gapY + obstacleGap, obstacleWidth, canvas.height - obstacle.gapY - obstacleGap);
      });
    }

    // Draw the bird with slight rotation based on velocity
    function drawBird() {
      ctx.save();
      // Determine rotation angle (capped for visual effect)
      const angle = Math.min(Math.max(bird.velocity / 10, -0.5), 1);
      ctx.translate(bird.x + bird.width / 2, bird.y + bird.height / 2);
      ctx.rotate(angle);

      // Draw bird body (a yellow circle with a red beak)
      ctx.fillStyle = '#FFD700';
      ctx.beginPath();
      ctx.arc(0, 0, bird.width / 2, 0, Math.PI * 2);
      ctx.fill();

      // Draw eye
      ctx.fillStyle = '#000';
      ctx.beginPath();
      ctx.arc(-5, -5, 3, 0, Math.PI * 2);
      ctx.fill();

      // Draw beak
      ctx.fillStyle = '#FF4500';
      ctx.beginPath();
      ctx.moveTo(bird.width / 2, 0);
      ctx.lineTo(bird.width / 2 + 10, -5);
      ctx.lineTo(bird.width / 2 + 10, 5);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    }

    function draw() {
      // Draw sky background gradient
      drawSky();
      // Draw parallax layers in order
      drawClouds();
      drawMountains(mountainOffset);
      drawHills(hillsOffset);
      // Draw obstacles and bird
      drawObstacles();
      drawBird();
    }

    function loop() {
      if (gameOver) {
        overlay.style.visibility = 'visible';
        return;
      }
      update();
      draw();
      requestAnimationFrame(loop);
    }

    // Start the game
    init();
    loop();
})();