/****
* 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();