Game

Anonymous
plain_text
02/07/2026 5:38 PM
30.0 KB
8
Indexable
<!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>Spider-Man: Web Runner</title>
    <style>
        body {
            margin: 0;
            background-color: #050510;
            overflow: hidden;
            font-family: 'Arial', sans-serif;
            touch-action: none; /* Prevent browser zooming/scrolling */
        }

        #game-container {
            position: relative;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: linear-gradient(to bottom, #000022, #001133);
        }

        /* The Game Canvas */
        canvas {
            image-rendering: pixelated;
            box-shadow: 0 0 20px rgba(0, 150, 255, 0.3);
            background: transparent; /* Let CSS gradient show through */
        }

        /* UI Overlays */
        #ui-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }

        .hud {
            padding: 20px;
            color: white;
            font-size: 20px;
            font-weight: bold;
            text-shadow: 2px 2px 0 #000;
            display: flex;
            justify-content: space-between;
        }

        /* Mobile Controls */
        #controls {
            pointer-events: auto;
            display: flex;
            justify-content: space-between;
            padding: 20px;
            padding-bottom: 40px;
        }

        .btn-group {
            display: flex;
            gap: 15px;
        }

        .btn {
            width: 70px;
            height: 70px;
            background: rgba(255, 255, 255, 0.15);
            border: 2px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            backdrop-filter: blur(4px);
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 24px;
            user-select: none;
        }

        .btn:active {
            background: rgba(255, 0, 0, 0.4);
            transform: scale(0.95);
        }

        .btn-action {
            background: rgba(255, 50, 50, 0.2);
            border-color: rgba(255, 50, 50, 0.5);
        }

        .btn-jump {
            background: rgba(50, 100, 255, 0.2);
            border-color: rgba(50, 100, 255, 0.5);
        }

        /* Messages */
        #message-overlay {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            text-align: center;
            color: white;
            display: none; /* Hidden by default */
            pointer-events: auto;
        }

        h1 { font-size: 40px; margin-bottom: 10px; color: #ff3333; text-shadow: 2px 2px #000; }
        p { font-size: 18px; margin-bottom: 20px; }
        button.start-btn {
            padding: 15px 40px;
            font-size: 20px;
            background: #e62429;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            box-shadow: 0 4px #990000;
        }
        button.start-btn:active {
            box-shadow: 0 0 #990000;
            transform: translateY(4px);
        }
    </style>
</head>
<body>

<div id="game-container">
    <canvas id="gameCanvas" width="800" height="450"></canvas>
    
    <div id="ui-layer">
        <div class="hud">
            <span id="score-display">SCORE: 0</span>
            <span id="level-display">LEVEL: 1</span>
        </div>

        <div id="message-overlay">
            <h1 id="msg-title">SPIDER-MAN</h1>
            <p id="msg-subtitle">Web Runner Prototype</p>
            <button class="start-btn" onclick="startGame()">START MISSION</button>
        </div>

        <div id="controls">
            <div class="btn-group">
                <div class="btn" id="btn-left">⬅️</div>
                <div class="btn" id="btn-right">➡️</div>
            </div>
            <div class="btn-group">
                <div class="btn btn-action" id="btn-shoot">🕸️</div>
                <div class="btn btn-jump" id="btn-jump">⬆️</div>
            </div>
        </div>
    </div>
</div>

<script>
/**
 * SENIOR GAMEPLAY ENGINEER - CORE ENGINE
 * * Architecture:
 * 1. Game Loop: Updates logic and renders frames.
 * 2. Entity Component System (Simplified): Player, Enemies, Blocks, Projectiles.
 * 3. Level Manager: ASCII map parsing.
 * 4. Input Manager: Maps Touch/Keyboard to actions.
 */

// --- CONFIGURATION ---
const GRAVITY = 0.5;
const TERMINAL_VELOCITY = 12;
const FRICTION = 0.8;
const PLAYER_SPEED = 0.6;
const JUMP_FORCE = -11;
const BULLET_SPEED = 8;

// --- STATE MANAGEMENT ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

let gameRunning = false;
let currentLevelIdx = 0;
let score = 0;
let cameraX = 0;

// Input State
const keys = { left: false, right: false, up: false, shoot: false };

// Game Objects Arrays
let platforms = [];
let enemies = [];
let bullets = [];
let particles = [];
let items = []; // Coins, etc.

// --- ASSETS (GENERATIVE DRAWING) ---
// We draw sprites via code to ensure functionality without external files.

// --- CLASSES ---

class Player {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.w = 24;
        this.h = 36;
        this.vx = 0;
        this.vy = 0;
        this.grounded = false;
        this.facingRight = true;
        this.cooldown = 0;
    }

    update() {
        // Horizontal Movement
        if (keys.left) {
            this.vx -= PLAYER_SPEED;
            this.facingRight = false;
        }
        if (keys.right) {
            this.vx += PLAYER_SPEED;
            this.facingRight = true;
        }

        // Friction & Limits
        this.vx *= FRICTION;
        
        // Jump
        if (keys.up && this.grounded) {
            this.vy = JUMP_FORCE;
            this.grounded = false;
        }

        // Gravity
        this.vy += GRAVITY;
        if(this.vy > TERMINAL_VELOCITY) this.vy = TERMINAL_VELOCITY;

        // Apply Velocity
        this.x += this.vx;
        this.checkHorizontalCollisions();
        this.y += this.vy;
        this.grounded = false; // Assume falling until collision proves otherwise
        this.checkVerticalCollisions();

        // Screen Bounds (Death Logic)
        if (this.y > canvas.height + 100) {
            deathHandler("FELL TO DEATH");
        }

        // Shooting
        if (keys.shoot && this.cooldown <= 0) {
            bullets.push(new Bullet(
                this.x + (this.facingRight ? this.w : 0), 
                this.y + 10, 
                this.facingRight ? BULLET_SPEED : -BULLET_SPEED, 
                true
            ));
            this.cooldown = 20; // Frames between shots
        }
        if (this.cooldown > 0) this.cooldown--;
    }

    checkHorizontalCollisions() {
        for (let p of platforms) {
            if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
                // Collision detected, move back
                if (this.vx > 0) this.x = p.x - this.w;
                else if (this.vx < 0) this.x = p.x + p.w;
                this.vx = 0;
            }
        }
    }

    checkVerticalCollisions() {
        for (let p of platforms) {
            if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
                if (this.vy > 0) {
                    // Landing on top
                    this.y = p.y - this.h;
                    this.vy = 0;
                    this.grounded = true;
                    
                    // Kill enemy by jumping on them (Mario Logic) is handled in Enemy loop
                } else if (this.vy < 0) {
                    // Hitting head on bottom
                    this.y = p.y + p.h;
                    this.vy = 0;
                    
                    // Mystery Box Logic
                    if (p.type === 'mystery' && p.active) {
                        p.active = false;
                        spawnCoin(p.x, p.y - 30);
                    }
                }
            }
        }
    }

    draw() {
        // Spiderman Body (Red)
        ctx.fillStyle = '#e62429';
        ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
        // Mask (Blue parts)
        ctx.fillStyle = '#0066cc';
        ctx.fillRect(this.x - cameraX + 4, this.y + 20, this.w - 8, 10);
        // Eyes
        ctx.fillStyle = 'white';
        let eyeX = this.facingRight ? 12 : 4;
        ctx.fillRect(this.x - cameraX + eyeX, this.y + 5, 8, 5);
    }
}

class Enemy {
    constructor(x, y, type) {
        this.x = x;
        this.y = y;
        this.w = 30;
        this.h = 30;
        this.type = type; // 'walker', 'shooter'
        this.vx = (type === 'walker') ? 2 : 0;
        this.vy = 0;
        this.patrolStart = x;
        this.patrolDist = 100;
        this.shootTimer = 0;
        this.hp = 1; 
    }

    update() {
        // Gravity
        this.vy += GRAVITY;
        this.y += this.vy;

        // Floor Collision
        let grounded = false;
        for (let p of platforms) {
            if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
                if (this.vy > 0) {
                    this.y = p.y - this.h;
                    this.vy = 0;
                    grounded = true;
                }
            }
        }

        if (this.type === 'walker') {
            this.x += this.vx;
            // Turn around on edges or patrol limits
            if (!grounded) {
                this.vx *= -1; // Turn if about to fall
                this.x += this.vx * 2; // Nudge back
            }
            // Simple Patrol logic
            if (Math.abs(this.x - this.patrolStart) > this.patrolDist) {
                this.vx *= -1;
            }
        }

        if (this.type === 'shooter') {
            // Look at player
            let dx = player.x - this.x;
            this.shootTimer++;
            if (this.shootTimer > 120 && Math.abs(dx) < 400) { // Shoot every 2 seconds if close
                let dir = dx > 0 ? 1 : -1;
                bullets.push(new Bullet(this.x + 15, this.y + 10, dir * 5, false));
                this.shootTimer = 0;
            }
        }

        // Collision with Player (DEATH)
        if (rectIntersect(this.x, this.y, this.w, this.h, player.x, player.y, player.w, player.h)) {
            // Check if player jumped on top
            if (player.vy > 0 && player.y + player.h < this.y + (this.h/2)) {
                this.die();
                player.vy = -6; // Bounce
            } else {
                deathHandler("TOUCHED ENEMY");
            }
        }
    }

    die() {
        this.hp = 0;
        score += 100;
        createExplosion(this.x, this.y, '#00ff00');
    }

    draw() {
        ctx.fillStyle = (this.type === 'walker') ? '#33cc33' : '#9900cc'; // Green (Goblin) or Purple (Venom-ish)
        ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
        
        // Eyes
        ctx.fillStyle = 'yellow';
        ctx.fillRect(this.x - cameraX + 5, this.y + 5, 5, 5);
        ctx.fillRect(this.x - cameraX + 20, this.y + 5, 5, 5);
    }
}

class Bullet {
    constructor(x, y, vx, isPlayer) {
        this.x = x;
        this.y = y;
        this.w = 8;
        this.h = 8;
        this.vx = vx;
        this.isPlayer = isPlayer;
        this.active = true;
    }

    update() {
        this.x += this.vx;

        // Collide with Walls
        for (let p of platforms) {
            if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
                this.active = false;
            }
        }

        if (this.isPlayer) {
            // Hit Enemy
            enemies.forEach(e => {
                if (rectIntersect(this.x, this.y, this.w, this.h, e.x, e.y, e.w, e.h)) {
                    e.die();
                    this.active = false;
                }
            });
        } else {
            // Hit Player (Instant Death)
            if (rectIntersect(this.x, this.y, this.w, this.h, player.x, player.y, player.w, player.h)) {
                deathHandler("SHOT BY ENEMY");
            }
        }

        // Out of bounds cleanup
        if (this.x - cameraX < -100 || this.x - cameraX > canvas.width + 100) this.active = false;
    }

    draw() {
        ctx.fillStyle = this.isPlayer ? '#ffffff' : '#ff00ff'; // White web or Purple laser
        ctx.beginPath();
        ctx.arc(this.x - cameraX + 4, this.y + 4, 4, 0, Math.PI*2);
        ctx.fill();
    }
}

class Platform {
    constructor(x, y, w, h, type) {
        this.x = x; this.y = y; this.w = w; this.h = h; this.type = type;
        this.active = true; // For mystery boxes
    }
    
    draw() {
        if (this.type === 'mystery') {
            ctx.fillStyle = this.active ? '#FFD700' : '#8B4513'; // Gold or Brown
            ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
            if(this.active) {
                ctx.fillStyle = 'black';
                ctx.fillText('?', this.x - cameraX + 10, this.y + 24);
            }
        } else if (this.type === 'ground') {
            // Grass top, dirt body
            ctx.fillStyle = '#228b22'; // Forest Green
            ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
            ctx.fillStyle = '#006400'; // Darker detail
            ctx.fillRect(this.x - cameraX, this.y + 5, this.w, this.h - 5);
        } else if (this.type === 'finish') {
            ctx.fillStyle = '#fff';
            ctx.fillRect(this.x - cameraX, this.y, 10, this.h);
            ctx.fillStyle = 'red';
            ctx.beginPath();
            ctx.moveTo(this.x - cameraX + 10, this.y);
            ctx.lineTo(this.x - cameraX + 40, this.y + 15);
            ctx.lineTo(this.x - cameraX + 10, this.y + 30);
            ctx.fill();
        }
    }
}

class Item {
    constructor(x, y, type) {
        this.x = x; this.y = y; this.w = 16; this.h = 16; this.type = type;
        this.vy = -5; // Pop up
        this.active = true;
    }
    
    update() {
        this.vy += GRAVITY;
        this.y += this.vy;
        
        // Simple ground check
        for (let p of platforms) {
            if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
                this.y = p.y - this.h;
                this.vy = 0;
            }
        }
        
        // Player collection
        if (rectIntersect(this.x, this.y, this.w, this.h, player.x, player.y, player.w, player.h)) {
            score += 50;
            this.active = false;
        }
    }
    
    draw() {
        ctx.fillStyle = '#FFD700';
        ctx.beginPath();
        ctx.arc(this.x - cameraX + 8, this.y + 8, 8, 0, Math.PI*2);
        ctx.fill();
    }
}

// --- LEVEL DESIGN ---

// Maps are 20 tiles high. '#' = ground, 'M' = mystery, 'E' = walker, 'S' = shooter, 'F' = finish
const maps = [
    // Level 1: The Basics
    [
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "      M M M         ",
        "                    ",
        "           E        ",
        "####################",
        "####################",
        "                    ",
        "                    ", // Pit
        "        ###   ######",
        "        ###   ######",
        "     E  ###   ######",
        "####### ###   ######",
        "####### ### F ######",
        "####### ############"
    ],
    // Level 2: Verticality
    [
        "                    ",
        "                    ",
        "         S          ",
        "       #####        ",
        "                    ",
        "     ###   ###      ",
        "     #       #      ",
        "   M #   E   # M    ",
        " ### ####### ###    ",
        " #             #    ",
        " #      S      #    ",
        "##    #####    ##   ",
        "     ##   ##        ",
        "    ##     ##       ",
        "   ##       ##      ",
        "  ##         ##    F",
        " ##     E     ##########",
        "########################",
        "########################",
        "########################"
    ],
    // Level 3: The Gauntlet
    [
        "                    ",
        "                    ",
        "   S      S      S  ",
        "  ###    ###    ### ",
        "                    ",
        "      M      M      ",
        "     ###    ###     ",
        "                    ",
        "   E      E      E  ",
        "#####  #####  ##### ",
        "                    ",
        "                    ",
        "      ##    ##      ",
        "      ##    ##     F",
        "  ##  ##    ##  #######",
        "  ##            #######",
        "  ##   E    E   #######",
        "#######################",
        "#######################",
        "#######################"
    ]
];

// --- UTILS ---

function rectIntersect(x1, y1, w1, h1, x2, y2, w2, h2) {
    return x2 < x1 + w1 && x2 + w2 > x1 && y2 < y1 + h1 && y2 + h2 > y1;
}

function createExplosion(x, y, color) {
    for(let i=0; i<8; i++) {
        particles.push({
            x: x, y: y,
            vx: (Math.random() - 0.5) * 10,
            vy: (Math.random() - 0.5) * 10,
            life: 20,
            color: color
        });
    }
}

function spawnCoin(x, y) {
    items.push(new Item(x, y, 'coin'));
}

// --- CORE FUNCTIONS ---

let player;

function initLevel(levelIndex) {
    currentLevelIdx = levelIndex;
    platforms = [];
    enemies = [];
    bullets = [];
    particles = [];
    items = [];
    
    const mapData = maps[levelIndex];
    const tileSize = 40;

    for (let row = 0; row < mapData.length; row++) {
        let line = mapData[row];
        for (let col = 0; col < line.length; col++) {
            let char = line[col];
            let x = col * tileSize;
            let y = row * tileSize;

            if (char === '#') platforms.push(new Platform(x, y, tileSize, tileSize, 'ground'));
            if (char === 'M') platforms.push(new Platform(x, y, tileSize, tileSize, 'mystery'));
            if (char === 'E') enemies.push(new Enemy(x, y, 'walker'));
            if (char === 'S') enemies.push(new Enemy(x, y, 'shooter'));
            if (char === 'F') platforms.push(new Platform(x, y, tileSize, tileSize * 2, 'finish'));
        }
    }

    player = new Player(100, 100);
    document.getElementById('level-display').innerText = "LEVEL: " + (levelIndex + 1);
}

function deathHandler(reason) {
    gameRunning = false;
    document.getElementById('msg-title').innerText = "GAME OVER";
    document.getElementById('msg-subtitle').innerText = reason;
    document.querySelector('.start-btn').innerText = "RETRY LEVEL";
    document.getElementById('message-overlay').style.display = 'block';
}

function levelComplete() {
    if (currentLevelIdx < maps.length - 1) {
        initLevel(currentLevelIdx + 1);
    } else {
        gameRunning = false;
        document.getElementById('msg-title').innerText = "VICTORY!";
        document.getElementById('msg-subtitle').innerText = "All Levels Cleared";
        document.querySelector('.start-btn').innerText = "PLAY AGAIN";
        document.getElementById('message-overlay').style.display = 'block';
        currentLevelIdx = -1; // Reset to 0 next time
    }
}

function startGame() {
    document.getElementById('message-overlay').style.display = 'none';
    if(currentLevelIdx === -1) score = 0;
    initLevel(currentLevelIdx === -1 ? 0 : currentLevelIdx);
    gameRunning = true;
    gameLoop();
}

function gameLoop() {
    if (!gameRunning) return;

    // 1. Clear
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 2. Logic Update
    player.update();
    
    // Camera Logic (Keep player in center)
    cameraX = player.x - 300;
    if (cameraX < 0) cameraX = 0;

    // Entity Updates
    enemies.forEach((e, i) => {
        if (e.hp <= 0) enemies.splice(i, 1);
        else e.update();
    });

    bullets.forEach((b, i) => {
        if (!b.active) bullets.splice(i, 1);
        else b.update();
    });

    items.forEach((item, i) => {
        if (!item.active) items.splice(i, 1);
        else item.update();
    });

    particles.forEach((p, i) => {
        p.x += p.vx;
        p.y += p.vy;
        p.life--;
        if (p.life <= 0) particles.splice(i, 1);
    });

    // Check Level Complete
    platforms.forEach(p => {
        if (p.type === 'finish' && rectIntersect(player.x, player.y, player.w, player.h, p.x, p.y, p.w, p.h)) {
            levelComplete();
        }
    });

    // 3. Render
    // Draw Stars (Background)
    ctx.fillStyle = '#ffffff';
    for(let i=0; i<50; i++) {
        let starX = (i * 100 - cameraX * 0.5) % canvas.width;
        if (starX < 0) starX += canvas.width;
        ctx.fillRect(starX, (Math.sin(i)*200 + 200), 2, 2);
    }

    platforms.forEach(p => p.draw());
    enemies.forEach(e => e.draw());
    items.forEach(i => i.draw());
    bullets.forEach(b => b.draw());
    player.draw();

    // Draw Particles
    particles.forEach(p => {
        ctx.fillStyle = p.color;
        ctx.fillRect(p.x - cameraX, p.y, 4, 4);
    });

    // UI Update
    document.getElementById('score-display').innerText = "SCORE: " + score;

    requestAnimationFrame(gameLoop);
}

// --- INPUT HANDLING ---

function setKey(key, val) {
    if (key === 'left') keys.left = val;
    if (key === 'right') keys.right = val;
    if (key === 'up') keys.up = val;
    if (key === 'shoot') keys.shoot = val;
}

// Keyboard
window.addEventListener('keydown', e => {
    if (e.code === 'ArrowLeft') setKey('left', true);
    if (e.code === 'ArrowRight') setKey('right', true);
    if (e.code === 'ArrowUp') setKey('up', true);
    if (e.code === 'Space') setKey('shoot', true);
});

window.addEventListener('keyup', e => {
    if (e.code === 'ArrowLeft') setKey('left', false);
    if (e.code === 'ArrowRight') setKey('right', false);
    if (e.code === 'ArrowUp') setKey('up', false);
    if (e.code === 'Space') setKey('shoot', false);
});

// Touch (Mobile)
const setupTouch = (id, key) => {
    const el = document.getElementById(id);
    el.addEventListener('touchstart', (e) => { e.preventDefault(); setKey(key, true); });
    el.addEventListener('touchend', (e) => { e.preventDefault(); setKey(key, false); });
};

setupTouch('btn-left', 'left');
setupTouch('btn-right', 'right');
setupTouch('btn-jump', 'up');
setupTouch('btn-shoot', 'shoot');

// Initial Setup
document.getElementById('message-overlay').style.display = 'block';

</script>
</body>
</html>
Editor is loading...
Leave a Comment