Drawaria.online Bird Game

Adds a detailed parallax bird game to drawaria.online

目前為 2025-02-04 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
})();