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