/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 12; self.directionX = 0; self.directionY = 0; self.update = function () { self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Add weapon to player var weaponGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); weaponGraphics.x = 25; // Position weapon to the right of player weaponGraphics.y = 0; self.speed = 0.05; self.health = 3; self.maxHealth = 3; self.lastShootTime = 0; self.shootInterval = 500; // milliseconds // Create health bar background var healthBarBg = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 0.6, tint: 0x333333 }); healthBarBg.y = -100; self.addChild(healthBarBg); // Create health bar fill var healthBarFill = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 0.6, tint: 0x00FF00 }); healthBarFill.y = -100; self.addChild(healthBarFill); self.healthBarFill = healthBarFill; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerUpGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, tint: 0x00FF00 }); self.type = 'speed'; // 'speed', 'damage', 'fireRate' self.duration = 600; // 10 seconds at 60fps // Remove bobbing animation for performance self.update = function () {}; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.health = 3; self.maxHealth = 3; // Create health bar background var healthBarBg = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 0.5, tint: 0x333333 }); healthBarBg.y = -140; self.addChild(healthBarBg); // Create health bar fill var healthBarFill = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 0.5, tint: 0xFF0000 }); healthBarFill.y = -140; self.addChild(healthBarFill); self.healthBarFill = healthBarFill; self.update = function () { if (player) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Add slight randomization to movement for more natural AI var randomOffset = (Math.random() - 0.5) * 0.3; var moveX = (dx / distance + randomOffset) * self.speed; var moveY = (dy / distance + randomOffset) * self.speed; self.x += moveX; self.y += moveY; // Rotate zombie to face player self.rotation = Math.atan2(dy, dx) + Math.PI / 2; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2E2E2E }); /**** * Game Code ****/ // Game state management var gameState = 'menu'; // 'menu', 'playing', 'gameOver' var menuContainer; var playButton; // Game variables var player; var zombies = []; var bullets = []; var powerUps = []; var dragNode = null; var gameTime = 0; var zombieSpawnRate = 40; // frames between spawns - faster spawning var zombieSpeed = 3; var zombiesKilled = 0; var waveNumber = 1; var comboCount = 0; var comboTimer = 0; var comboDecayTime = 180; // 3 seconds at 60fps var scoreMultiplier = 1; var playerFireRate = 12; // frames between shots - even faster shooting var playerDamage = 1; var playerSpeed = 0.05; // Make player speed much slower var maxZombiesOnScreen = 6; // Further reduce zombies for better performance var powerUpSpawnRate = 2400; // 40 seconds at 60fps // Map system variables var mapWidth = 6144; // 3x wider than screen var mapHeight = 8196; // 3x taller than screen var cameraOffsetX = 0; var cameraOffsetY = 0; var gameContainer; // Create game container for camera system gameContainer = new Container(); game.addChild(gameContainer); // Create main menu menuContainer = new Container(); game.addChild(menuContainer); // Menu title var titleTxt = new Text2('ZOMBIE SHOOTER', { size: 120, fill: 0xFF4444 }); titleTxt.anchor.set(0.5, 0.5); titleTxt.x = 2048 / 2; titleTxt.y = 2732 / 2 - 200; menuContainer.addChild(titleTxt); // Play button background var playButtonBg = LK.getAsset('zombie', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.8, tint: 0x44AA44 }); playButtonBg.x = 2048 / 2; playButtonBg.y = 2732 / 2 + 50; menuContainer.addChild(playButtonBg); // Play button text var playButtonTxt = new Text2('PLAY', { size: 80, fill: 0xFFFFFF }); playButtonTxt.anchor.set(0.5, 0.5); playButtonTxt.x = 2048 / 2; playButtonTxt.y = 2732 / 2 + 50; menuContainer.addChild(playButtonTxt); // Store reference to play button for event handling playButton = playButtonBg; // Make sure menu is visible at start menuContainer.visible = true; gameState = 'menu'; // Minimap system var minimapContainer; var minimapBackground; var minimapPlayerDot; var minimapZombieDots = []; var minimapWidth = 200; var minimapHeight = 200; var minimapScale = minimapWidth / mapWidth; // Game UI Elements (initially hidden) var scoreTxt = new Text2('Score: 0', { size: 55, fill: 0xFFDD00 }); scoreTxt.anchor.set(0.5, 0); scoreTxt.visible = false; LK.gui.top.addChild(scoreTxt); var waveTxt = new Text2('Level: 1', { size: 45, fill: 0x00DDFF }); waveTxt.anchor.set(0, 0); waveTxt.x = 120; waveTxt.y = 20; waveTxt.visible = false; LK.gui.top.addChild(waveTxt); var timeTxt = new Text2('Time: 0s', { size: 45, fill: 0xFFFFFF }); timeTxt.anchor.set(1, 0); timeTxt.x = -20; timeTxt.y = 20; timeTxt.visible = false; LK.gui.topRight.addChild(timeTxt); // Add kill counter var killTxt = new Text2('Kills: 0', { size: 45, fill: 0xFF6666 }); killTxt.anchor.set(0, 0); killTxt.x = 120; killTxt.y = 80; killTxt.visible = false; LK.gui.top.addChild(killTxt); var comboTxt = new Text2('', { size: 80, fill: 0xFFAA00 }); comboTxt.anchor.set(0.5, 0.5); comboTxt.visible = false; LK.gui.center.addChild(comboTxt); // Game initialization function function initializeGame() { // Reset game variables gameTime = 0; zombiesKilled = 0; waveNumber = 1; comboCount = 0; comboTimer = 0; scoreMultiplier = 1; zombieSpawnRate = 50; zombieSpeed = 4; // Clear existing game objects if (player) { player.destroy(); } for (var i = 0; i < zombies.length; i++) { zombies[i].destroy(); } for (var j = 0; j < bullets.length; j++) { bullets[j].destroy(); } zombies = []; bullets = []; // Initialize player player = gameContainer.addChild(new Player()); player.x = mapWidth / 2; player.y = mapHeight / 2; // Create map boundary indicators var mapBounds = []; // Top boundary var topBound = LK.getAsset('bullet', { anchorX: 0, anchorY: 0, scaleX: mapWidth / 16, scaleY: 2, tint: 0x444444 }); topBound.x = 0; topBound.y = 0; gameContainer.addChild(topBound); mapBounds.push(topBound); // Bottom boundary var bottomBound = LK.getAsset('bullet', { anchorX: 0, anchorY: 0, scaleX: mapWidth / 16, scaleY: 2, tint: 0x444444 }); bottomBound.x = 0; bottomBound.y = mapHeight - 32; gameContainer.addChild(bottomBound); mapBounds.push(bottomBound); // Left boundary var leftBound = LK.getAsset('bullet', { anchorX: 0, anchorY: 0, scaleX: 2, scaleY: mapHeight / 16, tint: 0x444444 }); leftBound.x = 0; leftBound.y = 0; gameContainer.addChild(leftBound); mapBounds.push(leftBound); // Right boundary var rightBound = LK.getAsset('bullet', { anchorX: 0, anchorY: 0, scaleX: 2, scaleY: mapHeight / 16, tint: 0x444444 }); rightBound.x = mapWidth - 32; rightBound.y = 0; gameContainer.addChild(rightBound); mapBounds.push(rightBound); // Reset camera position cameraOffsetX = 0; cameraOffsetY = 0; // Reset score LK.setScore(0); scoreTxt.setText('Score: 0'); // Initialize minimap if (minimapContainer) { minimapContainer.destroy(); } minimapContainer = new Container(); minimapContainer.x = -minimapWidth - 20; minimapContainer.y = -minimapHeight - 20; minimapContainer.visible = false; LK.gui.bottomRight.addChild(minimapContainer); // Minimap background minimapBackground = LK.getAsset('bullet', { anchorX: 0, anchorY: 0, scaleX: minimapWidth / 16, scaleY: minimapHeight / 16, tint: 0x222222 }); minimapContainer.addChild(minimapBackground); // Minimap border var minimapBorder = LK.getAsset('bullet', { anchorX: 0, anchorY: 0, scaleX: (minimapWidth + 4) / 16, scaleY: (minimapHeight + 4) / 16, tint: 0x666666 }); minimapBorder.x = -2; minimapBorder.y = -2; minimapContainer.addChild(minimapBorder); // Minimap title var minimapTitle = new Text2('MAP', { size: 24, fill: 0xFFFFFF }); minimapTitle.anchor.set(0.5, 1); minimapTitle.x = minimapWidth / 2; minimapTitle.y = -5; minimapContainer.addChild(minimapTitle); // Player dot on minimap minimapPlayerDot = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: 0x00FF00 }); minimapContainer.addChild(minimapPlayerDot); // Show game UI scoreTxt.visible = true; waveTxt.visible = true; timeTxt.visible = true; comboTxt.visible = true; killTxt.visible = true; if (minimapContainer) { minimapContainer.visible = true; } // Hide menu menuContainer.visible = false; // Set game state gameState = 'playing'; } // Don't initialize player immediately - wait for menu interaction // Helper functions function spawnZombie() { var zombie = new Zombie(); var side = Math.floor(Math.random() * 4); switch (side) { case 0: // top zombie.x = Math.random() * mapWidth; zombie.y = -50; break; case 1: // right zombie.x = mapWidth + 50; zombie.y = Math.random() * mapHeight; break; case 2: // bottom zombie.x = Math.random() * mapWidth; zombie.y = mapHeight + 50; break; case 3: // left zombie.x = -50; zombie.y = Math.random() * mapHeight; break; } // Create different zombie types based on level var zombieType = Math.random(); if (waveNumber >= 2 && zombieType < 0.25) { // Fast zombie (runner) zombie.speed = zombieSpeed * 1.5; zombie.health = 3; zombie.maxHealth = 3; zombie.tint = 0xFF6666; zombie.scaleX = 0.7; zombie.scaleY = 0.7; } else if (waveNumber >= 4 && zombieType < 0.4) { // Tank zombie (brute) zombie.speed = zombieSpeed * 0.5; zombie.health = 8; zombie.maxHealth = 8; zombie.tint = 0x666666; zombie.scaleX = 1.4; zombie.scaleY = 1.4; } else if (waveNumber >= 6 && zombieType < 0.15) { // Elite zombie (boss) zombie.speed = zombieSpeed * 0.8; zombie.health = 12; zombie.maxHealth = 12; zombie.tint = 0x440044; zombie.scaleX = 1.2; zombie.scaleY = 1.2; } else { // Normal zombie zombie.speed = zombieSpeed; zombie.health = 4; zombie.maxHealth = 4; } // Spawn effects removed zombies.push(zombie); gameContainer.addChild(zombie); } function findNearestZombie() { var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - player.x; var dy = zombie.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } return nearestZombie; } function shootBullet() { var target = findNearestZombie(); if (!target) return; var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; // Improved targeting with lead calculation var dx = target.x - player.x; var dy = target.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Calculate lead time based on bullet speed and target speed var leadTime = distance / bullet.speed; var predictedX = target.x + target.speed * Math.cos(target.rotation || 0) * leadTime; var predictedY = target.y + target.speed * Math.sin(target.rotation || 0) * leadTime; // Aim at predicted position var aimDx = predictedX - player.x; var aimDy = predictedY - player.y; var aimDistance = Math.sqrt(aimDx * aimDx + aimDy * aimDy); if (aimDistance > 0) { bullet.directionX = aimDx / aimDistance; bullet.directionY = aimDy / aimDistance; } // Add slight weapon spread for realism var spread = (Math.random() - 0.5) * 0.1; bullet.directionX += spread; bullet.directionY += spread; // Muzzle flash effect removed bullets.push(bullet); gameContainer.addChild(bullet); LK.getSound('shoot').play(); } function updateDifficulty() { waveNumber = Math.floor(gameTime / 1200) + 1; // New wave every 20 seconds zombieSpawnRate = Math.max(15, 50 - waveNumber * 8); zombieSpeed = 4 + (waveNumber - 1) * 1.2; waveTxt.setText('Wave: ' + waveNumber); } // Event handlers function handleMove(x, y, obj) { if (dragNode) { // Convert screen coordinates to world coordinates var worldX = x - cameraOffsetX; var worldY = y - cameraOffsetY; // Keep player within map bounds dragNode.x = Math.max(75, Math.min(mapWidth - 75, worldX)); dragNode.y = Math.max(75, Math.min(mapHeight - 75, worldY)); } } game.move = handleMove; game.down = function (x, y, obj) { if (gameState === 'menu') { // Check if play button was clicked var buttonBounds = playButton.getBounds(); var buttonCenterX = playButton.x; var buttonCenterY = playButton.y; var buttonWidth = buttonBounds.width; var buttonHeight = buttonBounds.height; // Simple bounds check if (x >= buttonCenterX - buttonWidth / 2 && x <= buttonCenterX + buttonWidth / 2 && y >= buttonCenterY - buttonHeight / 2 && y <= buttonCenterY + buttonHeight / 2) { initializeGame(); } } else if (gameState === 'playing') { // Normal gameplay - allow player movement dragNode = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // Main game loop game.update = function () { // Only update game logic when playing if (gameState !== 'playing') { return; } gameTime++; // Update UI timeTxt.setText('Time: ' + Math.floor(gameTime / 60) + 's'); // Update combo system if (comboTimer > 0) { comboTimer--; if (comboTimer <= 0) { comboCount = 0; scoreMultiplier = 1; comboTxt.setText(''); } } // Continuously increase difficulty based on score instead of waves var currentScore = LK.getScore(); zombieSpawnRate = Math.max(60, 100 - Math.floor(currentScore / 150) * 2); // Even slower spawning zombieSpeed = 1.8 + Math.floor(currentScore / 400) * 0.12; // Slower speed increase waveNumber = Math.floor(currentScore / 500) + 1; waveTxt.setText('Level: ' + waveNumber); // Spawn zombies with reduced frequency if (gameTime % zombieSpawnRate === 0 && zombies.length < maxZombiesOnScreen) { spawnZombie(); // Reduce additional spawns for performance var additionalSpawns = Math.floor(waveNumber / 6); for (var spawn = 0; spawn < additionalSpawns; spawn++) { if (gameTime % (zombieSpawnRate * (spawn + 4)) === 0 && zombies.length < maxZombiesOnScreen) { spawnZombie(); } } } // Update camera to follow player if (player) { var targetCameraX = -(player.x - 2048 / 2); var targetCameraY = -(player.y - 2732 / 2); // Direct camera movement cameraOffsetX = targetCameraX; cameraOffsetY = targetCameraY; // Keep camera within map bounds cameraOffsetX = Math.max(-(mapWidth - 2048), Math.min(0, cameraOffsetX)); cameraOffsetY = Math.max(-(mapHeight - 2732), Math.min(0, cameraOffsetY)); // Apply camera offset to game container gameContainer.x = cameraOffsetX; gameContainer.y = cameraOffsetY; } // Spawn power-ups occasionally if (gameTime % powerUpSpawnRate === 0) { // Every 15 seconds var powerUp = new PowerUp(); powerUp.x = Math.random() * (mapWidth - 200) + 100; powerUp.y = Math.random() * (mapHeight - 200) + 100; var powerTypes = ['speed', 'damage', 'fireRate', 'health']; powerUp.type = powerTypes[Math.floor(Math.random() * powerTypes.length)]; switch (powerUp.type) { case 'speed': powerUp.tint = 0x00FF00; break; case 'damage': powerUp.tint = 0xFF0000; break; case 'fireRate': powerUp.tint = 0x0000FF; break; case 'health': powerUp.tint = 0xFF00FF; break; } powerUps.push(powerUp); gameContainer.addChild(powerUp); } // Auto-shoot with much reduced frequency for performance if (gameTime % Math.max(playerFireRate, 35) === 0) { shootBullet(); } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Remove bullets that are off screen (expanded map) if (bullet.x < -200 || bullet.x > mapWidth + 200 || bullet.y < -200 || bullet.y > mapHeight + 200) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check bullet-zombie collisions for (var j = zombies.length - 1; j >= 0; j--) { var zombie = zombies[j]; if (bullet.intersects(zombie)) { // Zombie hit LK.getSound('zombieHit').play(); zombie.health -= playerDamage; // Update health bar if (zombie.healthBarFill) { zombie.healthBarFill.scaleX = 3 * (zombie.health / zombie.maxHealth); } // Only destroy zombie if health reaches 0 if (zombie.health <= 0) { // Update combo system comboCount++; comboTimer = comboDecayTime; scoreMultiplier = Math.min(5, 1 + Math.floor(comboCount / 3)); // Show combo text if (comboCount > 2) { comboTxt.setText('COMBO x' + scoreMultiplier); comboTxt.alpha = 1; comboTxt.scaleX = 1; comboTxt.scaleY = 1; // Combo text animation removed } // Explosion particles removed zombie.destroy(); zombies.splice(j, 1); zombiesKilled++; LK.setScore(LK.getScore() + 10 * scoreMultiplier + Math.floor(gameTime / 60)); scoreTxt.setText('Score: ' + LK.getScore()); killTxt.setText('Kills: ' + zombiesKilled); // Flash effects removed } else { // Zombie damaged but not destroyed // Flash effects removed } bullet.destroy(); bullets.splice(i, 1); break; } } } // Check power-up collection for (var p = powerUps.length - 1; p >= 0; p--) { var powerUp = powerUps[p]; if (player.intersects(powerUp)) { // Apply power-up effect switch (powerUp.type) { case 'speed': player.speed = Math.min(4, player.speed + 0.5); break; case 'damage': playerDamage += 1; break; case 'fireRate': playerFireRate = Math.max(5, playerFireRate - 3); break; case 'health': player.health = Math.min(player.maxHealth, player.health + 1); if (player.healthBarFill) { player.healthBarFill.scaleX = 4 * (player.health / player.maxHealth); } break; } // Power-up collection effect removed powerUp.destroy(); powerUps.splice(p, 1); LK.setScore(LK.getScore() + 50); scoreTxt.setText('Score: ' + LK.getScore()); } } // Check player-zombie collisions for (var k = 0; k < zombies.length; k++) { var zombie = zombies[k]; if (player.intersects(zombie)) { // Player hit - reduce health player.health -= 1; // Update health bar if (player.healthBarFill) { player.healthBarFill.scaleX = 4 * (player.health / player.maxHealth); } // Flash player red briefly LK.effects.flashObject(player, 0xFF0000, 500); // Remove the zombie that hit the player zombie.destroy(); zombies.splice(k, 1); k--; // Adjust index after removal // Check if player is dead if (player.health <= 0) { // Screen shake effect removed LK.effects.flashScreen(0xFF0000, 1000); // Reset to menu state gameState = 'menu'; menuContainer.visible = true; scoreTxt.visible = false; waveTxt.visible = false; timeTxt.visible = false; comboTxt.visible = false; killTxt.visible = false; if (minimapContainer) minimapContainer.visible = false; LK.showGameOver(); return; } } } // Update minimap every 45 frames to reduce lag if (minimapContainer && minimapContainer.visible && gameTime % 45 === 0) { // Update player position on minimap if (player && minimapPlayerDot) { minimapPlayerDot.x = player.x / mapWidth * minimapWidth; minimapPlayerDot.y = player.y / mapHeight * minimapHeight; } // Clear old zombie dots for (var m = 0; m < minimapZombieDots.length; m++) { minimapZombieDots[m].destroy(); } minimapZombieDots = []; // Add zombie dots to minimap (limit to closest 10 zombies) var sortedZombies = zombies.slice().sort(function (a, b) { var distA = Math.sqrt((a.x - player.x) * (a.x - player.x) + (a.y - player.y) * (a.y - player.y)); var distB = Math.sqrt((b.x - player.x) * (b.x - player.x) + (b.y - player.y) * (b.y - player.y)); return distA - distB; }); for (var n = 0; n < Math.min(5, sortedZombies.length); n++) { var zombie = sortedZombies[n]; var zombieDot = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0xFF0000 }); zombieDot.x = zombie.x / mapWidth * minimapWidth; zombieDot.y = zombie.y / mapHeight * minimapHeight; minimapContainer.addChild(zombieDot); minimapZombieDots.push(zombieDot); } } // Remove zombies that are too far off screen (cleanup) - check less frequently if (gameTime % 90 === 0) { for (var l = zombies.length - 1; l >= 0; l--) { var zombie = zombies[l]; if (zombie.x < -400 || zombie.x > mapWidth + 400 || zombie.y < -400 || zombie.y > mapHeight + 400) { zombie.destroy(); zombies.splice(l, 1); } } } }; // Start background music LK.playMusic('bgmusic');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12;
self.directionX = 0;
self.directionY = 0;
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add weapon to player
var weaponGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
weaponGraphics.x = 25; // Position weapon to the right of player
weaponGraphics.y = 0;
self.speed = 0.05;
self.health = 3;
self.maxHealth = 3;
self.lastShootTime = 0;
self.shootInterval = 500; // milliseconds
// Create health bar background
var healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.6,
tint: 0x333333
});
healthBarBg.y = -100;
self.addChild(healthBarBg);
// Create health bar fill
var healthBarFill = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 0.6,
tint: 0x00FF00
});
healthBarFill.y = -100;
self.addChild(healthBarFill);
self.healthBarFill = healthBarFill;
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
tint: 0x00FF00
});
self.type = 'speed'; // 'speed', 'damage', 'fireRate'
self.duration = 600; // 10 seconds at 60fps
// Remove bobbing animation for performance
self.update = function () {};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.health = 3;
self.maxHealth = 3;
// Create health bar background
var healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.5,
tint: 0x333333
});
healthBarBg.y = -140;
self.addChild(healthBarBg);
// Create health bar fill
var healthBarFill = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.5,
tint: 0xFF0000
});
healthBarFill.y = -140;
self.addChild(healthBarFill);
self.healthBarFill = healthBarFill;
self.update = function () {
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Add slight randomization to movement for more natural AI
var randomOffset = (Math.random() - 0.5) * 0.3;
var moveX = (dx / distance + randomOffset) * self.speed;
var moveY = (dy / distance + randomOffset) * self.speed;
self.x += moveX;
self.y += moveY;
// Rotate zombie to face player
self.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E2E2E
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'playing', 'gameOver'
var menuContainer;
var playButton;
// Game variables
var player;
var zombies = [];
var bullets = [];
var powerUps = [];
var dragNode = null;
var gameTime = 0;
var zombieSpawnRate = 40; // frames between spawns - faster spawning
var zombieSpeed = 3;
var zombiesKilled = 0;
var waveNumber = 1;
var comboCount = 0;
var comboTimer = 0;
var comboDecayTime = 180; // 3 seconds at 60fps
var scoreMultiplier = 1;
var playerFireRate = 12; // frames between shots - even faster shooting
var playerDamage = 1;
var playerSpeed = 0.05; // Make player speed much slower
var maxZombiesOnScreen = 6; // Further reduce zombies for better performance
var powerUpSpawnRate = 2400; // 40 seconds at 60fps
// Map system variables
var mapWidth = 6144; // 3x wider than screen
var mapHeight = 8196; // 3x taller than screen
var cameraOffsetX = 0;
var cameraOffsetY = 0;
var gameContainer;
// Create game container for camera system
gameContainer = new Container();
game.addChild(gameContainer);
// Create main menu
menuContainer = new Container();
game.addChild(menuContainer);
// Menu title
var titleTxt = new Text2('ZOMBIE SHOOTER', {
size: 120,
fill: 0xFF4444
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.x = 2048 / 2;
titleTxt.y = 2732 / 2 - 200;
menuContainer.addChild(titleTxt);
// Play button background
var playButtonBg = LK.getAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
tint: 0x44AA44
});
playButtonBg.x = 2048 / 2;
playButtonBg.y = 2732 / 2 + 50;
menuContainer.addChild(playButtonBg);
// Play button text
var playButtonTxt = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF
});
playButtonTxt.anchor.set(0.5, 0.5);
playButtonTxt.x = 2048 / 2;
playButtonTxt.y = 2732 / 2 + 50;
menuContainer.addChild(playButtonTxt);
// Store reference to play button for event handling
playButton = playButtonBg;
// Make sure menu is visible at start
menuContainer.visible = true;
gameState = 'menu';
// Minimap system
var minimapContainer;
var minimapBackground;
var minimapPlayerDot;
var minimapZombieDots = [];
var minimapWidth = 200;
var minimapHeight = 200;
var minimapScale = minimapWidth / mapWidth;
// Game UI Elements (initially hidden)
var scoreTxt = new Text2('Score: 0', {
size: 55,
fill: 0xFFDD00
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false;
LK.gui.top.addChild(scoreTxt);
var waveTxt = new Text2('Level: 1', {
size: 45,
fill: 0x00DDFF
});
waveTxt.anchor.set(0, 0);
waveTxt.x = 120;
waveTxt.y = 20;
waveTxt.visible = false;
LK.gui.top.addChild(waveTxt);
var timeTxt = new Text2('Time: 0s', {
size: 45,
fill: 0xFFFFFF
});
timeTxt.anchor.set(1, 0);
timeTxt.x = -20;
timeTxt.y = 20;
timeTxt.visible = false;
LK.gui.topRight.addChild(timeTxt);
// Add kill counter
var killTxt = new Text2('Kills: 0', {
size: 45,
fill: 0xFF6666
});
killTxt.anchor.set(0, 0);
killTxt.x = 120;
killTxt.y = 80;
killTxt.visible = false;
LK.gui.top.addChild(killTxt);
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFAA00
});
comboTxt.anchor.set(0.5, 0.5);
comboTxt.visible = false;
LK.gui.center.addChild(comboTxt);
// Game initialization function
function initializeGame() {
// Reset game variables
gameTime = 0;
zombiesKilled = 0;
waveNumber = 1;
comboCount = 0;
comboTimer = 0;
scoreMultiplier = 1;
zombieSpawnRate = 50;
zombieSpeed = 4;
// Clear existing game objects
if (player) {
player.destroy();
}
for (var i = 0; i < zombies.length; i++) {
zombies[i].destroy();
}
for (var j = 0; j < bullets.length; j++) {
bullets[j].destroy();
}
zombies = [];
bullets = [];
// Initialize player
player = gameContainer.addChild(new Player());
player.x = mapWidth / 2;
player.y = mapHeight / 2;
// Create map boundary indicators
var mapBounds = [];
// Top boundary
var topBound = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0,
scaleX: mapWidth / 16,
scaleY: 2,
tint: 0x444444
});
topBound.x = 0;
topBound.y = 0;
gameContainer.addChild(topBound);
mapBounds.push(topBound);
// Bottom boundary
var bottomBound = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0,
scaleX: mapWidth / 16,
scaleY: 2,
tint: 0x444444
});
bottomBound.x = 0;
bottomBound.y = mapHeight - 32;
gameContainer.addChild(bottomBound);
mapBounds.push(bottomBound);
// Left boundary
var leftBound = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: mapHeight / 16,
tint: 0x444444
});
leftBound.x = 0;
leftBound.y = 0;
gameContainer.addChild(leftBound);
mapBounds.push(leftBound);
// Right boundary
var rightBound = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0,
scaleX: 2,
scaleY: mapHeight / 16,
tint: 0x444444
});
rightBound.x = mapWidth - 32;
rightBound.y = 0;
gameContainer.addChild(rightBound);
mapBounds.push(rightBound);
// Reset camera position
cameraOffsetX = 0;
cameraOffsetY = 0;
// Reset score
LK.setScore(0);
scoreTxt.setText('Score: 0');
// Initialize minimap
if (minimapContainer) {
minimapContainer.destroy();
}
minimapContainer = new Container();
minimapContainer.x = -minimapWidth - 20;
minimapContainer.y = -minimapHeight - 20;
minimapContainer.visible = false;
LK.gui.bottomRight.addChild(minimapContainer);
// Minimap background
minimapBackground = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0,
scaleX: minimapWidth / 16,
scaleY: minimapHeight / 16,
tint: 0x222222
});
minimapContainer.addChild(minimapBackground);
// Minimap border
var minimapBorder = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0,
scaleX: (minimapWidth + 4) / 16,
scaleY: (minimapHeight + 4) / 16,
tint: 0x666666
});
minimapBorder.x = -2;
minimapBorder.y = -2;
minimapContainer.addChild(minimapBorder);
// Minimap title
var minimapTitle = new Text2('MAP', {
size: 24,
fill: 0xFFFFFF
});
minimapTitle.anchor.set(0.5, 1);
minimapTitle.x = minimapWidth / 2;
minimapTitle.y = -5;
minimapContainer.addChild(minimapTitle);
// Player dot on minimap
minimapPlayerDot = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x00FF00
});
minimapContainer.addChild(minimapPlayerDot);
// Show game UI
scoreTxt.visible = true;
waveTxt.visible = true;
timeTxt.visible = true;
comboTxt.visible = true;
killTxt.visible = true;
if (minimapContainer) {
minimapContainer.visible = true;
}
// Hide menu
menuContainer.visible = false;
// Set game state
gameState = 'playing';
}
// Don't initialize player immediately - wait for menu interaction
// Helper functions
function spawnZombie() {
var zombie = new Zombie();
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// top
zombie.x = Math.random() * mapWidth;
zombie.y = -50;
break;
case 1:
// right
zombie.x = mapWidth + 50;
zombie.y = Math.random() * mapHeight;
break;
case 2:
// bottom
zombie.x = Math.random() * mapWidth;
zombie.y = mapHeight + 50;
break;
case 3:
// left
zombie.x = -50;
zombie.y = Math.random() * mapHeight;
break;
}
// Create different zombie types based on level
var zombieType = Math.random();
if (waveNumber >= 2 && zombieType < 0.25) {
// Fast zombie (runner)
zombie.speed = zombieSpeed * 1.5;
zombie.health = 3;
zombie.maxHealth = 3;
zombie.tint = 0xFF6666;
zombie.scaleX = 0.7;
zombie.scaleY = 0.7;
} else if (waveNumber >= 4 && zombieType < 0.4) {
// Tank zombie (brute)
zombie.speed = zombieSpeed * 0.5;
zombie.health = 8;
zombie.maxHealth = 8;
zombie.tint = 0x666666;
zombie.scaleX = 1.4;
zombie.scaleY = 1.4;
} else if (waveNumber >= 6 && zombieType < 0.15) {
// Elite zombie (boss)
zombie.speed = zombieSpeed * 0.8;
zombie.health = 12;
zombie.maxHealth = 12;
zombie.tint = 0x440044;
zombie.scaleX = 1.2;
zombie.scaleY = 1.2;
} else {
// Normal zombie
zombie.speed = zombieSpeed;
zombie.health = 4;
zombie.maxHealth = 4;
}
// Spawn effects removed
zombies.push(zombie);
gameContainer.addChild(zombie);
}
function findNearestZombie() {
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - player.x;
var dy = zombie.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
return nearestZombie;
}
function shootBullet() {
var target = findNearestZombie();
if (!target) return;
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
// Improved targeting with lead calculation
var dx = target.x - player.x;
var dy = target.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate lead time based on bullet speed and target speed
var leadTime = distance / bullet.speed;
var predictedX = target.x + target.speed * Math.cos(target.rotation || 0) * leadTime;
var predictedY = target.y + target.speed * Math.sin(target.rotation || 0) * leadTime;
// Aim at predicted position
var aimDx = predictedX - player.x;
var aimDy = predictedY - player.y;
var aimDistance = Math.sqrt(aimDx * aimDx + aimDy * aimDy);
if (aimDistance > 0) {
bullet.directionX = aimDx / aimDistance;
bullet.directionY = aimDy / aimDistance;
}
// Add slight weapon spread for realism
var spread = (Math.random() - 0.5) * 0.1;
bullet.directionX += spread;
bullet.directionY += spread;
// Muzzle flash effect removed
bullets.push(bullet);
gameContainer.addChild(bullet);
LK.getSound('shoot').play();
}
function updateDifficulty() {
waveNumber = Math.floor(gameTime / 1200) + 1; // New wave every 20 seconds
zombieSpawnRate = Math.max(15, 50 - waveNumber * 8);
zombieSpeed = 4 + (waveNumber - 1) * 1.2;
waveTxt.setText('Wave: ' + waveNumber);
}
// Event handlers
function handleMove(x, y, obj) {
if (dragNode) {
// Convert screen coordinates to world coordinates
var worldX = x - cameraOffsetX;
var worldY = y - cameraOffsetY;
// Keep player within map bounds
dragNode.x = Math.max(75, Math.min(mapWidth - 75, worldX));
dragNode.y = Math.max(75, Math.min(mapHeight - 75, worldY));
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if play button was clicked
var buttonBounds = playButton.getBounds();
var buttonCenterX = playButton.x;
var buttonCenterY = playButton.y;
var buttonWidth = buttonBounds.width;
var buttonHeight = buttonBounds.height;
// Simple bounds check
if (x >= buttonCenterX - buttonWidth / 2 && x <= buttonCenterX + buttonWidth / 2 && y >= buttonCenterY - buttonHeight / 2 && y <= buttonCenterY + buttonHeight / 2) {
initializeGame();
}
} else if (gameState === 'playing') {
// Normal gameplay - allow player movement
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game loop
game.update = function () {
// Only update game logic when playing
if (gameState !== 'playing') {
return;
}
gameTime++;
// Update UI
timeTxt.setText('Time: ' + Math.floor(gameTime / 60) + 's');
// Update combo system
if (comboTimer > 0) {
comboTimer--;
if (comboTimer <= 0) {
comboCount = 0;
scoreMultiplier = 1;
comboTxt.setText('');
}
}
// Continuously increase difficulty based on score instead of waves
var currentScore = LK.getScore();
zombieSpawnRate = Math.max(60, 100 - Math.floor(currentScore / 150) * 2); // Even slower spawning
zombieSpeed = 1.8 + Math.floor(currentScore / 400) * 0.12; // Slower speed increase
waveNumber = Math.floor(currentScore / 500) + 1;
waveTxt.setText('Level: ' + waveNumber);
// Spawn zombies with reduced frequency
if (gameTime % zombieSpawnRate === 0 && zombies.length < maxZombiesOnScreen) {
spawnZombie();
// Reduce additional spawns for performance
var additionalSpawns = Math.floor(waveNumber / 6);
for (var spawn = 0; spawn < additionalSpawns; spawn++) {
if (gameTime % (zombieSpawnRate * (spawn + 4)) === 0 && zombies.length < maxZombiesOnScreen) {
spawnZombie();
}
}
}
// Update camera to follow player
if (player) {
var targetCameraX = -(player.x - 2048 / 2);
var targetCameraY = -(player.y - 2732 / 2);
// Direct camera movement
cameraOffsetX = targetCameraX;
cameraOffsetY = targetCameraY;
// Keep camera within map bounds
cameraOffsetX = Math.max(-(mapWidth - 2048), Math.min(0, cameraOffsetX));
cameraOffsetY = Math.max(-(mapHeight - 2732), Math.min(0, cameraOffsetY));
// Apply camera offset to game container
gameContainer.x = cameraOffsetX;
gameContainer.y = cameraOffsetY;
}
// Spawn power-ups occasionally
if (gameTime % powerUpSpawnRate === 0) {
// Every 15 seconds
var powerUp = new PowerUp();
powerUp.x = Math.random() * (mapWidth - 200) + 100;
powerUp.y = Math.random() * (mapHeight - 200) + 100;
var powerTypes = ['speed', 'damage', 'fireRate', 'health'];
powerUp.type = powerTypes[Math.floor(Math.random() * powerTypes.length)];
switch (powerUp.type) {
case 'speed':
powerUp.tint = 0x00FF00;
break;
case 'damage':
powerUp.tint = 0xFF0000;
break;
case 'fireRate':
powerUp.tint = 0x0000FF;
break;
case 'health':
powerUp.tint = 0xFF00FF;
break;
}
powerUps.push(powerUp);
gameContainer.addChild(powerUp);
}
// Auto-shoot with much reduced frequency for performance
if (gameTime % Math.max(playerFireRate, 35) === 0) {
shootBullet();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Remove bullets that are off screen (expanded map)
if (bullet.x < -200 || bullet.x > mapWidth + 200 || bullet.y < -200 || bullet.y > mapHeight + 200) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-zombie collisions
for (var j = zombies.length - 1; j >= 0; j--) {
var zombie = zombies[j];
if (bullet.intersects(zombie)) {
// Zombie hit
LK.getSound('zombieHit').play();
zombie.health -= playerDamage;
// Update health bar
if (zombie.healthBarFill) {
zombie.healthBarFill.scaleX = 3 * (zombie.health / zombie.maxHealth);
}
// Only destroy zombie if health reaches 0
if (zombie.health <= 0) {
// Update combo system
comboCount++;
comboTimer = comboDecayTime;
scoreMultiplier = Math.min(5, 1 + Math.floor(comboCount / 3));
// Show combo text
if (comboCount > 2) {
comboTxt.setText('COMBO x' + scoreMultiplier);
comboTxt.alpha = 1;
comboTxt.scaleX = 1;
comboTxt.scaleY = 1;
// Combo text animation removed
}
// Explosion particles removed
zombie.destroy();
zombies.splice(j, 1);
zombiesKilled++;
LK.setScore(LK.getScore() + 10 * scoreMultiplier + Math.floor(gameTime / 60));
scoreTxt.setText('Score: ' + LK.getScore());
killTxt.setText('Kills: ' + zombiesKilled);
// Flash effects removed
} else {
// Zombie damaged but not destroyed
// Flash effects removed
}
bullet.destroy();
bullets.splice(i, 1);
break;
}
}
}
// Check power-up collection
for (var p = powerUps.length - 1; p >= 0; p--) {
var powerUp = powerUps[p];
if (player.intersects(powerUp)) {
// Apply power-up effect
switch (powerUp.type) {
case 'speed':
player.speed = Math.min(4, player.speed + 0.5);
break;
case 'damage':
playerDamage += 1;
break;
case 'fireRate':
playerFireRate = Math.max(5, playerFireRate - 3);
break;
case 'health':
player.health = Math.min(player.maxHealth, player.health + 1);
if (player.healthBarFill) {
player.healthBarFill.scaleX = 4 * (player.health / player.maxHealth);
}
break;
}
// Power-up collection effect removed
powerUp.destroy();
powerUps.splice(p, 1);
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore());
}
}
// Check player-zombie collisions
for (var k = 0; k < zombies.length; k++) {
var zombie = zombies[k];
if (player.intersects(zombie)) {
// Player hit - reduce health
player.health -= 1;
// Update health bar
if (player.healthBarFill) {
player.healthBarFill.scaleX = 4 * (player.health / player.maxHealth);
}
// Flash player red briefly
LK.effects.flashObject(player, 0xFF0000, 500);
// Remove the zombie that hit the player
zombie.destroy();
zombies.splice(k, 1);
k--; // Adjust index after removal
// Check if player is dead
if (player.health <= 0) {
// Screen shake effect removed
LK.effects.flashScreen(0xFF0000, 1000);
// Reset to menu state
gameState = 'menu';
menuContainer.visible = true;
scoreTxt.visible = false;
waveTxt.visible = false;
timeTxt.visible = false;
comboTxt.visible = false;
killTxt.visible = false;
if (minimapContainer) minimapContainer.visible = false;
LK.showGameOver();
return;
}
}
}
// Update minimap every 45 frames to reduce lag
if (minimapContainer && minimapContainer.visible && gameTime % 45 === 0) {
// Update player position on minimap
if (player && minimapPlayerDot) {
minimapPlayerDot.x = player.x / mapWidth * minimapWidth;
minimapPlayerDot.y = player.y / mapHeight * minimapHeight;
}
// Clear old zombie dots
for (var m = 0; m < minimapZombieDots.length; m++) {
minimapZombieDots[m].destroy();
}
minimapZombieDots = [];
// Add zombie dots to minimap (limit to closest 10 zombies)
var sortedZombies = zombies.slice().sort(function (a, b) {
var distA = Math.sqrt((a.x - player.x) * (a.x - player.x) + (a.y - player.y) * (a.y - player.y));
var distB = Math.sqrt((b.x - player.x) * (b.x - player.x) + (b.y - player.y) * (b.y - player.y));
return distA - distB;
});
for (var n = 0; n < Math.min(5, sortedZombies.length); n++) {
var zombie = sortedZombies[n];
var zombieDot = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFF0000
});
zombieDot.x = zombie.x / mapWidth * minimapWidth;
zombieDot.y = zombie.y / mapHeight * minimapHeight;
minimapContainer.addChild(zombieDot);
minimapZombieDots.push(zombieDot);
}
}
// Remove zombies that are too far off screen (cleanup) - check less frequently
if (gameTime % 90 === 0) {
for (var l = zombies.length - 1; l >= 0; l--) {
var zombie = zombies[l];
if (zombie.x < -400 || zombie.x > mapWidth + 400 || zombie.y < -400 || zombie.y > mapHeight + 400) {
zombie.destroy();
zombies.splice(l, 1);
}
}
}
};
// Start background music
LK.playMusic('bgmusic');