🎮ArcadeLab

Zombie Energy Survival 2D

by LaserTiger28
1387 lines20.8 KB
▶ Play
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zombie Energy Survival 2D</title>

<style>
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    overflow: hidden;
    background: #111;
    font-family: Arial, sans-serif;
    user-select: none;
}

canvas {
    display: block;
}

#hud {
    position: fixed;
    top: 15px;
    left: 15px;
    color: white;
    font-size: 20px;
    font-weight: bold;
    text-shadow: 2px 2px 5px black;
    z-index: 5;
}

#menu {
    position: fixed;
    inset: 0;
    background: rgba(5, 8, 15, 0.92);
    color: white;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 20;
    text-align: center;
}

#menu h1 {
    font-size: 48px;
    margin: 0 0 10px;
}

#menu p {
    font-size: 18px;
}

button {
    margin-top: 20px;
    padding: 15px 35px;
    border: none;
    border-radius: 12px;
    background: #24d66b;
    color: white;
    font-size: 22px;
    font-weight: bold;
    cursor: pointer;
}

button:hover {
    transform: scale(1.05);
}

#crosshair {
    position: fixed;
    left: 50%;
    top: 50%;
    color: white;
    font-size: 25px;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 4;
}
</style>
</head>

<body>

<canvas id="game"></canvas>

<div id="hud">
    ❤️ Vida: <span id="health">100</span><br>
    ⭐ Pontos: <span id="score">0</span><br>
    🌊 Onda: <span id="wave">1</span><br>
    🧟 Zumbis: <span id="zombieCount">0</span>
</div>

<div id="crosshair">+</div>

<div id="menu">
    <h1>🧟 ZOMBIE SURVIVAL</h1>
    <p>Sobreviva às ondas de zumbis!</p>
    <p>WASD para andar • Mouse para mirar</p>
    <p>Clique para lançar bolas de energia ⚡</p>
    <button id="startButton">JOGAR</button>
</div>

<script>

const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

let W = window.innerWidth;
let H = window.innerHeight;

canvas.width = W;
canvas.height = H;


// ======================================================
// ESTADO DO JOGO
// ======================================================

let playing = false;

let health = 100;
let score = 0;
let wave = 1;

let zombies = [];
let projectiles = [];
let particles = [];

let keys = {};

let mouse = {
    x: W / 2,
    y: H / 2
};

let spawnTimer = 0;
let waveTimer = 0;


// ======================================================
// JOGADOR
// ======================================================

const player = {
    x: W / 2,
    y: H / 2,

    radius: 24,

    speed: 4,

    direction: 0,

    animation: 0
};


// ======================================================
// REDIMENSIONAR
// ======================================================

function resize() {

    W = window.innerWidth;
    H = window.innerHeight;

    canvas.width = W;
    canvas.height = H;

}

window.addEventListener("resize", resize);


// ======================================================
// TECLADO
// ======================================================

window.addEventListener("keydown", e => {

    keys[e.key.toLowerCase()] = true;

    if (
        e.key.toLowerCase() === "r" &&
        !playing
    ) {
        startGame();
    }

});

window.addEventListener("keyup", e => {

    keys[e.key.toLowerCase()] = false;

});


// ======================================================
// MOUSE
// ======================================================

canvas.addEventListener("mousemove", e => {

    mouse.x = e.clientX;
    mouse.y = e.clientY;

});

canvas.addEventListener("mousedown", e => {

    if (
        e.button === 0 &&
        playing
    ) {

        shootEnergy();

    }

});


// ======================================================
// COMEÇAR
// ======================================================

document
    .getElementById("startButton")
    .addEventListener("click", startGame);


function startGame() {

    playing = true;

    health = 100;
    score = 0;
    wave = 1;

    zombies = [];
    projectiles = [];
    particles = [];

    player.x = W / 2;
    player.y = H / 2;

    spawnTimer = 0;
    waveTimer = 0;

    document.getElementById("menu")
        .style.display = "none";

    for (let i = 0; i < 5; i++) {
        spawnZombie();
    }

}


// ======================================================
// MOVIMENTO DO JOGADOR
// ======================================================

function updatePlayer() {

    let dx = 0;
    let dy = 0;

    if (keys["w"] || keys["arrowup"]) {
        dy -= 1;
    }

    if (keys["s"] || keys["arrowdown"]) {
        dy += 1;
    }

    if (keys["a"] || keys["arrowleft"]) {
        dx -= 1;
    }

    if (keys["d"] || keys["arrowright"]) {
        dx += 1;
    }

    if (dx !== 0 || dy !== 0) {

        const length =
            Math.sqrt(dx * dx + dy * dy);

        dx /= length;
        dy /= length;

        player.x += dx * player.speed;
        player.y += dy * player.speed;

        player.animation += 0.18;
    }

    // Limites da tela

    player.x = Math.max(
        35,
        Math.min(W - 35, player.x)
    );

    player.y = Math.max(
        45,
        Math.min(H - 35, player.y)
    );


    // Direção do personagem

    player.direction =
        Math.atan2(
            mouse.y - player.y,
            mouse.x - player.x
        );

}


// ======================================================
// PERSONAGEM DO JOGADOR
// ======================================================

function drawPlayer() {

    ctx.save();

    ctx.translate(
        player.x,
        player.y
    );

    ctx.rotate(player.direction);

    const walking =
        Math.sin(player.animation) * 3;


    // Sombra

    ctx.fillStyle =
        "rgba(0,0,0,0.3)";

    ctx.beginPath();

    ctx.ellipse(
        0,
        25,
        23,
        8,
        0,
        0,
        Math.PI * 2
    );

    ctx.fill();


    // Pernas

    ctx.strokeStyle = "#17202a";
    ctx.lineWidth = 8;
    ctx.lineCap = "round";

    ctx.beginPath();

    ctx.moveTo(-7, 10);
    ctx.lineTo(-8, 27 + walking);

    ctx.moveTo(7, 10);
    ctx.lineTo(8, 27 - walking);

    ctx.stroke();


    // Corpo

    ctx.fillStyle = "#2471a3";

    ctx.beginPath();

    ctx.roundRect(
        -15,
        -5,
        30,
        30,
        8
    );

    ctx.fill();


    // Pescoço

    ctx.fillStyle = "#f1c27d";

    ctx.fillRect(
        -6,
        -12,
        12,
        10
    );


    // Cabeça

    ctx.fillStyle = "#f1c27d";

    ctx.beginPath();

    ctx.arc(
        0,
        -23,
        17,
        0,
        Math.PI * 2
    );

    ctx.fill();


    // Cabelo

    ctx.fillStyle = "#3b2416";

    ctx.beginPath();

    ctx.arc(
        0,
        -28,
        17,
        Math.PI,
        Math.PI * 2
    );

    ctx.fill();


    // Olho

    ctx.fillStyle = "white";

    ctx.beginPath();

    ctx.arc(
        7,
        -25,
        3,
        0,
        Math.PI * 2
    );

    ctx.fill();


    // Braço apontando

    ctx.strokeStyle = "#f1c27d";
    ctx.lineWidth = 7;

    ctx.beginPath();

    ctx.moveTo(7, 0);
    ctx.lineTo(28, 0);

    ctx.stroke();


    // Energia na mão

    ctx.fillStyle = "#00e5ff";

    ctx.shadowColor = "#00e5ff";
    ctx.shadowBlur = 15;

    ctx.beginPath();

    ctx.arc(
        31,
        0,
        7,
        0,
        Math.PI * 2
    );

    ctx.fill();

    ctx.shadowBlur = 0;

    ctx.restore();
}


// ======================================================
// CRIAR ZUMBI
// ======================================================

function spawnZombie() {

    let side =
        Math.floor(Math.random() * 4);

    let x;
    let y;

    if (side === 0) {
        x = -50;
        y = Math.random() * H;
    }

    if (side === 1) {
        x = W + 50;
        y = Math.random() * H;
    }

    if (side === 2) {
        x = Math.random() * W;
        y = -50;
    }

    if (side === 3) {
        x = Math.random() * W;
        y = H + 50;
    }


    zombies.push({

        x: x,
        y: y,

        radius: 25,

        speed:
            0.7 +
            Math.random() * 0.4 +
            wave * 0.04,

        health: 2,

        maxHealth: 2,

        animation:
            Math.random() * 10,

        hitFlash: 0

    });

}


// ======================================================
// DESENHAR ZUMBI
// ======================================================

function drawZombie(z) {

    ctx.save();

    ctx.translate(
        z.x,
        z.y
    );


    // sombra

    ctx.fillStyle =
        "rgba(0,0,0,0.35)";

    ctx.beginPath();

    ctx.ellipse(
        0,
        28,
        25,
        8,
        0,
        0,
        Math.PI * 2
    );

    ctx.fill();


    const walk =
        Math.sin(z.animation) * 4;


    // pernas

    ctx.strokeStyle = "#283618";
    ctx.lineWidth = 9;
    ctx.lineCap = "round";

    ctx.beginPath();

    ctx.moveTo(-8, 12);
    ctx.lineTo(-10, 29 + walk);

    ctx.moveTo(8, 12);
    ctx.lineTo(10, 29 - walk);

    ctx.stroke();


    // corpo

    ctx.fillStyle =
        z.hitFlash > 0
            ? "#ffffff"
            : "#556b2f";

    ctx.beginPath();

    ctx.roundRect(
        -17,
        -2,
        34,
        28,
        8
    );

    ctx.fill();


    // pescoço

    ctx.fillStyle = "#8fa65b";

    ctx.fillRect(
        -7,
        -13,
        14,
        10
    );


    // cabeça

    ctx.fillStyle =
        z.hitFlash > 0
            ? "#ffffff"
            : "#8fa65b";

    ctx.beginPath();

    ctx.arc(
        0,
        -25,
        18,
        0,
        Math.PI * 2
    );

    ctx.fill();


    // cabelo

    ctx.fillStyle = "#242424";

    ctx.beginPath();

    ctx.arc(
        0,
        -32,
        17,
        Math.PI,
        Math.PI * 2
    );

    ctx.fill();


    // olhos

    ctx.fillStyle = "#ff3333";

    ctx.shadowColor = "#ff0000";
    ctx.shadowBlur = 8;

    ctx.beginPath();

    ctx.arc(
        -7,
        -26,
        4,
        0,
        Math.PI * 2
    );

    ctx.arc(
        7,
        -26,
        4,
        0,
        Math.PI * 2
    );

    ctx.fill();

    ctx.shadowBlur = 0;


    // braços

    ctx.strokeStyle =
        "#8fa65b";

    ctx.lineWidth = 7;

    ctx.beginPath();

    ctx.moveTo(-12, 2);
    ctx.lineTo(-30, 10);

    ctx.moveTo(12, 2);
    ctx.lineTo(30, 10);

    ctx.stroke();


    // barra de vida

    const barWidth = 42;

    ctx.fillStyle = "#222";

    ctx.fillRect(
        -barWidth / 2,
        -53,
        barWidth,
        5
    );

    ctx.fillStyle = "#2ecc71";

    ctx.fillRect(
        -barWidth / 2,
        -53,
        barWidth *
        (z.health / z.maxHealth),
        5
    );


    ctx.restore();
}


// ======================================================
// LANÇAR ENERGIA
// ======================================================

function shootEnergy() {

    const angle =
        Math.atan2(
            mouse.y - player.y,
            mouse.x - player.x
        );

    const speed = 9;

    projectiles.push({

        x: player.x +
            Math.cos(angle) * 30,

        y: player.y +
            Math.sin(angle) * 30,

        vx:
            Math.cos(angle) * speed,

        vy:
            Math.sin(angle) * speed,

        radius: 8,

        life: 100

    });

}


// ======================================================
// ATUALIZAR PROJÉTEIS
// ======================================================

function updateProjectiles() {

    for (
        let i = projectiles.length - 1;
        i >= 0;
        i--
    ) {

        const p = projectiles[i];

        p.x += p.vx;
        p.y += p.vy;

        p.life--;

        createParticle(
            p.x,
            p.y,
            "#00e5ff",
            1
        );


        // colisão com zumbis

        for (
            let j = zombies.length - 1;
            j >= 0;
            j--
        ) {

            const z = zombies[j];

            const dx =
                p.x - z.x;

            const dy =
                p.y - z.y;

            const distance =
                Math.sqrt(
                    dx * dx +
                    dy * dy
                );


            if (
                distance <
                p.radius + z.radius
            ) {

                z.health--;

                z.hitFlash = 5;

                createExplosion(
                    z.x,
                    z.y
                );

                projectiles.splice(i, 1);


                if (z.health <= 0) {

                    score += 10;

                    createExplosion(
                        z.x,
                        z.y
                    );

                    zombies.splice(j, 1);
                }

                break;
            }
        }


        if (
            p.life <= 0 ||
            p.x < -100 ||
            p.x > W + 100 ||
            p.y < -100 ||
            p.y > H + 100
        ) {

            if (
                projectiles.includes(p)
            ) {
                projectiles.splice(i, 1);
            }

        }

    }

}


// ======================================================
// DESENHAR ENERGIA
// ======================================================

function drawProjectiles() {

    projectiles.forEach(p => {

        ctx.save();

        ctx.fillStyle = "#00e5ff";

        ctx.shadowColor = "#00e5ff";
        ctx.shadowBlur = 20;

        ctx.beginPath();

        ctx.arc(
            p.x,
            p.y,
            p.radius,
            0,
            Math.PI * 2
        );

        ctx.fill();

        ctx.restore();

    });

}


// ======================================================
// ATUALIZAR ZUMBIS
// ======================================================

function updateZombies() {

    for (
        let i = zombies.length - 1;
        i >= 0;
        i--
    ) {

        const z = zombies[i];


        const dx =
            player.x - z.x;

        const dy =
            player.y - z.y;

        const distance =
            Math.sqrt(
                dx * dx +
                dy * dy
            );


        if (distance > 1) {

            z.x +=
                (dx / distance) *
                z.speed;

            z.y +=
                (dy / distance) *
                z.speed;

        }


        z.animation += 0.12;


        if (z.hitFlash > 0) {
            z.hitFlash--;
        }


        // contato com jogador

        if (
            distance <
            player.radius +
            z.radius
        ) {

            health -= 0.35;

            // empurrar zumbi

            z.x -=
                (dx / distance) * 3;

            z.y -=
                (dy / distance) * 3;


            if (health <= 0) {

                health = 0;

                gameOver();

            }

        }

    }

}


// ======================================================
// PARTÍCULAS
// ======================================================

function createParticle(
    x,
    y,
    color,
    amount = 1
) {

    for (let i = 0; i < amount; i++) {

        particles.push({

            x: x,
            y: y,

            vx:
                (Math.random() - 0.5) * 2,

            vy:
                (Math.random() - 0.5) * 2,

            life: 20 +
                Math.random() * 20,

            color: color,

            size:
                2 +
                Math.random() * 3

        });

    }

}


function createExplosion(x, y) {

    for (let i = 0; i < 15; i++) {

        particles.push({

            x: x,
            y: y,

            vx:
                (Math.random() - 0.5) * 6,

            vy:
                (Math.random() - 0.5) * 6,

            life: 30,

            color:
                Math.random() > 0.5
                    ? "#00e5ff"
                    : "#7dff00",

            size:
                3 +
                Math.random() * 5

        });

    }

}


function updateParticles() {

    for (
        let i = particles.length - 1;
        i >= 0;
        i--
    ) {

        const p = particles[i];

        p.x += p.vx;
        p.y += p.vy;

        p.vx *= 0.96;
        p.vy *= 0.96;

        p.life--;

        if (p.life <= 0) {

            particles.splice(i, 1);

        }

    }

}


function drawParticles() {

    particles.forEach(p => {

        ctx.globalAlpha =
            Math.max(0, p.life / 30);

        ctx.fillStyle = p.color;

        ctx.beginPath();

        ctx.arc(
            p.x,
            p.y,
            p.size,
            0,
            Math.PI * 2
        );

        ctx.fill();

    });

    ctx.globalAlpha = 1;

}


// ======================================================
// FUNDO
// ======================================================

function drawBackground() {

    // céu/fundo

    ctx.fillStyle = "#18202a";

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    // chão

    ctx.fillStyle = "#263238";

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    // pequenos detalhes do chão

    ctx.strokeStyle =
        "rgba(255,255,255,0.035)";

    ctx.lineWidth = 1;

    const grid = 50;

    for (
        let x = 0;
        x < W;
        x += grid
    ) {

        ctx.beginPath();

        ctx.moveTo(x, 0);
        ctx.lineTo(x, H);

        ctx.stroke();

    }

    for (
        let y = 0;
        y < H;
        y += grid
    ) {

        ctx.beginPath();

        ctx.moveTo(0, y);
        ctx.lineTo(W, y);

        ctx.stroke();

    }

}


// ======================================================
// ONDAS
// ======================================================

function updateWaves() {

    waveTimer++;

    if (waveTimer >= 1800) {

        wave++;

        waveTimer = 0;

        // nova onda

        for (
            let i = 0;
            i < wave + 2;
            i++
        ) {

            spawnZombie();

        }

    }


    spawnTimer++;

    const spawnDelay =
        Math.max(
            25,
            100 - wave * 5
        );


    if (
        spawnTimer >= spawnDelay &&
        zombies.length < 8 + wave * 2
    ) {

        spawnZombie();

        spawnTimer = 0;

    }

}


// ======================================================
// HUD
// ======================================================

function updateHUD() {

    document.getElementById(
        "health"
    ).textContent =
        Math.floor(health);

    document.getElementById(
        "score"
    ).textContent =
        score;

    document.getElementById(
        "wave"
    ).textContent =
        wave;

    document.getElementById(
        "zombieCount"
    ).textContent =
        zombies.length;

}


// ======================================================
// GAME OVER
// ======================================================

function gameOver() {

    playing = false;

    document.getElementById(
        "menu"
    ).style.display = "flex";

    document.querySelector(
        "#menu h1"
    ).textContent =
        "💀 GAME OVER";

    document.querySelector(
        "#menu p"
    ).textContent =
        "Você fez " +
        score +
        " pontos!";

    document.querySelector(
        "#startButton"
    ).textContent =
        "JOGAR NOVAMENTE";

}


// ======================================================
// LOOP PRINCIPAL
// ======================================================

function gameLoop() {

    requestAnimationFrame(gameLoop);


    if (!playing) {

        drawBackground();

        return;

    }


    drawBackground();

    updatePlayer();

    updateProjectiles();

    updateZombies();

    updateParticles();

    updateWaves();


    drawProjectiles();

    zombies.forEach(
        drawZombie
    );

    drawParticles();

    drawPlayer();

    updateHUD();

}


gameLoop();

</script>

</body>
</html>

Game Source: Zombie Energy Survival 2D

Creator: LaserTiger28

Libraries: none

Complexity: complex (1387 lines, 20.8 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: zombie-energy-survival-2d-lasertiger28" to link back to the original. Then publish at arcadelab.ai/publish.