🎮ArcadeLab

Meteor Interceptor - Realistic Flight

by FrostCoder37
478 lines15.5 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Meteor Interceptor - Realistic Flight</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
            -webkit-user-select: none;
        }
        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: #050510;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            color: #fff;
        }
        canvas {
            display: block;
            background: radial-gradient(circle at center, #101525 0%, #020205 100%);
            width: 100vw;
            height: 100vh;
        }
        #ui {
            position: absolute;
            top: 15px;
            left: 15px;
            right: 15px;
            display: flex;
            justify-content: space-between;
            pointer-events: none;
            font-size: 16px;
            font-weight: bold;
            text-shadow: 0 2px 4px rgba(0,0,0,0.8);
            z-index: 10;
        }
        .hud-panel {
            background: rgba(0, 20, 40, 0.6);
            border: 1px solid rgba(0, 150, 255, 0.4);
            padding: 8px 14px;
            border-radius: 4px;
            backdrop-filter: blur(4px);
        }
        #controls-hint {
            position: absolute;
            bottom: 20px;
            width: 100%;
            text-align: center;
            pointer-events: none;
            color: rgba(255, 255, 255, 0.5);
            font-size: 13px;
            text-shadow: 0 1px 2px rgba(0,0,0,0.9);
        }
        #start-screen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(2, 2, 8, 0.85);
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            z-index: 20;
            padding: 20px;
            text-align: center;
        }
        h1 {
            margin: 0 0 10px 0;
            font-size: 28px;
            letter-spacing: 2px;
            color: #00bfff;
            text-shadow: 0 0 10px rgba(0,191,255,0.5);
        }
        p {
            color: #aaa;
            max-width: 400px;
            font-size: 14px;
            line-height: 1.5;
            margin-bottom: 25px;
        }
        .btn {
            background: linear-gradient(135deg, #0077ff, #0044aa);
            color: white;
            border: none;
            padding: 12px 30px;
            font-size: 16px;
            font-weight: bold;
            border-radius: 6px;
            cursor: pointer;
            box-shadow: 0 4px 15px rgba(0, 119, 255, 0.4);
            transition: all 0.2s;
        }
        .btn:active {
            transform: scale(0.95);
        }
    </style>
</head>
<body>

    <div id="ui">
        <div class="hud-panel" id="score-hud">SCORE: 0</div>
        <div class="hud-panel" id="health-hud">INTEGRITY: 100%</div>
    </div>

    <div id="start-screen">
        <h1>METEOR DEFENSE</h1>
        <p>Pilot a high-altitude interceptor jet. Drag your finger across the screen to steer, tap the right side or use automatic fire to obliterate incoming meteors before they impact the atmosphere!</p>
        <button class="btn" onclick="startGame()">ENGAGE</button>
    </div>

    <div id="controls-hint">Drag to Fly | Auto-Cannons Active</div>

    <canvas id="gameCanvas"></canvas>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        let width, height;
        function resize() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
        }
        window.addEventListener('resize', resize);
        resize();

        // Audio Context for Sound Synthesis
        let audioCtx = null;
        function initAudio() {
            if (!audioCtx) {
                audioCtx = new (window.AudioContext || window.webkitAudioContext)();
            }
        }

        function playSound(type) {
            if (!audioCtx) return;
            const osc = audioCtx.createOscillator();
            const gain = audioCtx.createGain();
            osc.connect(gain);
            gain.connect(audioCtx.destination);

            const now = audioCtx.currentTime;

            if (type === 'laser') {
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(800, now);
                osc.frequency.exponentialRampToValueAtTime(100, now + 0.15);
                gain.gain.setValueAtTime(0.15, now);
                gain.gain.linearRampToValueAtTime(0.01, now + 0.15);
                osc.start(now);
                osc.stop(now + 0.15);
            } else if (type === 'explosion') {
                osc.type = 'triangle';
                osc.frequency.setValueAtTime(120, now);
                osc.frequency.exponentialRampToValueAtTime(20, now + 0.4);
                gain.gain.setValueAtTime(0.3, now);
                gain.gain.linearRampToValueAtTime(0.01, now + 0.4);
                osc.start(now);
                osc.stop(now + 0.4);
            }
        }

        // Game State
        let gameState = 'START';
        let score = 0;
        let health = 100;
        let gameTime = 0;

        const player = {
            x: width / 2,
            y: height - 120,
            targetX: width / 2,
            targetY: height - 120,
            width: 45,
            height: 60,
            speed: 0.15
        };

        let bullets = [];
        let meteors = [];
        let particles = [];
        let stars = [];

        // Generate static starfield
        for (let i = 0; i < 80; i++) {
            stars.push({
                x: Math.random() * width,
                y: Math.random() * height,
                size: Math.random() * 1.5,
                alpha: Math.random()
            });
        }

        // Touch and Mouse Controls
        let isDragging = false;
        window.addEventListener('pointerdown', (e) => {
            if (gameState === 'PLAYING') {
                isDragging = true;
                updateTarget(e);
            }
        });
        window.addEventListener('pointermove', (e) => {
            if (isDragging && gameState === 'PLAYING') {
                updateTarget(e);
            }
        });
        window.addEventListener('pointerup', () => { isDragging = false; });

        function updateTarget(e) {
            player.targetX = e.clientX;
            player.targetY = e.clientY;
            // Clamp within bounds
            player.targetX = Math.max(30, Math.min(width - 30, player.targetX));
            player.targetY = Math.max(50, Math.min(height - 50, player.targetY));
        }

        function startGame() {
            initAudio();
            document.getElementById('start-screen').style.display = 'none';
            score = 0;
            health = 100;
            bullets = [];
            meteors = [];
            particles = [];
            gameTime = 0;
            gameState = 'PLAYING';
        }

        let shootTimer = 0;
        function update() {
            if (gameState !== 'PLAYING') return;

            gameTime++;

            // Smooth flight interpolation (Realistic inertia)
            player.x += (player.targetX - player.x) * player.speed;
            player.y += (player.targetY - player.y) * player.speed;

            // Auto-fire cannons
            shootTimer++;
            if (shootTimer % 10 === 0) {
                bullets.push({ x: player.x - 12, y: player.y - 20, vy: -18 });
                bullets.push({ x: player.x + 12, y: player.y - 20, vy: -18 });
                playSound('laser');
            }

            // Spawn Meteors dynamically based on score
            if (Math.random() < 0.02 + (score * 0.0005)) {
                meteors.push({
                    x: Math.random() * width,
                    y: -50,
                    size: Math.random() * 25 + 15,
                    vx: (Math.random() - 0.5) * 3,
                    vy: Math.random() * 2 + 2 + (score * 0.02),
                    rotation: Math.random() * Math.PI,
                    vRot: (Math.random() - 0.5) * 0.05,
                    hp: Math.floor(Math.random() * 2) + 1
                });
            }

            // Update Bullets
            for (let i = bullets.length - 1; i >= 0; i--) {
                bullets[i].y += bullets[i].vy;
                if (bullets[i].y < 0) bullets.splice(i, 1);
            }

            // Update Meteors
            for (let i = meteors.length - 1; i >= 0; i--) {
                let m = meteors[i];
                m.x += m.vx;
                m.y += m.vy;
                m.rotation += m.vRot;

                // Check collision with player
                let distToPlayer = Math.hypot(m.x - player.x, m.y - player.y);
                if (distToPlayer < m.size + 20) {
                    health -= 25;
                    createExplosion(m.x, m.y, 20, '#ff4500');
                    playSound('explosion');
                    meteors.splice(i, 1);
                    if (health <= 0) gameOver();
                    continue;
                }

                // Check out of bounds (ground impact)
                if (m.y > height + 50) {
                    health -= 10;
                    playSound('explosion');
                    meteors.splice(i, 1);
                    if (health <= 0) gameOver();
                    continue;
                }

                // Check bullet collisions
                for (let j = bullets.length - 1; j >= 0; j--) {
                    let b = bullets[j];
                    let dist = Math.hypot(m.x - b.x, m.y - b.y);
                    if (dist < m.size) {
                        m.hp--;
                        bullets.splice(j, 1);
                        createExplosion(b.x, b.y, 5, '#00bfff');
                        if (m.hp <= 0) {
                            createExplosion(m.x, m.y, 15, '#ff8800');
                            playSound('explosion');
                            score += 10;
                            meteors.splice(i, 1);
                        }
                        break;
                    }
                }
            }

            // Update Particles
            for (let i = particles.length - 1; i >= 0; i--) {
                let p = particles[i];
                p.x += p.vx;
                p.y += p.vy;
                p.alpha -= 0.02;
                if (p.alpha <= 0) particles.splice(i, 1);
            }

            // Update HUD
            document.getElementById('score-hud').innerText = `SCORE: ${score}`;
            document.getElementById('health-hud').innerText = `INTEGRITY: ${Math.max(0, health)}%`;
        }

        function createExplosion(x, y, count, color) {
            for (let i = 0; i < count; i++) {
                particles.push({
                    x: x,
                    y: y,
                    vx: (Math.random() - 0.5) * 6,
                    vy: (Math.random() - 0.5) * 6,
                    size: Math.random() * 3 + 1,
                    color: color,
                    alpha: 1
                });
            }
        }

        function gameOver() {
            gameState = 'GAMEOVER';
            let screen = document.getElementById('start-screen');
            screen.style.display = 'flex';
            screen.querySelector('h1').innerText = 'MISSION FAILED';
            screen.querySelector('p').innerText = `Your aircraft was overwhelmed. Final Score: ${score}`;
            screen.querySelector('.btn').innerText = 'PLAY AGAIN';
        }

        function draw() {
            ctx.clearRect(0, 0, width, height);

            // Draw Stars
            ctx.fillStyle = '#ffffff';
            stars.forEach(s => {
                ctx.globalAlpha = s.alpha;
                ctx.fillRect(s.x, s.y, s.size, s.size);
            });
            ctx.globalAlpha = 1;

            // Draw Particles
            particles.forEach(p => {
                ctx.save();
                ctx.globalAlpha = p.alpha;
                ctx.fillStyle = p.color;
                ctx.beginPath();
                ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
            });

            // Draw Bullets
            ctx.fillStyle = '#00ffff';
            ctx.shadowBlur = 10;
            ctx.shadowColor = '#00ffff';
            bullets.forEach(b => {
                ctx.fillRect(b.x - 1.5, b.y, 3, 12);
            });
            ctx.shadowBlur = 0;

            // Draw Realistic Jet Fighter
            if (gameState === 'PLAYING') {
                ctx.save();
                ctx.translate(player.x, player.y);
                // Slight bank angle based on movement direction
                let bankAngle = (player.targetX - player.x) * 0.003;
                ctx.rotate(bankAngle);

                // Jet Body
                ctx.fillStyle = '#1c2331';
                ctx.strokeStyle = '#00bfff';
                ctx.lineWidth = 1.5;

                ctx.beginPath();
                ctx.moveTo(0, -30); // Nose
                ctx.lineTo(18, 20);  // Right Wing tip
                ctx.lineTo(6, 15);
                ctx.lineTo(4, 30);   // Engine exhaust area
                ctx.lineTo(-4, 30);
                ctx.lineTo(-6, 15);
                ctx.lineTo(-18, 20); // Left Wing tip
                ctx.closePath();
                ctx.fill();
                ctx.stroke();

                // Cockpit Glass
                ctx.fillStyle = '#00e5ff';
                ctx.beginPath();
                ctx.moveTo(0, -15);
                ctx.lineTo(5, 5);
                ctx.lineTo(-5, 5);
                ctx.closePath();
                ctx.fill();

                // Engine Afterburner Glow
                ctx.fillStyle = '#ff4500';
                ctx.fillRect(-3, 30, 6, Math.random() * 10 + 5);

                ctx.restore();
            }

            // Draw Meteors
            meteors.forEach(m => {
                ctx.save();
                ctx.translate(m.x, m.y);
                ctx.rotate(m.rotation);

                // Fiery atmospheric trail effect
                let gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, m.size);
                gradient.addColorStop(0, '#ff4500');
                gradient.addColorStop(0.7, '#8b0000');
                gradient.addColorStop(1, 'transparent');

                ctx.fillStyle = gradient;
                ctx.beginPath();
                ctx.arc(0, 0, m.size * 1.3, 0, Math.PI * 2);
                ctx.fill();

                // Rocky core
                ctx.fillStyle = '#3a3a3a';
                ctx.strokeStyle = '#ff6347';
                ctx.lineWidth = 2;
                ctx.beginPath();
                // Irregular polygon for realistic rock shape
                let points = 6;
                for (let i = 0; i < points; i++) {
                    let angle = (i / points) * Math.PI * 2;
                    let r = m.size * (0.8 + Math.sin(i * 3) * 0.2);
                    let px = Math.cos(angle) * r;
                    let py = Math.sin(angle) * r;
                    if (i === 0) ctx.moveTo(px, py);
                    else ctx.lineTo(px, py);
                }
                ctx.closePath();
                ctx.fill();
                ctx.stroke();

                ctx.restore();
            });
        }

        function loop() {
            update();
            draw();
            requestAnimationFrame(loop);
        }

        loop();
    </script>
</body>
</html>

Game Source: Meteor Interceptor - Realistic Flight

Creator: FrostCoder37

Libraries: none

Complexity: complex (478 lines, 15.5 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: meteor-interceptor-realistic-flight-frostcoder37" to link back to the original. Then publish at arcadelab.ai/publish.