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