/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Base = Container.expand(function () { var self = Container.call(this); // Create a shadow for the base var baseShadow = self.attachAsset('base', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.08, scaleY: 1.08, alpha: 0.35 }); baseShadow.y += 18; baseShadow.x += 10; baseShadow.tint = 0x000000; // Main base graphics var baseGraphics = self.attachAsset('base', { anchorX: 0.5, anchorY: 0.5 }); var rangeIndicator = self.attachAsset('rangeIndicator', { anchorX: 0.5, anchorY: 0.5, alpha: 0.2 }); self.health = 100; self.maxHealth = 100; self.attackPower = 50; self.attackSpeed = 45; // frames between attacks self.attackRange = 400; self.multiAttack = 1; self.lastAttack = 0; self.updateRangeIndicator = function () { var scale = self.attackRange * 2 / 800; rangeIndicator.scaleX = scale; rangeIndicator.scaleY = scale; }; self.takeDamage = function (damage) { // Use provided damage or default to 20 for player bullets if (typeof damage !== 'number') damage = 20; self.health -= damage; LK.effects.flashObject(self, 0xff0000, 300); if (self.health <= 0) { LK.showGameOver(); } updateHealthDisplay(); }; self.canAttack = function () { return LK.ticks - self.lastAttack >= self.attackSpeed; }; self.attack = function () { if (!self.canAttack()) return; var targets = []; for (var i = 0; i < enemies.length && targets.length < self.multiAttack; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.attackRange) { targets.push(enemy); } } for (var j = 0; j < targets.length; j++) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.target = targets[j]; bullet.damage = self.attackPower; bullets.push(bullet); game.addChild(bullet); } if (targets.length > 0) { self.lastAttack = LK.ticks; LK.getSound('shoot').play(); } }; // Add orbit animation to base (planet-like orbit around center) self.orbitRadius = 220; // Distance from center of screen self.orbitAngle = Math.random() * Math.PI * 2; // Start at random angle self.orbitSpeed = 0.008 + Math.random() * 0.004; // Slower, smooth orbit self.rotationSpeed = 0.012 + Math.random() * 0.01; // Still spin in place for visual effect self.update = function () { // Orbit around the center of the screen self.orbitAngle += self.orbitSpeed; var centerX = 2048 / 2; var centerY = 2732 / 2; self.x = centerX + Math.cos(self.orbitAngle) * self.orbitRadius; self.y = centerY + Math.sin(self.orbitAngle) * self.orbitRadius; // Spin in place for visual effect baseGraphics.rotation += self.rotationSpeed; baseShadow.rotation += self.rotationSpeed; }; return self; }); var Boss = Container.expand(function () { var self = Container.call(this); // Add shadow for boss var bossShadow = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.10, scaleY: 1.10, alpha: 0.32 }); bossShadow.y += 20; bossShadow.x += 12; bossShadow.tint = 0x000000; var enemyGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); self.health = 200; self.maxHealth = 200; self.speed = 0.8; self.direction = { x: 0, y: 0 }; self.lastX = self.x; self.lastY = self.y; self.setTarget = 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.direction.x = dx / distance; self.direction.y = dy / distance; } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { LK.getSound('enemyHit').play(); // Create explosion effect // Create particle explosion effect for (var p = 0; p < 15; p++) { var particle = LK.getAsset('boss', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); particle.x = self.x; particle.y = self.y; game.addChild(particle); var angle = p / 15 * Math.PI * 2; var speed = 150 + Math.random() * 100; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 600 + Math.random() * 400, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } tween(self, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Boss will be removed from enemies array in game.update } }); return true; // Boss died } return false; }; self.update = function () { // If boss is dead (health <= 0), don't move and prepare for removal if (self.health <= 0) { return true; // Remove dead boss } self.lastX = self.x; self.lastY = self.y; // Keep shadow under boss bossShadow.x = enemyGraphics.x + 12; bossShadow.y = enemyGraphics.y + 20; bossShadow.rotation = enemyGraphics.rotation; // Move toward base self.setTarget(base.x, base.y); self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if reached base var dx = self.x - base.x; var dy = self.y - base.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 100) { // Boss deals double damage (4-10 total) var bossDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5 base.takeDamage(bossDamage); base.takeDamage(bossDamage); // Boss does double damage return true; // Boss reached base } 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 = 8; self.target = null; self.damage = 10; self.update = function () { if (!self.target || !self.target.parent) { return true; // Remove bullet if target is gone } // Check collision with enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var enemyBullet = enemyBullets[i]; var dx = enemyBullet.x - self.x; var dy = enemyBullet.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 15) { // Destroy enemy bullet if (enemyBullet && enemyBullet.parent) { enemyBullet.destroy(); } enemyBullets.splice(i, 1); // Give points for destroying enemy bullet points += 5; updatePointsDisplay(); LK.getSound('hit').play(); // Flash effect LK.effects.flashObject(self, 0x00ff00, 200); return true; // Destroy this bullet too } } // Move toward 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 < 20) { // Hit target var enemyDied = self.target.takeDamage(self.damage); if (enemyDied) { // Different point rewards for different enemy types if (self.target instanceof Boss) { points += 100; } else if (self.target instanceof EnemyTank) { points += 20; } else if (self.target instanceof EnemyFast) { points += 15; } else if (self.target instanceof EnemyShooter) { points += 25; } else { points += 10; } updatePointsDisplay(); } LK.getSound('hit').play(); return true; // Remove bullet } if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Check if bullet is still attached before returning if (!self.parent) { return true; // Remove bullet if it's already been removed } return false; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); // Add shadow for enemy var enemyShadow = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.08, scaleY: 1.08, alpha: 0.35 }); enemyShadow.y += 10; enemyShadow.x += 6; enemyShadow.tint = 0x000000; var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 20; self.maxHealth = 20; self.speed = 1; self.direction = { x: 0, y: 0 }; self.lastX = self.x; self.lastY = self.y; self.setTarget = 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.direction.x = dx / distance; self.direction.y = dy / distance; } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { LK.getSound('enemyHit').play(); // Create explosion effect // Create particle explosion effect for (var p = 0; p < 8; p++) { var particle = LK.getAsset('enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); particle.x = self.x; particle.y = self.y; game.addChild(particle); var angle = p / 8 * Math.PI * 2; var speed = 100 + Math.random() * 50; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Enemy will be removed from enemies array in game.update } }); return true; // Enemy died } return false; }; self.update = function () { // If enemy is dead (health <= 0), don't move and prepare for removal if (self.health <= 0) { return true; // Remove dead enemy } self.lastX = self.x; self.lastY = self.y; // Keep shadow under enemy enemyShadow.x = enemyGraphics.x + 6; enemyShadow.y = enemyGraphics.y + 10; enemyShadow.rotation = enemyGraphics.rotation; // Move toward base self.setTarget(base.x, base.y); self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if reached base var dx = self.x - base.x; var dy = self.y - base.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 70) { // Enemy deals 2-5 damage to base var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5 base.takeDamage(enemyDamage); // Defensive: Only remove if enemy has a parent (avoid undefined parent crash) if (self.parent) { self.parent.removeChild(self); } return true; // Enemy reached base } return false; }; return self; }); var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5, tint: 0xff6666 }); self.speed = 4; self.damage = 10; self.direction = { x: 0, y: 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.direction.x = dx / distance; self.direction.y = dy / distance; } }; self.update = function () { // Move bullet self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { return true; // Remove bullet } // Check collision with base var dx = self.x - base.x; var dy = self.y - base.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 80) { // Enemy bullet deals 2-5 damage to base var bulletDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5 base.takeDamage(bulletDamage); LK.getSound('hit').play(); return true; // Remove bullet } return false; }; return self; }); var EnemyFast = Container.expand(function () { var self = Container.call(this); // Add shadow for fast enemy var enemyShadow = self.attachAsset('enemyFast', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.08, scaleY: 1.08, alpha: 0.35 }); enemyShadow.y += 8; enemyShadow.x += 5; enemyShadow.tint = 0x000000; var enemyGraphics = self.attachAsset('enemyFast', { anchorX: 0.5, anchorY: 0.5 }); self.health = 5; self.maxHealth = 5; self.speed = 1.5; self.direction = { x: 0, y: 0 }; self.lastX = self.x; self.lastY = self.y; self.setTarget = 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.direction.x = dx / distance; self.direction.y = dy / distance; } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { LK.getSound('enemyHit').play(); // Create explosion effect // Create particle explosion effect for (var p = 0; p < 6; p++) { var particle = LK.getAsset('enemyFast', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); particle.x = self.x; particle.y = self.y; game.addChild(particle); var angle = p / 6 * Math.PI * 2; var speed = 80 + Math.random() * 40; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 350 + Math.random() * 150, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Enemy will be removed from enemies array in game.update } }); return true; // Enemy died } return false; }; self.update = function () { // If enemy is dead (health <= 0), don't move and prepare for removal if (self.health <= 0) { return true; // Remove dead enemy } self.lastX = self.x; self.lastY = self.y; // Keep shadow under fast enemy enemyShadow.x = enemyGraphics.x + 5; enemyShadow.y = enemyGraphics.y + 8; enemyShadow.rotation = enemyGraphics.rotation; // Move toward base self.setTarget(base.x, base.y); self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if reached base var dx = self.x - base.x; var dy = self.y - base.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 70) { // Enemy deals 2-5 damage to base var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5 base.takeDamage(enemyDamage); return true; // Enemy reached base } return false; }; return self; }); var EnemyShooter = Container.expand(function () { var self = Container.call(this); // Add shadow for shooter enemy var enemyShadow = self.attachAsset('enemyShooter', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.08, scaleY: 1.08, alpha: 0.35 }); enemyShadow.y += 10; enemyShadow.x += 6; enemyShadow.tint = 0x000000; var enemyGraphics = self.attachAsset('enemyShooter', { anchorX: 0.5, anchorY: 0.5 }); self.health = 30; self.maxHealth = 30; self.speed = 0.3; // Very slow movement self.direction = { x: 0, y: 0 }; self.lastX = self.x; self.lastY = self.y; self.lastShot = 0; self.shootCooldown = 60; // 1 second between shots - much faster self.shootRange = 800; // Increased range for earlier shooting self.setTarget = 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.direction.x = dx / distance; self.direction.y = dy / distance; } }; self.shoot = function () { if (LK.ticks - self.lastShot < self.shootCooldown) return; var dx = base.x - self.x; var dy = base.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.shootRange) { var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y; bullet.setDirection(base.x, base.y); enemyBullets.push(bullet); game.addChild(bullet); self.lastShot = LK.ticks; LK.effects.flashObject(self, 0xff6666, 200); } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { LK.getSound('enemyHit').play(); // Create explosion effect for (var p = 0; p < 8; p++) { var particle = LK.getAsset('enemyShooter', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); particle.x = self.x; particle.y = self.y; game.addChild(particle); var angle = p / 8 * Math.PI * 2; var speed = 100 + Math.random() * 50; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Enemy will be removed from enemies array in game.update } }); return true; // Enemy died } return false; }; self.update = function () { // If enemy is dead, don't move and prepare for removal if (self.health <= 0) { return true; // Remove dead enemy } self.lastX = self.x; self.lastY = self.y; // Keep shadow under enemy enemyShadow.x = enemyGraphics.x + 6; enemyShadow.y = enemyGraphics.y + 10; enemyShadow.rotation = enemyGraphics.rotation; // Move toward base slowly self.setTarget(base.x, base.y); self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Try to shoot at base self.shoot(); // Check if reached base var dx = self.x - base.x; var dy = self.y - base.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 70) { // Enemy deals 2-5 damage to base var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5 base.takeDamage(enemyDamage); return true; // Enemy reached base } return false; }; return self; }); // 3 seconds var EnemyTank = Container.expand(function () { var self = Container.call(this); // Add shadow for tank enemy var enemyShadow = self.attachAsset('enemyTank', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.08, scaleY: 1.08, alpha: 0.35 }); enemyShadow.y += 12; enemyShadow.x += 7; enemyShadow.tint = 0x000000; var enemyGraphics = self.attachAsset('enemyTank', { anchorX: 0.5, anchorY: 0.5 }); self.health = 40; self.maxHealth = 40; self.speed = 0.5; self.direction = { x: 0, y: 0 }; self.lastX = self.x; self.lastY = self.y; self.setTarget = 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.direction.x = dx / distance; self.direction.y = dy / distance; } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xffffff, 200); if (self.health <= 0) { LK.getSound('enemyHit').play(); // Create explosion effect // Create particle explosion effect for (var p = 0; p < 10; p++) { var particle = LK.getAsset('enemyTank', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4 }); particle.x = self.x; particle.y = self.y; game.addChild(particle); var angle = p / 10 * Math.PI * 2; var speed = 120 + Math.random() * 60; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 500 + Math.random() * 300, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Enemy will be removed from enemies array in game.update } }); return true; // Enemy died } return false; }; self.update = function () { // If enemy is dead (health <= 0), don't move and prepare for removal if (self.health <= 0) { return true; // Remove dead enemy } self.lastX = self.x; self.lastY = self.y; // Keep shadow under tank enemy enemyShadow.x = enemyGraphics.x + 7; enemyShadow.y = enemyGraphics.y + 12; enemyShadow.rotation = enemyGraphics.rotation; // Move toward base self.setTarget(base.x, base.y); self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if reached base var dx = self.x - base.x; var dy = self.y - base.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 70) { // Enemy deals 2-5 damage to base var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5 base.takeDamage(enemyDamage); return true; // Enemy reached base } return false; }; return self; }); var Shield = Container.expand(function () { var self = Container.call(this); var shieldGraphics = LK.getAsset('shield', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.25, alpha: 0.8 }); self.addChild(shieldGraphics); self.radius = 250; // Distance from base center self.rotationSpeed = 0.02; // Rotation speed self.angle = 0; self.update = function () { // Rotate around the base self.angle += self.rotationSpeed; self.x = base.x + Math.cos(self.angle) * self.radius; self.y = base.y + Math.sin(self.angle) * self.radius; // Check collision with enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Shield collision radius if (distance < 50) { // Destroy enemy on contact enemy.takeDamage(enemy.health); LK.effects.flashObject(self, 0x00ff00, 200); } } }; return self; }); var UpgradeButton = Container.expand(function (type, cost, description, fontSize) { var self = Container.call(this); var buttonGraphics = self.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, // Reduced width for side-by-side layout scaleY: 0.7 // Reduced height for compactness }); self.type = type; self.cost = cost; self.description = description; self.level = 0; self.maxLevel = 999; // Default no limit if (typeof fontSize !== "number" || fontSize < 10) fontSize = 36; var buttonText = new Text2(description + '\n' + cost + ' pts', { size: 64, // Match pointsText/waveText/healthText size fill: 0xFFFFFF, font: "bold 64px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma" // Match style for readability }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.updateCost = function (newCost) { self.cost = newCost; if (self.level >= self.maxLevel) { buttonText.setText(self.description + '\nMAX LEVEL'); buttonGraphics.tint = 0x808080; } else { buttonText.setText(self.description + '\n' + newCost + ' pts'); } }; self.down = function (x, y, obj) { if (points >= self.cost && self.level < self.maxLevel) { points -= self.cost; updatePointsDisplay(); self.level++; switch (self.type) { case 'power': base.attackPower += 20; powerUpgradeCost = Math.floor(powerUpgradeCost * 1.5); self.updateCost(powerUpgradeCost); break; case 'speed': base.attackSpeed = Math.max(10, base.attackSpeed - 10); speedUpgradeCost = Math.floor(speedUpgradeCost * 1.5); self.updateCost(speedUpgradeCost); break; case 'range': base.attackRange += 100; base.updateRangeIndicator(); rangeUpgradeCost = Math.floor(rangeUpgradeCost * 1.5); self.updateCost(rangeUpgradeCost); break; case 'multi': base.multiAttack += 1; multiUpgradeCost = Math.floor(multiUpgradeCost * 2); self.updateCost(multiUpgradeCost); break; case 'shield': if (!shield) { shield = new Shield(); game.addChild(shield); self.visible = false; // Hide shield button after purchase } break; } LK.effects.flashObject(self, 0xffffff, 300); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // Add background image behind all gameplay elements // Background image for the game (full screen, subtle, fits 2048x2732) var backgroundImage = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 1, scaleY: 1 }); game.addChild(backgroundImage); var base = game.addChild(new Base()); base.x = 2048 / 2; base.y = 2732 / 2; base.updateRangeIndicator(); var enemies = []; var bullets = []; var enemyBullets = []; var points = 0; var wave = 1; var waveInProgress = false; var enemiesSpawned = 0; var enemiesPerWave = 5; var spawnTimer = 0; var waveStartDelay = 0; var shield = null; // Upgrade costs var powerUpgradeCost = 50; var speedUpgradeCost = 30; var rangeUpgradeCost = 40; var multiUpgradeCost = 100; var shieldUpgradeCost = 500; // UI Elements var healthText = new Text2('Health: 100', { size: 48, fill: 0xFFFFFF }); healthText.anchor.set(0, 0); healthText.x = 50; healthText.y = 150; LK.gui.topLeft.addChild(healthText); var pointsText = new Text2('Points: 0', { size: 64, fill: 0xFFFFFF }); pointsText.anchor.set(0.5, 0); pointsText.x = 0; pointsText.y = 120; LK.gui.top.addChild(pointsText); var waveText = new Text2('Wave: 1', { size: 48, fill: 0xFFFFFF }); waveText.anchor.set(1, 0); waveText.x = -50; waveText.y = 150; LK.gui.topRight.addChild(waveText); var statusText = new Text2('Prepare for Wave 1!', { size: 48, fill: 0xF39C12 }); statusText.anchor.set(0.5, 0); statusText.x = 0; statusText.y = 50; LK.gui.top.addChild(statusText); // New upgrade menu: horizontal, text/buttons styled to match health/wave/points, large and readable // Layout constants for new menu var upgradeMenuY = 2732 - 220; var upgradeMenuButtonCount = 5; var upgradeMenuButtonSpacing = 340; var upgradeMenuButtonStartX = (2048 - upgradeMenuButtonSpacing * (upgradeMenuButtonCount - 1)) / 2; // Button info: [type, cost, label, maxLevel, description] var upgradeMenuData = [['power', powerUpgradeCost, 'Güç', undefined, 'Saldırı gücünü artırır'], ['speed', speedUpgradeCost, 'Hız', undefined, 'Saldırı hızını artırır'], ['range', rangeUpgradeCost, 'Menzil', 4, 'Menzili artırır'], ['multi', multiUpgradeCost, 'Çoklu', 3, 'Çoklu atış sağlar'], ['shield', shieldUpgradeCost, 'Kalkan', 1, 'Kalkan ekler']]; // Store references for cost/level updates var powerButton, speedButton, rangeButton, multiButton, shieldButton; var upgradeMenuButtons = []; for (var i = 0; i < upgradeMenuButtonCount; i++) { var btnType = upgradeMenuData[i][0]; var btnCost = upgradeMenuData[i][1]; var btnLabel = upgradeMenuData[i][2]; var btnMaxLevel = upgradeMenuData[i][3]; var btnDesc = upgradeMenuData[i][4]; // Create button background using 'upgradeButton' asset, styled to match general UI var btn = new UpgradeButton(btnType, btnCost, btnLabel, Math.floor(LK.width / 18)); // Remove default children (graphics/text) for custom layout while (btn.children.length > 0) { var child = btn.children[0]; // Defensive: Only remove if child has a parent (avoid undefined parent crash) if (child && child.parent) { child.parent.removeChild(child); } else if (typeof child !== "undefined") { // Fallback: forcibly remove from children array if not attached btn.children.splice(0, 1); } else { // If child is undefined, break to avoid infinite loop break; } } var bg = btn.attachAsset('upgradeButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.1, scaleY: 1.0, alpha: 0.98 }); btn.children.unshift(bg); // Position horizontally btn.x = upgradeMenuButtonStartX + i * upgradeMenuButtonSpacing; btn.y = upgradeMenuY; // Add label (large, bold, white, matching pointsText style) var labelText = new Text2(btnLabel, { size: Math.floor(LK.width / 18), fill: 0xffffff, font: "bold " + Math.floor(LK.width / 18) + "px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma" }); labelText.anchor.set(0.5, 1); labelText.x = 0; labelText.y = -10; btn.addChild(labelText); // Add cost (large, bold, yellow, matching pointsText style) var costText = new Text2(btnCost + ' pts', { size: Math.floor(LK.width / 22), fill: 0xf1c40f, font: "bold " + Math.floor(LK.width / 22) + "px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma" }); costText.anchor.set(0.5, 0); costText.x = 0; costText.y = 10; btn.addChild(costText); // Add description (smaller, white, below cost) var descText = new Text2(btnDesc, { size: Math.floor(LK.width / 32), fill: 0xffffff, font: "bold " + Math.floor(LK.width / 32) + "px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma" }); descText.anchor.set(0.5, 0); descText.x = 0; descText.y = 70; btn.addChild(descText); // Set maxLevel if needed if (typeof btnMaxLevel === "number") btn.maxLevel = btnMaxLevel; // Store reference for cost/level updates if (btnType === 'power') powerButton = btn; if (btnType === 'speed') speedButton = btn; if (btnType === 'range') rangeButton = btn; if (btnType === 'multi') multiButton = btn; if (btnType === 'shield') shieldButton = btn; upgradeMenuButtons.push(btn); game.addChild(btn); } // Patch UpgradeButton.updateCost to update the cost text inside the button UpgradeButton.prototype.updateCost = function (newCost) { this.cost = newCost; // Find costText (Text2) child for (var i = 0; i < this.children.length; i++) { var ch = this.children[i]; if (ch instanceof Text2 && ch.text && (ch.text.indexOf('pts') !== -1 || ch.text === 'MAX LEVEL')) { if (this.level >= this.maxLevel) { ch.setText('MAX LEVEL'); if (this.children[0] && this.children[0].tint !== undefined) this.children[0].tint = 0x808080; } else { ch.setText(newCost + ' pts'); if (this.children[0] && this.children[0].tint !== undefined) this.children[0].tint = 0xffffff; } } } }; function updateHealthDisplay() { healthText.setText('Health: ' + base.health); } function updatePointsDisplay() { pointsText.setText('Points: ' + points); } function startWave() { waveInProgress = true; enemiesSpawned = 0; // Boss waves have only 1 enemy (the boss) // Boss now appears every 5 waves (5, 10, 15, ...) if (wave % 5 === 0) { enemiesPerWave = 1; statusText.setText('BOSS WAVE ' + wave + ' - Prepare for battle!'); } else { enemiesPerWave = Math.floor(5 + Math.pow(wave, 1.3) * 2); statusText.setText('Wave ' + wave + ' - ' + enemiesPerWave + ' enemies incoming!'); } spawnTimer = 0; waveText.setText('Wave: ' + wave); } function spawnEnemy() { var enemy; // Boss every 5 waves if (wave % 5 === 0 && enemiesSpawned === 0) { enemy = new Boss(); // Boss number (1 for wave 5, 2 for wave 10, etc.) var bossNumber = Math.floor(wave / 5); // Each boss is stronger than the previous one enemy.health = 300 + (bossNumber - 1) * 400; enemy.maxHealth = enemy.health; enemy.speed = 1 + bossNumber * 0.15; } else { // Progressive enemy introduction based on wave var enemyType; if (wave === 1) { // Wave 1: Only basic red enemies enemyType = 0; } else if (wave <= 5) { // Waves 2-5: Basic enemies and tanks enemyType = Math.floor(Math.random() * 2); } else if (wave <= 10) { // Waves 6-10: Basic, tanks, and fast enemies enemyType = Math.floor(Math.random() * 3); } else { // Wave 11+: All enemy types including shooters enemyType = Math.floor(Math.random() * 4); } switch (enemyType) { case 0: enemy = new Enemy(); // Scale enemy stats with wave - more aggressive scaling enemy.health = 20 + Math.pow(wave - 1, 1.5) * 5; enemy.maxHealth = enemy.health; enemy.speed = 1 + wave * 0.2; break; case 1: enemy = new EnemyTank(); // Scale tank stats with wave - more aggressive scaling enemy.health = 60 + Math.pow(wave - 1, 1.5) * 12; enemy.maxHealth = enemy.health; enemy.speed = 0.5 + wave * 0.12; break; case 2: enemy = new EnemyFast(); // Scale fast enemy stats with wave - more aggressive scaling enemy.health = 10 + Math.pow(wave - 1, 1.5) * 2; enemy.maxHealth = enemy.health; enemy.speed = 2 + wave * 0.25; break; case 3: enemy = new EnemyShooter(); // Scale shooter stats with wave enemy.health = 30 + Math.pow(wave - 1, 1.5) * 8; enemy.maxHealth = enemy.health; enemy.speed = 0.3 + wave * 0.05; enemy.shootCooldown = Math.max(30, 90 - wave * 3); // Much faster shooting - starts at 1.5 seconds enemy.shootRange = 800; // Increased range so they shoot from farther away break; } } // Random spawn position at screen edges var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top enemy.x = Math.random() * 2048; enemy.y = -50; break; case 1: // Right enemy.x = 2048 + 50; enemy.y = Math.random() * 2732; break; case 2: // Bottom enemy.x = Math.random() * 2048; enemy.y = 2732 + 50; break; case 3: // Left enemy.x = -50; enemy.y = Math.random() * 2732; break; } enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; } function endWave() { waveInProgress = false; wave++; waveStartDelay = 300; // 5 seconds delay statusText.setText('Wave ' + (wave - 1) + ' Complete! Next wave in 5 seconds...'); // Bonus points for surviving the wave points += wave * 10; updatePointsDisplay(); } game.down = function (x, y, obj) { // Manual shooting when tapping the screen if (waveInProgress) { base.attack(); } }; game.update = function () { // Handle wave progression if (!waveInProgress) { if (waveStartDelay > 0) { waveStartDelay--; if (waveStartDelay === 0) { startWave(); } } } else { // Spawn enemies if (enemiesSpawned < enemiesPerWave) { spawnTimer++; var spawnDelay = Math.max(10, 25 - Math.floor(wave / 2)); // Much faster spawning as waves progress if (spawnTimer >= spawnDelay) { spawnEnemy(); spawnTimer = 0; } } // Check if wave is complete if (enemiesSpawned >= enemiesPerWave && enemies.length === 0) { endWave(); } } // Update shield if (shield) { shield.update(); } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var shouldRemove = enemy.update(); if (shouldRemove) { enemy.destroy(); enemies.splice(i, 1); } } // Update bullets for (var j = bullets.length - 1; j >= 0; j--) { var bullet = bullets[j]; var shouldRemove = bullet.update(); if (shouldRemove) { // Defensive: Only destroy if bullet has a parent (avoid undefined parent crash) if (bullet && bullet.parent) { bullet.destroy(); } bullets.splice(j, 1); } } // Update enemy bullets for (var k = enemyBullets.length - 1; k >= 0; k--) { var enemyBullet = enemyBullets[k]; var shouldRemove = enemyBullet.update(); if (shouldRemove) { // Defensive: Only destroy if bullet has a parent (avoid undefined parent crash) if (enemyBullet && enemyBullet.parent) { enemyBullet.destroy(); } enemyBullets.splice(k, 1); } } // Auto-attack if (waveInProgress) { base.attack(); } }; // Start first wave after delay waveStartDelay = 180;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Base = Container.expand(function () {
var self = Container.call(this);
// Create a shadow for the base
var baseShadow = self.attachAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.35
});
baseShadow.y += 18;
baseShadow.x += 10;
baseShadow.tint = 0x000000;
// Main base graphics
var baseGraphics = self.attachAsset('base', {
anchorX: 0.5,
anchorY: 0.5
});
var rangeIndicator = self.attachAsset('rangeIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2
});
self.health = 100;
self.maxHealth = 100;
self.attackPower = 50;
self.attackSpeed = 45; // frames between attacks
self.attackRange = 400;
self.multiAttack = 1;
self.lastAttack = 0;
self.updateRangeIndicator = function () {
var scale = self.attackRange * 2 / 800;
rangeIndicator.scaleX = scale;
rangeIndicator.scaleY = scale;
};
self.takeDamage = function (damage) {
// Use provided damage or default to 20 for player bullets
if (typeof damage !== 'number') damage = 20;
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
LK.showGameOver();
}
updateHealthDisplay();
};
self.canAttack = function () {
return LK.ticks - self.lastAttack >= self.attackSpeed;
};
self.attack = function () {
if (!self.canAttack()) return;
var targets = [];
for (var i = 0; i < enemies.length && targets.length < self.multiAttack; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange) {
targets.push(enemy);
}
}
for (var j = 0; j < targets.length; j++) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.target = targets[j];
bullet.damage = self.attackPower;
bullets.push(bullet);
game.addChild(bullet);
}
if (targets.length > 0) {
self.lastAttack = LK.ticks;
LK.getSound('shoot').play();
}
};
// Add orbit animation to base (planet-like orbit around center)
self.orbitRadius = 220; // Distance from center of screen
self.orbitAngle = Math.random() * Math.PI * 2; // Start at random angle
self.orbitSpeed = 0.008 + Math.random() * 0.004; // Slower, smooth orbit
self.rotationSpeed = 0.012 + Math.random() * 0.01; // Still spin in place for visual effect
self.update = function () {
// Orbit around the center of the screen
self.orbitAngle += self.orbitSpeed;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
self.x = centerX + Math.cos(self.orbitAngle) * self.orbitRadius;
self.y = centerY + Math.sin(self.orbitAngle) * self.orbitRadius;
// Spin in place for visual effect
baseGraphics.rotation += self.rotationSpeed;
baseShadow.rotation += self.rotationSpeed;
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
// Add shadow for boss
var bossShadow = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.10,
scaleY: 1.10,
alpha: 0.32
});
bossShadow.y += 20;
bossShadow.x += 12;
bossShadow.tint = 0x000000;
var enemyGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 200;
self.maxHealth = 200;
self.speed = 0.8;
self.direction = {
x: 0,
y: 0
};
self.lastX = self.x;
self.lastY = self.y;
self.setTarget = 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.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
LK.getSound('enemyHit').play();
// Create explosion effect
// Create particle explosion effect
for (var p = 0; p < 15; p++) {
var particle = LK.getAsset('boss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
var angle = p / 15 * Math.PI * 2;
var speed = 150 + Math.random() * 100;
var targetX = self.x + Math.cos(angle) * speed;
var targetY = self.y + Math.sin(angle) * speed;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
tween(self, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Boss will be removed from enemies array in game.update
}
});
return true; // Boss died
}
return false;
};
self.update = function () {
// If boss is dead (health <= 0), don't move and prepare for removal
if (self.health <= 0) {
return true; // Remove dead boss
}
self.lastX = self.x;
self.lastY = self.y;
// Keep shadow under boss
bossShadow.x = enemyGraphics.x + 12;
bossShadow.y = enemyGraphics.y + 20;
bossShadow.rotation = enemyGraphics.rotation;
// Move toward base
self.setTarget(base.x, base.y);
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Check if reached base
var dx = self.x - base.x;
var dy = self.y - base.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
// Boss deals double damage (4-10 total)
var bossDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5
base.takeDamage(bossDamage);
base.takeDamage(bossDamage); // Boss does double damage
return true; // Boss reached base
}
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 = 8;
self.target = null;
self.damage = 10;
self.update = function () {
if (!self.target || !self.target.parent) {
return true; // Remove bullet if target is gone
}
// Check collision with enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var enemyBullet = enemyBullets[i];
var dx = enemyBullet.x - self.x;
var dy = enemyBullet.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 15) {
// Destroy enemy bullet
if (enemyBullet && enemyBullet.parent) {
enemyBullet.destroy();
}
enemyBullets.splice(i, 1);
// Give points for destroying enemy bullet
points += 5;
updatePointsDisplay();
LK.getSound('hit').play();
// Flash effect
LK.effects.flashObject(self, 0x00ff00, 200);
return true; // Destroy this bullet too
}
}
// Move toward 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 < 20) {
// Hit target
var enemyDied = self.target.takeDamage(self.damage);
if (enemyDied) {
// Different point rewards for different enemy types
if (self.target instanceof Boss) {
points += 100;
} else if (self.target instanceof EnemyTank) {
points += 20;
} else if (self.target instanceof EnemyFast) {
points += 15;
} else if (self.target instanceof EnemyShooter) {
points += 25;
} else {
points += 10;
}
updatePointsDisplay();
}
LK.getSound('hit').play();
return true; // Remove bullet
}
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Check if bullet is still attached before returning
if (!self.parent) {
return true; // Remove bullet if it's already been removed
}
return false;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Add shadow for enemy
var enemyShadow = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.35
});
enemyShadow.y += 10;
enemyShadow.x += 6;
enemyShadow.tint = 0x000000;
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 20;
self.maxHealth = 20;
self.speed = 1;
self.direction = {
x: 0,
y: 0
};
self.lastX = self.x;
self.lastY = self.y;
self.setTarget = 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.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
LK.getSound('enemyHit').play();
// Create explosion effect
// Create particle explosion effect
for (var p = 0; p < 8; p++) {
var particle = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
var angle = p / 8 * Math.PI * 2;
var speed = 100 + Math.random() * 50;
var targetX = self.x + Math.cos(angle) * speed;
var targetY = self.y + Math.sin(angle) * speed;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Enemy will be removed from enemies array in game.update
}
});
return true; // Enemy died
}
return false;
};
self.update = function () {
// If enemy is dead (health <= 0), don't move and prepare for removal
if (self.health <= 0) {
return true; // Remove dead enemy
}
self.lastX = self.x;
self.lastY = self.y;
// Keep shadow under enemy
enemyShadow.x = enemyGraphics.x + 6;
enemyShadow.y = enemyGraphics.y + 10;
enemyShadow.rotation = enemyGraphics.rotation;
// Move toward base
self.setTarget(base.x, base.y);
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Check if reached base
var dx = self.x - base.x;
var dy = self.y - base.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 70) {
// Enemy deals 2-5 damage to base
var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5
base.takeDamage(enemyDamage);
// Defensive: Only remove if enemy has a parent (avoid undefined parent crash)
if (self.parent) {
self.parent.removeChild(self);
}
return true; // Enemy reached base
}
return false;
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff6666
});
self.speed = 4;
self.damage = 10;
self.direction = {
x: 0,
y: 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.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
self.update = function () {
// Move bullet
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Check if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
return true; // Remove bullet
}
// Check collision with base
var dx = self.x - base.x;
var dy = self.y - base.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
// Enemy bullet deals 2-5 damage to base
var bulletDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5
base.takeDamage(bulletDamage);
LK.getSound('hit').play();
return true; // Remove bullet
}
return false;
};
return self;
});
var EnemyFast = Container.expand(function () {
var self = Container.call(this);
// Add shadow for fast enemy
var enemyShadow = self.attachAsset('enemyFast', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.35
});
enemyShadow.y += 8;
enemyShadow.x += 5;
enemyShadow.tint = 0x000000;
var enemyGraphics = self.attachAsset('enemyFast', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.maxHealth = 5;
self.speed = 1.5;
self.direction = {
x: 0,
y: 0
};
self.lastX = self.x;
self.lastY = self.y;
self.setTarget = 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.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
LK.getSound('enemyHit').play();
// Create explosion effect
// Create particle explosion effect
for (var p = 0; p < 6; p++) {
var particle = LK.getAsset('enemyFast', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
var angle = p / 6 * Math.PI * 2;
var speed = 80 + Math.random() * 40;
var targetX = self.x + Math.cos(angle) * speed;
var targetY = self.y + Math.sin(angle) * speed;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 350 + Math.random() * 150,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Enemy will be removed from enemies array in game.update
}
});
return true; // Enemy died
}
return false;
};
self.update = function () {
// If enemy is dead (health <= 0), don't move and prepare for removal
if (self.health <= 0) {
return true; // Remove dead enemy
}
self.lastX = self.x;
self.lastY = self.y;
// Keep shadow under fast enemy
enemyShadow.x = enemyGraphics.x + 5;
enemyShadow.y = enemyGraphics.y + 8;
enemyShadow.rotation = enemyGraphics.rotation;
// Move toward base
self.setTarget(base.x, base.y);
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Check if reached base
var dx = self.x - base.x;
var dy = self.y - base.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 70) {
// Enemy deals 2-5 damage to base
var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5
base.takeDamage(enemyDamage);
return true; // Enemy reached base
}
return false;
};
return self;
});
var EnemyShooter = Container.expand(function () {
var self = Container.call(this);
// Add shadow for shooter enemy
var enemyShadow = self.attachAsset('enemyShooter', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.35
});
enemyShadow.y += 10;
enemyShadow.x += 6;
enemyShadow.tint = 0x000000;
var enemyGraphics = self.attachAsset('enemyShooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 30;
self.maxHealth = 30;
self.speed = 0.3; // Very slow movement
self.direction = {
x: 0,
y: 0
};
self.lastX = self.x;
self.lastY = self.y;
self.lastShot = 0;
self.shootCooldown = 60; // 1 second between shots - much faster
self.shootRange = 800; // Increased range for earlier shooting
self.setTarget = 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.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
self.shoot = function () {
if (LK.ticks - self.lastShot < self.shootCooldown) return;
var dx = base.x - self.x;
var dy = base.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.shootRange) {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.setDirection(base.x, base.y);
enemyBullets.push(bullet);
game.addChild(bullet);
self.lastShot = LK.ticks;
LK.effects.flashObject(self, 0xff6666, 200);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
LK.getSound('enemyHit').play();
// Create explosion effect
for (var p = 0; p < 8; p++) {
var particle = LK.getAsset('enemyShooter', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
var angle = p / 8 * Math.PI * 2;
var speed = 100 + Math.random() * 50;
var targetX = self.x + Math.cos(angle) * speed;
var targetY = self.y + Math.sin(angle) * speed;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Enemy will be removed from enemies array in game.update
}
});
return true; // Enemy died
}
return false;
};
self.update = function () {
// If enemy is dead, don't move and prepare for removal
if (self.health <= 0) {
return true; // Remove dead enemy
}
self.lastX = self.x;
self.lastY = self.y;
// Keep shadow under enemy
enemyShadow.x = enemyGraphics.x + 6;
enemyShadow.y = enemyGraphics.y + 10;
enemyShadow.rotation = enemyGraphics.rotation;
// Move toward base slowly
self.setTarget(base.x, base.y);
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Try to shoot at base
self.shoot();
// Check if reached base
var dx = self.x - base.x;
var dy = self.y - base.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 70) {
// Enemy deals 2-5 damage to base
var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5
base.takeDamage(enemyDamage);
return true; // Enemy reached base
}
return false;
};
return self;
});
// 3 seconds
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
// Add shadow for tank enemy
var enemyShadow = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.08,
scaleY: 1.08,
alpha: 0.35
});
enemyShadow.y += 12;
enemyShadow.x += 7;
enemyShadow.tint = 0x000000;
var enemyGraphics = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 40;
self.maxHealth = 40;
self.speed = 0.5;
self.direction = {
x: 0,
y: 0
};
self.lastX = self.x;
self.lastY = self.y;
self.setTarget = 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.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
if (self.health <= 0) {
LK.getSound('enemyHit').play();
// Create explosion effect
// Create particle explosion effect
for (var p = 0; p < 10; p++) {
var particle = LK.getAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
particle.x = self.x;
particle.y = self.y;
game.addChild(particle);
var angle = p / 10 * Math.PI * 2;
var speed = 120 + Math.random() * 60;
var targetX = self.x + Math.cos(angle) * speed;
var targetY = self.y + Math.sin(angle) * speed;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 500 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Enemy will be removed from enemies array in game.update
}
});
return true; // Enemy died
}
return false;
};
self.update = function () {
// If enemy is dead (health <= 0), don't move and prepare for removal
if (self.health <= 0) {
return true; // Remove dead enemy
}
self.lastX = self.x;
self.lastY = self.y;
// Keep shadow under tank enemy
enemyShadow.x = enemyGraphics.x + 7;
enemyShadow.y = enemyGraphics.y + 12;
enemyShadow.rotation = enemyGraphics.rotation;
// Move toward base
self.setTarget(base.x, base.y);
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Check if reached base
var dx = self.x - base.x;
var dy = self.y - base.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 70) {
// Enemy deals 2-5 damage to base
var enemyDamage = 2 + Math.floor(Math.random() * 4); // Random between 2-5
base.takeDamage(enemyDamage);
return true; // Enemy reached base
}
return false;
};
return self;
});
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldGraphics = LK.getAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
alpha: 0.8
});
self.addChild(shieldGraphics);
self.radius = 250; // Distance from base center
self.rotationSpeed = 0.02; // Rotation speed
self.angle = 0;
self.update = function () {
// Rotate around the base
self.angle += self.rotationSpeed;
self.x = base.x + Math.cos(self.angle) * self.radius;
self.y = base.y + Math.sin(self.angle) * self.radius;
// Check collision with enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Shield collision radius
if (distance < 50) {
// Destroy enemy on contact
enemy.takeDamage(enemy.health);
LK.effects.flashObject(self, 0x00ff00, 200);
}
}
};
return self;
});
var UpgradeButton = Container.expand(function (type, cost, description, fontSize) {
var self = Container.call(this);
var buttonGraphics = self.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Reduced width for side-by-side layout
scaleY: 0.7 // Reduced height for compactness
});
self.type = type;
self.cost = cost;
self.description = description;
self.level = 0;
self.maxLevel = 999; // Default no limit
if (typeof fontSize !== "number" || fontSize < 10) fontSize = 36;
var buttonText = new Text2(description + '\n' + cost + ' pts', {
size: 64,
// Match pointsText/waveText/healthText size
fill: 0xFFFFFF,
font: "bold 64px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma" // Match style for readability
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.updateCost = function (newCost) {
self.cost = newCost;
if (self.level >= self.maxLevel) {
buttonText.setText(self.description + '\nMAX LEVEL');
buttonGraphics.tint = 0x808080;
} else {
buttonText.setText(self.description + '\n' + newCost + ' pts');
}
};
self.down = function (x, y, obj) {
if (points >= self.cost && self.level < self.maxLevel) {
points -= self.cost;
updatePointsDisplay();
self.level++;
switch (self.type) {
case 'power':
base.attackPower += 20;
powerUpgradeCost = Math.floor(powerUpgradeCost * 1.5);
self.updateCost(powerUpgradeCost);
break;
case 'speed':
base.attackSpeed = Math.max(10, base.attackSpeed - 10);
speedUpgradeCost = Math.floor(speedUpgradeCost * 1.5);
self.updateCost(speedUpgradeCost);
break;
case 'range':
base.attackRange += 100;
base.updateRangeIndicator();
rangeUpgradeCost = Math.floor(rangeUpgradeCost * 1.5);
self.updateCost(rangeUpgradeCost);
break;
case 'multi':
base.multiAttack += 1;
multiUpgradeCost = Math.floor(multiUpgradeCost * 2);
self.updateCost(multiUpgradeCost);
break;
case 'shield':
if (!shield) {
shield = new Shield();
game.addChild(shield);
self.visible = false; // Hide shield button after purchase
}
break;
}
LK.effects.flashObject(self, 0xffffff, 300);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Add background image behind all gameplay elements
// Background image for the game (full screen, subtle, fits 2048x2732)
var backgroundImage = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1
});
game.addChild(backgroundImage);
var base = game.addChild(new Base());
base.x = 2048 / 2;
base.y = 2732 / 2;
base.updateRangeIndicator();
var enemies = [];
var bullets = [];
var enemyBullets = [];
var points = 0;
var wave = 1;
var waveInProgress = false;
var enemiesSpawned = 0;
var enemiesPerWave = 5;
var spawnTimer = 0;
var waveStartDelay = 0;
var shield = null;
// Upgrade costs
var powerUpgradeCost = 50;
var speedUpgradeCost = 30;
var rangeUpgradeCost = 40;
var multiUpgradeCost = 100;
var shieldUpgradeCost = 500;
// UI Elements
var healthText = new Text2('Health: 100', {
size: 48,
fill: 0xFFFFFF
});
healthText.anchor.set(0, 0);
healthText.x = 50;
healthText.y = 150;
LK.gui.topLeft.addChild(healthText);
var pointsText = new Text2('Points: 0', {
size: 64,
fill: 0xFFFFFF
});
pointsText.anchor.set(0.5, 0);
pointsText.x = 0;
pointsText.y = 120;
LK.gui.top.addChild(pointsText);
var waveText = new Text2('Wave: 1', {
size: 48,
fill: 0xFFFFFF
});
waveText.anchor.set(1, 0);
waveText.x = -50;
waveText.y = 150;
LK.gui.topRight.addChild(waveText);
var statusText = new Text2('Prepare for Wave 1!', {
size: 48,
fill: 0xF39C12
});
statusText.anchor.set(0.5, 0);
statusText.x = 0;
statusText.y = 50;
LK.gui.top.addChild(statusText);
// New upgrade menu: horizontal, text/buttons styled to match health/wave/points, large and readable
// Layout constants for new menu
var upgradeMenuY = 2732 - 220;
var upgradeMenuButtonCount = 5;
var upgradeMenuButtonSpacing = 340;
var upgradeMenuButtonStartX = (2048 - upgradeMenuButtonSpacing * (upgradeMenuButtonCount - 1)) / 2;
// Button info: [type, cost, label, maxLevel, description]
var upgradeMenuData = [['power', powerUpgradeCost, 'Güç', undefined, 'Saldırı gücünü artırır'], ['speed', speedUpgradeCost, 'Hız', undefined, 'Saldırı hızını artırır'], ['range', rangeUpgradeCost, 'Menzil', 4, 'Menzili artırır'], ['multi', multiUpgradeCost, 'Çoklu', 3, 'Çoklu atış sağlar'], ['shield', shieldUpgradeCost, 'Kalkan', 1, 'Kalkan ekler']];
// Store references for cost/level updates
var powerButton, speedButton, rangeButton, multiButton, shieldButton;
var upgradeMenuButtons = [];
for (var i = 0; i < upgradeMenuButtonCount; i++) {
var btnType = upgradeMenuData[i][0];
var btnCost = upgradeMenuData[i][1];
var btnLabel = upgradeMenuData[i][2];
var btnMaxLevel = upgradeMenuData[i][3];
var btnDesc = upgradeMenuData[i][4];
// Create button background using 'upgradeButton' asset, styled to match general UI
var btn = new UpgradeButton(btnType, btnCost, btnLabel, Math.floor(LK.width / 18));
// Remove default children (graphics/text) for custom layout
while (btn.children.length > 0) {
var child = btn.children[0];
// Defensive: Only remove if child has a parent (avoid undefined parent crash)
if (child && child.parent) {
child.parent.removeChild(child);
} else if (typeof child !== "undefined") {
// Fallback: forcibly remove from children array if not attached
btn.children.splice(0, 1);
} else {
// If child is undefined, break to avoid infinite loop
break;
}
}
var bg = btn.attachAsset('upgradeButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.0,
alpha: 0.98
});
btn.children.unshift(bg);
// Position horizontally
btn.x = upgradeMenuButtonStartX + i * upgradeMenuButtonSpacing;
btn.y = upgradeMenuY;
// Add label (large, bold, white, matching pointsText style)
var labelText = new Text2(btnLabel, {
size: Math.floor(LK.width / 18),
fill: 0xffffff,
font: "bold " + Math.floor(LK.width / 18) + "px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma"
});
labelText.anchor.set(0.5, 1);
labelText.x = 0;
labelText.y = -10;
btn.addChild(labelText);
// Add cost (large, bold, yellow, matching pointsText style)
var costText = new Text2(btnCost + ' pts', {
size: Math.floor(LK.width / 22),
fill: 0xf1c40f,
font: "bold " + Math.floor(LK.width / 22) + "px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma"
});
costText.anchor.set(0.5, 0);
costText.x = 0;
costText.y = 10;
btn.addChild(costText);
// Add description (smaller, white, below cost)
var descText = new Text2(btnDesc, {
size: Math.floor(LK.width / 32),
fill: 0xffffff,
font: "bold " + Math.floor(LK.width / 32) + "px Arial, 'GillSans-Bold', Impact, 'Arial Black', Tahoma"
});
descText.anchor.set(0.5, 0);
descText.x = 0;
descText.y = 70;
btn.addChild(descText);
// Set maxLevel if needed
if (typeof btnMaxLevel === "number") btn.maxLevel = btnMaxLevel;
// Store reference for cost/level updates
if (btnType === 'power') powerButton = btn;
if (btnType === 'speed') speedButton = btn;
if (btnType === 'range') rangeButton = btn;
if (btnType === 'multi') multiButton = btn;
if (btnType === 'shield') shieldButton = btn;
upgradeMenuButtons.push(btn);
game.addChild(btn);
}
// Patch UpgradeButton.updateCost to update the cost text inside the button
UpgradeButton.prototype.updateCost = function (newCost) {
this.cost = newCost;
// Find costText (Text2) child
for (var i = 0; i < this.children.length; i++) {
var ch = this.children[i];
if (ch instanceof Text2 && ch.text && (ch.text.indexOf('pts') !== -1 || ch.text === 'MAX LEVEL')) {
if (this.level >= this.maxLevel) {
ch.setText('MAX LEVEL');
if (this.children[0] && this.children[0].tint !== undefined) this.children[0].tint = 0x808080;
} else {
ch.setText(newCost + ' pts');
if (this.children[0] && this.children[0].tint !== undefined) this.children[0].tint = 0xffffff;
}
}
}
};
function updateHealthDisplay() {
healthText.setText('Health: ' + base.health);
}
function updatePointsDisplay() {
pointsText.setText('Points: ' + points);
}
function startWave() {
waveInProgress = true;
enemiesSpawned = 0;
// Boss waves have only 1 enemy (the boss)
// Boss now appears every 5 waves (5, 10, 15, ...)
if (wave % 5 === 0) {
enemiesPerWave = 1;
statusText.setText('BOSS WAVE ' + wave + ' - Prepare for battle!');
} else {
enemiesPerWave = Math.floor(5 + Math.pow(wave, 1.3) * 2);
statusText.setText('Wave ' + wave + ' - ' + enemiesPerWave + ' enemies incoming!');
}
spawnTimer = 0;
waveText.setText('Wave: ' + wave);
}
function spawnEnemy() {
var enemy;
// Boss every 5 waves
if (wave % 5 === 0 && enemiesSpawned === 0) {
enemy = new Boss();
// Boss number (1 for wave 5, 2 for wave 10, etc.)
var bossNumber = Math.floor(wave / 5);
// Each boss is stronger than the previous one
enemy.health = 300 + (bossNumber - 1) * 400;
enemy.maxHealth = enemy.health;
enemy.speed = 1 + bossNumber * 0.15;
} else {
// Progressive enemy introduction based on wave
var enemyType;
if (wave === 1) {
// Wave 1: Only basic red enemies
enemyType = 0;
} else if (wave <= 5) {
// Waves 2-5: Basic enemies and tanks
enemyType = Math.floor(Math.random() * 2);
} else if (wave <= 10) {
// Waves 6-10: Basic, tanks, and fast enemies
enemyType = Math.floor(Math.random() * 3);
} else {
// Wave 11+: All enemy types including shooters
enemyType = Math.floor(Math.random() * 4);
}
switch (enemyType) {
case 0:
enemy = new Enemy();
// Scale enemy stats with wave - more aggressive scaling
enemy.health = 20 + Math.pow(wave - 1, 1.5) * 5;
enemy.maxHealth = enemy.health;
enemy.speed = 1 + wave * 0.2;
break;
case 1:
enemy = new EnemyTank();
// Scale tank stats with wave - more aggressive scaling
enemy.health = 60 + Math.pow(wave - 1, 1.5) * 12;
enemy.maxHealth = enemy.health;
enemy.speed = 0.5 + wave * 0.12;
break;
case 2:
enemy = new EnemyFast();
// Scale fast enemy stats with wave - more aggressive scaling
enemy.health = 10 + Math.pow(wave - 1, 1.5) * 2;
enemy.maxHealth = enemy.health;
enemy.speed = 2 + wave * 0.25;
break;
case 3:
enemy = new EnemyShooter();
// Scale shooter stats with wave
enemy.health = 30 + Math.pow(wave - 1, 1.5) * 8;
enemy.maxHealth = enemy.health;
enemy.speed = 0.3 + wave * 0.05;
enemy.shootCooldown = Math.max(30, 90 - wave * 3); // Much faster shooting - starts at 1.5 seconds
enemy.shootRange = 800; // Increased range so they shoot from farther away
break;
}
}
// Random spawn position at screen edges
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -50;
break;
case 1:
// Right
enemy.x = 2048 + 50;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2732 + 50;
break;
case 3:
// Left
enemy.x = -50;
enemy.y = Math.random() * 2732;
break;
}
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function endWave() {
waveInProgress = false;
wave++;
waveStartDelay = 300; // 5 seconds delay
statusText.setText('Wave ' + (wave - 1) + ' Complete! Next wave in 5 seconds...');
// Bonus points for surviving the wave
points += wave * 10;
updatePointsDisplay();
}
game.down = function (x, y, obj) {
// Manual shooting when tapping the screen
if (waveInProgress) {
base.attack();
}
};
game.update = function () {
// Handle wave progression
if (!waveInProgress) {
if (waveStartDelay > 0) {
waveStartDelay--;
if (waveStartDelay === 0) {
startWave();
}
}
} else {
// Spawn enemies
if (enemiesSpawned < enemiesPerWave) {
spawnTimer++;
var spawnDelay = Math.max(10, 25 - Math.floor(wave / 2)); // Much faster spawning as waves progress
if (spawnTimer >= spawnDelay) {
spawnEnemy();
spawnTimer = 0;
}
}
// Check if wave is complete
if (enemiesSpawned >= enemiesPerWave && enemies.length === 0) {
endWave();
}
}
// Update shield
if (shield) {
shield.update();
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var shouldRemove = enemy.update();
if (shouldRemove) {
enemy.destroy();
enemies.splice(i, 1);
}
}
// Update bullets
for (var j = bullets.length - 1; j >= 0; j--) {
var bullet = bullets[j];
var shouldRemove = bullet.update();
if (shouldRemove) {
// Defensive: Only destroy if bullet has a parent (avoid undefined parent crash)
if (bullet && bullet.parent) {
bullet.destroy();
}
bullets.splice(j, 1);
}
}
// Update enemy bullets
for (var k = enemyBullets.length - 1; k >= 0; k--) {
var enemyBullet = enemyBullets[k];
var shouldRemove = enemyBullet.update();
if (shouldRemove) {
// Defensive: Only destroy if bullet has a parent (avoid undefined parent crash)
if (enemyBullet && enemyBullet.parent) {
enemyBullet.destroy();
}
enemyBullets.splice(k, 1);
}
}
// Auto-attack
if (waveInProgress) {
base.attack();
}
};
// Start first wave after delay
waveStartDelay = 180;
thin range circle. In-Game asset. 2d. High contrast. No shadows
yellow meteor ball no flame effect. In-Game asset. 2d. High contrast. No shadows
purple meteor ball no flame effect. In-Game asset. 2d. High contrast. No shadows
red meteor mall no flame effect. In-Game asset. 2d. High contrast. No shadows
colorful meteor ball no flame effect. In-Game asset. 2d. High contrast. No shadows
green button empty. In-Game asset. 2d. High contrast. No shadows
yuvarlak mermi roket şeklinde. In-Game asset. 2d. High contrast. No shadows
circle shape starship. In-Game asset. 2d. High contrast. No shadows