User prompt
When a Zombie dies, the turret still facing the dead Zombie and continues to shoot.
User prompt
Add background asset
User prompt
Add background
User prompt
make SSC rotate randomly after some time on a regular basis ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add cardboard zombie (when dead will spawn a random zombie (except Crowzombie)),box zombies (same as Cardboard zombies but spawns three),football zombies,Buried zombies: appear randomly on the map (not too close to home) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add wall(block zombies and get attacked by zombies) and attractor(attract all zombies) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add Electrical turret,Spike turret,ball turret ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
S.S.C spawn 15 sun instead of 10 sun
User prompt
change boss zombie, divided into three types: Shaman zombie: summon fake zombie, heal when low on health, Ninja zombie: teleport, clone, Crazy zombie: dig the ground, buff other zombies, Soldier zombie: go in groups with 4 other and two tanks ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
S.S.C only spawn 10sun instead of 20sun
User prompt
Runners and Tough only appear after 45s
User prompt
added cooldown for building turrets
User prompt
change the zombie spawn mechanism, only when there are 1-2 zombies left, then the next wave will be born, SSC only spawns 10 suns in about 15-25 seconds
User prompt
When zombies are not in range, turrets will turn back to default direction and stop shooting.
User prompt
Add arrows, when pressed will switch to pages containing turrets to make the game UI more compact
User prompt
Add turret: Frozen turret (causes freezing, slows down), Flame thrower (causes fire damage over time, if the zombie is frozen, slowing down will end that effect), Acid turret: is a flame thrower but the damage over time is smaller but will not affect the freezing effect), Automatic bullet turret: very large range, bullets will automatically chase the enemy, in return the damage is small, Minigun turret, rocket launcher ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Special zombies only appear if the player defends the house for 100s
User prompt
added new zombies: Basic zombies now include Tough zombies and Runners, special zombies: Umbrella zombie (umbrella works like a shield), Crows Zombie (when dead, it will release 3 crow zombies)
User prompt
Increases turret damage and slows zombies down a bit
User prompt
Day only turns Night when progress bar runs out
User prompt
increase the range of turrets
User prompt
Make gridcell full screen,except house area
User prompt
make the drag and drop area and build turret full screen, except the house area
User prompt
When not in range, the turrets will stop firing and wait until another zombie enters, increasing the turret's firing range further, when dragging and dropping the turret, the player can see how far the turret's firing range is.
User prompt
increase gridcell size
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BossZombie = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('bossZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.5; self.health = 500; self.damage = 25; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; bossGraphics.tint = 0x87CEEB; self.speed = 0.25; } else { bossGraphics.tint = 0xFFFFFF; self.speed = 0.5; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 60) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 45) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { self.x += self.speed; if (self.x > 2048) { lives -= 5; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; // Prioritize houses first for (var i = 0; i < houses.length; i++) { // Create a virtual target point spread across the house width var houseLeft = houses[i].x - 1024; // Left edge of house var houseRight = houses[i].x + 1024; // Right edge of house var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } // Skip solar collectors and towers - they are not targeted by boss zombies }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 100); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 120; }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 15; self.targetX = 0; self.targetY = 0; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 5) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { // Hit target area, check for zombie collision for (var i = 0; i < zombies.length; i++) { var zdx = zombies[i].x - self.x; var zdy = zombies[i].y - self.y; var zdist = Math.sqrt(zdx * zdx + zdy * zdy); if (zdist < 30) { zombies[i].takeDamage(self.damage); self.destroy(); return; } } self.destroy(); } }; return self; }); var ClassicTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('classicTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 15; self.range = 180; self.fireRate = 45; self.fireTimer = 0; self.health = 120; self.maxHealth = 120; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = zombies[i]; } } if (target) { // Aim turret at target var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; self.shootBullet(target); } }; self.shootBullet = function (target) { LK.getSound('laser').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var DoubleBarrelTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('doubleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 12; self.range = 200; self.fireRate = 35; self.fireTimer = 0; self.health = 140; self.maxHealth = 140; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var targets = []; // Find all zombies in range and sort by distance var zombiesInRange = []; for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { zombiesInRange.push({ zombie: zombies[i], distance: dist }); } } // Sort by distance and take closest 2 zombiesInRange.sort(function (a, b) { return a.distance - b.distance; }); for (var i = 0; i < Math.min(2, zombiesInRange.length); i++) { targets.push(zombiesInRange[i].zombie); } if (targets.length > 0) { // Aim at closest target var dx = targets[0].x - self.x; var dy = targets[0].y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; // Shoot two bullets for (var i = 0; i < targets.length; i++) { self.shootBullet(targets[i]); } } }; self.shootBullet = function (target) { LK.getSound('laser').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var ExplosiveTower = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('explosiveTower', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 50; self.range = 200; self.explosionRange = 100; self.fireRate = 90; self.fireTimer = 0; self.health = 120; self.maxHealth = 120; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndExplode(); } }; self.findAndExplode = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = zombies[i]; } } if (target) { self.explode(target.x, target.y); } }; self.explode = function (x, y) { LK.getSound('explode').play(); var explosion = game.addChild(LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 })); explosion.x = x; explosion.y = y; explosion.alpha = 0.8; tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, onFinish: function onFinish() { explosion.destroy(); } }); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - x; var dy = zombies[i].y - y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.explosionRange) { zombies[i].takeDamage(self.damage); } } }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var FreezeTower = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('freezeTower', { anchorX: 0.5, anchorY: 0.5 }); self.range = 150; self.fireRate = 120; self.fireTimer = 0; self.health = 100; self.maxHealth = 100; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.freezeArea(); } }; self.freezeArea = function () { LK.getSound('freeze').play(); for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { zombies[i].freeze(); } } var freezeEffect = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); freezeEffect.tint = 0x00BFFF; freezeEffect.alpha = 0.5; freezeEffect.width = self.range * 2; freezeEffect.height = self.range * 2; tween(freezeEffect, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { freezeEffect.destroy(); } }); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var House = Container.expand(function () { var self = Container.call(this); var houseGraphics = self.attachAsset('house', { anchorX: 0.5, anchorY: 0.5 }); // Scale house to span the width of the screen houseGraphics.width = 2048; // Full screen width houseGraphics.height = 100; // Keep original height self.health = 500; // Increased health since it's the only house self.maxHealth = 500; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { lives -= 10; // Lose more lives when the main house is destroyed self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var LaserTower = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('laserTower', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 20; self.range = 200; self.fireRate = 30; self.fireTimer = 0; self.health = 150; self.maxHealth = 150; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var target = null; var minDist = self.range; for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = zombies[i]; } } if (target) { self.shootLaser(target); } }; self.shootLaser = function (target) { LK.getSound('laser').play(); var laser = self.attachAsset('laserBeam', { anchorX: 0.5, anchorY: 1 }); var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; laser.rotation = angle; var dist = Math.sqrt(dx * dx + dy * dy); laser.height = dist; tween(laser, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { laser.destroy(); } }); target.takeDamage(self.damage); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var SolarCollector = Container.expand(function () { var self = Container.call(this); var collector = self.attachAsset('solarCollector', { anchorX: 0.5, anchorY: 0.5 }); self.energyRate = 1; self.health = 100; self.maxHealth = 100; self.isDaytime = true; self.energyTimer = 0; self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds (600-900 frames at 60fps) self.update = function () { self.energyTimer++; if (self.energyTimer >= self.energyGenerationTime) { self.energyTimer = 0; // Set new random interval for next generation self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds var energyGain = 20; energy += energyGain; LK.getSound('collect').play(); var energyText = new Text2('+' + energyGain, { size: 30, fill: 0xFFD700 }); energyText.anchor.set(0.5, 0.5); energyText.x = 0; energyText.y = -40; self.addChild(energyText); tween(energyText, { y: -80, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { energyText.destroy(); } }); } collector.tint = self.isDaytime ? 0xFFD700 : 0xB8860B; }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; return self; }); var TripleBarrelTurret = Container.expand(function () { var self = Container.call(this); var tower = self.attachAsset('tripleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 10; self.range = 220; self.fireRate = 30; self.fireTimer = 0; self.health = 160; self.maxHealth = 160; self.update = function () { self.fireTimer++; if (self.fireTimer >= self.fireRate) { self.fireTimer = 0; self.findAndShoot(); } }; self.findAndShoot = function () { var targets = []; // Find all zombies in range and sort by distance var zombiesInRange = []; for (var i = 0; i < zombies.length; i++) { var dx = zombies[i].x - self.x; var dy = zombies[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.range) { zombiesInRange.push({ zombie: zombies[i], distance: dist }); } } // Sort by distance and take closest 3 zombiesInRange.sort(function (a, b) { return a.distance - b.distance; }); for (var i = 0; i < Math.min(3, zombiesInRange.length); i++) { targets.push(zombiesInRange[i].zombie); } if (targets.length > 0) { // Aim at closest target var dx = targets[0].x - self.x; var dy = targets[0].y - self.y; var angle = Math.atan2(dy, dx) + Math.PI / 2; self.children[0].rotation = angle; // Shoot three bullets for (var i = 0; i < targets.length; i++) { self.shootBullet(targets[i]); } } }; self.shootBullet = function (target) { LK.getSound('laser').play(); var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; game.addChild(bullet); }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.destroy(); return true; } LK.effects.flashObject(self, 0xFF0000, 300); return false; }; 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 = 1; self.health = 50; self.damage = 10; self.target = null; self.attackTimer = 0; self.frozen = 0; self.update = function () { if (self.frozen > 0) { self.frozen--; zombieGraphics.tint = 0x87CEEB; self.speed = 0.5; } else { zombieGraphics.tint = 0xFFFFFF; self.speed = 1; } if (!self.target || self.target.destroyed) { self.findTarget(); } if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { self.attackTimer++; if (self.attackTimer >= 60) { self.attackTimer = 0; if (self.target.takeDamage) { if (self.target.takeDamage(self.damage)) { self.target = null; } } } } } else { // Move towards bottom of screen when no target self.y += self.speed; if (self.y > 2732) { lives--; self.destroy(); } } }; self.findTarget = function () { var minDist = Infinity; self.target = null; // Prioritize houses first for (var i = 0; i < houses.length; i++) { // Create a virtual target point spread across the house width var houseLeft = houses[i].x - 1024; // Left edge of house var houseRight = houses[i].x + 1024; // Right edge of house var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width var dx = targetX - self.x; var dy = houses[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; self.target = { x: targetX, y: houses[i].y, takeDamage: houses[i].takeDamage.bind(houses[i]), destroyed: houses[i].destroyed }; } } // Skip solar collectors and towers - they are not targeted by zombies }; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { LK.setScore(LK.getScore() + 10); LK.getSound('zombieDeath').play(); self.destroy(); return true; } LK.effects.flashObject(self, 0xFFFFFF, 200); return false; }; self.freeze = function () { self.frozen = 180; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var energy = 100; var wave = 0; var lives = 20; var isDaytime = true; var dayTimer = 0; var waveTimer = 0; var zombieSpawnTimer = 0; var zombiesPerWave = 5; var zombiesToSpawn = 0; var bossProgress = 0; var bossProgressMax = 1000; // Will be 300 seconds at 60fps with new increment rate var currentStage = 1; var bossActive = false; var difficultyMultiplier = 1; var easyPeriodTimer = 0; var easyPeriodDuration = 2100; // 35 seconds at 60fps var isEasyPeriod = true; var solarCollectors = []; var towers = []; var zombies = []; var houses = []; var gridCells = []; var selectedTower = null; var placementMode = false; var placementCost = 0; var dragging = false; var dragTurret = null; var GRID_SIZE = 120; var GRID_COLS = Math.floor(2048 / GRID_SIZE); var GRID_ROWS = Math.floor(2000 / GRID_SIZE); var energyText = new Text2('Energy: ' + energy, { size: 60, fill: 0xFFD700 }); energyText.anchor.set(0.5, 0); LK.gui.top.addChild(energyText); var waveText = new Text2('Wave: ' + wave, { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(0, 0); waveText.x = -900; waveText.y = 100; LK.gui.top.addChild(waveText); var livesText = new Text2('Lives: ' + lives, { size: 50, fill: 0xFF0000 }); livesText.anchor.set(1, 0); livesText.x = 900; livesText.y = 100; LK.gui.top.addChild(livesText); var dayText = new Text2('Day', { size: 40, fill: 0xFFD700 }); dayText.anchor.set(0.5, 0); dayText.y = 80; LK.gui.top.addChild(dayText); var progressBarBg = LK.getAsset('progressBarBg', { anchorX: 0.5, anchorY: 0.5 }); progressBarBg.x = 0; progressBarBg.y = 150; LK.gui.top.addChild(progressBarBg); var progressBarFill = LK.getAsset('progressBarFill', { anchorX: 0, anchorY: 0.5 }); progressBarFill.x = -200; progressBarFill.y = 150; progressBarFill.width = 0; LK.gui.top.addChild(progressBarFill); var progressText = new Text2('Boss Progress', { size: 30, fill: 0xFFFFFF }); progressText.anchor.set(0.5, 0.5); progressText.x = 0; progressText.y = 180; LK.gui.top.addChild(progressText); var stageText = new Text2('Stage: 1', { size: 35, fill: 0xFFD700 }); stageText.anchor.set(0, 0); stageText.x = -900; stageText.y = 150; LK.gui.top.addChild(stageText); var solarButton = new Container(); var solarButtonBg = solarButton.attachAsset('solarCollector', { anchorX: 0.5, anchorY: 0.5 }); solarButtonBg.width = 100; solarButtonBg.height = 100; var solarButtonText = new Text2('Solar: 25', { size: 25, fill: 0xFFD700 }); solarButtonText.anchor.set(0.5, 0.5); solarButtonText.y = -60; solarButton.addChild(solarButtonText); solarButton.x = -300; LK.gui.bottom.addChild(solarButton); var classicButton = new Container(); var classicButtonBg = classicButton.attachAsset('classicTurret', { anchorX: 0.5, anchorY: 0.5 }); classicButtonBg.width = 100; classicButtonBg.height = 100; var classicButtonText = new Text2('Solar: 50', { size: 25, fill: 0xFFD700 }); classicButtonText.anchor.set(0.5, 0.5); classicButtonText.y = -60; classicButton.addChild(classicButtonText); classicButton.x = -100; LK.gui.bottom.addChild(classicButton); var doubleButton = new Container(); var doubleButtonBg = doubleButton.attachAsset('doubleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); doubleButtonBg.width = 100; doubleButtonBg.height = 100; var doubleButtonText = new Text2('Solar: 75', { size: 25, fill: 0xFFD700 }); doubleButtonText.anchor.set(0.5, 0.5); doubleButtonText.y = -60; doubleButton.addChild(doubleButtonText); doubleButton.x = 100; LK.gui.bottom.addChild(doubleButton); var tripleButton = new Container(); var tripleButtonBg = tripleButton.attachAsset('tripleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 }); tripleButtonBg.width = 100; tripleButtonBg.height = 100; var tripleButtonText = new Text2('Solar: 100', { size: 25, fill: 0xFFD700 }); tripleButtonText.anchor.set(0.5, 0.5); tripleButtonText.y = -60; tripleButton.addChild(tripleButtonText); tripleButton.x = 300; LK.gui.bottom.addChild(tripleButton); for (var row = 0; row < GRID_ROWS; row++) { for (var col = 0; col < GRID_COLS; col++) { var cell = game.addChild(LK.getAsset('gridCell', { anchorX: 0, anchorY: 0 })); cell.x = col * GRID_SIZE; cell.y = row * GRID_SIZE + 200; cell.alpha = 0.1; cell.gridX = col; cell.gridY = row; cell.occupied = false; gridCells.push(cell); } } // Create single house spanning the bottom of screen var house = new House(); house.x = 1024; // Center of screen width (2048/2) house.y = 2500; houses.push(house); game.addChild(house); solarButton.down = function (x, y, obj) { if (energy < 25) { // Flash button red to indicate insufficient energy LK.effects.flashObject(solarButton, 0xff0000, 300); return; } dragging = true; selectedTower = 'solar'; placementCost = 25; dragTurret = game.addChild(LK.getAsset('solarCollector', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 120; dragTurret.height = 120; dragTurret.alpha = 0.8; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: solarButton.x, y: solarButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; // Flash button green to indicate drag started LK.effects.flashObject(solarButton, 0x00ff00, 200); }; classicButton.down = function (x, y, obj) { if (energy < 50) return; dragging = true; selectedTower = 'classic'; placementCost = 50; dragTurret = game.addChild(LK.getAsset('classicTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: classicButton.x, y: classicButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; doubleButton.down = function (x, y, obj) { if (energy < 75) return; dragging = true; selectedTower = 'double'; placementCost = 75; dragTurret = game.addChild(LK.getAsset('doubleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: doubleButton.x, y: doubleButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; tripleButton.down = function (x, y, obj) { if (energy < 100) return; dragging = true; selectedTower = 'triple'; placementCost = 100; dragTurret = game.addChild(LK.getAsset('tripleBarrelTurret', { anchorX: 0.5, anchorY: 0.5 })); dragTurret.width = 60; dragTurret.height = 60; dragTurret.alpha = 0.7; // Convert GUI position to game coordinates var globalPos = game.toLocal({ x: tripleButton.x, y: tripleButton.y }); dragTurret.x = globalPos.x; dragTurret.y = globalPos.y; }; function highlightValidCells(show) { for (var i = 0; i < gridCells.length; i++) { if (!gridCells[i].occupied) { gridCells[i].alpha = show ? 0.3 : 0.1; gridCells[i].tint = show ? 0x00ff00 : 0xffffff; } else { gridCells[i].alpha = show ? 0.2 : 0.1; gridCells[i].tint = show ? 0xff0000 : 0xffffff; } } } game.down = function (x, y, obj) { // Allow starting drag from anywhere in the game area when not already dragging if (!dragging) { // Check if we clicked near any existing turret buttons area var buttonAreaY = 2732 - 200; // Near bottom of screen if (y > buttonAreaY) { // Near button area, let button handlers manage this return; } } }; game.move = function (x, y, obj) { if (dragging && dragTurret) { dragTurret.x = x; dragTurret.y = y; highlightValidCells(true); // Show visual feedback for valid placement area var gridX = Math.floor(x / GRID_SIZE); var gridY = Math.floor((y - 200) / GRID_SIZE); if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) { var cellIndex = gridY * GRID_COLS + gridX; if (!gridCells[cellIndex].occupied) { dragTurret.tint = 0x00ff00; // Green tint for valid placement dragTurret.alpha = 0.8; } else { dragTurret.tint = 0xff0000; // Red tint for invalid placement dragTurret.alpha = 0.5; } } else { dragTurret.tint = 0xff0000; // Red tint for out of bounds dragTurret.alpha = 0.5; } } }; game.up = function (x, y, obj) { if (dragging && dragTurret) { highlightValidCells(false); var gridX = Math.floor(x / GRID_SIZE); var gridY = Math.floor((y - 200) / GRID_SIZE); var validPlacement = false; // Expanded validation - check if within game bounds and valid grid if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) { var cellIndex = gridY * GRID_COLS + gridX; if (!gridCells[cellIndex].occupied && energy >= placementCost) { validPlacement = true; energy -= placementCost; gridCells[cellIndex].occupied = true; var newX = gridX * GRID_SIZE + GRID_SIZE / 2; var newY = gridY * GRID_SIZE + GRID_SIZE / 2 + 200; if (selectedTower === 'solar') { var collector = new SolarCollector(); collector.x = newX; collector.y = newY; solarCollectors.push(collector); game.addChild(collector); } else if (selectedTower === 'classic') { var tower = new ClassicTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'double') { var tower = new DoubleBarrelTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } else if (selectedTower === 'triple') { var tower = new TripleBarrelTurret(); tower.x = newX; tower.y = newY; towers.push(tower); game.addChild(tower); } LK.getSound('build').play(); // Flash effect for successful placement LK.effects.flashObject(dragTurret, 0x00ff00, 300); } else { // Invalid placement - show error feedback LK.effects.flashObject(dragTurret, 0xff0000, 300); } } else { // Out of bounds - show error feedback LK.effects.flashObject(dragTurret, 0xff0000, 300); } // Clean up drag state dragTurret.destroy(); dragTurret = null; dragging = false; selectedTower = null; placementCost = 0; } }; function spawnZombie(isBoss) { var zombie; if (isBoss) { zombie = new BossZombie(); zombie.health *= difficultyMultiplier; zombie.damage = Math.floor(zombie.damage * difficultyMultiplier); bossActive = true; } else { zombie = new Zombie(); if (!isEasyPeriod) { zombie.health = Math.floor(zombie.health * difficultyMultiplier); zombie.damage = Math.floor(zombie.damage * difficultyMultiplier); zombie.speed *= Math.min(difficultyMultiplier * 0.3 + 0.7, 2); } } // Spawn only from top of screen zombie.x = Math.random() * 2048; zombie.y = 150; zombies.push(zombie); game.addChild(zombie); } function onBossDefeated() { bossActive = false; bossProgress = 0; currentStage++; difficultyMultiplier += 0.5; // Switch day/night cycle isDaytime = !isDaytime; dayText.setText(isDaytime ? 'Day' : 'Night'); dayText.tint = isDaytime ? 0xFFD700 : 0x4169E1; game.setBackgroundColor(isDaytime ? 0x87CEEB : 0x191970); // Update solar collectors for (var i = 0; i < solarCollectors.length; i++) { solarCollectors[i].isDaytime = isDaytime; } // Flash screen effect for stage transition LK.effects.flashScreen(isDaytime ? 0xFFD700 : 0x191970, 1000); // Increase boss progress requirement by 25 seconds (1500 frames at 60fps) bossProgressMax += 84; // 25 seconds * 60fps * 0.056 increment = ~84 progress units } game.update = function () { // Handle easy period for new players if (isEasyPeriod) { easyPeriodTimer++; if (easyPeriodTimer >= easyPeriodDuration) { isEasyPeriod = false; } } // Update progress bar based on time and zombie kills if (!bossActive) { var progressIncrease = isEasyPeriod ? 0.028 : 0.056; // Reduced to make 300 seconds initially bossProgress += progressIncrease; // Check if boss should spawn if (bossProgress >= bossProgressMax) { spawnZombie(true); } } // Update progress bar visual var progressPercent = Math.min(bossProgress / bossProgressMax, 1); progressBarFill.width = 400 * progressPercent; // Change progress bar color based on proximity to boss if (progressPercent > 0.8) { progressBarFill.tint = 0xff0000; // Red when close to boss } else if (progressPercent > 0.6) { progressBarFill.tint = 0xffaa00; // Orange } else { progressBarFill.tint = 0xff6600; // Default orange } // Animate progress bar when close to boss spawn if (progressPercent > 0.9 && !bossActive) { var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1; progressBarFill.scaleY = pulseScale; progressBarBg.scaleY = pulseScale; } else { progressBarFill.scaleY = 1; progressBarBg.scaleY = 1; } dayTimer++; if (dayTimer >= 600) { dayTimer = 0; // Only auto-switch day/night if not controlled by boss system if (!bossActive && currentStage === 1) { isDaytime = !isDaytime; dayText.setText(isDaytime ? 'Day' : 'Night'); dayText.tint = isDaytime ? 0xFFD700 : 0x4169E1; game.setBackgroundColor(isDaytime ? 0x87CEEB : 0x191970); for (var i = 0; i < solarCollectors.length; i++) { solarCollectors[i].isDaytime = isDaytime; } } } // Wave-based zombie spawning with limited numbers per wave if (zombiesToSpawn <= 0 && !bossActive) { waveTimer++; // Random delay between waves (10-25 seconds based on difficulty) var minWaveDelay = isEasyPeriod ? 600 : Math.max(600, 900 - Math.floor(difficultyMultiplier * 60)); // 10-15 seconds var maxWaveDelay = isEasyPeriod ? 1500 : Math.max(900, 1500 - Math.floor(difficultyMultiplier * 30)); // 15-25 seconds var randomWaveDelay = Math.floor(Math.random() * (maxWaveDelay - minWaveDelay + 1)) + minWaveDelay; if (waveTimer >= randomWaveDelay) { waveTimer = 0; wave++; // Spawn 3-4 zombies per wave depending on difficulty var baseZombies = isEasyPeriod ? 3 : 3; var maxZombies = isEasyPeriod ? 3 : 4; // Increase chance of 4 zombies as difficulty rises var zombieCount = baseZombies; if (!isEasyPeriod && Math.random() < (difficultyMultiplier - 1) * 0.3) { zombieCount = maxZombies; } zombiesPerWave = zombieCount; zombiesToSpawn = zombiesPerWave; } } else if (zombiesToSpawn > 0) { zombieSpawnTimer++; // Spawn zombies quickly within a wave (every 0.5-1 second) var spawnDelay = isEasyPeriod ? 60 : Math.max(30, 60 - Math.floor(difficultyMultiplier * 5)); if (zombieSpawnTimer >= spawnDelay) { zombieSpawnTimer = 0; zombiesToSpawn--; spawnZombie(false); } } for (var i = solarCollectors.length - 1; i >= 0; i--) { if (solarCollectors[i].destroyed) { solarCollectors.splice(i, 1); } } for (var i = towers.length - 1; i >= 0; i--) { if (towers[i].destroyed) { towers.splice(i, 1); } } for (var i = zombies.length - 1; i >= 0; i--) { if (zombies[i].destroyed) { // Check if destroyed zombie was a boss if (zombies[i].health > 100 && bossActive) { // Boss zombies have much higher health onBossDefeated(); } else if (!bossActive) { // Regular zombie kill increases boss progress slightly bossProgress += 0.1; // Reduced to match slower progress bar } zombies.splice(i, 1); } } for (var i = houses.length - 1; i >= 0; i--) { if (houses[i].destroyed) { houses.splice(i, 1); } } energyText.setText('Energy: ' + energy); waveText.setText('Wave: ' + wave); livesText.setText('Lives: ' + lives); stageText.setText('Stage: ' + currentStage); // Update progress text based on state if (bossActive) { progressText.setText('BOSS ACTIVE!'); progressText.tint = 0xff0000; var pulseAlpha = 0.5 + Math.sin(LK.ticks * 0.5) * 0.5; progressText.alpha = pulseAlpha; } else if (isEasyPeriod) { progressText.setText('Preparation Time'); progressText.tint = 0x00ff00; progressText.alpha = 1; } else { progressText.setText('Boss Progress: ' + Math.floor(progressPercent * 100) + '%'); progressText.tint = 0xFFFFFF; progressText.alpha = 1; } if (lives <= 0) { LK.showGameOver(); } }; LK.playMusic('gameMusic');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BossZombie = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('bossZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 500;
self.damage = 25;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
bossGraphics.tint = 0x87CEEB;
self.speed = 0.25;
} else {
bossGraphics.tint = 0xFFFFFF;
self.speed = 0.5;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 60) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 45) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
self.x += self.speed;
if (self.x > 2048) {
lives -= 5;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
// Prioritize houses first
for (var i = 0; i < houses.length; i++) {
// Create a virtual target point spread across the house width
var houseLeft = houses[i].x - 1024; // Left edge of house
var houseRight = houses[i].x + 1024; // Right edge of house
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
// Skip solar collectors and towers - they are not targeted by boss zombies
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 100);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 120;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 15;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 5) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
// Hit target area, check for zombie collision
for (var i = 0; i < zombies.length; i++) {
var zdx = zombies[i].x - self.x;
var zdy = zombies[i].y - self.y;
var zdist = Math.sqrt(zdx * zdx + zdy * zdy);
if (zdist < 30) {
zombies[i].takeDamage(self.damage);
self.destroy();
return;
}
}
self.destroy();
}
};
return self;
});
var ClassicTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('classicTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 15;
self.range = 180;
self.fireRate = 45;
self.fireTimer = 0;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
if (target) {
// Aim turret at target
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
self.shootBullet(target);
}
};
self.shootBullet = function (target) {
LK.getSound('laser').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var DoubleBarrelTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('doubleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 12;
self.range = 200;
self.fireRate = 35;
self.fireTimer = 0;
self.health = 140;
self.maxHealth = 140;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var targets = [];
// Find all zombies in range and sort by distance
var zombiesInRange = [];
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
zombiesInRange.push({
zombie: zombies[i],
distance: dist
});
}
}
// Sort by distance and take closest 2
zombiesInRange.sort(function (a, b) {
return a.distance - b.distance;
});
for (var i = 0; i < Math.min(2, zombiesInRange.length); i++) {
targets.push(zombiesInRange[i].zombie);
}
if (targets.length > 0) {
// Aim at closest target
var dx = targets[0].x - self.x;
var dy = targets[0].y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
// Shoot two bullets
for (var i = 0; i < targets.length; i++) {
self.shootBullet(targets[i]);
}
}
};
self.shootBullet = function (target) {
LK.getSound('laser').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var ExplosiveTower = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('explosiveTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 50;
self.range = 200;
self.explosionRange = 100;
self.fireRate = 90;
self.fireTimer = 0;
self.health = 120;
self.maxHealth = 120;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndExplode();
}
};
self.findAndExplode = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
if (target) {
self.explode(target.x, target.y);
}
};
self.explode = function (x, y) {
LK.getSound('explode').play();
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
}));
explosion.x = x;
explosion.y = y;
explosion.alpha = 0.8;
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - x;
var dy = zombies[i].y - y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.explosionRange) {
zombies[i].takeDamage(self.damage);
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var FreezeTower = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('freezeTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.range = 150;
self.fireRate = 120;
self.fireTimer = 0;
self.health = 100;
self.maxHealth = 100;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.freezeArea();
}
};
self.freezeArea = function () {
LK.getSound('freeze').play();
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
zombies[i].freeze();
}
}
var freezeEffect = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
freezeEffect.tint = 0x00BFFF;
freezeEffect.alpha = 0.5;
freezeEffect.width = self.range * 2;
freezeEffect.height = self.range * 2;
tween(freezeEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
onFinish: function onFinish() {
freezeEffect.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var House = Container.expand(function () {
var self = Container.call(this);
var houseGraphics = self.attachAsset('house', {
anchorX: 0.5,
anchorY: 0.5
});
// Scale house to span the width of the screen
houseGraphics.width = 2048; // Full screen width
houseGraphics.height = 100; // Keep original height
self.health = 500; // Increased health since it's the only house
self.maxHealth = 500;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
lives -= 10; // Lose more lives when the main house is destroyed
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var LaserTower = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('laserTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 20;
self.range = 200;
self.fireRate = 30;
self.fireTimer = 0;
self.health = 150;
self.maxHealth = 150;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var target = null;
var minDist = self.range;
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = zombies[i];
}
}
if (target) {
self.shootLaser(target);
}
};
self.shootLaser = function (target) {
LK.getSound('laser').play();
var laser = self.attachAsset('laserBeam', {
anchorX: 0.5,
anchorY: 1
});
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
laser.rotation = angle;
var dist = Math.sqrt(dx * dx + dy * dy);
laser.height = dist;
tween(laser, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
laser.destroy();
}
});
target.takeDamage(self.damage);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var SolarCollector = Container.expand(function () {
var self = Container.call(this);
var collector = self.attachAsset('solarCollector', {
anchorX: 0.5,
anchorY: 0.5
});
self.energyRate = 1;
self.health = 100;
self.maxHealth = 100;
self.isDaytime = true;
self.energyTimer = 0;
self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds (600-900 frames at 60fps)
self.update = function () {
self.energyTimer++;
if (self.energyTimer >= self.energyGenerationTime) {
self.energyTimer = 0;
// Set new random interval for next generation
self.energyGenerationTime = Math.floor(Math.random() * 300) + 600; // Random 10-15 seconds
var energyGain = 20;
energy += energyGain;
LK.getSound('collect').play();
var energyText = new Text2('+' + energyGain, {
size: 30,
fill: 0xFFD700
});
energyText.anchor.set(0.5, 0.5);
energyText.x = 0;
energyText.y = -40;
self.addChild(energyText);
tween(energyText, {
y: -80,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
energyText.destroy();
}
});
}
collector.tint = self.isDaytime ? 0xFFD700 : 0xB8860B;
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
return self;
});
var TripleBarrelTurret = Container.expand(function () {
var self = Container.call(this);
var tower = self.attachAsset('tripleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 10;
self.range = 220;
self.fireRate = 30;
self.fireTimer = 0;
self.health = 160;
self.maxHealth = 160;
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireRate) {
self.fireTimer = 0;
self.findAndShoot();
}
};
self.findAndShoot = function () {
var targets = [];
// Find all zombies in range and sort by distance
var zombiesInRange = [];
for (var i = 0; i < zombies.length; i++) {
var dx = zombies[i].x - self.x;
var dy = zombies[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.range) {
zombiesInRange.push({
zombie: zombies[i],
distance: dist
});
}
}
// Sort by distance and take closest 3
zombiesInRange.sort(function (a, b) {
return a.distance - b.distance;
});
for (var i = 0; i < Math.min(3, zombiesInRange.length); i++) {
targets.push(zombiesInRange[i].zombie);
}
if (targets.length > 0) {
// Aim at closest target
var dx = targets[0].x - self.x;
var dy = targets[0].y - self.y;
var angle = Math.atan2(dy, dx) + Math.PI / 2;
self.children[0].rotation = angle;
// Shoot three bullets
for (var i = 0; i < targets.length; i++) {
self.shootBullet(targets[i]);
}
}
};
self.shootBullet = function (target) {
LK.getSound('laser').play();
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
game.addChild(bullet);
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFF0000, 300);
return false;
};
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 = 1;
self.health = 50;
self.damage = 10;
self.target = null;
self.attackTimer = 0;
self.frozen = 0;
self.update = function () {
if (self.frozen > 0) {
self.frozen--;
zombieGraphics.tint = 0x87CEEB;
self.speed = 0.5;
} else {
zombieGraphics.tint = 0xFFFFFF;
self.speed = 1;
}
if (!self.target || self.target.destroyed) {
self.findTarget();
}
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 50) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= 60) {
self.attackTimer = 0;
if (self.target.takeDamage) {
if (self.target.takeDamage(self.damage)) {
self.target = null;
}
}
}
}
} else {
// Move towards bottom of screen when no target
self.y += self.speed;
if (self.y > 2732) {
lives--;
self.destroy();
}
}
};
self.findTarget = function () {
var minDist = Infinity;
self.target = null;
// Prioritize houses first
for (var i = 0; i < houses.length; i++) {
// Create a virtual target point spread across the house width
var houseLeft = houses[i].x - 1024; // Left edge of house
var houseRight = houses[i].x + 1024; // Right edge of house
var targetX = Math.max(houseLeft, Math.min(houseRight, self.x)); // Clamp zombie x to house width
var dx = targetX - self.x;
var dy = houses[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
self.target = {
x: targetX,
y: houses[i].y,
takeDamage: houses[i].takeDamage.bind(houses[i]),
destroyed: houses[i].destroyed
};
}
}
// Skip solar collectors and towers - they are not targeted by zombies
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
LK.setScore(LK.getScore() + 10);
LK.getSound('zombieDeath').play();
self.destroy();
return true;
}
LK.effects.flashObject(self, 0xFFFFFF, 200);
return false;
};
self.freeze = function () {
self.frozen = 180;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var energy = 100;
var wave = 0;
var lives = 20;
var isDaytime = true;
var dayTimer = 0;
var waveTimer = 0;
var zombieSpawnTimer = 0;
var zombiesPerWave = 5;
var zombiesToSpawn = 0;
var bossProgress = 0;
var bossProgressMax = 1000; // Will be 300 seconds at 60fps with new increment rate
var currentStage = 1;
var bossActive = false;
var difficultyMultiplier = 1;
var easyPeriodTimer = 0;
var easyPeriodDuration = 2100; // 35 seconds at 60fps
var isEasyPeriod = true;
var solarCollectors = [];
var towers = [];
var zombies = [];
var houses = [];
var gridCells = [];
var selectedTower = null;
var placementMode = false;
var placementCost = 0;
var dragging = false;
var dragTurret = null;
var GRID_SIZE = 120;
var GRID_COLS = Math.floor(2048 / GRID_SIZE);
var GRID_ROWS = Math.floor(2000 / GRID_SIZE);
var energyText = new Text2('Energy: ' + energy, {
size: 60,
fill: 0xFFD700
});
energyText.anchor.set(0.5, 0);
LK.gui.top.addChild(energyText);
var waveText = new Text2('Wave: ' + wave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
waveText.x = -900;
waveText.y = 100;
LK.gui.top.addChild(waveText);
var livesText = new Text2('Lives: ' + lives, {
size: 50,
fill: 0xFF0000
});
livesText.anchor.set(1, 0);
livesText.x = 900;
livesText.y = 100;
LK.gui.top.addChild(livesText);
var dayText = new Text2('Day', {
size: 40,
fill: 0xFFD700
});
dayText.anchor.set(0.5, 0);
dayText.y = 80;
LK.gui.top.addChild(dayText);
var progressBarBg = LK.getAsset('progressBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
progressBarBg.x = 0;
progressBarBg.y = 150;
LK.gui.top.addChild(progressBarBg);
var progressBarFill = LK.getAsset('progressBarFill', {
anchorX: 0,
anchorY: 0.5
});
progressBarFill.x = -200;
progressBarFill.y = 150;
progressBarFill.width = 0;
LK.gui.top.addChild(progressBarFill);
var progressText = new Text2('Boss Progress', {
size: 30,
fill: 0xFFFFFF
});
progressText.anchor.set(0.5, 0.5);
progressText.x = 0;
progressText.y = 180;
LK.gui.top.addChild(progressText);
var stageText = new Text2('Stage: 1', {
size: 35,
fill: 0xFFD700
});
stageText.anchor.set(0, 0);
stageText.x = -900;
stageText.y = 150;
LK.gui.top.addChild(stageText);
var solarButton = new Container();
var solarButtonBg = solarButton.attachAsset('solarCollector', {
anchorX: 0.5,
anchorY: 0.5
});
solarButtonBg.width = 100;
solarButtonBg.height = 100;
var solarButtonText = new Text2('Solar: 25', {
size: 25,
fill: 0xFFD700
});
solarButtonText.anchor.set(0.5, 0.5);
solarButtonText.y = -60;
solarButton.addChild(solarButtonText);
solarButton.x = -300;
LK.gui.bottom.addChild(solarButton);
var classicButton = new Container();
var classicButtonBg = classicButton.attachAsset('classicTurret', {
anchorX: 0.5,
anchorY: 0.5
});
classicButtonBg.width = 100;
classicButtonBg.height = 100;
var classicButtonText = new Text2('Solar: 50', {
size: 25,
fill: 0xFFD700
});
classicButtonText.anchor.set(0.5, 0.5);
classicButtonText.y = -60;
classicButton.addChild(classicButtonText);
classicButton.x = -100;
LK.gui.bottom.addChild(classicButton);
var doubleButton = new Container();
var doubleButtonBg = doubleButton.attachAsset('doubleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
doubleButtonBg.width = 100;
doubleButtonBg.height = 100;
var doubleButtonText = new Text2('Solar: 75', {
size: 25,
fill: 0xFFD700
});
doubleButtonText.anchor.set(0.5, 0.5);
doubleButtonText.y = -60;
doubleButton.addChild(doubleButtonText);
doubleButton.x = 100;
LK.gui.bottom.addChild(doubleButton);
var tripleButton = new Container();
var tripleButtonBg = tripleButton.attachAsset('tripleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
});
tripleButtonBg.width = 100;
tripleButtonBg.height = 100;
var tripleButtonText = new Text2('Solar: 100', {
size: 25,
fill: 0xFFD700
});
tripleButtonText.anchor.set(0.5, 0.5);
tripleButtonText.y = -60;
tripleButton.addChild(tripleButtonText);
tripleButton.x = 300;
LK.gui.bottom.addChild(tripleButton);
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var cell = game.addChild(LK.getAsset('gridCell', {
anchorX: 0,
anchorY: 0
}));
cell.x = col * GRID_SIZE;
cell.y = row * GRID_SIZE + 200;
cell.alpha = 0.1;
cell.gridX = col;
cell.gridY = row;
cell.occupied = false;
gridCells.push(cell);
}
}
// Create single house spanning the bottom of screen
var house = new House();
house.x = 1024; // Center of screen width (2048/2)
house.y = 2500;
houses.push(house);
game.addChild(house);
solarButton.down = function (x, y, obj) {
if (energy < 25) {
// Flash button red to indicate insufficient energy
LK.effects.flashObject(solarButton, 0xff0000, 300);
return;
}
dragging = true;
selectedTower = 'solar';
placementCost = 25;
dragTurret = game.addChild(LK.getAsset('solarCollector', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 120;
dragTurret.height = 120;
dragTurret.alpha = 0.8;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: solarButton.x,
y: solarButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
// Flash button green to indicate drag started
LK.effects.flashObject(solarButton, 0x00ff00, 200);
};
classicButton.down = function (x, y, obj) {
if (energy < 50) return;
dragging = true;
selectedTower = 'classic';
placementCost = 50;
dragTurret = game.addChild(LK.getAsset('classicTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: classicButton.x,
y: classicButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
doubleButton.down = function (x, y, obj) {
if (energy < 75) return;
dragging = true;
selectedTower = 'double';
placementCost = 75;
dragTurret = game.addChild(LK.getAsset('doubleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: doubleButton.x,
y: doubleButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
tripleButton.down = function (x, y, obj) {
if (energy < 100) return;
dragging = true;
selectedTower = 'triple';
placementCost = 100;
dragTurret = game.addChild(LK.getAsset('tripleBarrelTurret', {
anchorX: 0.5,
anchorY: 0.5
}));
dragTurret.width = 60;
dragTurret.height = 60;
dragTurret.alpha = 0.7;
// Convert GUI position to game coordinates
var globalPos = game.toLocal({
x: tripleButton.x,
y: tripleButton.y
});
dragTurret.x = globalPos.x;
dragTurret.y = globalPos.y;
};
function highlightValidCells(show) {
for (var i = 0; i < gridCells.length; i++) {
if (!gridCells[i].occupied) {
gridCells[i].alpha = show ? 0.3 : 0.1;
gridCells[i].tint = show ? 0x00ff00 : 0xffffff;
} else {
gridCells[i].alpha = show ? 0.2 : 0.1;
gridCells[i].tint = show ? 0xff0000 : 0xffffff;
}
}
}
game.down = function (x, y, obj) {
// Allow starting drag from anywhere in the game area when not already dragging
if (!dragging) {
// Check if we clicked near any existing turret buttons area
var buttonAreaY = 2732 - 200; // Near bottom of screen
if (y > buttonAreaY) {
// Near button area, let button handlers manage this
return;
}
}
};
game.move = function (x, y, obj) {
if (dragging && dragTurret) {
dragTurret.x = x;
dragTurret.y = y;
highlightValidCells(true);
// Show visual feedback for valid placement area
var gridX = Math.floor(x / GRID_SIZE);
var gridY = Math.floor((y - 200) / GRID_SIZE);
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) {
var cellIndex = gridY * GRID_COLS + gridX;
if (!gridCells[cellIndex].occupied) {
dragTurret.tint = 0x00ff00; // Green tint for valid placement
dragTurret.alpha = 0.8;
} else {
dragTurret.tint = 0xff0000; // Red tint for invalid placement
dragTurret.alpha = 0.5;
}
} else {
dragTurret.tint = 0xff0000; // Red tint for out of bounds
dragTurret.alpha = 0.5;
}
}
};
game.up = function (x, y, obj) {
if (dragging && dragTurret) {
highlightValidCells(false);
var gridX = Math.floor(x / GRID_SIZE);
var gridY = Math.floor((y - 200) / GRID_SIZE);
var validPlacement = false;
// Expanded validation - check if within game bounds and valid grid
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) {
var cellIndex = gridY * GRID_COLS + gridX;
if (!gridCells[cellIndex].occupied && energy >= placementCost) {
validPlacement = true;
energy -= placementCost;
gridCells[cellIndex].occupied = true;
var newX = gridX * GRID_SIZE + GRID_SIZE / 2;
var newY = gridY * GRID_SIZE + GRID_SIZE / 2 + 200;
if (selectedTower === 'solar') {
var collector = new SolarCollector();
collector.x = newX;
collector.y = newY;
solarCollectors.push(collector);
game.addChild(collector);
} else if (selectedTower === 'classic') {
var tower = new ClassicTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'double') {
var tower = new DoubleBarrelTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
} else if (selectedTower === 'triple') {
var tower = new TripleBarrelTurret();
tower.x = newX;
tower.y = newY;
towers.push(tower);
game.addChild(tower);
}
LK.getSound('build').play();
// Flash effect for successful placement
LK.effects.flashObject(dragTurret, 0x00ff00, 300);
} else {
// Invalid placement - show error feedback
LK.effects.flashObject(dragTurret, 0xff0000, 300);
}
} else {
// Out of bounds - show error feedback
LK.effects.flashObject(dragTurret, 0xff0000, 300);
}
// Clean up drag state
dragTurret.destroy();
dragTurret = null;
dragging = false;
selectedTower = null;
placementCost = 0;
}
};
function spawnZombie(isBoss) {
var zombie;
if (isBoss) {
zombie = new BossZombie();
zombie.health *= difficultyMultiplier;
zombie.damage = Math.floor(zombie.damage * difficultyMultiplier);
bossActive = true;
} else {
zombie = new Zombie();
if (!isEasyPeriod) {
zombie.health = Math.floor(zombie.health * difficultyMultiplier);
zombie.damage = Math.floor(zombie.damage * difficultyMultiplier);
zombie.speed *= Math.min(difficultyMultiplier * 0.3 + 0.7, 2);
}
}
// Spawn only from top of screen
zombie.x = Math.random() * 2048;
zombie.y = 150;
zombies.push(zombie);
game.addChild(zombie);
}
function onBossDefeated() {
bossActive = false;
bossProgress = 0;
currentStage++;
difficultyMultiplier += 0.5;
// Switch day/night cycle
isDaytime = !isDaytime;
dayText.setText(isDaytime ? 'Day' : 'Night');
dayText.tint = isDaytime ? 0xFFD700 : 0x4169E1;
game.setBackgroundColor(isDaytime ? 0x87CEEB : 0x191970);
// Update solar collectors
for (var i = 0; i < solarCollectors.length; i++) {
solarCollectors[i].isDaytime = isDaytime;
}
// Flash screen effect for stage transition
LK.effects.flashScreen(isDaytime ? 0xFFD700 : 0x191970, 1000);
// Increase boss progress requirement by 25 seconds (1500 frames at 60fps)
bossProgressMax += 84; // 25 seconds * 60fps * 0.056 increment = ~84 progress units
}
game.update = function () {
// Handle easy period for new players
if (isEasyPeriod) {
easyPeriodTimer++;
if (easyPeriodTimer >= easyPeriodDuration) {
isEasyPeriod = false;
}
}
// Update progress bar based on time and zombie kills
if (!bossActive) {
var progressIncrease = isEasyPeriod ? 0.028 : 0.056; // Reduced to make 300 seconds initially
bossProgress += progressIncrease;
// Check if boss should spawn
if (bossProgress >= bossProgressMax) {
spawnZombie(true);
}
}
// Update progress bar visual
var progressPercent = Math.min(bossProgress / bossProgressMax, 1);
progressBarFill.width = 400 * progressPercent;
// Change progress bar color based on proximity to boss
if (progressPercent > 0.8) {
progressBarFill.tint = 0xff0000; // Red when close to boss
} else if (progressPercent > 0.6) {
progressBarFill.tint = 0xffaa00; // Orange
} else {
progressBarFill.tint = 0xff6600; // Default orange
}
// Animate progress bar when close to boss spawn
if (progressPercent > 0.9 && !bossActive) {
var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1;
progressBarFill.scaleY = pulseScale;
progressBarBg.scaleY = pulseScale;
} else {
progressBarFill.scaleY = 1;
progressBarBg.scaleY = 1;
}
dayTimer++;
if (dayTimer >= 600) {
dayTimer = 0;
// Only auto-switch day/night if not controlled by boss system
if (!bossActive && currentStage === 1) {
isDaytime = !isDaytime;
dayText.setText(isDaytime ? 'Day' : 'Night');
dayText.tint = isDaytime ? 0xFFD700 : 0x4169E1;
game.setBackgroundColor(isDaytime ? 0x87CEEB : 0x191970);
for (var i = 0; i < solarCollectors.length; i++) {
solarCollectors[i].isDaytime = isDaytime;
}
}
}
// Wave-based zombie spawning with limited numbers per wave
if (zombiesToSpawn <= 0 && !bossActive) {
waveTimer++;
// Random delay between waves (10-25 seconds based on difficulty)
var minWaveDelay = isEasyPeriod ? 600 : Math.max(600, 900 - Math.floor(difficultyMultiplier * 60)); // 10-15 seconds
var maxWaveDelay = isEasyPeriod ? 1500 : Math.max(900, 1500 - Math.floor(difficultyMultiplier * 30)); // 15-25 seconds
var randomWaveDelay = Math.floor(Math.random() * (maxWaveDelay - minWaveDelay + 1)) + minWaveDelay;
if (waveTimer >= randomWaveDelay) {
waveTimer = 0;
wave++;
// Spawn 3-4 zombies per wave depending on difficulty
var baseZombies = isEasyPeriod ? 3 : 3;
var maxZombies = isEasyPeriod ? 3 : 4;
// Increase chance of 4 zombies as difficulty rises
var zombieCount = baseZombies;
if (!isEasyPeriod && Math.random() < (difficultyMultiplier - 1) * 0.3) {
zombieCount = maxZombies;
}
zombiesPerWave = zombieCount;
zombiesToSpawn = zombiesPerWave;
}
} else if (zombiesToSpawn > 0) {
zombieSpawnTimer++;
// Spawn zombies quickly within a wave (every 0.5-1 second)
var spawnDelay = isEasyPeriod ? 60 : Math.max(30, 60 - Math.floor(difficultyMultiplier * 5));
if (zombieSpawnTimer >= spawnDelay) {
zombieSpawnTimer = 0;
zombiesToSpawn--;
spawnZombie(false);
}
}
for (var i = solarCollectors.length - 1; i >= 0; i--) {
if (solarCollectors[i].destroyed) {
solarCollectors.splice(i, 1);
}
}
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i].destroyed) {
towers.splice(i, 1);
}
}
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].destroyed) {
// Check if destroyed zombie was a boss
if (zombies[i].health > 100 && bossActive) {
// Boss zombies have much higher health
onBossDefeated();
} else if (!bossActive) {
// Regular zombie kill increases boss progress slightly
bossProgress += 0.1; // Reduced to match slower progress bar
}
zombies.splice(i, 1);
}
}
for (var i = houses.length - 1; i >= 0; i--) {
if (houses[i].destroyed) {
houses.splice(i, 1);
}
}
energyText.setText('Energy: ' + energy);
waveText.setText('Wave: ' + wave);
livesText.setText('Lives: ' + lives);
stageText.setText('Stage: ' + currentStage);
// Update progress text based on state
if (bossActive) {
progressText.setText('BOSS ACTIVE!');
progressText.tint = 0xff0000;
var pulseAlpha = 0.5 + Math.sin(LK.ticks * 0.5) * 0.5;
progressText.alpha = pulseAlpha;
} else if (isEasyPeriod) {
progressText.setText('Preparation Time');
progressText.tint = 0x00ff00;
progressText.alpha = 1;
} else {
progressText.setText('Boss Progress: ' + Math.floor(progressPercent * 100) + '%');
progressText.tint = 0xFFFFFF;
progressText.alpha = 1;
}
if (lives <= 0) {
LK.showGameOver();
}
};
LK.playMusic('gameMusic');
top view of a garden. 2d. No shadows. In-game assetmodern, simple
top view of a grassy area. In-Game asset. 2d. High contrast. No shadows
Top view of a house. In-Game asset. 2d. High contrast. No shadows
Acid turret. In-Game asset. 2d. High contrast. No shadows
A zombie. In-Game asset. 2d. High contrast. No shadows
solar collector. In-Game asset. 2d. High contrast. No shadows
A turret. In-Game asset. 2d. High contrast. No shadows
A duo barrel turret. In-Game asset. 2d. High contrast. No shadows
Triple barrel turret. In-Game asset. 2d. High contrast. No shadows
Tough zombie. In-Game asset. 2d. High contrast. No shadows
Light-skin runner zombie. In-Game asset. 2d. High contrast. No shadows
a zombie raises an umbrella forward. In-Game asset. 2d. High contrast. No shadows
Zombie dressed as a crow. In-Game asset. 2d. High contrast. No shadows
Zombie crow. In-Game asset. 2d. High contrast. No shadows
Ice turret. In-Game asset. 2d. High contrast. No shadows
Flame thrower turret. In-Game asset. 2d. High contrast. No shadows
Homing bullet turret. In-Game asset. 2d. High contrast. No shadows
Minigun turret. In-Game asset. 2d. High contrast. No shadows
Rocket launcher turret. In-Game asset. 2d. High contrast. No shadows
Missile. In-Game asset. 2d. High contrast. No shadows
Old abandoned, broken tank. In-Game asset. 2d. High contrast. No shadows
Ninja zombie. In-Game asset. 2d. High contrast. No shadows
Grey zombie. In-Game asset. 2d. High contrast. No shadows
Soldier zombie. In-Game asset. 2d. High contrast. No shadows
Very crazy zombie. In-Game asset. 2d. High contrast. No shadows
A shaman zombie,head wearing a cow skull. In-Game asset. 2d. High contrast. No shadows
Spike turret. In-Game asset. 2d. High contrast. No shadows
Ball turret. In-Game asset. 2d. High contrast. No shadows
Electrical turret. In-Game asset. 2d. High contrast. No shadows
Sci-fi wall. In-Game asset. 2d. High contrast. No shadows
A shield with radar. In-Game asset. 2d. High contrast. No shadows
Football zombie. In-Game asset. 2d. High contrast. No shadows
a zombie covered in mud. In-Game asset. 2d. High contrast. No shadows
A cardboard. In-Game asset. 2d. High contrast. No shadows
A wooden crate. In-Game asset. 2d. High contrast. No shadows