DayZ 2D - Winter Survival v2
by SparkLegend68797 lines20.2 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DayZ 2D - Winter Survival v2</title>
<style>
html, body {
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
font-family: Arial, sans-serif;
}
#game {
display: block;
}
#ui {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.screen {
pointer-events: auto;
background: rgba(0,0,0,0.85);
padding: 20px 40px;
border-radius: 8px;
border: 2px solid #555;
text-align: center;
color: #eee;
}
button {
padding: 10px 20px;
margin: 10px;
background: #222;
color: #eee;
border: 1px solid #444;
cursor: pointer;
}
button:hover { background: #333; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="ui">
<div id="startScreen" class="screen">
<h1>DayZ 2D</h1>
<p>Winter pixel survival prototype</p>
<button id="playBtn">Play</button>
<button id="mpBtn">Multiplayer (Coming Soon)</button>
</div>
<div id="gameOverScreen" class="screen" style="display:none;">
<h2>You Died</h2>
<p>Press restart to try again.</p>
<button id="restartBtn">Restart</button>
</div>
</div>
<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener("resize", resize);
// UI
const startScreen = document.getElementById("startScreen");
const gameOverScreen = document.getElementById("gameOverScreen");
const playBtn = document.getElementById("playBtn");
const restartBtn = document.getElementById("restartBtn");
const mpBtn = document.getElementById("mpBtn");
mpBtn.onclick = () => {
alert("Multiplayer placeholder.\nLater you can add lobbies, servers, or WebSockets here.");
};
// World / camera
const worldWidth = 4000;
const worldHeight = 4000;
let camX = 0;
let camY = 0;
// Input
const keys = {};
window.addEventListener("keydown", e => {
keys[e.key.toLowerCase()] = true;
});
window.addEventListener("keyup", e => {
keys[e.key.toLowerCase()] = false;
});
// Mouse for aiming + shooting
let mouseX = 0;
let mouseY = 0;
let shooting = false;
canvas.addEventListener("mousemove", e => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
});
canvas.addEventListener("mousedown", e => {
if (e.button === 0) shooting = true;
});
canvas.addEventListener("mouseup", e => {
if (e.button === 0) shooting = false;
});
// Game state
let running = false;
let lastTime = 0;
let player;
let zombies = [];
let bullets = [];
let pickups = []; // ammo + food + weapons
let trees = [];
let buildings = [];
let shootCooldown = 0;
// Weapons definition
const weapons = {
ak: { name: "AK", magSize: 30, fireRate: 0.15, bulletSpeed: 420, ammoType: "rifle", reloadTime: 1.2 },
m4: { name: "M4", magSize: 35, fireRate: 0.12, bulletSpeed: 460, ammoType: "rifle", reloadTime: 1.1 },
sniper:{name: "Sniper", magSize: 5, fireRate: 0.8, bulletSpeed: 700, ammoType: "sniper", reloadTime: 1.8 },
lmg: { name: "LMG", magSize: 60, fireRate: 0.08, bulletSpeed: 400, ammoType: "lmg", reloadTime: 2.0 },
rpg: { name: "RPG", magSize: 1, fireRate: 1.2, bulletSpeed: 260, ammoType: "rocket", reloadTime: 2.5 }
};
function randomInRange(min, max) {
return Math.random() * (max - min) + min;
}
function generateWorldDecor() {
trees = [];
buildings = [];
// Trees
for (let i = 0; i < 260; i++) {
trees.push({
x: Math.random() * worldWidth,
y: Math.random() * worldHeight
});
}
// More buildings
for (let i = 0; i < 16; i++) {
const w = randomInRange(120, 260);
const h = randomInRange(100, 220);
buildings.push({
x: Math.random() * (worldWidth - w),
y: Math.random() * (worldHeight - h),
w,
h
});
}
}
function resetGame() {
generateWorldDecor();
player = {
x: worldWidth / 2,
y: worldHeight / 2,
baseSpeed: 100,
speed: 100,
hp: 100,
hunger: 100,
stamina: 100,
dirX: 1,
dirY: 0,
currentWeaponKey: "ak",
ammoInMag: 30,
ammoReserve: {
rifle: 90,
sniper: 20,
lmg: 120,
rocket: 5
},
reloading: false,
reloadTimer: 0,
walkAnim: 0 // for walking animation
};
const w = weapons[player.currentWeaponKey];
player.magSize = w.magSize;
player.reloadTime = w.reloadTime;
zombies = [];
bullets = [];
pickups = [];
for (let i = 0; i < 30; i++) {
zombies.push(spawnZombie());
}
// Ammo pickups
for (let i = 0; i < 24; i++) {
pickups.push(spawnPickup("ammo"));
}
// Food pickups
for (let i = 0; i < 14; i++) {
pickups.push(spawnPickup("food"));
}
// Weapon pickups
const weaponTypes = ["m4", "sniper", "lmg", "rpg"];
weaponTypes.forEach(type => {
for (let i = 0; i < 3; i++) {
pickups.push(spawnWeaponPickup(type));
}
});
lastTime = performance.now();
shootCooldown = 0;
}
function spawnZombie() {
const edge = Math.floor(Math.random() * 4);
let x, y;
if (edge === 0) { // top
x = Math.random() * worldWidth;
y = -40;
} else if (edge === 1) { // right
x = worldWidth + 40;
y = Math.random() * worldHeight;
} else if (edge === 2) { // bottom
x = Math.random() * worldWidth;
y = worldHeight + 40;
} else { // left
x = -40;
y = Math.random() * worldHeight;
}
return {
x,
y,
speed: 50 + Math.random() * 20,
aggro: false,
walkAnim: 0
};
}
function spawnPickup(type) {
return {
x: Math.random() * worldWidth,
y: Math.random() * worldHeight,
type
};
}
function spawnWeaponPickup(weaponKey) {
return {
x: Math.random() * worldWidth,
y: Math.random() * worldHeight,
type: "weapon",
weaponKey
};
}
function tryReload() {
if (player.reloading) return;
const w = weapons[player.currentWeaponKey];
if (player.ammoInMag >= w.magSize) return;
const ammoType = w.ammoType;
if (!player.ammoReserve[ammoType] || player.ammoReserve[ammoType] <= 0) return;
player.reloading = true;
player.reloadTimer = w.reloadTime;
}
function finishReload() {
const w = weapons[player.currentWeaponKey];
const ammoType = w.ammoType;
const needed = w.magSize - player.ammoInMag;
const available = player.ammoReserve[ammoType] || 0;
const taken = Math.min(needed, available);
player.ammoInMag += taken;
player.ammoReserve[ammoType] -= taken;
player.reloading = false;
}
function update(dt) {
// Hunger drain
player.hunger -= 2 * dt;
if (player.hunger < 0) player.hunger = 0;
if (player.hunger <= 0) {
player.hp -= 5 * dt; // starving damage
}
// Stamina & sprint (Shift)
const sprinting = keys["shift"];
if (sprinting && player.stamina > 0) {
player.speed = player.baseSpeed * 1.5;
player.stamina -= 25 * dt;
if (player.stamina < 0) player.stamina = 0;
} else {
player.speed = player.baseSpeed;
player.stamina += 15 * dt;
if (player.stamina > 100) player.stamina = 100;
}
// Movement
let dx = 0, 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;
const moving = (dx !== 0 || dy !== 0);
if (moving) {
const len = Math.hypot(dx, dy) || 1;
dx /= len;
dy /= len;
player.dirX = dx;
player.dirY = dy;
player.walkAnim += dt * 8; // speed of walk animation
} else {
// slowly return to neutral
player.walkAnim *= 0.9;
}
player.x += dx * player.speed * dt;
player.y += dy * player.speed * dt;
// Clamp to world
player.x = Math.max(0, Math.min(worldWidth, player.x));
player.y = Math.max(0, Math.min(worldHeight, player.y));
// Camera follow
camX = player.x - canvas.width / 2;
camY = player.y - canvas.height / 2;
camX = Math.max(0, Math.min(worldWidth - canvas.width, camX));
camY = Math.max(0, Math.min(worldHeight - canvas.height, camY));
// Aim direction from player to mouse (convert mouse to world coords)
const worldMouseX = camX + mouseX;
const worldMouseY = camY + mouseY;
const ax = worldMouseX - player.x;
const ay = worldMouseY - player.y;
const ad = Math.hypot(ax, ay) || 1;
const aimX = ax / ad;
const aimY = ay / ad;
if (ad > 4) {
player.dirX = aimX;
player.dirY = aimY;
}
// Reload logic
if (keys["r"]) {
tryReload();
}
if (player.reloading) {
player.reloadTimer -= dt;
if (player.reloadTimer <= 0) {
finishReload();
}
}
const w = weapons[player.currentWeaponKey];
// Shooting
shootCooldown -= dt;
if (shooting && shootCooldown <= 0 && !player.reloading) {
if (player.ammoInMag > 0) {
shootCooldown = w.fireRate;
player.ammoInMag--;
const speed = w.bulletSpeed;
const isRocket = (w.ammoType === "rocket");
bullets.push({
x: player.x + aimX * 14,
y: player.y + aimY * 10,
vx: aimX * speed,
vy: aimY * speed,
life: isRocket ? 2.0 : 1.2,
rocket: isRocket
});
}
}
// Update bullets
bullets.forEach(b => {
b.x += b.vx * dt;
b.y += b.vy * dt;
b.life -= dt;
});
bullets = bullets.filter(b =>
b.life > 0 &&
b.x > -100 && b.x < worldWidth + 100 &&
b.y > -100 && b.y < worldHeight + 100
);
// Zombies: only aggro when close, with simple walk animation
zombies.forEach(z => {
const zx = player.x - z.x;
const zy = player.y - z.y;
const dist = Math.hypot(zx, zy) || 1;
// Aggro radius
if (!z.aggro && dist < 260) {
z.aggro = true;
}
// De-aggro if far away
if (z.aggro && dist > 420) {
z.aggro = false;
}
if (z.aggro) {
const vx = (zx / dist) * z.speed * dt;
const vy = (zy / dist) * z.speed * dt;
z.x += vx;
z.y += vy;
z.walkAnim += dt * 6;
} else {
// idle shuffle
z.walkAnim *= 0.9;
}
// Collision damage only if very close
if (dist < 24) {
player.hp -= 15 * dt;
}
});
// Bullet–zombie collisions (rockets have bigger radius)
bullets.forEach(b => {
zombies.forEach(z => {
const d = Math.hypot(b.x - z.x, b.y - z.y);
const hitRadius = b.rocket ? 40 : 18;
if (d < hitRadius && !z.dead) {
z.dead = true;
if (b.rocket) {
b.life = 0;
} else {
b.life = 0;
}
}
});
});
// Remove dead zombies and respawn
zombies = zombies.filter(z => !z.dead);
while (zombies.length < 30) {
zombies.push(spawnZombie());
}
// Pickup collisions
pickups.forEach(p => {
const d = Math.hypot(player.x - p.x, player.y - p.y);
if (d < 26 && !p.collected) {
p.collected = true;
if (p.type === "ammo") {
// random ammo type
const types = ["rifle", "sniper", "lmg", "rocket"];
const t = types[Math.floor(Math.random() * types.length)];
const amount = (t === "rocket") ? 2 : 30;
player.ammoReserve[t] = (player.ammoReserve[t] || 0) + amount;
} else if (p.type === "food") {
player.hunger += 35;
if (player.hunger > 100) player.hunger = 100;
player.hp += 15;
if (player.hp > 100) player.hp = 100;
} else if (p.type === "weapon") {
player.currentWeaponKey = p.weaponKey;
const nw = weapons[p.weaponKey];
player.magSize = nw.magSize;
player.reloadTime = nw.reloadTime;
// auto-fill mag if ammo exists
const ammoType = nw.ammoType;
const have = player.ammoReserve[ammoType] || 0;
const needed = nw.magSize;
const taken = Math.min(needed, have);
player.ammoInMag = taken;
player.ammoReserve[ammoType] = have - taken;
}
}
});
pickups = pickups.filter(p => !p.collected);
// Keep some pickups around
while (pickups.filter(p => p.type === "ammo").length < 24) {
pickups.push(spawnPickup("ammo"));
}
while (pickups.filter(p => p.type === "food").length < 14) {
pickups.push(spawnPickup("food"));
}
if (player.hp <= 0) {
running = false;
gameOverScreen.style.display = "block";
}
}
// Pixel-art helpers
function drawPixelRect(x, y, w, h, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
// Draw soldier with gun (top-down pixel art) + walk animation
function drawPlayer(px, py, dirX, dirY) {
const size = 24;
const half = size / 2;
const baseX = px - half - camX;
const baseY = py - half - camY;
const legOffset = Math.sin(player.walkAnim) * 2;
// Body (armor)
drawPixelRect(baseX + 6, baseY + 8, 12, 12, "#2b3b2b"); // torso
// Head
drawPixelRect(baseX + 8, baseY + 2, 8, 8, "#c9b28a"); // face
drawPixelRect(baseX + 8, baseY + 0, 8, 4, "#3b5b3b"); // helmet
// Legs with simple animation
drawPixelRect(baseX + 6, baseY + 20 + legOffset, 4, 6, "#1f2525");
drawPixelRect(baseX + 14, baseY + 20 - legOffset, 4, 6, "#1f2525");
// Arms
drawPixelRect(baseX + 2, baseY + 10, 4, 8, "#c9b28a");
drawPixelRect(baseX + 18, baseY + 10, 4, 8, "#c9b28a");
// Gun style by weapon
const w = weapons[player.currentWeaponKey];
const gunLenMap = {
ak: 16,
m4: 18,
sniper: 22,
lmg: 20,
rpg: 24
};
const gunLen = gunLenMap[player.currentWeaponKey] || 16;
const gunThick = 4;
const gx = px - camX + dirX * 10;
const gy = py - camY + dirY * 6;
ctx.save();
ctx.translate(gx, gy);
const angle = Math.atan2(dirY, dirX);
ctx.rotate(angle);
if (player.currentWeaponKey === "rpg") {
// RPG tube
drawPixelRect(-6, -gunThick, gunLen + 8, gunThick * 2, "#555");
drawPixelRect(gunLen - 2, -gunThick - 2, 6, gunThick * 2 + 4, "#777");
} else {
// Stock
drawPixelRect(-4, -gunThick / 2, 6, gunThick, "#3b2b1b");
// Barrel
drawPixelRect(2, -gunThick / 2, gunLen, gunThick, "#444");
// Magazine
drawPixelRect(6, gunThick / 2, 4, 6, "#222");
}
ctx.restore();
}
// Draw zombie (pixel art) with walk animation
function drawZombie(zx, zy, walkAnim) {
const size = 22;
const half = size / 2;
const baseX = zx - half - camX;
const baseY = zy - half - camY;
const legOffset = Math.sin(walkAnim) * 2;
// Body
drawPixelRect(baseX + 6, baseY + 8, 10, 12, "#305030");
// Head
drawPixelRect(baseX + 6, baseY + 0, 10, 8, "#6fbf6f");
// Eyes
drawPixelRect(baseX + 7, baseY + 2, 2, 2, "#000");
drawPixelRect(baseX + 13, baseY + 2, 2, 2, "#000");
// Mouth
drawPixelRect(baseX + 8, baseY + 5, 6, 2, "#900");
// Arms
drawPixelRect(baseX + 2, baseY + 10, 4, 8, "#6fbf6f");
drawPixelRect(baseX + 16, baseY + 10, 4, 8, "#6fbf6f");
// Legs with animation
drawPixelRect(baseX + 6, baseY + 20 + legOffset, 4, 6, "#203020");
drawPixelRect(baseX + 12, baseY + 20 - legOffset, 4, 6, "#203020");
}
// Draw ammo pickup (pixel box)
function drawAmmoPickup(px, py) {
const size = 16;
const half = size / 2;
const x = px - half - camX;
const y = py - half - camY;
drawPixelRect(x, y, size, size, "#c9a23b"); // box
drawPixelRect(x + 2, y + 4, size - 4, 4, "#e6d27a"); // stripe
drawPixelRect(x + 4, y + 10, size - 8, 3, "#7a5b1b"); // label
}
// Draw food pickup (pixel can)
function drawFoodPickup(px, py) {
const w = 12;
const h = 18;
const x = px - w / 2 - camX;
const y = py - h / 2 - camY;
drawPixelRect(x, y, w, h, "#b03030"); // can body
drawPixelRect(x, y, w, 3, "#d8d8d8"); // top rim
drawPixelRect(x, y + h - 3, w, 3, "#d8d8d8"); // bottom rim
drawPixelRect(x + 3, y + 7, 6, 4, "#f5e08a"); // label
}
// Draw weapon pickup (small gun icon)
function drawWeaponPickup(px, py, weaponKey) {
const x = px - camX;
const y = py - camY;
ctx.save();
ctx.translate(x, y);
if (weaponKey === "rpg") {
drawPixelRect(-10, -3, 20, 6, "#555");
drawPixelRect(6, -5, 6, 10, "#777");
} else if (weaponKey === "sniper") {
drawPixelRect(-12, -2, 24, 4, "#444");
drawPixelRect(-4, -4, 6, 2, "#222");
} else if (weaponKey === "lmg") {
drawPixelRect(-10, -3, 20, 6, "#444");
drawPixelRect(-4, 3, 8, 4, "#222");
} else if (weaponKey === "m4") {
drawPixelRect(-8, -2, 16, 4, "#555");
drawPixelRect(-2, 2, 4, 4, "#222");
}
ctx.restore();
}
// Winter background
function drawBackground() {
ctx.fillStyle = "#e0e5f0";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Trees
trees.forEach(t => {
const x = t.x - camX;
const y = t.y - camY;
if (x < -40 || x > canvas.width + 40 || y < -40 || y > canvas.height + 40) return;
// trunk
drawPixelRect(x - 2, y, 4, 10, "#5b4630");
// snow crown
drawPixelRect(x - 8, y - 10, 16, 10, "#f5f7fb");
drawPixelRect(x - 6, y - 16, 12, 6, "#f5f7fb");
});
// Buildings
buildings.forEach(b => {
const x = b.x - camX;
const y = b.y - camY;
if (x > canvas.width || y > canvas.height || x + b.w < 0 || y + b.h < 0) return;
drawPixelRect(x, y, b.w, b.h, "#c0c4cc");
drawPixelRect(x, y, b.w, 6, "#a0a4ac");
// door
drawPixelRect(x + b.w / 2 - 8, y + b.h - 24, 16, 24, "#4b3b2b");
});
}
function drawBullets() {
bullets.forEach(b => {
const x = b.x - camX;
const y = b.y - camY;
if (b.rocket) {
ctx.fillStyle = "#ffb74d";
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fill();
} else {
ctx.fillStyle = "#f5e08a";
ctx.fillRect(x - 2, y - 2, 4, 4);
}
});
}
function drawPickups() {
pickups.forEach(p => {
if (p.type === "ammo") {
drawAmmoPickup(p.x, p.y);
} else if (p.type === "food") {
drawFoodPickup(p.x, p.y);
} else if (p.type === "weapon") {
drawWeaponPickup(p.x, p.y, p.weaponKey);
}
});
}
function drawBars() {
const barWidth = 220;
const barHeight = 12;
const x = 20;
// HP
ctx.fillStyle = "#444";
ctx.fillRect(x, 60, barWidth, barHeight);
ctx.fillStyle = "#4caf50";
ctx.fillRect(x, 60, barWidth * (player.hp / 100), barHeight);
ctx.fillStyle = "#fff";
ctx.font = "12px Arial";
ctx.fillText("HP", x + 4, 70);
// Hunger
ctx.fillStyle = "#444";
ctx.fillRect(x, 80, barWidth, barHeight);
ctx.fillStyle = "#ffb74d";
ctx.fillRect(x, 80, barWidth * (player.hunger / 100), barHeight);
ctx.fillStyle = "#fff";
ctx.fillText("Hunger", x + 4, 90);
// Stamina
ctx.fillStyle = "#444";
ctx.fillRect(x, 100, barWidth, barHeight);
ctx.fillStyle = "#64b5f6";
ctx.fillRect(x, 100, barWidth * (player.stamina / 100), barHeight);
ctx.fillStyle = "#fff";
ctx.fillText("Stamina (Shift to sprint)", x + 4, 110);
}
function drawHUD() {
ctx.fillStyle = "#fff";
ctx.font = "16px Arial";
ctx.fillText("DayZ 2D - Winter Survival", 20, 30);
const w = weapons[player.currentWeaponKey];
const ammoType = w.ammoType;
const reserve = player.ammoReserve[ammoType] || 0;
const ammoText = `${w.name} | Ammo: ${player.ammoInMag}/${w.magSize} | Reserve (${ammoType}): ${reserve}` +
(player.reloading ? " (Reloading...)" : "");
ctx.fillText(ammoText, 20, 50);
drawBars();
}
function draw() {
drawBackground();
// Pickups
drawPickups();
// Player
drawPlayer(player.x, player.y, player.dirX, player.dirY);
// Zombies
zombies.forEach(z => {
drawZombie(z.x, z.y, z.walkAnim);
});
// Bullets
drawBullets();
// HUD
drawHUD();
}
function loop(timestamp) {
if (!running) return;
const dt = (timestamp - lastTime) / 1000;
lastTime = timestamp;
update(dt);
draw();
requestAnimationFrame(loop);
}
function startGame() {
resetGame();
startScreen.style.display = "none";
gameOverScreen.style.display = "none";
running = true;
requestAnimationFrame(loop);
}
playBtn.onclick = startGame;
restartBtn.onclick = startGame;
</script>
</body>
</html>
Game Source: DayZ 2D - Winter Survival v2
Creator: SparkLegend68
Libraries: none
Complexity: complex (797 lines, 20.2 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: dayz-2d-winter-survival-v2-sparklegend68" to link back to the original. Then publish at arcadelab.ai/publish.