/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var FastZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('fastZombie', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.maxHealth = 1; self.speed = 3; self.points = 15; self.lastX = 0; self.lastY = 0; self.update = function () { if (player) { self.lastX = self.x; self.lastY = self.y; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Chase player self.x += dx / distance * self.speed * zombieSpeedMultiplier; self.y += dy / distance * self.speed * zombieSpeedMultiplier; } } }; return self; }); var Menu = Container.expand(function () { var self = Container.call(this); self.interactive = true; var playButton = self.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -100, scaleX: 1.5, scaleY: 1.5 }); var playText = new Text2('PLAY', { size: 60, fill: 0xFFFFFF }); playText.anchor.set(0.5, 0.5); playText.y = -100; self.addChild(playText); var settingsButton = self.attachAsset('settingButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 50, scaleX: 1.8, scaleY: 1.8 }); var settingsText = new Text2('SETTINGS', { size: 40, fill: 0xFFFFFF }); settingsText.anchor.set(0.5, 0.5); settingsText.y = 50; self.addChild(settingsText); self.down = function (x, y, obj) { // Use the x,y parameters directly instead of obj.parent.toGlobal var localPos = self.toLocal({ x: x, y: y }); if (localPos.y > -175 && localPos.y < -25) { LK.getSound('menuClick').play(); startGame(); } else if (localPos.y > -22 && localPos.y < 122) { LK.getSound('menuClick').play(); showSettings(); } }; return self; }); var Particle = Container.expand(function () { var self = Container.call(this); var particleGraphics = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); self.vx = 0; self.vy = 0; self.life = 60; self.maxLife = 60; self.update = function () { self.x += self.vx; self.y += self.vy; self.life--; self.alpha = self.life / self.maxLife; if (self.life <= 0) { self.destroy(); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.lastCollidedZombies = []; return self; }); var SettingsMenu = Container.expand(function () { var self = Container.call(this); self.interactive = true; var titleText = new Text2('SETTINGS', { size: 80, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.y = -300; self.addChild(titleText); var musicVolumeText = new Text2('Music Volume: ' + Math.round(musicVolume * 100) + '%', { size: 50, fill: 0xFFFFFF }); musicVolumeText.anchor.set(0.5, 0.5); musicVolumeText.y = -200; self.addChild(musicVolumeText); var volumeUpButton = self.attachAsset('settingButton', { anchorX: 0.5, anchorY: 0.5, x: -200, y: -150, scaleX: 1.4, scaleY: 1.4 }); var volumeUpText = new Text2('+', { size: 60, fill: 0xFFFFFF }); volumeUpText.anchor.set(0.5, 0.5); volumeUpText.x = -200; volumeUpText.y = -150; self.addChild(volumeUpText); var volumeDownButton = self.attachAsset('settingButton', { anchorX: 0.5, anchorY: 0.5, x: 200, y: -150, scaleX: 1.4, scaleY: 1.4 }); var volumeDownText = new Text2('-', { size: 60, fill: 0xFFFFFF }); volumeDownText.anchor.set(0.5, 0.5); volumeDownText.x = 200; volumeDownText.y = -150; self.addChild(volumeDownText); var zombieSpeedText = new Text2('Zombie Speed: ' + zombieSpeedMultiplier, { size: 50, fill: 0xFFFFFF }); zombieSpeedText.anchor.set(0.5, 0.5); zombieSpeedText.y = -50; self.addChild(zombieSpeedText); var speedUpButton = self.attachAsset('settingButton', { anchorX: 0.5, anchorY: 0.5, x: -200, y: 0, scaleX: 1.4, scaleY: 1.4 }); var speedUpText = new Text2('+', { size: 60, fill: 0xFFFFFF }); speedUpText.anchor.set(0.5, 0.5); speedUpText.x = -200; speedUpText.y = 0; self.addChild(speedUpText); var speedDownButton = self.attachAsset('settingButton', { anchorX: 0.5, anchorY: 0.5, x: 200, y: 0, scaleX: 1.4, scaleY: 1.4 }); var speedDownText = new Text2('-', { size: 60, fill: 0xFFFFFF }); speedDownText.anchor.set(0.5, 0.5); speedDownText.x = 200; speedDownText.y = 0; self.addChild(speedDownText); var backButton = self.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 200, scaleX: 1.5, scaleY: 1.5 }); var backText = new Text2('BACK', { size: 60, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); backText.y = 200; self.addChild(backText); self.down = function (x, y, obj) { // Use the x,y parameters directly instead of obj.parent.toGlobal var localPos = self.toLocal({ x: x, y: y }); if (localPos.x > -410 && localPos.x < 10 && localPos.y > -206 && localPos.y < -94) { // Volume up musicVolume = Math.min(1, musicVolume + 0.1); musicVolumeText.setText('Music Volume: ' + Math.round(musicVolume * 100) + '%'); storage.musicVolume = musicVolume; LK.getSound('menuClick').play(); } else if (localPos.x > -10 && localPos.x < 410 && localPos.y > -206 && localPos.y < -94) { // Volume down musicVolume = Math.max(0, musicVolume - 0.1); musicVolumeText.setText('Music Volume: ' + Math.round(musicVolume * 100) + '%'); storage.musicVolume = musicVolume; LK.getSound('menuClick').play(); } else if (localPos.x > -410 && localPos.x < 10 && localPos.y > -56 && localPos.y < 56) { // Speed up zombieSpeedMultiplier = Math.min(3, zombieSpeedMultiplier + 0.2); zombieSpeedText.setText('Zombie Speed: ' + zombieSpeedMultiplier.toFixed(1)); LK.getSound('menuClick').play(); } else if (localPos.x > -10 && localPos.x < 410 && localPos.y > -56 && localPos.y < 56) { // Speed down zombieSpeedMultiplier = Math.max(0.2, zombieSpeedMultiplier - 0.2); zombieSpeedText.setText('Zombie Speed: ' + zombieSpeedMultiplier.toFixed(1)); LK.getSound('menuClick').play(); } else if (localPos.y > 125 && localPos.y < 275) { // Back LK.getSound('menuClick').play(); showMenu(); } }; return self; }); var TankZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('tankZombie', { anchorX: 0.5, anchorY: 0.5 }); self.health = 3; self.maxHealth = 3; self.speed = 1; self.points = 25; self.lastX = 0; self.lastY = 0; self.update = function () { if (player) { self.lastX = self.x; self.lastY = self.y; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Chase player self.x += dx / distance * self.speed * zombieSpeedMultiplier; self.y += dy / distance * self.speed * zombieSpeedMultiplier; } } }; return self; }); var TrailParticle = Container.expand(function () { var self = Container.call(this); var trailGraphics = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); self.life = 30; self.maxLife = 30; self.update = function () { self.life--; self.alpha = self.life / self.maxLife; self.scaleX = self.alpha * trailSize; self.scaleY = self.alpha * trailSize; if (self.life <= 0) { self.destroy(); } }; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.maxHealth = 1; self.speed = 1.5; self.points = 10; self.lastX = 0; self.lastY = 0; self.update = function () { if (player) { self.lastX = self.x; self.lastY = self.y; var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Chase player self.x += dx / distance * self.speed * zombieSpeedMultiplier; self.y += dy / distance * self.speed * zombieSpeedMultiplier; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2F4F2F }); /**** * Game Code ****/ // Game state variables var gameState = 'playing'; // Always playing now var particles = []; var zombieSpeedMultiplier = 1.0; var musicVolume = storage.musicVolume || 1.0; // Game variables var player; var zombies = []; var trailParticles = []; var playerLastX = 0; var playerLastY = 0; var trailTimer = 0; var currentWave = 1; var zombiesSpawned = 0; var zombiesKilled = 0; var gameTime = 0; var waveTime = 0; var spawnTimer = 0; var dragNode = null; var killStreak = 0; var killStreakTimer = 0; var combo = 0; var comboTimer = 0; var maxCombo = 0; var trailSize = 1.5; // Make trail bigger // Wave configuration var waveConfigs = [{ zombieCount: 8, spawnDelay: 90, zombieTypes: ['zombie'] }, { zombieCount: 12, spawnDelay: 75, zombieTypes: ['zombie', 'fastZombie'] }, { zombieCount: 16, spawnDelay: 60, zombieTypes: ['zombie', 'fastZombie'] }, { zombieCount: 20, spawnDelay: 50, zombieTypes: ['zombie', 'fastZombie', 'tankZombie'] }, { zombieCount: 25, spawnDelay: 45, zombieTypes: ['zombie', 'fastZombie', 'tankZombie'] }, { zombieCount: 30, spawnDelay: 40, zombieTypes: ['zombie', 'fastZombie', 'tankZombie'] }, { zombieCount: 35, spawnDelay: 35, zombieTypes: ['zombie', 'fastZombie', 'tankZombie'] }, { zombieCount: 40, spawnDelay: 30, zombieTypes: ['zombie', 'fastZombie', 'tankZombie'] }]; // UI Elements var scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); scoreText.x = 120; scoreText.y = 20; LK.gui.topLeft.addChild(scoreText); var waveText = new Text2('Wave: 1', { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); var timerText = new Text2('Time: 90', { size: 60, fill: 0xFFFFFF }); timerText.anchor.set(1, 0); timerText.x = -20; timerText.y = 20; LK.gui.topRight.addChild(timerText); var instructionText = new Text2('Tap the cubes to get points', { size: 50, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 1); instructionText.y = -20; LK.gui.bottom.addChild(instructionText); var comboText = new Text2('Combo: 0', { size: 60, fill: 0xFFD700 }); comboText.anchor.set(1, 0); comboText.x = -20; comboText.y = 100; LK.gui.topRight.addChild(comboText); // Initialize game function function initializeGame() { // Initialize player if (!player) { player = game.addChild(new Player()); player.x = 1024; player.y = 1366; playerLastX = player.x; playerLastY = player.y; } // Start background music LK.playMusic('backgroundMusic', { loop: true, volume: musicVolume }); } function createParticles(x, y, count, color) { for (var i = 0; i < count; i++) { var particle = new Particle(); particle.x = x; particle.y = y; particle.vx = (Math.random() - 0.5) * 10; particle.vy = (Math.random() - 0.5) * 10; if (color) { tween(particle, { tint: color }, { duration: 0 }); } particles.push(particle); game.addChild(particle); } } // Create volume slider at bottom left var volumeSliderBg = LK.getAsset('settingButton', { anchorX: 0, anchorY: 1, x: 20, y: 0, scaleX: 0.5, scaleY: 2 }); LK.gui.bottomLeft.addChild(volumeSliderBg); var volumeSliderHandle = LK.getAsset('player', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); volumeSliderBg.addChild(volumeSliderHandle); var volumeText = new Text2(Math.round(musicVolume * 100) + '%', { size: 30, fill: 0xFFFFFF }); volumeText.anchor.set(0.5, 0.5); volumeText.x = 75; volumeText.y = 0; volumeSliderBg.addChild(volumeText); // Position handle based on volume (0 = bottom, 1 = top) volumeSliderHandle.x = 75; volumeSliderHandle.y = -20 - musicVolume * 120; // Make slider interactive volumeSliderBg.interactive = true; volumeSliderBg.down = function (x, y, obj) { var localPos = volumeSliderBg.toLocal({ x: x, y: y }); // Calculate volume based on Y position (inverted since 0,0 is top-left) var newVolume = Math.max(0, Math.min(1, (-localPos.y + 20) / 120)); musicVolume = newVolume; storage.musicVolume = musicVolume; // Update handle position tween(volumeSliderHandle, { y: -20 - musicVolume * 120 }, { duration: 200 }); volumeText.setText(Math.round(musicVolume * 100) + '%'); LK.getSound('menuClick').play(); }; // Start game after 2 seconds LK.setTimeout(function () { initializeGame(); }, 2000); // Spawn zombie function function spawnZombie() { var config = waveConfigs[Math.min(currentWave - 1, waveConfigs.length - 1)]; var zombieTypes = config.zombieTypes; var randomType = zombieTypes[Math.floor(Math.random() * zombieTypes.length)]; var zombie; if (randomType === 'fastZombie') { zombie = new FastZombie(); } else if (randomType === 'tankZombie') { zombie = new TankZombie(); } else { zombie = new Zombie(); } // Apply speed multiplier zombie.speed *= zombieSpeedMultiplier; // Spawn from random edge var edge = Math.floor(Math.random() * 4); if (edge === 0) { // Top zombie.x = Math.random() * 2048; zombie.y = -30; } else if (edge === 1) { // Right zombie.x = 2078; zombie.y = Math.random() * 2732; } else if (edge === 2) { // Bottom zombie.x = Math.random() * 2048; zombie.y = 2762; } else { // Left zombie.x = -30; zombie.y = Math.random() * 2732; } zombie.lastX = zombie.x; zombie.lastY = zombie.y; // Create spawn particles createParticles(zombie.x, zombie.y, 6, 0x8b4513); zombies.push(zombie); game.addChild(zombie); zombiesSpawned++; } // Update UI function function updateUI() { scoreText.setText('Score: ' + LK.getScore()); waveText.setText('Wave: ' + currentWave); timerText.setText('Time: ' + Math.max(0, Math.ceil((150 * 60 - gameTime) / 60))); comboText.setText('Combo: ' + combo + (maxCombo > 0 ? ' (Best: ' + maxCombo + ')' : '')); } // Handle move events function handleMove(x, y, obj) { if (dragNode) { // Keep target position within bounds var targetX = Math.max(40, Math.min(2008, x)); var targetY = Math.max(40, Math.min(2692, y)); // Stop any existing movement tween tween.stop(dragNode, { x: true, y: true }); // Smoothly move to target position tween(dragNode, { x: targetX, y: targetY }, { duration: 300, easing: tween.easeOut }); } } // Game event handlers game.move = handleMove; game.down = function (x, y, obj) { if (player) { dragNode = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // Main game update loop game.update = function () { // Update particles for (var p = particles.length - 1; p >= 0; p--) { var particle = particles[p]; if (particle.life <= 0) { particles.splice(p, 1); } } // Update trail particles for (var t = trailParticles.length - 1; t >= 0; t--) { var trailParticle = trailParticles[t]; if (trailParticle.life <= 0) { trailParticles.splice(t, 1); } } // Create player trail when moving if (player) { var playerMoved = Math.abs(player.x - playerLastX) > 5 || Math.abs(player.y - playerLastY) > 5; if (playerMoved) { trailTimer++; if (trailTimer >= 3) { // Create trail every 3 frames when moving var trail = new TrailParticle(); trail.x = playerLastX; trail.y = playerLastY; tween(trail, { tint: 0x4caf50 }, { duration: 0 }); trailParticles.push(trail); game.addChild(trail); trailTimer = 0; } } playerLastX = player.x; playerLastY = player.y; } // Only run game logic when player exists if (!player) { return; } gameTime++; waveTime++; // Update kill streak timer if (killStreakTimer > 0) { killStreakTimer--; if (killStreakTimer <= 0) { killStreak = 0; } } // Update combo timer if (comboTimer > 0) { comboTimer--; if (comboTimer <= 0) { combo = 0; } } // Wave management var config = waveConfigs[Math.min(currentWave - 1, waveConfigs.length - 1)]; // Spawn zombies if (zombiesSpawned < config.zombieCount) { spawnTimer++; if (spawnTimer >= config.spawnDelay) { spawnZombie(); spawnTimer = 0; } } // Check wave completion if (zombiesSpawned >= config.zombieCount && zombies.length === 0) { currentWave++; zombiesSpawned = 0; waveTime = 0; // Bonus points for wave completion LK.setScore(LK.getScore() + currentWave * 50); LK.getSound('waveStart').play(); // Restore some health player.health = Math.min(player.maxHealth, player.health + 20); // Create celebration particles createParticles(player.x, player.y, 15, 0x00ff00); } // Update zombies and check player collision for combat for (var k = zombies.length - 1; k >= 0; k--) { var zombie = zombies[k]; // Initialize collision tracking if not exists if (zombie.lastCollidingWithPlayer === undefined) { zombie.lastCollidingWithPlayer = false; } // Check current collision state var currentlyColliding = zombie.intersects(player); // If collision just started (transition from false to true) if (!zombie.lastCollidingWithPlayer && currentlyColliding) { // Player kills zombie by running into it zombie.health--; // Flash zombie when hit LK.effects.flashObject(zombie, 0xff0000, 200); LK.getSound('playerAttack').play(); // Create hit particles createParticles(zombie.x, zombie.y, 8, 0xff0000); if (zombie.health <= 0) { // Update combo system combo++; comboTimer = 120; // 2 seconds to maintain combo if (combo > maxCombo) { maxCombo = combo; } // Add score with kill streak and combo multiplier var basePoints = zombie.points; var killStreakMultiplier = 1 + killStreak * 0.1; var comboMultiplier = 1 + Math.min(combo - 1, 10) * 0.2; // Max 3x from combo var points = basePoints * killStreakMultiplier * comboMultiplier; LK.setScore(LK.getScore() + Math.floor(points)); killStreak++; killStreakTimer = 180; // 3 seconds // Flash combo text when combo increases if (combo > 1) { tween(comboText, { tint: 0xFFFFFF }, { duration: 100, onFinish: function onFinish() { tween(comboText, { tint: 0xFFD700 }, { duration: 100 }); } }); } // Create death particles createParticles(zombie.x, zombie.y, 12, 0xffff00); LK.getSound('zombieDeath').play(); zombie.destroy(); zombies.splice(k, 1); zombiesKilled++; } else { // If zombie survives, player takes damage player.health -= 5; LK.effects.flashScreen(0xff0000, 300); LK.getSound('playerHurt').play(); if (player.health <= 0) { LK.showGameOver(); return; } } } // Update collision tracking zombie.lastCollidingWithPlayer = currentlyColliding; } // Check win condition (150 seconds = 9000 frames at 60fps) if (gameTime >= 9000) { LK.showYouWin(); return; } // Update UI updateUI(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var FastZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('fastZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.maxHealth = 1;
self.speed = 3;
self.points = 15;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
if (player) {
self.lastX = self.x;
self.lastY = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Chase player
self.x += dx / distance * self.speed * zombieSpeedMultiplier;
self.y += dy / distance * self.speed * zombieSpeedMultiplier;
}
}
};
return self;
});
var Menu = Container.expand(function () {
var self = Container.call(this);
self.interactive = true;
var playButton = self.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scaleX: 1.5,
scaleY: 1.5
});
var playText = new Text2('PLAY', {
size: 60,
fill: 0xFFFFFF
});
playText.anchor.set(0.5, 0.5);
playText.y = -100;
self.addChild(playText);
var settingsButton = self.attachAsset('settingButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 50,
scaleX: 1.8,
scaleY: 1.8
});
var settingsText = new Text2('SETTINGS', {
size: 40,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsText.y = 50;
self.addChild(settingsText);
self.down = function (x, y, obj) {
// Use the x,y parameters directly instead of obj.parent.toGlobal
var localPos = self.toLocal({
x: x,
y: y
});
if (localPos.y > -175 && localPos.y < -25) {
LK.getSound('menuClick').play();
startGame();
} else if (localPos.y > -22 && localPos.y < 122) {
LK.getSound('menuClick').play();
showSettings();
}
};
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.life = 60;
self.maxLife = 60;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.life--;
self.alpha = self.life / self.maxLife;
if (self.life <= 0) {
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.lastCollidedZombies = [];
return self;
});
var SettingsMenu = Container.expand(function () {
var self = Container.call(this);
self.interactive = true;
var titleText = new Text2('SETTINGS', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -300;
self.addChild(titleText);
var musicVolumeText = new Text2('Music Volume: ' + Math.round(musicVolume * 100) + '%', {
size: 50,
fill: 0xFFFFFF
});
musicVolumeText.anchor.set(0.5, 0.5);
musicVolumeText.y = -200;
self.addChild(musicVolumeText);
var volumeUpButton = self.attachAsset('settingButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -150,
scaleX: 1.4,
scaleY: 1.4
});
var volumeUpText = new Text2('+', {
size: 60,
fill: 0xFFFFFF
});
volumeUpText.anchor.set(0.5, 0.5);
volumeUpText.x = -200;
volumeUpText.y = -150;
self.addChild(volumeUpText);
var volumeDownButton = self.attachAsset('settingButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -150,
scaleX: 1.4,
scaleY: 1.4
});
var volumeDownText = new Text2('-', {
size: 60,
fill: 0xFFFFFF
});
volumeDownText.anchor.set(0.5, 0.5);
volumeDownText.x = 200;
volumeDownText.y = -150;
self.addChild(volumeDownText);
var zombieSpeedText = new Text2('Zombie Speed: ' + zombieSpeedMultiplier, {
size: 50,
fill: 0xFFFFFF
});
zombieSpeedText.anchor.set(0.5, 0.5);
zombieSpeedText.y = -50;
self.addChild(zombieSpeedText);
var speedUpButton = self.attachAsset('settingButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 0,
scaleX: 1.4,
scaleY: 1.4
});
var speedUpText = new Text2('+', {
size: 60,
fill: 0xFFFFFF
});
speedUpText.anchor.set(0.5, 0.5);
speedUpText.x = -200;
speedUpText.y = 0;
self.addChild(speedUpText);
var speedDownButton = self.attachAsset('settingButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 0,
scaleX: 1.4,
scaleY: 1.4
});
var speedDownText = new Text2('-', {
size: 60,
fill: 0xFFFFFF
});
speedDownText.anchor.set(0.5, 0.5);
speedDownText.x = 200;
speedDownText.y = 0;
self.addChild(speedDownText);
var backButton = self.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 200,
scaleX: 1.5,
scaleY: 1.5
});
var backText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backText.y = 200;
self.addChild(backText);
self.down = function (x, y, obj) {
// Use the x,y parameters directly instead of obj.parent.toGlobal
var localPos = self.toLocal({
x: x,
y: y
});
if (localPos.x > -410 && localPos.x < 10 && localPos.y > -206 && localPos.y < -94) {
// Volume up
musicVolume = Math.min(1, musicVolume + 0.1);
musicVolumeText.setText('Music Volume: ' + Math.round(musicVolume * 100) + '%');
storage.musicVolume = musicVolume;
LK.getSound('menuClick').play();
} else if (localPos.x > -10 && localPos.x < 410 && localPos.y > -206 && localPos.y < -94) {
// Volume down
musicVolume = Math.max(0, musicVolume - 0.1);
musicVolumeText.setText('Music Volume: ' + Math.round(musicVolume * 100) + '%');
storage.musicVolume = musicVolume;
LK.getSound('menuClick').play();
} else if (localPos.x > -410 && localPos.x < 10 && localPos.y > -56 && localPos.y < 56) {
// Speed up
zombieSpeedMultiplier = Math.min(3, zombieSpeedMultiplier + 0.2);
zombieSpeedText.setText('Zombie Speed: ' + zombieSpeedMultiplier.toFixed(1));
LK.getSound('menuClick').play();
} else if (localPos.x > -10 && localPos.x < 410 && localPos.y > -56 && localPos.y < 56) {
// Speed down
zombieSpeedMultiplier = Math.max(0.2, zombieSpeedMultiplier - 0.2);
zombieSpeedText.setText('Zombie Speed: ' + zombieSpeedMultiplier.toFixed(1));
LK.getSound('menuClick').play();
} else if (localPos.y > 125 && localPos.y < 275) {
// Back
LK.getSound('menuClick').play();
showMenu();
}
};
return self;
});
var TankZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1;
self.points = 25;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
if (player) {
self.lastX = self.x;
self.lastY = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Chase player
self.x += dx / distance * self.speed * zombieSpeedMultiplier;
self.y += dy / distance * self.speed * zombieSpeedMultiplier;
}
}
};
return self;
});
var TrailParticle = Container.expand(function () {
var self = Container.call(this);
var trailGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.life = 30;
self.maxLife = 30;
self.update = function () {
self.life--;
self.alpha = self.life / self.maxLife;
self.scaleX = self.alpha * trailSize;
self.scaleY = self.alpha * trailSize;
if (self.life <= 0) {
self.destroy();
}
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.maxHealth = 1;
self.speed = 1.5;
self.points = 10;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
if (player) {
self.lastX = self.x;
self.lastY = self.y;
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Chase player
self.x += dx / distance * self.speed * zombieSpeedMultiplier;
self.y += dy / distance * self.speed * zombieSpeedMultiplier;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Game state variables
var gameState = 'playing'; // Always playing now
var particles = [];
var zombieSpeedMultiplier = 1.0;
var musicVolume = storage.musicVolume || 1.0;
// Game variables
var player;
var zombies = [];
var trailParticles = [];
var playerLastX = 0;
var playerLastY = 0;
var trailTimer = 0;
var currentWave = 1;
var zombiesSpawned = 0;
var zombiesKilled = 0;
var gameTime = 0;
var waveTime = 0;
var spawnTimer = 0;
var dragNode = null;
var killStreak = 0;
var killStreakTimer = 0;
var combo = 0;
var comboTimer = 0;
var maxCombo = 0;
var trailSize = 1.5; // Make trail bigger
// Wave configuration
var waveConfigs = [{
zombieCount: 8,
spawnDelay: 90,
zombieTypes: ['zombie']
}, {
zombieCount: 12,
spawnDelay: 75,
zombieTypes: ['zombie', 'fastZombie']
}, {
zombieCount: 16,
spawnDelay: 60,
zombieTypes: ['zombie', 'fastZombie']
}, {
zombieCount: 20,
spawnDelay: 50,
zombieTypes: ['zombie', 'fastZombie', 'tankZombie']
}, {
zombieCount: 25,
spawnDelay: 45,
zombieTypes: ['zombie', 'fastZombie', 'tankZombie']
}, {
zombieCount: 30,
spawnDelay: 40,
zombieTypes: ['zombie', 'fastZombie', 'tankZombie']
}, {
zombieCount: 35,
spawnDelay: 35,
zombieTypes: ['zombie', 'fastZombie', 'tankZombie']
}, {
zombieCount: 40,
spawnDelay: 30,
zombieTypes: ['zombie', 'fastZombie', 'tankZombie']
}];
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 120;
scoreText.y = 20;
LK.gui.topLeft.addChild(scoreText);
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
var timerText = new Text2('Time: 90', {
size: 60,
fill: 0xFFFFFF
});
timerText.anchor.set(1, 0);
timerText.x = -20;
timerText.y = 20;
LK.gui.topRight.addChild(timerText);
var instructionText = new Text2('Tap the cubes to get points', {
size: 50,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 1);
instructionText.y = -20;
LK.gui.bottom.addChild(instructionText);
var comboText = new Text2('Combo: 0', {
size: 60,
fill: 0xFFD700
});
comboText.anchor.set(1, 0);
comboText.x = -20;
comboText.y = 100;
LK.gui.topRight.addChild(comboText);
// Initialize game function
function initializeGame() {
// Initialize player
if (!player) {
player = game.addChild(new Player());
player.x = 1024;
player.y = 1366;
playerLastX = player.x;
playerLastY = player.y;
}
// Start background music
LK.playMusic('backgroundMusic', {
loop: true,
volume: musicVolume
});
}
function createParticles(x, y, count, color) {
for (var i = 0; i < count; i++) {
var particle = new Particle();
particle.x = x;
particle.y = y;
particle.vx = (Math.random() - 0.5) * 10;
particle.vy = (Math.random() - 0.5) * 10;
if (color) {
tween(particle, {
tint: color
}, {
duration: 0
});
}
particles.push(particle);
game.addChild(particle);
}
}
// Create volume slider at bottom left
var volumeSliderBg = LK.getAsset('settingButton', {
anchorX: 0,
anchorY: 1,
x: 20,
y: 0,
scaleX: 0.5,
scaleY: 2
});
LK.gui.bottomLeft.addChild(volumeSliderBg);
var volumeSliderHandle = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
volumeSliderBg.addChild(volumeSliderHandle);
var volumeText = new Text2(Math.round(musicVolume * 100) + '%', {
size: 30,
fill: 0xFFFFFF
});
volumeText.anchor.set(0.5, 0.5);
volumeText.x = 75;
volumeText.y = 0;
volumeSliderBg.addChild(volumeText);
// Position handle based on volume (0 = bottom, 1 = top)
volumeSliderHandle.x = 75;
volumeSliderHandle.y = -20 - musicVolume * 120;
// Make slider interactive
volumeSliderBg.interactive = true;
volumeSliderBg.down = function (x, y, obj) {
var localPos = volumeSliderBg.toLocal({
x: x,
y: y
});
// Calculate volume based on Y position (inverted since 0,0 is top-left)
var newVolume = Math.max(0, Math.min(1, (-localPos.y + 20) / 120));
musicVolume = newVolume;
storage.musicVolume = musicVolume;
// Update handle position
tween(volumeSliderHandle, {
y: -20 - musicVolume * 120
}, {
duration: 200
});
volumeText.setText(Math.round(musicVolume * 100) + '%');
LK.getSound('menuClick').play();
};
// Start game after 2 seconds
LK.setTimeout(function () {
initializeGame();
}, 2000);
// Spawn zombie function
function spawnZombie() {
var config = waveConfigs[Math.min(currentWave - 1, waveConfigs.length - 1)];
var zombieTypes = config.zombieTypes;
var randomType = zombieTypes[Math.floor(Math.random() * zombieTypes.length)];
var zombie;
if (randomType === 'fastZombie') {
zombie = new FastZombie();
} else if (randomType === 'tankZombie') {
zombie = new TankZombie();
} else {
zombie = new Zombie();
}
// Apply speed multiplier
zombie.speed *= zombieSpeedMultiplier;
// Spawn from random edge
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// Top
zombie.x = Math.random() * 2048;
zombie.y = -30;
} else if (edge === 1) {
// Right
zombie.x = 2078;
zombie.y = Math.random() * 2732;
} else if (edge === 2) {
// Bottom
zombie.x = Math.random() * 2048;
zombie.y = 2762;
} else {
// Left
zombie.x = -30;
zombie.y = Math.random() * 2732;
}
zombie.lastX = zombie.x;
zombie.lastY = zombie.y;
// Create spawn particles
createParticles(zombie.x, zombie.y, 6, 0x8b4513);
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
}
// Update UI function
function updateUI() {
scoreText.setText('Score: ' + LK.getScore());
waveText.setText('Wave: ' + currentWave);
timerText.setText('Time: ' + Math.max(0, Math.ceil((150 * 60 - gameTime) / 60)));
comboText.setText('Combo: ' + combo + (maxCombo > 0 ? ' (Best: ' + maxCombo + ')' : ''));
}
// Handle move events
function handleMove(x, y, obj) {
if (dragNode) {
// Keep target position within bounds
var targetX = Math.max(40, Math.min(2008, x));
var targetY = Math.max(40, Math.min(2692, y));
// Stop any existing movement tween
tween.stop(dragNode, {
x: true,
y: true
});
// Smoothly move to target position
tween(dragNode, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Game event handlers
game.move = handleMove;
game.down = function (x, y, obj) {
if (player) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game update loop
game.update = function () {
// Update particles
for (var p = particles.length - 1; p >= 0; p--) {
var particle = particles[p];
if (particle.life <= 0) {
particles.splice(p, 1);
}
}
// Update trail particles
for (var t = trailParticles.length - 1; t >= 0; t--) {
var trailParticle = trailParticles[t];
if (trailParticle.life <= 0) {
trailParticles.splice(t, 1);
}
}
// Create player trail when moving
if (player) {
var playerMoved = Math.abs(player.x - playerLastX) > 5 || Math.abs(player.y - playerLastY) > 5;
if (playerMoved) {
trailTimer++;
if (trailTimer >= 3) {
// Create trail every 3 frames when moving
var trail = new TrailParticle();
trail.x = playerLastX;
trail.y = playerLastY;
tween(trail, {
tint: 0x4caf50
}, {
duration: 0
});
trailParticles.push(trail);
game.addChild(trail);
trailTimer = 0;
}
}
playerLastX = player.x;
playerLastY = player.y;
}
// Only run game logic when player exists
if (!player) {
return;
}
gameTime++;
waveTime++;
// Update kill streak timer
if (killStreakTimer > 0) {
killStreakTimer--;
if (killStreakTimer <= 0) {
killStreak = 0;
}
}
// Update combo timer
if (comboTimer > 0) {
comboTimer--;
if (comboTimer <= 0) {
combo = 0;
}
}
// Wave management
var config = waveConfigs[Math.min(currentWave - 1, waveConfigs.length - 1)];
// Spawn zombies
if (zombiesSpawned < config.zombieCount) {
spawnTimer++;
if (spawnTimer >= config.spawnDelay) {
spawnZombie();
spawnTimer = 0;
}
}
// Check wave completion
if (zombiesSpawned >= config.zombieCount && zombies.length === 0) {
currentWave++;
zombiesSpawned = 0;
waveTime = 0;
// Bonus points for wave completion
LK.setScore(LK.getScore() + currentWave * 50);
LK.getSound('waveStart').play();
// Restore some health
player.health = Math.min(player.maxHealth, player.health + 20);
// Create celebration particles
createParticles(player.x, player.y, 15, 0x00ff00);
}
// Update zombies and check player collision for combat
for (var k = zombies.length - 1; k >= 0; k--) {
var zombie = zombies[k];
// Initialize collision tracking if not exists
if (zombie.lastCollidingWithPlayer === undefined) {
zombie.lastCollidingWithPlayer = false;
}
// Check current collision state
var currentlyColliding = zombie.intersects(player);
// If collision just started (transition from false to true)
if (!zombie.lastCollidingWithPlayer && currentlyColliding) {
// Player kills zombie by running into it
zombie.health--;
// Flash zombie when hit
LK.effects.flashObject(zombie, 0xff0000, 200);
LK.getSound('playerAttack').play();
// Create hit particles
createParticles(zombie.x, zombie.y, 8, 0xff0000);
if (zombie.health <= 0) {
// Update combo system
combo++;
comboTimer = 120; // 2 seconds to maintain combo
if (combo > maxCombo) {
maxCombo = combo;
}
// Add score with kill streak and combo multiplier
var basePoints = zombie.points;
var killStreakMultiplier = 1 + killStreak * 0.1;
var comboMultiplier = 1 + Math.min(combo - 1, 10) * 0.2; // Max 3x from combo
var points = basePoints * killStreakMultiplier * comboMultiplier;
LK.setScore(LK.getScore() + Math.floor(points));
killStreak++;
killStreakTimer = 180; // 3 seconds
// Flash combo text when combo increases
if (combo > 1) {
tween(comboText, {
tint: 0xFFFFFF
}, {
duration: 100,
onFinish: function onFinish() {
tween(comboText, {
tint: 0xFFD700
}, {
duration: 100
});
}
});
}
// Create death particles
createParticles(zombie.x, zombie.y, 12, 0xffff00);
LK.getSound('zombieDeath').play();
zombie.destroy();
zombies.splice(k, 1);
zombiesKilled++;
} else {
// If zombie survives, player takes damage
player.health -= 5;
LK.effects.flashScreen(0xff0000, 300);
LK.getSound('playerHurt').play();
if (player.health <= 0) {
LK.showGameOver();
return;
}
}
}
// Update collision tracking
zombie.lastCollidingWithPlayer = currentlyColliding;
}
// Check win condition (150 seconds = 9000 frames at 60fps)
if (gameTime >= 9000) {
LK.showYouWin();
return;
}
// Update UI
updateUI();
};