/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bullet class var Bullet = Container.expand(function () { var self = Container.call(this); // Attach bullet asset (yellow box) var bulletSprite = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -32; self.damage = 1; // Defensive: initialize vx/vy to 0 self.vx = 0; self.vy = 0; // Defensive: initialize lastX/lastY for state tracking self.lastX = self.x; self.lastY = self.y; self.update = function () { // Defensive: track lastX/lastY for good practice self.lastX = self.x; self.lastY = self.y; self.x += self.vx; self.y += self.vy; }; return self; }); // LootBox class var LootBox = Container.expand(function () { var self = Container.call(this); // Attach lootbox asset (orange ellipse) var lootSprite = self.attachAsset('lootbox', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'ammo'; // 'ammo', 'medkit', 'money' self.value = 1; self.speed = 2.5; self.update = function () { self.x -= self.speed; }; return self; }); // Player class (stationary) var Player = Container.expand(function () { var self = Container.call(this); // Attach player asset (red box) var playerSprite = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Gun barrel position (relative to player) self.getGunPos = function () { // Gun at right edge, center vertically return { x: self.x + playerSprite.width / 2, y: self.y }; }; return self; }); // Zombie class var Zombie = Container.expand(function (style) { var self = Container.call(this); // Default to style 1 if not provided if (typeof style !== "number" || style < 1 || style > 4) style = 1; var zombieAssetId = "zombie" + style; // Attach zombie asset (varied style) var zombieSprite = self.attachAsset(zombieAssetId, { anchorX: 0.5, anchorY: 1 }); // Set up stats self.maxHealth = 1; self.health = 1; self.speed = 2; self.reward = 10; // money for killing // Health bar var healthBar = self.addChild(LK.getAsset('zombieHealthBar', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 10, color: 0xff0000 })); healthBar.y = -zombieSprite.height + 10; // Update health bar self.updateHealthBar = function () { healthBar.width = 60 * (self.health / self.maxHealth); }; // Called every tick self.update = function () { self.x -= self.speed; // Health bar follows self.updateHealthBar(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Add background image (centered, covers full game area) // --- Asset Initialization --- var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, width: 2048, height: 2732 }); game.addChild(background); // --- Game State --- var player = null; var bullets = []; var zombies = []; var lootboxes = []; var ammo = 10; var maxAmmo = 10; var health = 5; var maxHealth = 5; var level = 1; var zombiesToSpawn = 0; var zombiesSpawned = 0; var zombiesKilled = 0; var spawnTimer = 0; var reloadTimer = 0; var isReloading = false; var weaponIndex = 0; // --- Zombie Speed Scaling --- var zombieBaseSpeedMin = 2.0; var zombieBaseSpeedMax = 2.5; var zombieSpeedIncreasePerMinute = 0.5; // total increase per minute var zombieMaxSpeed = 4.0; // maximum allowed zombie speed var gameStartTick = null; // will be set at game start var weapons = [{ name: "Pistol", damage: 1, ammo: 20, // doubled from 10 reload: 60, bulletSpeed: 18, color: 0xf7e12b }, { name: "SMG", damage: 1, ammo: 40, // doubled from 20 reload: 40, bulletSpeed: 20, color: 0x00bfff }, { name: "Shotgun", damage: 3, ammo: 10, // doubled from 5 reload: 90, bulletSpeed: 14, color: 0xffffff }]; var unlockedWeapons = 1; // Start with 1 weapon // --- UI Elements --- // Weapon name text (top center, above score) var weaponNameTxt = new Text2('', { size: 70, fill: "#fff" }); weaponNameTxt.anchor.set(0.5, 1); weaponNameTxt.x = LK.gui.top.width / 2; weaponNameTxt.y = 0; LK.gui.top.addChild(weaponNameTxt); var scoreTxt = new Text2('Score: 0', { size: 100, fill: "#fff" }); // Place score at true top center scoreTxt.anchor.set(0.5, 0); // Position: top center scoreTxt.x = LK.gui.top.width / 2; scoreTxt.y = 0; LK.gui.top.addChild(scoreTxt); // Animate score color as rainbow using tween plugin var rainbowColors = [0xFF0000, // red 0xFF7F00, // orange 0xFFFF00, // yellow 0x00FF00, // green 0x0000FF, // blue 0x4B0082, // indigo 0x9400D3 // violet ]; var rainbowIndex = 0; function animateScoreColor() { var nextIndex = (rainbowIndex + 1) % rainbowColors.length; var fromColor = rainbowColors[rainbowIndex]; var toColor = rainbowColors[nextIndex]; // Animate over 400ms for smooth transition tween(scoreTxt, { tint: toColor }, { duration: 400, easing: tween.linear, onFinish: function onFinish() { rainbowIndex = nextIndex; animateScoreColor(); } }); } animateScoreColor(); // Money UI removed var ammoTxt = new Text2('Ammo: 10 / 10', { size: 60, fill: "#fff" }); ammoTxt.anchor.set(0, 0); LK.gui.bottomLeft.addChild(ammoTxt); var healthTxt = new Text2('♥♥♥♥♥', { size: 100, fill: 0xFF4D4D }); healthTxt.anchor.set(0, 1); healthTxt.x = 20; healthTxt.y = -10; LK.gui.bottomLeft.addChild(healthTxt); // --- Bullet Count UI --- var bulletCountTxt = new Text2('Bullets: 0', { size: 70, fill: "#fff" }); bulletCountTxt.anchor.set(1, 1); bulletCountTxt.x = LK.gui.bottomRight.width - 20; bulletCountTxt.y = LK.gui.bottomRight.height - 20; LK.gui.bottomRight.addChild(bulletCountTxt); // No rainbow color animation for bullet count text // --- Helper Functions --- function updateUI() { scoreTxt.setText('Score: ' + zombiesKilled); ammoTxt.setText('Ammo: ' + ammo + ' / ' + maxAmmo); var hearts = ''; for (var i = 0; i < health; i++) hearts += '♥'; for (var i = health; i < maxHealth; i++) hearts += '♡'; healthTxt.setText(hearts); // Show weapon name if (weapons && weapons[weaponIndex]) { weaponNameTxt.setText(weapons[weaponIndex].name); } else { weaponNameTxt.setText(''); } // Update bullet count text to show true ammo in reserve bulletCountTxt.setText('Bullets: ' + ammo); } function startLevel(lvl) { // No level or wave logic needed zombiesKilled = 0; spawnTimer = 0; } function spawnZombie() { // Pick a random zombie style (1-4) var zombieStyle = 1 + Math.floor(Math.random() * 4); var z = new Zombie(zombieStyle); // Position at right edge, random y z.x = 2048 + 80; z.y = 400 + Math.floor(Math.random() * (2732 - 800)); // Set fixed health and slow speed for zombies (health halved) if (zombieStyle === 2) { z.maxHealth = 5; // more health for zombie2 z.health = z.maxHealth; } else if (zombieStyle === 3) { z.maxHealth = 0.75; // less health for zombie3 z.health = z.maxHealth; } else if (zombieStyle === 4) { z.maxHealth = 2.25; // 1.5x health for zombie4 z.health = z.maxHealth; } else { z.maxHealth = 1.5; // default health for zombie1 z.health = z.maxHealth; } // --- Calculate elapsed minutes since game start --- var elapsedTicks = gameStartTick !== null ? LK.ticks - gameStartTick : 0; var elapsedMinutes = elapsedTicks / (60 * 60); // 60 ticks per second * 60 seconds // --- Calculate speed increase --- var speedIncrease = zombieSpeedIncreasePerMinute * elapsedMinutes; var minSpeed = zombieBaseSpeedMin + speedIncrease; var maxSpeed = zombieBaseSpeedMax + speedIncrease; // --- Clamp to max speed --- if (minSpeed > zombieMaxSpeed) minSpeed = zombieMaxSpeed; if (maxSpeed > zombieMaxSpeed) maxSpeed = zombieMaxSpeed; // --- Assign speed within range --- z.speed = minSpeed + Math.random() * (maxSpeed - minSpeed); zombies.push(z); game.addChild(z); // Play zombie voice sound on spawn LK.getSound('zombie_voice').play(); } function spawnLootBox(x, y) { var loot = new LootBox(); loot.x = x; loot.y = y; // Randomize type var r = Math.random(); if (r < 0.5) { loot.type = 'ammo'; loot.value = 5 + Math.floor(Math.random() * 5); } else { loot.type = 'medkit'; loot.value = 1; } lootboxes.push(loot); game.addChild(loot); } // --- Game Setup --- player = new Player(); player.x = 180; player.y = 2732 / 2; game.addChild(player); // --- Input Handling --- var lastShotTick = 0; var dragNode = null; // Mouse/touch move: aim gun game.move = function (x, y, obj) { // No drag, just aiming // Optionally, could rotate gun sprite, but for now, just use for shooting }; // Mouse/touch down: shoot game.down = function (x, y, obj) { // Only shoot if not reloading if (isReloading) return; if (ammo <= 0) { // Start reload isReloading = true; reloadTimer = 0; return; } // Fire bullet var gunPos = player.getGunPos(); var b = new Bullet(); b.x = gunPos.x; b.y = gunPos.y; // Defensive: initialize lastX/lastY for state tracking b.lastX = b.x; b.lastY = b.y; // Aim: calculate angle to (x, y) var dx = x - gunPos.x; var dy = y - gunPos.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) dist = 1; b.speed = weapons[weaponIndex].bulletSpeed; b.damage = weapons[weaponIndex].damage; // Set bullet direction b.vx = b.speed * (dx / dist); b.vy = b.speed * (dy / dist); // No need to overwrite update, Bullet class now uses vx/vy bullets.push(b); game.addChild(b); // Play pistol sound LK.getSound('pistol').play(); ammo--; updateUI(); }; // Mouse/touch up: nothing game.up = function (x, y, obj) {}; // --- Weapon Switching removed (no weapon name or tap to switch) --- // --- Main Game Loop --- game.update = function () { // --- Spawning zombies continuously --- spawnTimer++; if (spawnTimer > 55) { spawnZombie(); spawnTimer = 0; } // --- Bullets update --- for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; b.update(); // Remove if off screen if (b.x > 2048 + 100 || b.y < -100 || b.y > 2732 + 100) { b.destroy(); bullets.splice(i, 1); continue; } // Check collision with zombies var bulletDestroyed = false; for (var j = zombies.length - 1; j >= 0; j--) { var z = zombies[j]; if (b.intersects(z)) { z.health -= b.damage; if (z.health <= 0) { // Zombie dies zombiesKilled++; // Only spawn a lootbox 30% of the time if (Math.random() < 0.3) { spawnLootBox(z.x, z.y - 40); } z.destroy(); zombies.splice(j, 1); } b.destroy(); bullets.splice(i, 1); bulletDestroyed = true; break; } } if (bulletDestroyed) continue; // Check collision with lootboxes (shoot to open) for (var k = lootboxes.length - 1; k >= 0; k--) { var l = lootboxes[k]; if (b.intersects(l)) { // Open lootbox and grant reward if (l.type === 'ammo') { ammo += l.value; if (ammo > maxAmmo) ammo = maxAmmo; } else if (l.type === 'medkit') { health += l.value; if (health > maxHealth) health = maxHealth; } // Increase score when shooting and opening a lootbox zombiesKilled++; l.destroy(); lootboxes.splice(k, 1); b.destroy(); bullets.splice(i, 1); bulletDestroyed = true; break; } } if (bulletDestroyed) continue; } // --- Zombies update --- for (var i = zombies.length - 1; i >= 0; i--) { var z = zombies[i]; z.update(); // If zombie reaches player if (z.x < player.x + 60) { // Damage player (reduced by half) health -= 0.5; // Play player voice sound when losing health LK.getSound('player_voice').play(); LK.effects.flashObject(player, 0xff0000, 400); z.destroy(); zombies.splice(i, 1); if (health <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } } // --- Lootboxes update --- for (var i = lootboxes.length - 1; i >= 0; i--) { var l = lootboxes[i]; l.update(); // If off screen if (l.x < -100) { l.destroy(); lootboxes.splice(i, 1); continue; } // No automatic pickup; lootboxes must be shot to open } // --- Reloading --- if (isReloading) { reloadTimer++; if (reloadTimer > weapons[weaponIndex].reload) { isReloading = false; ammo = maxAmmo; updateUI(); } } // No level progression or waves updateUI(); }; // --- Start Game --- ammo = weapons[weaponIndex].ammo; maxAmmo = weapons[weaponIndex].ammo; health = maxHealth; zombiesKilled = 0; unlockedWeapons = 1; weaponIndex = 0; gameStartTick = LK.ticks; // Track when the game started for zombie speed scaling LK.playMusic('theme'); startLevel(1); updateUI();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (yellow box)
var bulletSprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -32;
self.damage = 1;
// Defensive: initialize vx/vy to 0
self.vx = 0;
self.vy = 0;
// Defensive: initialize lastX/lastY for state tracking
self.lastX = self.x;
self.lastY = self.y;
self.update = function () {
// Defensive: track lastX/lastY for good practice
self.lastX = self.x;
self.lastY = self.y;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// LootBox class
var LootBox = Container.expand(function () {
var self = Container.call(this);
// Attach lootbox asset (orange ellipse)
var lootSprite = self.attachAsset('lootbox', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'ammo'; // 'ammo', 'medkit', 'money'
self.value = 1;
self.speed = 2.5;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player class (stationary)
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player asset (red box)
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Gun barrel position (relative to player)
self.getGunPos = function () {
// Gun at right edge, center vertically
return {
x: self.x + playerSprite.width / 2,
y: self.y
};
};
return self;
});
// Zombie class
var Zombie = Container.expand(function (style) {
var self = Container.call(this);
// Default to style 1 if not provided
if (typeof style !== "number" || style < 1 || style > 4) style = 1;
var zombieAssetId = "zombie" + style;
// Attach zombie asset (varied style)
var zombieSprite = self.attachAsset(zombieAssetId, {
anchorX: 0.5,
anchorY: 1
});
// Set up stats
self.maxHealth = 1;
self.health = 1;
self.speed = 2;
self.reward = 10; // money for killing
// Health bar
var healthBar = self.addChild(LK.getAsset('zombieHealthBar', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 10,
color: 0xff0000
}));
healthBar.y = -zombieSprite.height + 10;
// Update health bar
self.updateHealthBar = function () {
healthBar.width = 60 * (self.health / self.maxHealth);
};
// Called every tick
self.update = function () {
self.x -= self.speed;
// Health bar follows
self.updateHealthBar();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Add background image (centered, covers full game area)
// --- Asset Initialization ---
var background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 2048,
height: 2732
});
game.addChild(background);
// --- Game State ---
var player = null;
var bullets = [];
var zombies = [];
var lootboxes = [];
var ammo = 10;
var maxAmmo = 10;
var health = 5;
var maxHealth = 5;
var level = 1;
var zombiesToSpawn = 0;
var zombiesSpawned = 0;
var zombiesKilled = 0;
var spawnTimer = 0;
var reloadTimer = 0;
var isReloading = false;
var weaponIndex = 0;
// --- Zombie Speed Scaling ---
var zombieBaseSpeedMin = 2.0;
var zombieBaseSpeedMax = 2.5;
var zombieSpeedIncreasePerMinute = 0.5; // total increase per minute
var zombieMaxSpeed = 4.0; // maximum allowed zombie speed
var gameStartTick = null; // will be set at game start
var weapons = [{
name: "Pistol",
damage: 1,
ammo: 20,
// doubled from 10
reload: 60,
bulletSpeed: 18,
color: 0xf7e12b
}, {
name: "SMG",
damage: 1,
ammo: 40,
// doubled from 20
reload: 40,
bulletSpeed: 20,
color: 0x00bfff
}, {
name: "Shotgun",
damage: 3,
ammo: 10,
// doubled from 5
reload: 90,
bulletSpeed: 14,
color: 0xffffff
}];
var unlockedWeapons = 1; // Start with 1 weapon
// --- UI Elements ---
// Weapon name text (top center, above score)
var weaponNameTxt = new Text2('', {
size: 70,
fill: "#fff"
});
weaponNameTxt.anchor.set(0.5, 1);
weaponNameTxt.x = LK.gui.top.width / 2;
weaponNameTxt.y = 0;
LK.gui.top.addChild(weaponNameTxt);
var scoreTxt = new Text2('Score: 0', {
size: 100,
fill: "#fff"
});
// Place score at true top center
scoreTxt.anchor.set(0.5, 0);
// Position: top center
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = 0;
LK.gui.top.addChild(scoreTxt);
// Animate score color as rainbow using tween plugin
var rainbowColors = [0xFF0000,
// red
0xFF7F00,
// orange
0xFFFF00,
// yellow
0x00FF00,
// green
0x0000FF,
// blue
0x4B0082,
// indigo
0x9400D3 // violet
];
var rainbowIndex = 0;
function animateScoreColor() {
var nextIndex = (rainbowIndex + 1) % rainbowColors.length;
var fromColor = rainbowColors[rainbowIndex];
var toColor = rainbowColors[nextIndex];
// Animate over 400ms for smooth transition
tween(scoreTxt, {
tint: toColor
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
rainbowIndex = nextIndex;
animateScoreColor();
}
});
}
animateScoreColor();
// Money UI removed
var ammoTxt = new Text2('Ammo: 10 / 10', {
size: 60,
fill: "#fff"
});
ammoTxt.anchor.set(0, 0);
LK.gui.bottomLeft.addChild(ammoTxt);
var healthTxt = new Text2('♥♥♥♥♥', {
size: 100,
fill: 0xFF4D4D
});
healthTxt.anchor.set(0, 1);
healthTxt.x = 20;
healthTxt.y = -10;
LK.gui.bottomLeft.addChild(healthTxt);
// --- Bullet Count UI ---
var bulletCountTxt = new Text2('Bullets: 0', {
size: 70,
fill: "#fff"
});
bulletCountTxt.anchor.set(1, 1);
bulletCountTxt.x = LK.gui.bottomRight.width - 20;
bulletCountTxt.y = LK.gui.bottomRight.height - 20;
LK.gui.bottomRight.addChild(bulletCountTxt);
// No rainbow color animation for bullet count text
// --- Helper Functions ---
function updateUI() {
scoreTxt.setText('Score: ' + zombiesKilled);
ammoTxt.setText('Ammo: ' + ammo + ' / ' + maxAmmo);
var hearts = '';
for (var i = 0; i < health; i++) hearts += '♥';
for (var i = health; i < maxHealth; i++) hearts += '♡';
healthTxt.setText(hearts);
// Show weapon name
if (weapons && weapons[weaponIndex]) {
weaponNameTxt.setText(weapons[weaponIndex].name);
} else {
weaponNameTxt.setText('');
}
// Update bullet count text to show true ammo in reserve
bulletCountTxt.setText('Bullets: ' + ammo);
}
function startLevel(lvl) {
// No level or wave logic needed
zombiesKilled = 0;
spawnTimer = 0;
}
function spawnZombie() {
// Pick a random zombie style (1-4)
var zombieStyle = 1 + Math.floor(Math.random() * 4);
var z = new Zombie(zombieStyle);
// Position at right edge, random y
z.x = 2048 + 80;
z.y = 400 + Math.floor(Math.random() * (2732 - 800));
// Set fixed health and slow speed for zombies (health halved)
if (zombieStyle === 2) {
z.maxHealth = 5; // more health for zombie2
z.health = z.maxHealth;
} else if (zombieStyle === 3) {
z.maxHealth = 0.75; // less health for zombie3
z.health = z.maxHealth;
} else if (zombieStyle === 4) {
z.maxHealth = 2.25; // 1.5x health for zombie4
z.health = z.maxHealth;
} else {
z.maxHealth = 1.5; // default health for zombie1
z.health = z.maxHealth;
}
// --- Calculate elapsed minutes since game start ---
var elapsedTicks = gameStartTick !== null ? LK.ticks - gameStartTick : 0;
var elapsedMinutes = elapsedTicks / (60 * 60); // 60 ticks per second * 60 seconds
// --- Calculate speed increase ---
var speedIncrease = zombieSpeedIncreasePerMinute * elapsedMinutes;
var minSpeed = zombieBaseSpeedMin + speedIncrease;
var maxSpeed = zombieBaseSpeedMax + speedIncrease;
// --- Clamp to max speed ---
if (minSpeed > zombieMaxSpeed) minSpeed = zombieMaxSpeed;
if (maxSpeed > zombieMaxSpeed) maxSpeed = zombieMaxSpeed;
// --- Assign speed within range ---
z.speed = minSpeed + Math.random() * (maxSpeed - minSpeed);
zombies.push(z);
game.addChild(z);
// Play zombie voice sound on spawn
LK.getSound('zombie_voice').play();
}
function spawnLootBox(x, y) {
var loot = new LootBox();
loot.x = x;
loot.y = y;
// Randomize type
var r = Math.random();
if (r < 0.5) {
loot.type = 'ammo';
loot.value = 5 + Math.floor(Math.random() * 5);
} else {
loot.type = 'medkit';
loot.value = 1;
}
lootboxes.push(loot);
game.addChild(loot);
}
// --- Game Setup ---
player = new Player();
player.x = 180;
player.y = 2732 / 2;
game.addChild(player);
// --- Input Handling ---
var lastShotTick = 0;
var dragNode = null;
// Mouse/touch move: aim gun
game.move = function (x, y, obj) {
// No drag, just aiming
// Optionally, could rotate gun sprite, but for now, just use for shooting
};
// Mouse/touch down: shoot
game.down = function (x, y, obj) {
// Only shoot if not reloading
if (isReloading) return;
if (ammo <= 0) {
// Start reload
isReloading = true;
reloadTimer = 0;
return;
}
// Fire bullet
var gunPos = player.getGunPos();
var b = new Bullet();
b.x = gunPos.x;
b.y = gunPos.y;
// Defensive: initialize lastX/lastY for state tracking
b.lastX = b.x;
b.lastY = b.y;
// Aim: calculate angle to (x, y)
var dx = x - gunPos.x;
var dy = y - gunPos.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) dist = 1;
b.speed = weapons[weaponIndex].bulletSpeed;
b.damage = weapons[weaponIndex].damage;
// Set bullet direction
b.vx = b.speed * (dx / dist);
b.vy = b.speed * (dy / dist);
// No need to overwrite update, Bullet class now uses vx/vy
bullets.push(b);
game.addChild(b);
// Play pistol sound
LK.getSound('pistol').play();
ammo--;
updateUI();
};
// Mouse/touch up: nothing
game.up = function (x, y, obj) {};
// --- Weapon Switching removed (no weapon name or tap to switch) ---
// --- Main Game Loop ---
game.update = function () {
// --- Spawning zombies continuously ---
spawnTimer++;
if (spawnTimer > 55) {
spawnZombie();
spawnTimer = 0;
}
// --- Bullets update ---
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.update();
// Remove if off screen
if (b.x > 2048 + 100 || b.y < -100 || b.y > 2732 + 100) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Check collision with zombies
var bulletDestroyed = false;
for (var j = zombies.length - 1; j >= 0; j--) {
var z = zombies[j];
if (b.intersects(z)) {
z.health -= b.damage;
if (z.health <= 0) {
// Zombie dies
zombiesKilled++;
// Only spawn a lootbox 30% of the time
if (Math.random() < 0.3) {
spawnLootBox(z.x, z.y - 40);
}
z.destroy();
zombies.splice(j, 1);
}
b.destroy();
bullets.splice(i, 1);
bulletDestroyed = true;
break;
}
}
if (bulletDestroyed) continue;
// Check collision with lootboxes (shoot to open)
for (var k = lootboxes.length - 1; k >= 0; k--) {
var l = lootboxes[k];
if (b.intersects(l)) {
// Open lootbox and grant reward
if (l.type === 'ammo') {
ammo += l.value;
if (ammo > maxAmmo) ammo = maxAmmo;
} else if (l.type === 'medkit') {
health += l.value;
if (health > maxHealth) health = maxHealth;
}
// Increase score when shooting and opening a lootbox
zombiesKilled++;
l.destroy();
lootboxes.splice(k, 1);
b.destroy();
bullets.splice(i, 1);
bulletDestroyed = true;
break;
}
}
if (bulletDestroyed) continue;
}
// --- Zombies update ---
for (var i = zombies.length - 1; i >= 0; i--) {
var z = zombies[i];
z.update();
// If zombie reaches player
if (z.x < player.x + 60) {
// Damage player (reduced by half)
health -= 0.5;
// Play player voice sound when losing health
LK.getSound('player_voice').play();
LK.effects.flashObject(player, 0xff0000, 400);
z.destroy();
zombies.splice(i, 1);
if (health <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
}
// --- Lootboxes update ---
for (var i = lootboxes.length - 1; i >= 0; i--) {
var l = lootboxes[i];
l.update();
// If off screen
if (l.x < -100) {
l.destroy();
lootboxes.splice(i, 1);
continue;
}
// No automatic pickup; lootboxes must be shot to open
}
// --- Reloading ---
if (isReloading) {
reloadTimer++;
if (reloadTimer > weapons[weaponIndex].reload) {
isReloading = false;
ammo = maxAmmo;
updateUI();
}
}
// No level progression or waves
updateUI();
};
// --- Start Game ---
ammo = weapons[weaponIndex].ammo;
maxAmmo = weapons[weaponIndex].ammo;
health = maxHealth;
zombiesKilled = 0;
unlockedWeapons = 1;
weaponIndex = 0;
gameStartTick = LK.ticks; // Track when the game started for zombie speed scaling
LK.playMusic('theme');
startLevel(1);
updateUI();