Zombie Energy Survival 2D
by LaserTiger281387 lines20.8 KB
<!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.