/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Base = Container.expand(function () { var self = Container.call(this); var baseGraphics = self.attachAsset('base', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.takeDamage = function (damage) { self.health -= damage; tween(baseGraphics, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { tween(baseGraphics, { tint: 0xffffff }, { duration: 100 }); } }); if (self.health <= 0) { LK.showGameOver(); } }; self.down = function (x, y, obj) { if (!upgradePanel.visible) { upgradePanel.visible = true; } else { upgradePanel.visible = false; } }; return self; }); // BossZombie class for invasion event var BossZombie = Container.expand(function () { var self = Container.call(this); // Use zombie1 as base, but scale up var bossGraphics = self.attachAsset('zombie1', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.health = 40; // 20x normal zombie health (normal is 2) self.maxHealth = self.health; self.speed = 1.2; // Slightly slower than normal zombie self.damage = 10; // Big damage to base/towers self.value = 200; // Big coin reward // --- Health bar for boss zombie --- self.healthBarBg = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, width: 160, // Increased width height: 24, // Increased height color: 0x222222 }); self.healthBarBg.y = -120; // Move up slightly to fit larger bar self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('bullet', { anchorX: 0, anchorY: 0.5, width: 152, // Increased width height: 18, // Increased height color: 0x00ff00 }); self.healthBar.x = -80; // Adjusted for new width self.healthBar.y = -120; self.addChild(self.healthBar); self.updateHealthBar = function () { // Clamp health if (self.health < 0) self.health = 0; if (typeof self.maxHealth === "undefined" || self.maxHealth <= 0) self.maxHealth = 1; var ratio = Math.max(0, Math.min(1, self.health / self.maxHealth)); self.healthBar.width = 152 * ratio; // Color: green >50%, yellow >25%, red <=25% if (ratio > 0.5) { self.healthBar.tint = 0x00ff00; } else if (ratio > 0.25) { self.healthBar.tint = 0xffff00; } else { self.healthBar.tint = 0xff0000; } // Hide if dead self.healthBar.visible = self.health > 0; self.healthBarBg.visible = self.health > 0; }; self.updateHealthBar(); self.update = function () { // Priority: nearest tower, then nearest trap, then base var target = null; var minDist = Infinity; for (var i = 0; i < towers.length; i++) { var t = towers[i]; if (t.health > 0) { var dx = t.x - self.x; var dy = t.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = t; } } } if (!target) { for (var i = 0; i < traps.length; i++) { var tr = traps[i]; if (tr.health > 0) { var dx = tr.x - self.x; var dy = tr.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = tr; } } } } if (!target) { target = base; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; bossGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } self.updateHealthBar(); }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); tween(bossGraphics, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { tween(bossGraphics, { tint: 0xffffff }, { duration: 100 }); } }); if (self.health <= 0) { self.updateHealthBar(); return true; } return false; }; 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 = 15; self.damage = 1; self.dx = 0; self.dy = 0; self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.dx = dx / distance * self.speed; self.dy = dy / distance * self.speed; self.rotation = Math.atan2(dy, dx); if (bulletGraphics) { bulletGraphics.rotation = self.rotation; } } }; self.update = function () { self.x += self.dx; self.y += self.dy; if (typeof self.rotation !== "undefined" && bulletGraphics) { bulletGraphics.rotation = self.rotation; } }; return self; }); var Character = Container.expand(function () { var self = Container.call(this); var characterGraphics = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5 }); self.fireRate = 40; self.fireCooldown = 0; self.update = function () { if (self.fireCooldown > 0) { self.fireCooldown--; } // Find closest zombie for automatic shooting if (self.fireCooldown === 0 && zombies.length > 0) { var closestZombie = null; var closestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestZombie = zombie; } } if (closestZombie) { self.shoot(closestZombie.x, closestZombie.y); } } }; self.shoot = function (targetX, targetY) { if (self.fireCooldown > 0) return; var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.setDirection(targetX, targetY); bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.fireCooldown = self.fireRate; }; return self; }); var Coin = Container.expand(function () { var self = Container.call(this); var coinGraphics = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.value = 10; self.lifetime = 300; self.update = function () { self.lifetime--; coinGraphics.alpha = Math.min(1, self.lifetime / 100); }; return self; }); var Drone = Container.expand(function () { var self = Container.call(this); var vacuumField = self.attachAsset('vacuumField', { anchorX: 0.5, anchorY: 0.5, alpha: 0.1, scaleX: 0.445, scaleY: 0.445 }); var droneGraphics = self.attachAsset('drone', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.target = null; self.collectRange = 22; // Reduced from 50 to achieve 80% area reduction self.vacuumRange = 89; // Reduced from 200 to achieve 80% area reduction self.vacuumPower = 0.15; // Strength of vacuum pull self.floatOffset = 0; self.floatSpeed = 0.05; self.vx = 0; self.vy = 0; self.update = function () { // Floating animation self.floatOffset += self.floatSpeed; droneGraphics.y = Math.sin(self.floatOffset) * 10; // Vacuum field pulsing animation vacuumField.scaleX = 0.445 + Math.sin(self.floatOffset * 2) * 0.045; vacuumField.scaleY = 0.445 + Math.sin(self.floatOffset * 2) * 0.045; vacuumField.alpha = 0.05 + Math.sin(self.floatOffset * 3) * 0.03; // Find closest coin var closestCoin = null; var closestDistance = Infinity; for (var i = 0; i < coins.length; i++) { var coin = coins[i]; var dx = coin.x - self.x; var dy = coin.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestCoin = coin; } } self.target = closestCoin; // Calculate desired velocity towards target var desiredVx = 0; var desiredVy = 0; if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.collectRange) { desiredVx = dx / distance * self.speed; desiredVy = dy / distance * self.speed; } } else { // Return to base when no coins var dx = base.x - self.x; var dy = base.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 150) { desiredVx = dx / distance * self.speed; desiredVy = dy / distance * self.speed; } } // Smoothly interpolate current velocity towards desired velocity self.vx = self.vx * 0.85 + desiredVx * 0.15; self.vy = self.vy * 0.85 + desiredVy * 0.15; // Apply smoothed velocity self.x += self.vx; self.y += self.vy; // Rotate drone based on movement direction if (Math.abs(self.vx) > 0.1 || Math.abs(self.vy) > 0.1) { droneGraphics.rotation = Math.atan2(self.vy, self.vx); } // Apply vacuum effect to nearby coins for (var i = 0; i < coins.length; i++) { var coin = coins[i]; var dx = self.x - coin.x; var dy = self.y - coin.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.vacuumRange && distance > 0) { // Pull coins towards drone var pullStrength = (1 - distance / self.vacuumRange) * self.vacuumPower; coin.x += dx / distance * pullStrength * distance; coin.y += dy / distance * pullStrength * distance; } } }; return self; }); var HowToPlayPanel = Container.expand(function () { var self = Container.call(this); // Background panel var bg = self.attachAsset('upgradePanel', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.5 }); bg.tint = 0x222222; // Title var title = new Text2('How to Play', { size: 60, fill: 0xFFFF00 }); title.anchor.set(0.5, 0.5); title.y = -400; self.addChild(title); // Game info text var infoText = new Text2('Welcome to Zombie Defense!\n\n' + '• Tap anywhere to shoot zombies\n' + '• Collect coins from defeated zombies\n' + '• Click the base to open upgrade menu\n' + '• Build towers and traps for defense\n' + '• Survive as many waves as possible!\n\n' + 'Upgrades:\n' + '• Towers - Auto-shoot nearby zombies\n' + '• Traps - Damage zombies on contact\n' + '• Base Repair - Restore base health\n' + '• Fire Power - Increase damage\n' + '• Fire Rate - Shoot faster\n' + '• Auto Repair - Towers repair at 50% HP\n\n' + 'Boss Zombie:\n' + '• Every 5th wave, a giant Boss Zombie will attack!\n' + '• Boss Zombie is much stronger and bigger than normal zombies\n' + '• Defeat the Boss to continue to the next waves\n\n' + 'Tips:\n' + '• Drone automatically collects coins\n' + '• Each wave gets harder\n' + '• Coin rewards increase each wave', { size: 32, fill: 0xFFFFFF, align: 'left', wordWrap: true, wordWrapWidth: 700 }); infoText.anchor.set(0.5, 0); infoText.y = -320; self.addChild(infoText); // Close button // Move to top row above the panel title (e.g. y = -500, above title at y = -400) var closeBtn = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: -500, scaleX: 0.4, scaleY: 0.6 }); var closeTxt = new Text2('Close', { size: 40, fill: 0xFFFFFF }); closeTxt.anchor.set(0.5, 0.5); closeTxt.y = -500; self.addChild(closeTxt); closeBtn.down = function () { self.visible = false; }; return self; }); var Tower = Container.expand(function () { var self = Container.call(this); var towerIndex = Math.floor(Math.random() * 10) + 1; var towerGraphics = self.attachAsset('tower' + towerIndex, { anchorX: 0.5, anchorY: 0.5 }); // Health bar self.maxHealth = 20; self.health = self.maxHealth; self.healthBar = new Text2('', { size: 24, fill: 0x00ff00 }); self.healthBar.anchor.set(0.5, 1); self.healthBar.y = -50; self.addChild(self.healthBar); self.updateHealthBar = function () { self.healthBar.setText(self.health + '/' + self.maxHealth); if (self.health > self.maxHealth * 0.5) { self.healthBar.fill = 0x00ff00; } else if (self.health > self.maxHealth * 0.25) { self.healthBar.fill = 0xffff00; } else { self.healthBar.fill = 0xff0000; } }; self.updateHealthBar(); // Repair button self.repairCost = 30; self.repairButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: 60, scaleX: 0.5, scaleY: 0.5 }); self.repairText = new Text2('Tamir: ' + self.repairCost, { size: 28, fill: 0xffffff }); self.repairText.anchor.set(0.5, 0.5); self.repairText.y = 60; self.addChild(self.repairText); self.repairButton.visible = false; self.repairText.visible = false; self.down = function (x, y, obj) { // Show repair button if tower is damaged if (self.health < self.maxHealth) { self.repairButton.visible = true; self.repairText.visible = true; } }; self.repairButton.down = function () { if (coinAmount >= self.repairCost && self.health < self.maxHealth) { coinAmount -= self.repairCost; self.health = self.maxHealth; self.updateHealthBar(); self.repairButton.visible = false; self.repairText.visible = false; } else if (self.health < self.maxHealth) { // Show warning above repair button if (self._insufficientWarning) { self.removeChild(self._insufficientWarning); self._insufficientWarning.destroy(); self._insufficientWarning = null; } var warning = new Text2('The balance is insufficient', { size: 24, fill: 0xff4444 }); warning.anchor.set(0.5, 1); warning.x = self.repairButton.x; warning.y = self.repairButton.y - 50; self.addChild(warning); self._insufficientWarning = warning; tween(warning, { alpha: 0 }, { duration: 1200, onFinish: function onFinish() { if (self && warning && warning.parent) { self.removeChild(warning); warning.destroy(); self._insufficientWarning = null; } } }); } }; self.fireRate = 60; self.fireCooldown = 0; self.range = 300; self.damage = 1; self.takeDamage = function (dmg) { self.health -= dmg; self.updateHealthBar(); if (self.health <= 0) { self.destroy(); var idx = towers.indexOf(self); if (idx !== -1) towers.splice(idx, 1); // Clean up from autoRepairActive array var autoIdx = autoRepairActive.indexOf(self); if (autoIdx !== -1) autoRepairActive.splice(autoIdx, 1); } }; self.update = function () { if (self.fireCooldown > 0) { self.fireCooldown--; return; } var closestZombie = null; var closestDistance = self.range; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var dx = zombie.x - self.x; var dy = zombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestZombie = zombie; } } if (closestZombie) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.setDirection(closestZombie.x, closestZombie.y); bullet.damage = self.damage; bullets.push(bullet); game.addChild(bullet); self.fireCooldown = self.fireRate; } // Hide repair button if not needed if (self.health >= self.maxHealth) { self.repairButton.visible = false; self.repairText.visible = false; } // --- AUTO REPAIR LOGIC --- if (self.autoRepair && self.health < self.maxHealth * 0.5) { // Only repair if not already at max and below 50% if (!self.repairIcon) { self.repairIcon = new Text2('🔧', { size: 48, fill: 0x00ffcc }); self.repairIcon.anchor.set(0.5, 0.5); self.repairIcon.y = -80; self.addChild(self.repairIcon); } // Check if we have enough coins for auto-repair (100 coins per repair) if (coinAmount >= 100) { coinAmount -= 100; // Deduct 100 coins for auto-repair self.health = self.maxHealth; self.updateHealthBar(); } else { // Not enough coins - show red repair icon to indicate failed repair attempt self.repairIcon.fill = 0xff0000; } } else if (self.repairIcon) { self.removeChild(self.repairIcon); self.repairIcon = null; } }; return self; }); var Trap = Container.expand(function () { var self = Container.call(this); var trapIndex = Math.floor(Math.random() * 5) + 1; var trapGraphics = self.attachAsset('trap' + trapIndex, { anchorX: 0.5, anchorY: 0.5 }); // Health bar self.maxHealth = 10; self.health = self.maxHealth; self.healthBar = new Text2('', { size: 20, fill: 0x00ff00 }); self.healthBar.anchor.set(0.5, 1); self.healthBar.y = -35; self.addChild(self.healthBar); self.updateHealthBar = function () { self.healthBar.setText(self.health + '/' + self.maxHealth); if (self.health > self.maxHealth * 0.5) { self.healthBar.fill = 0x00ff00; } else if (self.health > self.maxHealth * 0.25) { self.healthBar.fill = 0xffff00; } else { self.healthBar.fill = 0xff0000; } }; self.updateHealthBar(); self.takeDamage = function (dmg) { self.health -= dmg; self.updateHealthBar(); if (self.health <= 0) { self.destroy(); var idx = traps.indexOf(self); if (idx !== -1) traps.splice(idx, 1); } }; self.damage = 2; self.cooldown = 0; self.maxCooldown = 120; self.update = function () { if (self.cooldown > 0) { self.cooldown--; trapGraphics.alpha = 0.5; return; } trapGraphics.alpha = 1; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (self.intersects(zombie)) { zombie.takeDamage(self.damage); self.cooldown = self.maxCooldown; break; } } }; return self; }); var UpgradePanel = Container.expand(function () { var self = Container.call(this); var panelGraphics = self.attachAsset('upgradePanel', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1.2, // Extend 20% at the bottom width: 600, height: 800 * 1.2 // Also increase height property for consistency }); // Tower Attack Power Upgrade Button (2 rows above Tower button, y: -200 - 2*120 = -440) var towerPowerButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: -440 }); self.towerPowerText = new Text2('Tower Power + (100)', { size: 36, fill: 0xFFFFFF }); self.towerPowerText.anchor.set(0.5, 0.5); self.towerPowerText.y = -440; self.addChild(self.towerPowerText); // Tower Fire Rate Upgrade Button (1 row above Tower button, y: -200 - 120 = -320) var towerFireRateButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: -320 }); self.towerFireRateText = new Text2('Tower Fire Rate + (100)', { size: 36, fill: 0xFFFFFF }); self.towerFireRateText.anchor.set(0.5, 0.5); self.towerFireRateText.y = -320; self.addChild(self.towerFireRateText); var towerButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: -200 }); var trapButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: -50 }); var healthButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: 100 }); var closeButton = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, y: 250, scaleX: 0.3, scaleY: 0.8 }); self.towerText = new Text2('Tower - ' + towerCost + ' coins', { size: 40, fill: 0xFFFFFF }); self.towerText.anchor.set(0.5, 0.5); self.towerText.y = -200; self.addChild(self.towerText); self.trapText = new Text2('Trap - ' + trapCost + ' coins', { size: 40, fill: 0xFFFFFF }); self.trapText.anchor.set(0.5, 0.5); self.trapText.y = -50; self.addChild(self.trapText); self.healthText = new Text2('Repair Base - ' + healthCost + ' coins', { size: 40, fill: 0xFFFFFF }); self.healthText.anchor.set(0.5, 0.5); self.healthText.y = 100; self.addChild(self.healthText); self.closeText = new Text2('Close', { size: 40, fill: 0xFFFFFF }); self.closeText.anchor.set(0.5, 0.5); self.closeText.y = 250; self.addChild(self.closeText); // Helper to show insufficient balance warning above a button function showInsufficientBalanceWarning(parentContainer, button, yOffset) { // Remove any previous warning if (button._insufficientWarning) { parentContainer.removeChild(button._insufficientWarning); button._insufficientWarning.destroy(); button._insufficientWarning = null; } var warning = new Text2('The balance is insufficient', { size: 28, fill: 0xff4444 }); warning.anchor.set(0.5, 1); warning.x = button.x; warning.y = button.y - (yOffset || 70); parentContainer.addChild(warning); button._insufficientWarning = warning; // Fade out and destroy after 1.2s tween(warning, { alpha: 0 }, { duration: 1200, onFinish: function onFinish() { if (parentContainer && warning && warning.parent) { parentContainer.removeChild(warning); warning.destroy(); button._insufficientWarning = null; } } }); } towerButton.down = function () { if (coinAmount >= towerCost) { coinAmount -= towerCost; towerCost *= 2; self.towerText.setText('Tower - ' + towerCost + ' coins'); var tower = new Tower(); var angle = Math.random() * Math.PI * 2; var distance = 250 + Math.random() * 100; tower.x = base.x + Math.cos(angle) * distance; tower.y = base.y + Math.sin(angle) * distance; // Inherit auto-repair if already enabled if (typeof autoRepairActive !== "undefined" && autoRepairActive.length > 0) { tower.autoRepair = true; autoRepairActive.push(tower); } towers.push(tower); game.addChild(tower); LK.getSound('build').play(); self.visible = false; } else { showInsufficientBalanceWarning(self, towerButton, 70); } }; trapButton.down = function () { if (coinAmount >= trapCost) { coinAmount -= trapCost; trapCost *= 2; self.trapText.setText('Trap - ' + trapCost + ' coins'); var trap = new Trap(); var angle = Math.random() * Math.PI * 2; var distance = 200 + Math.random() * 150; trap.x = base.x + Math.cos(angle) * distance; trap.y = base.y + Math.sin(angle) * distance; traps.push(trap); game.addChild(trap); LK.getSound('build').play(); self.visible = false; } else { showInsufficientBalanceWarning(self, trapButton, 70); } }; healthButton.down = function () { if (coinAmount >= healthCost && base.health < base.maxHealth) { coinAmount -= healthCost; healthCost *= 2; self.healthText.setText('Repair Base - ' + healthCost + ' coins'); base.health = Math.min(base.health + 20, base.maxHealth); self.visible = false; } else if (base.health < base.maxHealth) { showInsufficientBalanceWarning(self, healthButton, 70); } }; closeButton.down = function () { self.visible = false; }; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieIndex = Math.floor(Math.random() * 10) + 1; var zombieGraphics = self.attachAsset('zombie' + zombieIndex, { anchorX: 0.5, anchorY: 0.5 }); self.health = 2; self.speed = 1.5; self.damage = 1; self.value = 10; // --- Health bar for zombie --- self.maxHealth = self.health; self.healthBarBg = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 12, color: 0x222222 }); self.healthBarBg.y = -60; self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('bullet', { anchorX: 0, anchorY: 0.5, width: 56, height: 8, color: 0x00ff00 }); self.healthBar.x = -28; self.healthBar.y = -60; self.addChild(self.healthBar); self.updateHealthBar = function () { // Clamp health if (self.health < 0) self.health = 0; if (typeof self.maxHealth === "undefined" || self.maxHealth <= 0) self.maxHealth = 1; var ratio = Math.max(0, Math.min(1, self.health / self.maxHealth)); self.healthBar.width = 38 * ratio; // Color: green >50%, yellow >25%, red <=25% if (ratio > 0.5) { self.healthBar.tint = 0x00ff00; } else if (ratio > 0.25) { self.healthBar.tint = 0xffff00; } else { self.healthBar.tint = 0xff0000; } // Hide if dead self.healthBar.visible = self.health > 0; self.healthBarBg.visible = self.health > 0; }; self.updateHealthBar(); self.update = function () { // Priority: nearest tower, then nearest trap, then base var target = null; var minDist = Infinity; // Find closest alive tower for (var i = 0; i < towers.length; i++) { var t = towers[i]; if (t.health > 0) { var dx = t.x - self.x; var dy = t.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = t; } } } // If no tower, find closest alive trap if (!target) { for (var i = 0; i < traps.length; i++) { var tr = traps[i]; if (tr.health > 0) { var dx = tr.x - self.x; var dy = tr.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; target = tr; } } } } // If no tower or trap, target base if (!target) { target = base; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate zombie to face movement direction zombieGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2; } self.updateHealthBar(); }; self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); tween(zombieGraphics, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { tween(zombieGraphics, { tint: 0xffffff }, { duration: 100 }); } }); if (self.health <= 0) { self.updateHealthBar(); return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a1a }); /**** * Game Code ****/ // Background music asset // Music toggle button images (simple on/off icons) // Background image for the game (full screen, 2048x2732, placeholder id) var zombies = []; var bullets = []; var coins = []; var towers = []; var traps = []; var base; var character; var upgradePanel; var drone; var coinAmount = 0; var towerCost = 50; var trapCost = 30; var healthCost = 20; var wave = 1; var zombiesInWave = 5; var zombiesSpawned = 0; var waveDelay = 180; var spawnTimer = 0; // Coin drop multiplier, increases by 50% after each wave var coinDropMultiplier = 1.0; // Upgrade costs var firepowerCost = 100; var firerateCost = 100; var autoRepairCost = 100; // --- Zombie Invasion Logic --- var zombieInvasionActive = false; var zombieInvasionWarning = null; var zombieInvasionSpawnPoint = { x: 0, y: 0 }; var zombieInvasionZombiesLeft = 0; // Add background image to the game scene, behind all gameplay elements var backgroundImage = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(backgroundImage); base = game.addChild(new Base()); base.x = 1024; base.y = 1366; character = game.addChild(new Character()); character.x = base.x; character.y = base.y - 150; drone = game.addChild(new Drone()); drone.x = base.x + 100; drone.y = base.y; upgradePanel = new UpgradePanel(); upgradePanel.x = 0; upgradePanel.y = 0; upgradePanel.visible = false; LK.gui.center.addChild(upgradePanel); var howToPlayPanel = new HowToPlayPanel(); howToPlayPanel.visible = false; LK.gui.center.addChild(howToPlayPanel); var coinText = new Text2('Coins: 0', { size: 60, fill: 0xFFFF00 }); coinText.anchor.set(0.5, 0); LK.gui.top.addChild(coinText); // Add reset button var resetButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.5, x: -350, y: 30 }); var resetText = new Text2('Reset', { size: 30, fill: 0xFFFFFF }); resetText.anchor.set(0.5, 0.5); resetText.x = -350; resetText.y = 30; LK.gui.top.addChild(resetButton); LK.gui.top.addChild(resetText); // Add How to Play button var howToPlayButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.5, x: 350, y: 30 }); var howToPlayText = new Text2('How to play?', { size: 28, fill: 0xFFFFFF }); howToPlayText.anchor.set(0.5, 0.5); howToPlayText.x = 350; howToPlayText.y = 30; LK.gui.top.addChild(howToPlayButton); LK.gui.top.addChild(howToPlayText); howToPlayButton.down = function () { howToPlayPanel.visible = true; }; // --- Music Toggle Button --- // 3 rows to the right of How to Play button (row = 100px, so +300px) var musicButtonX = 350 + 300; var musicButtonY = 30; var musicOnIcon = LK.getAsset('musicOnIcon', { anchorX: 0.5, anchorY: 0.5, x: musicButtonX, y: musicButtonY }); var musicOffIcon = LK.getAsset('musicOffIcon', { anchorX: 0.5, anchorY: 0.5, x: musicButtonX, y: musicButtonY }); musicOnIcon.visible = true; musicOffIcon.visible = false; LK.gui.top.addChild(musicOnIcon); LK.gui.top.addChild(musicOffIcon); var musicPlaying = true; LK.playMusic('bgmusic', { volume: 0.2 }); function updateMusicButton() { musicOnIcon.visible = musicPlaying; musicOffIcon.visible = !musicPlaying; } musicOnIcon.down = function () { LK.stopMusic(); musicPlaying = false; updateMusicButton(); }; musicOffIcon.down = function () { LK.playMusic('bgmusic', { volume: 0.2 }); musicPlaying = true; updateMusicButton(); }; resetButton.down = function () { // Clear all zombies for (var i = zombies.length - 1; i >= 0; i--) { zombies[i].destroy(); } zombies = []; // Clear all bullets for (var i = bullets.length - 1; i >= 0; i--) { bullets[i].destroy(); } bullets = []; // Clear all coins for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); } coins = []; // Clear all towers for (var i = towers.length - 1; i >= 0; i--) { towers[i].destroy(); } towers = []; autoRepairActive = []; // Clear all traps for (var i = traps.length - 1; i >= 0; i--) { traps[i].destroy(); } traps = []; // Reset game variables coinAmount = 0; towerCost = 50; trapCost = 30; healthCost = 20; wave = 1; zombiesInWave = 5; zombiesSpawned = 0; waveDelay = 180; spawnTimer = 0; coinDropMultiplier = 1.0; firepowerCost = 100; firerateCost = 100; autoRepairCost = 100; // Reset base health base.health = base.maxHealth; // Reset base repair button text baseRepairText.setText('Repair'); // Reset character stats character.fireRate = 40; character.damage = 1; // Reset upgrade panel texts upgradePanel.towerText.setText('Tower - ' + towerCost + ' coins'); upgradePanel.trapText.setText('Trap - ' + trapCost + ' coins'); upgradePanel.healthText.setText('Repair Base - ' + healthCost + ' coins'); firepowerText.setText('Fire Power + (100)'); firerateText.setText('Fire Rate + (100)'); autoRepairText.setText('Auto Repair: Off (100 x tower)'); // Hide upgrade panel upgradePanel.visible = false; }; var waveText = new Text2('Wave: 1', { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); waveText.y = 70; LK.gui.top.addChild(waveText); // Add info text one line below waveText (each line ~70px, so y = 70 + 1*70 = 140) var baseUpgradeInfoText = new Text2("Don't forget about the main building upgrades!", { size: 28, fill: 0xcccccc }); baseUpgradeInfoText.anchor.set(0.5, 0); baseUpgradeInfoText.y = 140; LK.gui.top.addChild(baseUpgradeInfoText); var healthText = new Text2('Base Health: 100', { size: 50, fill: 0x00FF00 }); healthText.anchor.set(0.5, 1); LK.gui.bottom.addChild(healthText); // Add info text above repair button var baseRepairInfoText = new Text2('10 HP = 50 coins, multiples allowed', { size: 22, fill: 0xcccccc }); baseRepairInfoText.anchor.set(0.5, 0.5); baseRepairInfoText.x = 0; baseRepairInfoText.y = -150; LK.gui.bottom.addChild(baseRepairInfoText); // Add repair button above Base Health text, moved 1 row up var baseRepairButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.5, x: 0, y: -90 }); var baseRepairText = new Text2('Repair', { size: 30, fill: 0xFFFFFF }); baseRepairText.anchor.set(0.5, 0.5); baseRepairText.x = 0; baseRepairText.y = -90; LK.gui.bottom.addChild(baseRepairButton); LK.gui.bottom.addChild(baseRepairText); baseRepairButton.down = function () { // Calculate how much health is missing var missingHealth = base.maxHealth - base.health; if (missingHealth > 0 && coinAmount >= 50) { // Calculate how much we can repair (10 HP per 50 coins) var repairAmount = Math.min(10, missingHealth); var cost = 50; // Check if we need multiple repairs to fully heal var totalRepairs = Math.ceil(missingHealth / 10); var totalCost = totalRepairs * 50; // If player has enough coins for full repair, do it if (coinAmount >= totalCost) { coinAmount -= totalCost; base.health = base.maxHealth; } else { // Otherwise repair as much as possible in 10 HP increments var possibleRepairs = Math.floor(coinAmount / 50); var actualRepairAmount = possibleRepairs * 10; actualRepairAmount = Math.min(actualRepairAmount, missingHealth); coinAmount -= possibleRepairs * 50; base.health = Math.min(base.health + actualRepairAmount, base.maxHealth); } } else if (missingHealth > 0) { showInsufficientBalanceWarning(LK.gui.bottom, baseRepairButton, 70); } }; // --- BASE UPGRADE PANEL (moved inside base upgrade popup) --- // Panel width/height and button size/spacing match main upgradePanel var panelWidth = 600; var panelHeight = 420; var bottomPanel = new Container(); var panelBg = LK.getAsset('upgradePanel', { anchorX: 0.5, anchorY: 0.5, width: panelWidth, height: panelHeight, x: 0, y: 0 }); bottomPanel.addChild(panelBg); // Use same button size as main upgradePanel (upgradeButton asset, no scale) var buttonWidth = 500; var buttonHeight = 100; var buttonSpacing = 20; var startY = -panelHeight / 2 + buttonHeight / 2 + 20; // Firepower Upgrade Button var firepowerButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: startY, width: buttonWidth, height: buttonHeight }); var firepowerText = new Text2('Fire Power + (100)', { size: 40, fill: 0xffffff }); firepowerText.anchor.set(0.5, 0.5); firepowerText.x = 0; firepowerText.y = startY; bottomPanel.addChild(firepowerButton); bottomPanel.addChild(firepowerText); // Fire Rate Upgrade Button var firerateButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: startY + buttonHeight + buttonSpacing, width: buttonWidth, height: buttonHeight }); var firerateText = new Text2('Fire Rate + (100)', { size: 40, fill: 0xffffff }); firerateText.anchor.set(0.5, 0.5); firerateText.x = 0; firerateText.y = startY + buttonHeight + buttonSpacing; bottomPanel.addChild(firerateButton); bottomPanel.addChild(firerateText); // Auto Repair Toggle Button var autoRepairActive = []; var autoRepairButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: startY + 2 * (buttonHeight + buttonSpacing), width: buttonWidth, height: buttonHeight }); var autoRepairText = new Text2('Auto Repair: Off (100 x tower)', { size: 30, // Reduced size to fit better in button fill: 0xffffff }); autoRepairText.anchor.set(0.5, 0.5); autoRepairText.x = 0; autoRepairText.y = startY + 2 * (buttonHeight + buttonSpacing); bottomPanel.addChild(autoRepairButton); bottomPanel.addChild(autoRepairText); // Close Button (moved to bottom, same size as others) var closeButtonY = startY + 3 * (buttonHeight + buttonSpacing); var closeButton = LK.getAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, x: 0, y: closeButtonY, width: buttonWidth, height: buttonHeight }); var closeText = new Text2('Close', { size: 40, fill: 0xffffff }); closeText.anchor.set(0.5, 0.5); closeText.x = 0; closeText.y = closeButtonY; bottomPanel.addChild(closeButton); bottomPanel.addChild(closeText); // Center and align bottomPanel background and content inside upgradePanel bottomPanel.x = 0; bottomPanel.y = panelHeight / 2 + 40; // Place just below main upgradePanel content, visually aligned panelBg.x = 0; panelBg.y = 0; // Center all buttons/texts horizontally (already x:0), ensure vertical stacking is visually centered firepowerButton.x = 0; firepowerText.x = 0; firerateButton.x = 0; firerateText.x = 0; autoRepairButton.x = 0; autoRepairText.x = 0; closeButton.x = 0; closeText.x = 0; // Add to upgradePanel upgradePanel.addChild(bottomPanel); // Close logic for new closeButton closeButton.down = function () { upgradePanel.visible = false; }; // --- BUTTON LOGIC (now inside base upgrade panel) --- firepowerButton.down = function () { if (coinAmount >= firepowerCost) { coinAmount -= firepowerCost; firepowerCost *= 2; firepowerText.setText('Fire Power + (' + firepowerCost + ')'); // Upgrade character and all towers character.damage = (character.damage || 1) + 1; for (var i = 0; i < towers.length; i++) { towers[i].damage = (towers[i].damage || 1) + 1; } } else { showInsufficientBalanceWarning(bottomPanel, firepowerButton, 70); } }; // Limit Fire Rate upgrade to a maximum of 8 levels var firerateUpgradeLevel = 0; firerateButton.down = function () { if (firerateUpgradeLevel >= 8) { firerateText.setText('Fire Rate MAX'); return; } if (coinAmount >= firerateCost) { coinAmount -= firerateCost; firerateCost *= 2; firerateUpgradeLevel++; if (firerateUpgradeLevel >= 8) { firerateText.setText('Fire Rate MAX'); } else { firerateText.setText('Fire Rate + (' + firerateCost + ')'); } // Upgrade character and all towers character.fireRate = Math.max(5, Math.floor(character.fireRate * 0.85)); for (var i = 0; i < towers.length; i++) { towers[i].fireRate = Math.max(5, Math.floor(towers[i].fireRate * 0.85)); } } else { showInsufficientBalanceWarning(bottomPanel, firerateButton, 70); } }; // --- Tower Power Upgrade Logic --- var towerPowerUpgradeLevel = 0; var towerPowerUpgradeCost = 100; upgradePanel.towerPowerText.setText('Tower Power + (' + towerPowerUpgradeCost + ')'); // Assign .down directly to the correct button asset (towerPowerButton) upgradePanel.children.forEach(function (child) { if (child.anchorY === 0.5 && child.y === -440 && child.width === 500 && child.height === 100) { child.down = function () { if (coinAmount >= towerPowerUpgradeCost) { coinAmount -= towerPowerUpgradeCost; towerPowerUpgradeCost *= 2; towerPowerUpgradeLevel++; upgradePanel.towerPowerText.setText('Tower Power + (' + towerPowerUpgradeCost + ')'); for (var i = 0; i < towers.length; i++) { towers[i].damage = (towers[i].damage || 1) + 1; } } else { showInsufficientBalanceWarning(upgradePanel, child, 70); } }; } }); // --- Tower Fire Rate Upgrade Logic (max 5 levels) --- var towerFireRateUpgradeLevel = 0; var towerFireRateUpgradeCost = 100; upgradePanel.towerFireRateText.setText('Tower Fire Rate + (' + towerFireRateUpgradeCost + ')'); // Assign .down directly to the correct button asset (towerFireRateButton) upgradePanel.children.forEach(function (child) { if (child.anchorY === 0.5 && child.y === -320 && child.width === 500 && child.height === 100) { child.down = function () { if (towerFireRateUpgradeLevel >= 5) { upgradePanel.towerFireRateText.setText('Tower Fire Rate MAX'); return; } if (coinAmount >= towerFireRateUpgradeCost) { coinAmount -= towerFireRateUpgradeCost; towerFireRateUpgradeCost *= 2; towerFireRateUpgradeLevel++; if (towerFireRateUpgradeLevel >= 5) { upgradePanel.towerFireRateText.setText('Tower Fire Rate MAX'); } else { upgradePanel.towerFireRateText.setText('Tower Fire Rate + (' + towerFireRateUpgradeCost + ')'); } for (var i = 0; i < towers.length; i++) { towers[i].fireRate = Math.max(5, Math.floor(towers[i].fireRate * 0.85)); } } else { showInsufficientBalanceWarning(upgradePanel, child, 70); } }; } }); autoRepairButton.down = function () { // Check if any towers have auto-repair enabled var hasAutoRepair = false; for (var i = 0; i < towers.length; i++) { if (towers[i].autoRepair) { hasAutoRepair = true; break; } } if (hasAutoRepair) { // Disable auto-repair for all towers for (var i = 0; i < towers.length; i++) { towers[i].autoRepair = false; } autoRepairActive = []; autoRepairText.setText('Auto Repair: Off (100 x tower)'); } else if (towers.length > 0) { // Enable auto-repair if we have enough coins var needed = towers.length * autoRepairCost; if (coinAmount >= needed) { coinAmount -= needed; // Mark all towers as auto-repair enabled for (var i = 0; i < towers.length; i++) { towers[i].autoRepair = true; } autoRepairActive = towers; autoRepairText.setText('Auto Repair: On'); } else { showInsufficientBalanceWarning(bottomPanel, autoRepairButton, 70); } } }; game.down = function (x, y, obj) { if (!upgradePanel.visible) { character.shoot(x, y); } }; game.update = function () { coinText.setText('Coins: ' + coinAmount); waveText.setText('Wave: ' + wave); healthText.setText('Base Health: ' + base.health); if (base.health > 50) { healthText.fill = 0x00ff00; } else if (base.health > 20) { healthText.fill = 0xffff00; } else { healthText.fill = 0xff0000; } // Update auto-repair button text based on current state var hasAutoRepair = false; for (var i = 0; i < towers.length; i++) { if (towers[i].autoRepair) { hasAutoRepair = true; break; } } if (hasAutoRepair) { autoRepairText.setText('Auto Repair: On'); } else { autoRepairText.setText('Auto Repair: Off (100 x tower)'); } // --- Zombie Invasion Event Logic --- if (zombieInvasionActive) { // Show warning text if not already shown if (!zombieInvasionWarning) { // Place warning text 4 lines below waveText (each line ~70px, so y = waveText.y + 4*70) zombieInvasionWarning = new Text2('Zombie invasion!', { size: 60, fill: 0xFF0000 }); zombieInvasionWarning.anchor.set(0.5, 0); zombieInvasionWarning.y = waveText.y + 4 * 70; LK.gui.top.addChild(zombieInvasionWarning); } // Spawn BossZombie if not already spawned if (zombieInvasionZombiesLeft > 0 && zombies.length < 1) { var boss = new BossZombie(); var scale = Math.pow(1.15, wave - 1); boss.health = Math.round(boss.health * scale); boss.speed = boss.speed * scale; boss.damage = Math.round(boss.damage * scale); boss.value = Math.round(boss.value * scale); boss.x = zombieInvasionSpawnPoint.x; boss.y = zombieInvasionSpawnPoint.y; zombies.push(boss); game.addChild(boss); zombieInvasionZombiesLeft = 0; } // If all zombies are dead, remove warning and end invasion if (zombies.length === 0) { if (zombieInvasionWarning) { LK.gui.top.removeChild(zombieInvasionWarning); zombieInvasionWarning.destroy(); zombieInvasionWarning = null; } zombieInvasionActive = false; // Prepare for next wave as usual wave++; zombiesInWave = Math.round((5 + wave * 2) * Math.pow(1.15, wave - 1)); zombiesSpawned = 0; waveDelay = 180; coinDropMultiplier *= 1.5; } } else { // Normal wave logic if (waveDelay > 0) { waveDelay--; } else if (zombiesSpawned < zombiesInWave) { spawnTimer++; if (spawnTimer >= 60) { spawnTimer = 0; zombiesSpawned++; var zombie = new Zombie(); var scale = Math.pow(1.15, wave - 1); zombie.health = Math.round(zombie.health * scale); zombie.speed = zombie.speed * scale; zombie.damage = Math.round(zombie.damage * scale); zombie.value = Math.round(zombie.value * scale); // Spawn zombies from outside the map edges var edge = Math.floor(Math.random() * 4); // 0: left, 1: right, 2: top, 3: bottom var spawnX, spawnY; if (edge === 0) { // left spawnX = -100; spawnY = Math.random() * 2732; } else if (edge === 1) { // right spawnX = 2048 + 100; spawnY = Math.random() * 2732; } else if (edge === 2) { // top spawnX = Math.random() * 2048; spawnY = -100; } else { // bottom spawnX = Math.random() * 2048; spawnY = 2732 + 100; } zombie.x = spawnX; zombie.y = spawnY; zombies.push(zombie); game.addChild(zombie); } } else if (zombies.length === 0) { // Check for zombie invasion trigger if (wave % 5 === 0) { zombieInvasionActive = true; // Spawn point: center of top edge (x = 1024, y = -100) zombieInvasionSpawnPoint.x = 1024; zombieInvasionSpawnPoint.y = -100; zombieInvasionZombiesLeft = 1; // Only 1 boss per invasion // Remove any previous warning just in case if (zombieInvasionWarning) { LK.gui.top.removeChild(zombieInvasionWarning); zombieInvasionWarning.destroy(); zombieInvasionWarning = null; } } else { wave++; zombiesInWave = Math.round((5 + wave * 2) * Math.pow(1.15, wave - 1)); zombiesSpawned = 0; waveDelay = 180; // Increase coin drop multiplier by 50% after each wave coinDropMultiplier *= 1.5; } } } for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; var attacked = false; // Check towers for (var ti = 0; ti < towers.length; ti++) { var tower = towers[ti]; if (tower.health > 0 && zombie.intersects(tower)) { tower.takeDamage(zombie.damage); zombie.destroy(); zombies.splice(i, 1); attacked = true; break; } } if (attacked) continue; // Check traps for (var trpi = 0; trpi < traps.length; trpi++) { var trap = traps[trpi]; if (trap.health > 0 && zombie.intersects(trap)) { trap.takeDamage(zombie.damage); zombie.destroy(); zombies.splice(i, 1); attacked = true; break; } } if (attacked) continue; // Check base if (zombie.intersects(base)) { base.takeDamage(zombie.damage); zombie.destroy(); zombies.splice(i, 1); continue; } for (var j = bullets.length - 1; j >= 0; j--) { var bullet = bullets[j]; if (zombie.intersects(bullet)) { LK.getSound('zombieHit').play(); // --- Explosion effect --- // Play explosion sound exactly when explosion visual is created LK.getSound('explosion').play(); var explosion = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: bullet.x, y: bullet.y, scaleX: 0.5, scaleY: 0.5, alpha: 0.7, tint: 0xffcc00 }); game.addChild(explosion); tween(explosion, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 250, onFinish: function onFinish() { if (explosion && explosion.parent) { explosion.destroy(); } } }); // --- End explosion effect --- if (zombie.takeDamage(bullet.damage)) { var coin = new Coin(); coin.x = zombie.x; coin.y = zombie.y; // Apply coin drop multiplier coin.value = Math.round(zombie.value * coinDropMultiplier); coins.push(coin); game.addChild(coin); zombie.destroy(); zombies.splice(i, 1); } bullet.destroy(); bullets.splice(j, 1); break; } } } for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (bullet.x < -100 || bullet.x > 2148 || bullet.y < -100 || bullet.y > 2832) { bullet.destroy(); bullets.splice(i, 1); } } for (var i = coins.length - 1; i >= 0; i--) { var coin = coins[i]; if (coin.lifetime <= 0) { coin.destroy(); coins.splice(i, 1); continue; } var dx = character.x - coin.x; var dy = character.y - coin.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 100) { coinAmount += coin.value; LK.getSound('coinCollect').play(); coin.destroy(); coins.splice(i, 1); continue; } // Drone coin collection var droneDx = drone.x - coin.x; var droneDy = drone.y - coin.y; var droneDistance = Math.sqrt(droneDx * droneDx + droneDy * droneDy); if (droneDistance < drone.collectRange) { coinAmount += coin.value; LK.getSound('coinCollect').play(); coin.destroy(); coins.splice(i, 1); } } }; // Helper to show insufficient balance warning above a button (global version for all panels) function showInsufficientBalanceWarning(parentContainer, button, yOffset) { // Remove any previous warning if (button._insufficientWarning) { parentContainer.removeChild(button._insufficientWarning); button._insufficientWarning.destroy(); button._insufficientWarning = null; } var warning = new Text2('The balance is insufficient', { size: 28, fill: 0xff4444 }); warning.anchor.set(0.5, 1); warning.x = button.x; warning.y = button.y - (yOffset || 70); parentContainer.addChild(warning); button._insufficientWarning = warning; // Fade out and destroy after 1.2s tween(warning, { alpha: 0 }, { duration: 1200, onFinish: function onFinish() { if (parentContainer && warning && warning.parent) { parentContainer.removeChild(warning); warning.destroy(); button._insufficientWarning = null; } } }); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Base = Container.expand(function () {
var self = Container.call(this);
var baseGraphics = self.attachAsset('base', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.takeDamage = function (damage) {
self.health -= damage;
tween(baseGraphics, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(baseGraphics, {
tint: 0xffffff
}, {
duration: 100
});
}
});
if (self.health <= 0) {
LK.showGameOver();
}
};
self.down = function (x, y, obj) {
if (!upgradePanel.visible) {
upgradePanel.visible = true;
} else {
upgradePanel.visible = false;
}
};
return self;
});
// BossZombie class for invasion event
var BossZombie = Container.expand(function () {
var self = Container.call(this);
// Use zombie1 as base, but scale up
var bossGraphics = self.attachAsset('zombie1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.health = 40; // 20x normal zombie health (normal is 2)
self.maxHealth = self.health;
self.speed = 1.2; // Slightly slower than normal zombie
self.damage = 10; // Big damage to base/towers
self.value = 200; // Big coin reward
// --- Health bar for boss zombie ---
self.healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 160,
// Increased width
height: 24,
// Increased height
color: 0x222222
});
self.healthBarBg.y = -120; // Move up slightly to fit larger bar
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0.5,
width: 152,
// Increased width
height: 18,
// Increased height
color: 0x00ff00
});
self.healthBar.x = -80; // Adjusted for new width
self.healthBar.y = -120;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
// Clamp health
if (self.health < 0) self.health = 0;
if (typeof self.maxHealth === "undefined" || self.maxHealth <= 0) self.maxHealth = 1;
var ratio = Math.max(0, Math.min(1, self.health / self.maxHealth));
self.healthBar.width = 152 * ratio;
// Color: green >50%, yellow >25%, red <=25%
if (ratio > 0.5) {
self.healthBar.tint = 0x00ff00;
} else if (ratio > 0.25) {
self.healthBar.tint = 0xffff00;
} else {
self.healthBar.tint = 0xff0000;
}
// Hide if dead
self.healthBar.visible = self.health > 0;
self.healthBarBg.visible = self.health > 0;
};
self.updateHealthBar();
self.update = function () {
// Priority: nearest tower, then nearest trap, then base
var target = null;
var minDist = Infinity;
for (var i = 0; i < towers.length; i++) {
var t = towers[i];
if (t.health > 0) {
var dx = t.x - self.x;
var dy = t.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = t;
}
}
}
if (!target) {
for (var i = 0; i < traps.length; i++) {
var tr = traps[i];
if (tr.health > 0) {
var dx = tr.x - self.x;
var dy = tr.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = tr;
}
}
}
}
if (!target) {
target = base;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
bossGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
self.updateHealthBar();
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
tween(bossGraphics, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(bossGraphics, {
tint: 0xffffff
}, {
duration: 100
});
}
});
if (self.health <= 0) {
self.updateHealthBar();
return true;
}
return false;
};
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 = 15;
self.damage = 1;
self.dx = 0;
self.dy = 0;
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.dx = dx / distance * self.speed;
self.dy = dy / distance * self.speed;
self.rotation = Math.atan2(dy, dx);
if (bulletGraphics) {
bulletGraphics.rotation = self.rotation;
}
}
};
self.update = function () {
self.x += self.dx;
self.y += self.dy;
if (typeof self.rotation !== "undefined" && bulletGraphics) {
bulletGraphics.rotation = self.rotation;
}
};
return self;
});
var Character = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
self.fireRate = 40;
self.fireCooldown = 0;
self.update = function () {
if (self.fireCooldown > 0) {
self.fireCooldown--;
}
// Find closest zombie for automatic shooting
if (self.fireCooldown === 0 && zombies.length > 0) {
var closestZombie = null;
var closestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestZombie = zombie;
}
}
if (closestZombie) {
self.shoot(closestZombie.x, closestZombie.y);
}
}
};
self.shoot = function (targetX, targetY) {
if (self.fireCooldown > 0) return;
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.setDirection(targetX, targetY);
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.fireCooldown = self.fireRate;
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 10;
self.lifetime = 300;
self.update = function () {
self.lifetime--;
coinGraphics.alpha = Math.min(1, self.lifetime / 100);
};
return self;
});
var Drone = Container.expand(function () {
var self = Container.call(this);
var vacuumField = self.attachAsset('vacuumField', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.1,
scaleX: 0.445,
scaleY: 0.445
});
var droneGraphics = self.attachAsset('drone', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.target = null;
self.collectRange = 22; // Reduced from 50 to achieve 80% area reduction
self.vacuumRange = 89; // Reduced from 200 to achieve 80% area reduction
self.vacuumPower = 0.15; // Strength of vacuum pull
self.floatOffset = 0;
self.floatSpeed = 0.05;
self.vx = 0;
self.vy = 0;
self.update = function () {
// Floating animation
self.floatOffset += self.floatSpeed;
droneGraphics.y = Math.sin(self.floatOffset) * 10;
// Vacuum field pulsing animation
vacuumField.scaleX = 0.445 + Math.sin(self.floatOffset * 2) * 0.045;
vacuumField.scaleY = 0.445 + Math.sin(self.floatOffset * 2) * 0.045;
vacuumField.alpha = 0.05 + Math.sin(self.floatOffset * 3) * 0.03;
// Find closest coin
var closestCoin = null;
var closestDistance = Infinity;
for (var i = 0; i < coins.length; i++) {
var coin = coins[i];
var dx = coin.x - self.x;
var dy = coin.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestCoin = coin;
}
}
self.target = closestCoin;
// Calculate desired velocity towards target
var desiredVx = 0;
var desiredVy = 0;
if (self.target) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.collectRange) {
desiredVx = dx / distance * self.speed;
desiredVy = dy / distance * self.speed;
}
} else {
// Return to base when no coins
var dx = base.x - self.x;
var dy = base.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 150) {
desiredVx = dx / distance * self.speed;
desiredVy = dy / distance * self.speed;
}
}
// Smoothly interpolate current velocity towards desired velocity
self.vx = self.vx * 0.85 + desiredVx * 0.15;
self.vy = self.vy * 0.85 + desiredVy * 0.15;
// Apply smoothed velocity
self.x += self.vx;
self.y += self.vy;
// Rotate drone based on movement direction
if (Math.abs(self.vx) > 0.1 || Math.abs(self.vy) > 0.1) {
droneGraphics.rotation = Math.atan2(self.vy, self.vx);
}
// Apply vacuum effect to nearby coins
for (var i = 0; i < coins.length; i++) {
var coin = coins[i];
var dx = self.x - coin.x;
var dy = self.y - coin.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.vacuumRange && distance > 0) {
// Pull coins towards drone
var pullStrength = (1 - distance / self.vacuumRange) * self.vacuumPower;
coin.x += dx / distance * pullStrength * distance;
coin.y += dy / distance * pullStrength * distance;
}
}
};
return self;
});
var HowToPlayPanel = Container.expand(function () {
var self = Container.call(this);
// Background panel
var bg = self.attachAsset('upgradePanel', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.5
});
bg.tint = 0x222222;
// Title
var title = new Text2('How to Play', {
size: 60,
fill: 0xFFFF00
});
title.anchor.set(0.5, 0.5);
title.y = -400;
self.addChild(title);
// Game info text
var infoText = new Text2('Welcome to Zombie Defense!\n\n' + '• Tap anywhere to shoot zombies\n' + '• Collect coins from defeated zombies\n' + '• Click the base to open upgrade menu\n' + '• Build towers and traps for defense\n' + '• Survive as many waves as possible!\n\n' + 'Upgrades:\n' + '• Towers - Auto-shoot nearby zombies\n' + '• Traps - Damage zombies on contact\n' + '• Base Repair - Restore base health\n' + '• Fire Power - Increase damage\n' + '• Fire Rate - Shoot faster\n' + '• Auto Repair - Towers repair at 50% HP\n\n' + 'Boss Zombie:\n' + '• Every 5th wave, a giant Boss Zombie will attack!\n' + '• Boss Zombie is much stronger and bigger than normal zombies\n' + '• Defeat the Boss to continue to the next waves\n\n' + 'Tips:\n' + '• Drone automatically collects coins\n' + '• Each wave gets harder\n' + '• Coin rewards increase each wave', {
size: 32,
fill: 0xFFFFFF,
align: 'left',
wordWrap: true,
wordWrapWidth: 700
});
infoText.anchor.set(0.5, 0);
infoText.y = -320;
self.addChild(infoText);
// Close button
// Move to top row above the panel title (e.g. y = -500, above title at y = -400)
var closeBtn = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: -500,
scaleX: 0.4,
scaleY: 0.6
});
var closeTxt = new Text2('Close', {
size: 40,
fill: 0xFFFFFF
});
closeTxt.anchor.set(0.5, 0.5);
closeTxt.y = -500;
self.addChild(closeTxt);
closeBtn.down = function () {
self.visible = false;
};
return self;
});
var Tower = Container.expand(function () {
var self = Container.call(this);
var towerIndex = Math.floor(Math.random() * 10) + 1;
var towerGraphics = self.attachAsset('tower' + towerIndex, {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar
self.maxHealth = 20;
self.health = self.maxHealth;
self.healthBar = new Text2('', {
size: 24,
fill: 0x00ff00
});
self.healthBar.anchor.set(0.5, 1);
self.healthBar.y = -50;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
self.healthBar.setText(self.health + '/' + self.maxHealth);
if (self.health > self.maxHealth * 0.5) {
self.healthBar.fill = 0x00ff00;
} else if (self.health > self.maxHealth * 0.25) {
self.healthBar.fill = 0xffff00;
} else {
self.healthBar.fill = 0xff0000;
}
};
self.updateHealthBar();
// Repair button
self.repairCost = 30;
self.repairButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: 60,
scaleX: 0.5,
scaleY: 0.5
});
self.repairText = new Text2('Tamir: ' + self.repairCost, {
size: 28,
fill: 0xffffff
});
self.repairText.anchor.set(0.5, 0.5);
self.repairText.y = 60;
self.addChild(self.repairText);
self.repairButton.visible = false;
self.repairText.visible = false;
self.down = function (x, y, obj) {
// Show repair button if tower is damaged
if (self.health < self.maxHealth) {
self.repairButton.visible = true;
self.repairText.visible = true;
}
};
self.repairButton.down = function () {
if (coinAmount >= self.repairCost && self.health < self.maxHealth) {
coinAmount -= self.repairCost;
self.health = self.maxHealth;
self.updateHealthBar();
self.repairButton.visible = false;
self.repairText.visible = false;
} else if (self.health < self.maxHealth) {
// Show warning above repair button
if (self._insufficientWarning) {
self.removeChild(self._insufficientWarning);
self._insufficientWarning.destroy();
self._insufficientWarning = null;
}
var warning = new Text2('The balance is insufficient', {
size: 24,
fill: 0xff4444
});
warning.anchor.set(0.5, 1);
warning.x = self.repairButton.x;
warning.y = self.repairButton.y - 50;
self.addChild(warning);
self._insufficientWarning = warning;
tween(warning, {
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (self && warning && warning.parent) {
self.removeChild(warning);
warning.destroy();
self._insufficientWarning = null;
}
}
});
}
};
self.fireRate = 60;
self.fireCooldown = 0;
self.range = 300;
self.damage = 1;
self.takeDamage = function (dmg) {
self.health -= dmg;
self.updateHealthBar();
if (self.health <= 0) {
self.destroy();
var idx = towers.indexOf(self);
if (idx !== -1) towers.splice(idx, 1);
// Clean up from autoRepairActive array
var autoIdx = autoRepairActive.indexOf(self);
if (autoIdx !== -1) autoRepairActive.splice(autoIdx, 1);
}
};
self.update = function () {
if (self.fireCooldown > 0) {
self.fireCooldown--;
return;
}
var closestZombie = null;
var closestDistance = self.range;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestZombie = zombie;
}
}
if (closestZombie) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.setDirection(closestZombie.x, closestZombie.y);
bullet.damage = self.damage;
bullets.push(bullet);
game.addChild(bullet);
self.fireCooldown = self.fireRate;
}
// Hide repair button if not needed
if (self.health >= self.maxHealth) {
self.repairButton.visible = false;
self.repairText.visible = false;
}
// --- AUTO REPAIR LOGIC ---
if (self.autoRepair && self.health < self.maxHealth * 0.5) {
// Only repair if not already at max and below 50%
if (!self.repairIcon) {
self.repairIcon = new Text2('🔧', {
size: 48,
fill: 0x00ffcc
});
self.repairIcon.anchor.set(0.5, 0.5);
self.repairIcon.y = -80;
self.addChild(self.repairIcon);
}
// Check if we have enough coins for auto-repair (100 coins per repair)
if (coinAmount >= 100) {
coinAmount -= 100; // Deduct 100 coins for auto-repair
self.health = self.maxHealth;
self.updateHealthBar();
} else {
// Not enough coins - show red repair icon to indicate failed repair attempt
self.repairIcon.fill = 0xff0000;
}
} else if (self.repairIcon) {
self.removeChild(self.repairIcon);
self.repairIcon = null;
}
};
return self;
});
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapIndex = Math.floor(Math.random() * 5) + 1;
var trapGraphics = self.attachAsset('trap' + trapIndex, {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar
self.maxHealth = 10;
self.health = self.maxHealth;
self.healthBar = new Text2('', {
size: 20,
fill: 0x00ff00
});
self.healthBar.anchor.set(0.5, 1);
self.healthBar.y = -35;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
self.healthBar.setText(self.health + '/' + self.maxHealth);
if (self.health > self.maxHealth * 0.5) {
self.healthBar.fill = 0x00ff00;
} else if (self.health > self.maxHealth * 0.25) {
self.healthBar.fill = 0xffff00;
} else {
self.healthBar.fill = 0xff0000;
}
};
self.updateHealthBar();
self.takeDamage = function (dmg) {
self.health -= dmg;
self.updateHealthBar();
if (self.health <= 0) {
self.destroy();
var idx = traps.indexOf(self);
if (idx !== -1) traps.splice(idx, 1);
}
};
self.damage = 2;
self.cooldown = 0;
self.maxCooldown = 120;
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
trapGraphics.alpha = 0.5;
return;
}
trapGraphics.alpha = 1;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (self.intersects(zombie)) {
zombie.takeDamage(self.damage);
self.cooldown = self.maxCooldown;
break;
}
}
};
return self;
});
var UpgradePanel = Container.expand(function () {
var self = Container.call(this);
var panelGraphics = self.attachAsset('upgradePanel', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1.2,
// Extend 20% at the bottom
width: 600,
height: 800 * 1.2 // Also increase height property for consistency
});
// Tower Attack Power Upgrade Button (2 rows above Tower button, y: -200 - 2*120 = -440)
var towerPowerButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: -440
});
self.towerPowerText = new Text2('Tower Power + (100)', {
size: 36,
fill: 0xFFFFFF
});
self.towerPowerText.anchor.set(0.5, 0.5);
self.towerPowerText.y = -440;
self.addChild(self.towerPowerText);
// Tower Fire Rate Upgrade Button (1 row above Tower button, y: -200 - 120 = -320)
var towerFireRateButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: -320
});
self.towerFireRateText = new Text2('Tower Fire Rate + (100)', {
size: 36,
fill: 0xFFFFFF
});
self.towerFireRateText.anchor.set(0.5, 0.5);
self.towerFireRateText.y = -320;
self.addChild(self.towerFireRateText);
var towerButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: -200
});
var trapButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: -50
});
var healthButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: 100
});
var closeButton = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
y: 250,
scaleX: 0.3,
scaleY: 0.8
});
self.towerText = new Text2('Tower - ' + towerCost + ' coins', {
size: 40,
fill: 0xFFFFFF
});
self.towerText.anchor.set(0.5, 0.5);
self.towerText.y = -200;
self.addChild(self.towerText);
self.trapText = new Text2('Trap - ' + trapCost + ' coins', {
size: 40,
fill: 0xFFFFFF
});
self.trapText.anchor.set(0.5, 0.5);
self.trapText.y = -50;
self.addChild(self.trapText);
self.healthText = new Text2('Repair Base - ' + healthCost + ' coins', {
size: 40,
fill: 0xFFFFFF
});
self.healthText.anchor.set(0.5, 0.5);
self.healthText.y = 100;
self.addChild(self.healthText);
self.closeText = new Text2('Close', {
size: 40,
fill: 0xFFFFFF
});
self.closeText.anchor.set(0.5, 0.5);
self.closeText.y = 250;
self.addChild(self.closeText);
// Helper to show insufficient balance warning above a button
function showInsufficientBalanceWarning(parentContainer, button, yOffset) {
// Remove any previous warning
if (button._insufficientWarning) {
parentContainer.removeChild(button._insufficientWarning);
button._insufficientWarning.destroy();
button._insufficientWarning = null;
}
var warning = new Text2('The balance is insufficient', {
size: 28,
fill: 0xff4444
});
warning.anchor.set(0.5, 1);
warning.x = button.x;
warning.y = button.y - (yOffset || 70);
parentContainer.addChild(warning);
button._insufficientWarning = warning;
// Fade out and destroy after 1.2s
tween(warning, {
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (parentContainer && warning && warning.parent) {
parentContainer.removeChild(warning);
warning.destroy();
button._insufficientWarning = null;
}
}
});
}
towerButton.down = function () {
if (coinAmount >= towerCost) {
coinAmount -= towerCost;
towerCost *= 2;
self.towerText.setText('Tower - ' + towerCost + ' coins');
var tower = new Tower();
var angle = Math.random() * Math.PI * 2;
var distance = 250 + Math.random() * 100;
tower.x = base.x + Math.cos(angle) * distance;
tower.y = base.y + Math.sin(angle) * distance;
// Inherit auto-repair if already enabled
if (typeof autoRepairActive !== "undefined" && autoRepairActive.length > 0) {
tower.autoRepair = true;
autoRepairActive.push(tower);
}
towers.push(tower);
game.addChild(tower);
LK.getSound('build').play();
self.visible = false;
} else {
showInsufficientBalanceWarning(self, towerButton, 70);
}
};
trapButton.down = function () {
if (coinAmount >= trapCost) {
coinAmount -= trapCost;
trapCost *= 2;
self.trapText.setText('Trap - ' + trapCost + ' coins');
var trap = new Trap();
var angle = Math.random() * Math.PI * 2;
var distance = 200 + Math.random() * 150;
trap.x = base.x + Math.cos(angle) * distance;
trap.y = base.y + Math.sin(angle) * distance;
traps.push(trap);
game.addChild(trap);
LK.getSound('build').play();
self.visible = false;
} else {
showInsufficientBalanceWarning(self, trapButton, 70);
}
};
healthButton.down = function () {
if (coinAmount >= healthCost && base.health < base.maxHealth) {
coinAmount -= healthCost;
healthCost *= 2;
self.healthText.setText('Repair Base - ' + healthCost + ' coins');
base.health = Math.min(base.health + 20, base.maxHealth);
self.visible = false;
} else if (base.health < base.maxHealth) {
showInsufficientBalanceWarning(self, healthButton, 70);
}
};
closeButton.down = function () {
self.visible = false;
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieIndex = Math.floor(Math.random() * 10) + 1;
var zombieGraphics = self.attachAsset('zombie' + zombieIndex, {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.speed = 1.5;
self.damage = 1;
self.value = 10;
// --- Health bar for zombie ---
self.maxHealth = self.health;
self.healthBarBg = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 12,
color: 0x222222
});
self.healthBarBg.y = -60;
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('bullet', {
anchorX: 0,
anchorY: 0.5,
width: 56,
height: 8,
color: 0x00ff00
});
self.healthBar.x = -28;
self.healthBar.y = -60;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
// Clamp health
if (self.health < 0) self.health = 0;
if (typeof self.maxHealth === "undefined" || self.maxHealth <= 0) self.maxHealth = 1;
var ratio = Math.max(0, Math.min(1, self.health / self.maxHealth));
self.healthBar.width = 38 * ratio;
// Color: green >50%, yellow >25%, red <=25%
if (ratio > 0.5) {
self.healthBar.tint = 0x00ff00;
} else if (ratio > 0.25) {
self.healthBar.tint = 0xffff00;
} else {
self.healthBar.tint = 0xff0000;
}
// Hide if dead
self.healthBar.visible = self.health > 0;
self.healthBarBg.visible = self.health > 0;
};
self.updateHealthBar();
self.update = function () {
// Priority: nearest tower, then nearest trap, then base
var target = null;
var minDist = Infinity;
// Find closest alive tower
for (var i = 0; i < towers.length; i++) {
var t = towers[i];
if (t.health > 0) {
var dx = t.x - self.x;
var dy = t.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = t;
}
}
}
// If no tower, find closest alive trap
if (!target) {
for (var i = 0; i < traps.length; i++) {
var tr = traps[i];
if (tr.health > 0) {
var dx = tr.x - self.x;
var dy = tr.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
target = tr;
}
}
}
}
// If no tower or trap, target base
if (!target) {
target = base;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate zombie to face movement direction
zombieGraphics.rotation = Math.atan2(dy, dx) + Math.PI / 2;
}
self.updateHealthBar();
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
tween(zombieGraphics, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(zombieGraphics, {
tint: 0xffffff
}, {
duration: 100
});
}
});
if (self.health <= 0) {
self.updateHealthBar();
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Background music asset
// Music toggle button images (simple on/off icons)
// Background image for the game (full screen, 2048x2732, placeholder id)
var zombies = [];
var bullets = [];
var coins = [];
var towers = [];
var traps = [];
var base;
var character;
var upgradePanel;
var drone;
var coinAmount = 0;
var towerCost = 50;
var trapCost = 30;
var healthCost = 20;
var wave = 1;
var zombiesInWave = 5;
var zombiesSpawned = 0;
var waveDelay = 180;
var spawnTimer = 0;
// Coin drop multiplier, increases by 50% after each wave
var coinDropMultiplier = 1.0;
// Upgrade costs
var firepowerCost = 100;
var firerateCost = 100;
var autoRepairCost = 100;
// --- Zombie Invasion Logic ---
var zombieInvasionActive = false;
var zombieInvasionWarning = null;
var zombieInvasionSpawnPoint = {
x: 0,
y: 0
};
var zombieInvasionZombiesLeft = 0;
// Add background image to the game scene, behind all gameplay elements
var backgroundImage = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(backgroundImage);
base = game.addChild(new Base());
base.x = 1024;
base.y = 1366;
character = game.addChild(new Character());
character.x = base.x;
character.y = base.y - 150;
drone = game.addChild(new Drone());
drone.x = base.x + 100;
drone.y = base.y;
upgradePanel = new UpgradePanel();
upgradePanel.x = 0;
upgradePanel.y = 0;
upgradePanel.visible = false;
LK.gui.center.addChild(upgradePanel);
var howToPlayPanel = new HowToPlayPanel();
howToPlayPanel.visible = false;
LK.gui.center.addChild(howToPlayPanel);
var coinText = new Text2('Coins: 0', {
size: 60,
fill: 0xFFFF00
});
coinText.anchor.set(0.5, 0);
LK.gui.top.addChild(coinText);
// Add reset button
var resetButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.5,
x: -350,
y: 30
});
var resetText = new Text2('Reset', {
size: 30,
fill: 0xFFFFFF
});
resetText.anchor.set(0.5, 0.5);
resetText.x = -350;
resetText.y = 30;
LK.gui.top.addChild(resetButton);
LK.gui.top.addChild(resetText);
// Add How to Play button
var howToPlayButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.5,
x: 350,
y: 30
});
var howToPlayText = new Text2('How to play?', {
size: 28,
fill: 0xFFFFFF
});
howToPlayText.anchor.set(0.5, 0.5);
howToPlayText.x = 350;
howToPlayText.y = 30;
LK.gui.top.addChild(howToPlayButton);
LK.gui.top.addChild(howToPlayText);
howToPlayButton.down = function () {
howToPlayPanel.visible = true;
};
// --- Music Toggle Button ---
// 3 rows to the right of How to Play button (row = 100px, so +300px)
var musicButtonX = 350 + 300;
var musicButtonY = 30;
var musicOnIcon = LK.getAsset('musicOnIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: musicButtonX,
y: musicButtonY
});
var musicOffIcon = LK.getAsset('musicOffIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: musicButtonX,
y: musicButtonY
});
musicOnIcon.visible = true;
musicOffIcon.visible = false;
LK.gui.top.addChild(musicOnIcon);
LK.gui.top.addChild(musicOffIcon);
var musicPlaying = true;
LK.playMusic('bgmusic', {
volume: 0.2
});
function updateMusicButton() {
musicOnIcon.visible = musicPlaying;
musicOffIcon.visible = !musicPlaying;
}
musicOnIcon.down = function () {
LK.stopMusic();
musicPlaying = false;
updateMusicButton();
};
musicOffIcon.down = function () {
LK.playMusic('bgmusic', {
volume: 0.2
});
musicPlaying = true;
updateMusicButton();
};
resetButton.down = function () {
// Clear all zombies
for (var i = zombies.length - 1; i >= 0; i--) {
zombies[i].destroy();
}
zombies = [];
// Clear all bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
// Clear all coins
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
}
coins = [];
// Clear all towers
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
towers = [];
autoRepairActive = [];
// Clear all traps
for (var i = traps.length - 1; i >= 0; i--) {
traps[i].destroy();
}
traps = [];
// Reset game variables
coinAmount = 0;
towerCost = 50;
trapCost = 30;
healthCost = 20;
wave = 1;
zombiesInWave = 5;
zombiesSpawned = 0;
waveDelay = 180;
spawnTimer = 0;
coinDropMultiplier = 1.0;
firepowerCost = 100;
firerateCost = 100;
autoRepairCost = 100;
// Reset base health
base.health = base.maxHealth;
// Reset base repair button text
baseRepairText.setText('Repair');
// Reset character stats
character.fireRate = 40;
character.damage = 1;
// Reset upgrade panel texts
upgradePanel.towerText.setText('Tower - ' + towerCost + ' coins');
upgradePanel.trapText.setText('Trap - ' + trapCost + ' coins');
upgradePanel.healthText.setText('Repair Base - ' + healthCost + ' coins');
firepowerText.setText('Fire Power + (100)');
firerateText.setText('Fire Rate + (100)');
autoRepairText.setText('Auto Repair: Off (100 x tower)');
// Hide upgrade panel
upgradePanel.visible = false;
};
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 70;
LK.gui.top.addChild(waveText);
// Add info text one line below waveText (each line ~70px, so y = 70 + 1*70 = 140)
var baseUpgradeInfoText = new Text2("Don't forget about the main building upgrades!", {
size: 28,
fill: 0xcccccc
});
baseUpgradeInfoText.anchor.set(0.5, 0);
baseUpgradeInfoText.y = 140;
LK.gui.top.addChild(baseUpgradeInfoText);
var healthText = new Text2('Base Health: 100', {
size: 50,
fill: 0x00FF00
});
healthText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(healthText);
// Add info text above repair button
var baseRepairInfoText = new Text2('10 HP = 50 coins, multiples allowed', {
size: 22,
fill: 0xcccccc
});
baseRepairInfoText.anchor.set(0.5, 0.5);
baseRepairInfoText.x = 0;
baseRepairInfoText.y = -150;
LK.gui.bottom.addChild(baseRepairInfoText);
// Add repair button above Base Health text, moved 1 row up
var baseRepairButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.5,
x: 0,
y: -90
});
var baseRepairText = new Text2('Repair', {
size: 30,
fill: 0xFFFFFF
});
baseRepairText.anchor.set(0.5, 0.5);
baseRepairText.x = 0;
baseRepairText.y = -90;
LK.gui.bottom.addChild(baseRepairButton);
LK.gui.bottom.addChild(baseRepairText);
baseRepairButton.down = function () {
// Calculate how much health is missing
var missingHealth = base.maxHealth - base.health;
if (missingHealth > 0 && coinAmount >= 50) {
// Calculate how much we can repair (10 HP per 50 coins)
var repairAmount = Math.min(10, missingHealth);
var cost = 50;
// Check if we need multiple repairs to fully heal
var totalRepairs = Math.ceil(missingHealth / 10);
var totalCost = totalRepairs * 50;
// If player has enough coins for full repair, do it
if (coinAmount >= totalCost) {
coinAmount -= totalCost;
base.health = base.maxHealth;
} else {
// Otherwise repair as much as possible in 10 HP increments
var possibleRepairs = Math.floor(coinAmount / 50);
var actualRepairAmount = possibleRepairs * 10;
actualRepairAmount = Math.min(actualRepairAmount, missingHealth);
coinAmount -= possibleRepairs * 50;
base.health = Math.min(base.health + actualRepairAmount, base.maxHealth);
}
} else if (missingHealth > 0) {
showInsufficientBalanceWarning(LK.gui.bottom, baseRepairButton, 70);
}
};
// --- BASE UPGRADE PANEL (moved inside base upgrade popup) ---
// Panel width/height and button size/spacing match main upgradePanel
var panelWidth = 600;
var panelHeight = 420;
var bottomPanel = new Container();
var panelBg = LK.getAsset('upgradePanel', {
anchorX: 0.5,
anchorY: 0.5,
width: panelWidth,
height: panelHeight,
x: 0,
y: 0
});
bottomPanel.addChild(panelBg);
// Use same button size as main upgradePanel (upgradeButton asset, no scale)
var buttonWidth = 500;
var buttonHeight = 100;
var buttonSpacing = 20;
var startY = -panelHeight / 2 + buttonHeight / 2 + 20;
// Firepower Upgrade Button
var firepowerButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: startY,
width: buttonWidth,
height: buttonHeight
});
var firepowerText = new Text2('Fire Power + (100)', {
size: 40,
fill: 0xffffff
});
firepowerText.anchor.set(0.5, 0.5);
firepowerText.x = 0;
firepowerText.y = startY;
bottomPanel.addChild(firepowerButton);
bottomPanel.addChild(firepowerText);
// Fire Rate Upgrade Button
var firerateButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: startY + buttonHeight + buttonSpacing,
width: buttonWidth,
height: buttonHeight
});
var firerateText = new Text2('Fire Rate + (100)', {
size: 40,
fill: 0xffffff
});
firerateText.anchor.set(0.5, 0.5);
firerateText.x = 0;
firerateText.y = startY + buttonHeight + buttonSpacing;
bottomPanel.addChild(firerateButton);
bottomPanel.addChild(firerateText);
// Auto Repair Toggle Button
var autoRepairActive = [];
var autoRepairButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: startY + 2 * (buttonHeight + buttonSpacing),
width: buttonWidth,
height: buttonHeight
});
var autoRepairText = new Text2('Auto Repair: Off (100 x tower)', {
size: 30,
// Reduced size to fit better in button
fill: 0xffffff
});
autoRepairText.anchor.set(0.5, 0.5);
autoRepairText.x = 0;
autoRepairText.y = startY + 2 * (buttonHeight + buttonSpacing);
bottomPanel.addChild(autoRepairButton);
bottomPanel.addChild(autoRepairText);
// Close Button (moved to bottom, same size as others)
var closeButtonY = startY + 3 * (buttonHeight + buttonSpacing);
var closeButton = LK.getAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: closeButtonY,
width: buttonWidth,
height: buttonHeight
});
var closeText = new Text2('Close', {
size: 40,
fill: 0xffffff
});
closeText.anchor.set(0.5, 0.5);
closeText.x = 0;
closeText.y = closeButtonY;
bottomPanel.addChild(closeButton);
bottomPanel.addChild(closeText);
// Center and align bottomPanel background and content inside upgradePanel
bottomPanel.x = 0;
bottomPanel.y = panelHeight / 2 + 40; // Place just below main upgradePanel content, visually aligned
panelBg.x = 0;
panelBg.y = 0;
// Center all buttons/texts horizontally (already x:0), ensure vertical stacking is visually centered
firepowerButton.x = 0;
firepowerText.x = 0;
firerateButton.x = 0;
firerateText.x = 0;
autoRepairButton.x = 0;
autoRepairText.x = 0;
closeButton.x = 0;
closeText.x = 0;
// Add to upgradePanel
upgradePanel.addChild(bottomPanel);
// Close logic for new closeButton
closeButton.down = function () {
upgradePanel.visible = false;
};
// --- BUTTON LOGIC (now inside base upgrade panel) ---
firepowerButton.down = function () {
if (coinAmount >= firepowerCost) {
coinAmount -= firepowerCost;
firepowerCost *= 2;
firepowerText.setText('Fire Power + (' + firepowerCost + ')');
// Upgrade character and all towers
character.damage = (character.damage || 1) + 1;
for (var i = 0; i < towers.length; i++) {
towers[i].damage = (towers[i].damage || 1) + 1;
}
} else {
showInsufficientBalanceWarning(bottomPanel, firepowerButton, 70);
}
};
// Limit Fire Rate upgrade to a maximum of 8 levels
var firerateUpgradeLevel = 0;
firerateButton.down = function () {
if (firerateUpgradeLevel >= 8) {
firerateText.setText('Fire Rate MAX');
return;
}
if (coinAmount >= firerateCost) {
coinAmount -= firerateCost;
firerateCost *= 2;
firerateUpgradeLevel++;
if (firerateUpgradeLevel >= 8) {
firerateText.setText('Fire Rate MAX');
} else {
firerateText.setText('Fire Rate + (' + firerateCost + ')');
}
// Upgrade character and all towers
character.fireRate = Math.max(5, Math.floor(character.fireRate * 0.85));
for (var i = 0; i < towers.length; i++) {
towers[i].fireRate = Math.max(5, Math.floor(towers[i].fireRate * 0.85));
}
} else {
showInsufficientBalanceWarning(bottomPanel, firerateButton, 70);
}
};
// --- Tower Power Upgrade Logic ---
var towerPowerUpgradeLevel = 0;
var towerPowerUpgradeCost = 100;
upgradePanel.towerPowerText.setText('Tower Power + (' + towerPowerUpgradeCost + ')');
// Assign .down directly to the correct button asset (towerPowerButton)
upgradePanel.children.forEach(function (child) {
if (child.anchorY === 0.5 && child.y === -440 && child.width === 500 && child.height === 100) {
child.down = function () {
if (coinAmount >= towerPowerUpgradeCost) {
coinAmount -= towerPowerUpgradeCost;
towerPowerUpgradeCost *= 2;
towerPowerUpgradeLevel++;
upgradePanel.towerPowerText.setText('Tower Power + (' + towerPowerUpgradeCost + ')');
for (var i = 0; i < towers.length; i++) {
towers[i].damage = (towers[i].damage || 1) + 1;
}
} else {
showInsufficientBalanceWarning(upgradePanel, child, 70);
}
};
}
});
// --- Tower Fire Rate Upgrade Logic (max 5 levels) ---
var towerFireRateUpgradeLevel = 0;
var towerFireRateUpgradeCost = 100;
upgradePanel.towerFireRateText.setText('Tower Fire Rate + (' + towerFireRateUpgradeCost + ')');
// Assign .down directly to the correct button asset (towerFireRateButton)
upgradePanel.children.forEach(function (child) {
if (child.anchorY === 0.5 && child.y === -320 && child.width === 500 && child.height === 100) {
child.down = function () {
if (towerFireRateUpgradeLevel >= 5) {
upgradePanel.towerFireRateText.setText('Tower Fire Rate MAX');
return;
}
if (coinAmount >= towerFireRateUpgradeCost) {
coinAmount -= towerFireRateUpgradeCost;
towerFireRateUpgradeCost *= 2;
towerFireRateUpgradeLevel++;
if (towerFireRateUpgradeLevel >= 5) {
upgradePanel.towerFireRateText.setText('Tower Fire Rate MAX');
} else {
upgradePanel.towerFireRateText.setText('Tower Fire Rate + (' + towerFireRateUpgradeCost + ')');
}
for (var i = 0; i < towers.length; i++) {
towers[i].fireRate = Math.max(5, Math.floor(towers[i].fireRate * 0.85));
}
} else {
showInsufficientBalanceWarning(upgradePanel, child, 70);
}
};
}
});
autoRepairButton.down = function () {
// Check if any towers have auto-repair enabled
var hasAutoRepair = false;
for (var i = 0; i < towers.length; i++) {
if (towers[i].autoRepair) {
hasAutoRepair = true;
break;
}
}
if (hasAutoRepair) {
// Disable auto-repair for all towers
for (var i = 0; i < towers.length; i++) {
towers[i].autoRepair = false;
}
autoRepairActive = [];
autoRepairText.setText('Auto Repair: Off (100 x tower)');
} else if (towers.length > 0) {
// Enable auto-repair if we have enough coins
var needed = towers.length * autoRepairCost;
if (coinAmount >= needed) {
coinAmount -= needed;
// Mark all towers as auto-repair enabled
for (var i = 0; i < towers.length; i++) {
towers[i].autoRepair = true;
}
autoRepairActive = towers;
autoRepairText.setText('Auto Repair: On');
} else {
showInsufficientBalanceWarning(bottomPanel, autoRepairButton, 70);
}
}
};
game.down = function (x, y, obj) {
if (!upgradePanel.visible) {
character.shoot(x, y);
}
};
game.update = function () {
coinText.setText('Coins: ' + coinAmount);
waveText.setText('Wave: ' + wave);
healthText.setText('Base Health: ' + base.health);
if (base.health > 50) {
healthText.fill = 0x00ff00;
} else if (base.health > 20) {
healthText.fill = 0xffff00;
} else {
healthText.fill = 0xff0000;
}
// Update auto-repair button text based on current state
var hasAutoRepair = false;
for (var i = 0; i < towers.length; i++) {
if (towers[i].autoRepair) {
hasAutoRepair = true;
break;
}
}
if (hasAutoRepair) {
autoRepairText.setText('Auto Repair: On');
} else {
autoRepairText.setText('Auto Repair: Off (100 x tower)');
}
// --- Zombie Invasion Event Logic ---
if (zombieInvasionActive) {
// Show warning text if not already shown
if (!zombieInvasionWarning) {
// Place warning text 4 lines below waveText (each line ~70px, so y = waveText.y + 4*70)
zombieInvasionWarning = new Text2('Zombie invasion!', {
size: 60,
fill: 0xFF0000
});
zombieInvasionWarning.anchor.set(0.5, 0);
zombieInvasionWarning.y = waveText.y + 4 * 70;
LK.gui.top.addChild(zombieInvasionWarning);
}
// Spawn BossZombie if not already spawned
if (zombieInvasionZombiesLeft > 0 && zombies.length < 1) {
var boss = new BossZombie();
var scale = Math.pow(1.15, wave - 1);
boss.health = Math.round(boss.health * scale);
boss.speed = boss.speed * scale;
boss.damage = Math.round(boss.damage * scale);
boss.value = Math.round(boss.value * scale);
boss.x = zombieInvasionSpawnPoint.x;
boss.y = zombieInvasionSpawnPoint.y;
zombies.push(boss);
game.addChild(boss);
zombieInvasionZombiesLeft = 0;
}
// If all zombies are dead, remove warning and end invasion
if (zombies.length === 0) {
if (zombieInvasionWarning) {
LK.gui.top.removeChild(zombieInvasionWarning);
zombieInvasionWarning.destroy();
zombieInvasionWarning = null;
}
zombieInvasionActive = false;
// Prepare for next wave as usual
wave++;
zombiesInWave = Math.round((5 + wave * 2) * Math.pow(1.15, wave - 1));
zombiesSpawned = 0;
waveDelay = 180;
coinDropMultiplier *= 1.5;
}
} else {
// Normal wave logic
if (waveDelay > 0) {
waveDelay--;
} else if (zombiesSpawned < zombiesInWave) {
spawnTimer++;
if (spawnTimer >= 60) {
spawnTimer = 0;
zombiesSpawned++;
var zombie = new Zombie();
var scale = Math.pow(1.15, wave - 1);
zombie.health = Math.round(zombie.health * scale);
zombie.speed = zombie.speed * scale;
zombie.damage = Math.round(zombie.damage * scale);
zombie.value = Math.round(zombie.value * scale);
// Spawn zombies from outside the map edges
var edge = Math.floor(Math.random() * 4); // 0: left, 1: right, 2: top, 3: bottom
var spawnX, spawnY;
if (edge === 0) {
// left
spawnX = -100;
spawnY = Math.random() * 2732;
} else if (edge === 1) {
// right
spawnX = 2048 + 100;
spawnY = Math.random() * 2732;
} else if (edge === 2) {
// top
spawnX = Math.random() * 2048;
spawnY = -100;
} else {
// bottom
spawnX = Math.random() * 2048;
spawnY = 2732 + 100;
}
zombie.x = spawnX;
zombie.y = spawnY;
zombies.push(zombie);
game.addChild(zombie);
}
} else if (zombies.length === 0) {
// Check for zombie invasion trigger
if (wave % 5 === 0) {
zombieInvasionActive = true;
// Spawn point: center of top edge (x = 1024, y = -100)
zombieInvasionSpawnPoint.x = 1024;
zombieInvasionSpawnPoint.y = -100;
zombieInvasionZombiesLeft = 1; // Only 1 boss per invasion
// Remove any previous warning just in case
if (zombieInvasionWarning) {
LK.gui.top.removeChild(zombieInvasionWarning);
zombieInvasionWarning.destroy();
zombieInvasionWarning = null;
}
} else {
wave++;
zombiesInWave = Math.round((5 + wave * 2) * Math.pow(1.15, wave - 1));
zombiesSpawned = 0;
waveDelay = 180;
// Increase coin drop multiplier by 50% after each wave
coinDropMultiplier *= 1.5;
}
}
}
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var attacked = false;
// Check towers
for (var ti = 0; ti < towers.length; ti++) {
var tower = towers[ti];
if (tower.health > 0 && zombie.intersects(tower)) {
tower.takeDamage(zombie.damage);
zombie.destroy();
zombies.splice(i, 1);
attacked = true;
break;
}
}
if (attacked) continue;
// Check traps
for (var trpi = 0; trpi < traps.length; trpi++) {
var trap = traps[trpi];
if (trap.health > 0 && zombie.intersects(trap)) {
trap.takeDamage(zombie.damage);
zombie.destroy();
zombies.splice(i, 1);
attacked = true;
break;
}
}
if (attacked) continue;
// Check base
if (zombie.intersects(base)) {
base.takeDamage(zombie.damage);
zombie.destroy();
zombies.splice(i, 1);
continue;
}
for (var j = bullets.length - 1; j >= 0; j--) {
var bullet = bullets[j];
if (zombie.intersects(bullet)) {
LK.getSound('zombieHit').play();
// --- Explosion effect ---
// Play explosion sound exactly when explosion visual is created
LK.getSound('explosion').play();
var explosion = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: bullet.x,
y: bullet.y,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.7,
tint: 0xffcc00
});
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 250,
onFinish: function onFinish() {
if (explosion && explosion.parent) {
explosion.destroy();
}
}
});
// --- End explosion effect ---
if (zombie.takeDamage(bullet.damage)) {
var coin = new Coin();
coin.x = zombie.x;
coin.y = zombie.y;
// Apply coin drop multiplier
coin.value = Math.round(zombie.value * coinDropMultiplier);
coins.push(coin);
game.addChild(coin);
zombie.destroy();
zombies.splice(i, 1);
}
bullet.destroy();
bullets.splice(j, 1);
break;
}
}
}
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.x < -100 || bullet.x > 2148 || bullet.y < -100 || bullet.y > 2832) {
bullet.destroy();
bullets.splice(i, 1);
}
}
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
if (coin.lifetime <= 0) {
coin.destroy();
coins.splice(i, 1);
continue;
}
var dx = character.x - coin.x;
var dy = character.y - coin.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
coinAmount += coin.value;
LK.getSound('coinCollect').play();
coin.destroy();
coins.splice(i, 1);
continue;
}
// Drone coin collection
var droneDx = drone.x - coin.x;
var droneDy = drone.y - coin.y;
var droneDistance = Math.sqrt(droneDx * droneDx + droneDy * droneDy);
if (droneDistance < drone.collectRange) {
coinAmount += coin.value;
LK.getSound('coinCollect').play();
coin.destroy();
coins.splice(i, 1);
}
}
};
// Helper to show insufficient balance warning above a button (global version for all panels)
function showInsufficientBalanceWarning(parentContainer, button, yOffset) {
// Remove any previous warning
if (button._insufficientWarning) {
parentContainer.removeChild(button._insufficientWarning);
button._insufficientWarning.destroy();
button._insufficientWarning = null;
}
var warning = new Text2('The balance is insufficient', {
size: 28,
fill: 0xff4444
});
warning.anchor.set(0.5, 1);
warning.x = button.x;
warning.y = button.y - (yOffset || 70);
parentContainer.addChild(warning);
button._insufficientWarning = warning;
// Fade out and destroy after 1.2s
tween(warning, {
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (parentContainer && warning && warning.parent) {
parentContainer.removeChild(warning);
warning.destroy();
button._insufficientWarning = null;
}
}
});
}
Askeri karakol, üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Askeri Kule, gerçekçi, üstten görünüm, yazısız. In-Game asset. High contrast. No shadows
Askeri drone, üstten görünüm, gerçekçi, yazısız. In-Game asset. 2d. High contrast. No shadows
Coin, üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Namusu olmayan Teknolojik askeri saldırı kulesi, üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Namlusu olmayan, Teknolojik askeri kule, üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Namlusu olmayan, Teknolojik askeri kule, üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Tuzak üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Füze, üstten görünüm, gerçekçi, yazısız. In-Game asset. High contrast. No shadows
Patlama, gerçekçi, üstten görünüm, yazısız. In-Game asset. High contrast. No shadows