User prompt
Panel içeriği görünmüyor. Sadece arkaplan görünüyor.
User prompt
Bunu düzelt.
User prompt
En son oluşturduğumuz panel oyunun ekranında görünmüyor. Sol alt kenara hizalı şekilde yerleştir.
User prompt
Ekranın sol altına bir panel ekle. Bu panelde "Askerin ve Kulelerin, Atış Gücü ve Atış Hızı yükseltme" Ayrıca "Herhangi bir kulenin canı %50 altına düşünce otomatik tamir etmeyi aktif etme" butonları olsun. Bunun maliyeti Her kule başı 100 ve katları coin olsun. Otomatik tamir aktif olan kulenin üzerinde tamir iconu belirsin.
User prompt
Her wave sonrası gelen zombilerden düşen coin oranı %50 artsın.
User prompt
Her wave sonrası gelen zombilerden düşen coin oranı %15 artsın.
User prompt
Zombiler, kule ve tuzak varsa önce onlara saldırsınlar. Kule ve tuzakların can barı olsun. Sadece kulelerin üzerinde tamir et butonu olsun. Belli bir maliyeti olsun.
User prompt
Zombiler Base %50 oranında yaklaşınca asker saldırmaya başlasın.
User prompt
Askerin menzil alanı, base zombiler %50 oranında olsun.
User prompt
Asker menzil alanı, %50 düşür.
User prompt
Zombiler harita dışından gelsinler.
User prompt
Bina ve yükseltme maliyeti, her satın alımda 2 kat artsın.
User prompt
Her wave seviyesinde, %15 daha fazla ve daha güçlü zombiler saldırsın.
User prompt
Oyuncu mermi sayısını yarıya düşür.
User prompt
Düzenle ve düzelt.
User prompt
Dron dönüş hareketi keskin olmasın.
User prompt
Dron etkin alanını %80 düşür.
User prompt
Dron ani dönüşler yapmasın. Havada uçuyor gibi gitsin. Ayrıca, bir hortum çekiyor gibi coin çekim alanı olsun.
User prompt
Coinleri toplamak için Dron oluştur.
User prompt
Oyuncu otomatik mermi sıkar
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'healthText.style.fill = "#00ff00";' Line Number: 396
Code edit (1 edits merged)
Please save this source code
User prompt
Zombie Base Defense
Initial prompt
Oyuncuya ait oyun ekranının ortasında AnaBase var. Oyuncunun karakteri bu base doğru gelen, zombi istilasına karşı tüfekle savunma yapıyor. Her wave sonunda daha fazla zombi istilası geliyor. Bu base de bir savunma kule çeşitliği var. Zombilerden kazanılan coin ile, bu kulelerden satın alınıyor. Kuleler zombilere yavaş yavaş silah sıkıyor. Satın alınan kuleler, base yakınına otomatik rastgele yerleştiriliyor. Ancak yerleştirilen herhangi bir şey, üst üste gelmeyecek şekilde tamamen base yakınından başlayarak rastgele konumlanıyor. Kuleler haricinde tuzaklar da var, bu tuzaklar zombileri yavaşlatmaya veya öldürmeye yarıyor. Anabase tıklayınca, yükseltme ve satın alma butonlarının olduğu panel açılıyor. Kazandığımız coinleri burada kullanıyoruz. Böylelikle, sonsuza dek gelen zombi akınına karşı hayatta kalmaya çalışıyoruz.
/**** * 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