/**** * Plugins ****/ var storage = LK.import("@upit/storage.v1"); var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Arrow = Container.expand(function (team, startX, startY, targetX, targetY, damage) { var self = Container.call(this); var graphics = self.attachAsset('Arrow', { anchorX: 0.5, anchorY: 0.5 }); self.team = team; self.damage = damage; self.speed = 8; self.target = null; // Calculate direction var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.directionX = dx / distance; self.directionY = dy / distance; // Set rotation to point toward target self.rotation = Math.atan2(dy, dx); // Position arrow at start self.x = startX; self.y = startY; self.update = function () { // Move arrow self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Check if arrow is off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.destroy(); // Remove from arrows array for (var i = arrows.length - 1; i >= 0; i--) { if (arrows[i] === self) { arrows.splice(i, 1); break; } } } // Check collision with enemy units var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; if (self.intersects(enemy)) { enemy.health -= self.damage; if (enemy.health <= 0) { enemyUnits.splice(i, 1); enemy.destroy(); } self.destroy(); // Remove from arrows array for (var j = arrows.length - 1; j >= 0; j--) { if (arrows[j] === self) { arrows.splice(j, 1); break; } } return; } } // Check collision with enemy towers var enemyTowers = self.team === 'player' ? aiTowers : playerTowers; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (self.intersects(tower)) { tower.health -= self.damage; if (tower.health <= 0) { enemyTowers.splice(i, 1); tower.destroy(); LK.getSound('towerDestroyed').play(); } self.destroy(); // Remove from arrows array for (var j = arrows.length - 1; j >= 0; j--) { if (arrows[j] === self) { arrows.splice(j, 1); break; } } return; } } }; return self; }); var Coin = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.value = 1; self.collected = false; return self; }); var CombatUnit = Container.expand(function (team, unitType) { var self = Container.call(this); var assetName = team === 'player' ? unitType : 'ai' + unitType.charAt(0).toUpperCase() + unitType.slice(1); var graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.team = team; self.unitType = unitType; self.speed = unitType === 'dragon' ? 3 : unitType === 'wizard' ? 1.5 : unitType === 'archer' ? 2.5 : 2; self.damage = unitType === 'dragon' ? 40 : unitType === 'wizard' ? 30 : unitType === 'archer' ? 20 : 10; self.health = unitType === 'dragon' ? 50 : unitType === 'wizard' ? 40 : unitType === 'archer' ? 30 : unitType === 'warrior' ? 20 : 10; self.maxHealth = self.health; self.attackRange = unitType === 'wizard' ? 100 : unitType === 'archer' ? 200 : 50; self.attackCooldown = 0; self.target = null; self.isWaiting = !zombieMode && team === 'player'; // Only player units wait in normal mode // Create health bar background (black) self.healthBarBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.75 }); self.healthBarBg.x = 0; self.healthBarBg.y = -120; self.healthBarBg.tint = team === 'player' ? 0x0000ff : 0x000000; self.addChild(self.healthBarBg); // Create health bar (green for player, red for AI) self.healthBar = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.75 }); self.healthBar.x = 0; self.healthBar.y = -120; self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000; self.addChild(self.healthBar); self.healthText = new Text2(self.health.toString(), { size: 50, fill: '#ffffff' }); self.healthText.anchor.set(0.5, 0.5); self.addChild(self.healthText); self.healthText.x = 0; self.healthText.y = -120; self.update = function () { if (self.attackCooldown > 0) { self.attackCooldown--; } // In zombie mode, player units prioritize attacking zombies if (zombieMode && self.team === 'player' && self.unitType !== 'worker') { self.attackZombies(); } else if (!zombieMode && self.team === 'player' && self.isWaiting) { // In normal mode, player units wait unless attack is triggered // Stay in position and don't move } else { if (!self.target) { self.findNearestEnemyTower(); } else { self.moveToTarget(); } // Combat with nearby enemy units self.combatNearbyEnemies(); } self.healthText.setText(self.health.toString()); // Update health bar scale based on health percentage var healthPercentage = self.health / self.maxHealth; tween(self.healthBar, { scaleX: 1.5 * healthPercentage }, { duration: 200 }); }; self.findNearestEnemyTower = function () { // In zombie mode, player units should prioritize attacking zombies if (zombieMode && self.team === 'player' && self.unitType !== 'worker') { var nearestZombie = null; var nearestDistance = Infinity; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2)); if (distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } // If we found a zombie, target it if (nearestZombie) { self.target = nearestZombie; return; } } // Find nearest target - prioritize ALL enemy units first (including workers), then towers var nearestTarget = null; var nearestDistance = Infinity; // First check all enemy units (including workers) var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; // Target all enemy units now, including workers if (self.unitType !== 'worker') { var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance < nearestDistance) { nearestDistance = distance; nearestTarget = enemy; } } } // Only target towers if no enemy units remain if (!nearestTarget) { var enemyTowers = self.team === 'player' ? aiTowers : playerTowers; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2)); if (distance < nearestDistance) { nearestDistance = distance; nearestTarget = tower; } } } self.target = nearestTarget; }; self.moveToTarget = function () { if (!self.target) { return; } 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.attackRange) { self.attackTarget(); } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.attackTarget = function () { if (self.attackCooldown <= 0 && self.target) { if (self.unitType === 'archer') { // Archer shoots arrow var arrow = new Arrow(self.team, self.x, self.y, self.target.x, self.target.y, self.damage); arrows.push(arrow); game.addChild(arrow); } else if (self.unitType === 'wizard') { // Wizard shoots magic var magic = new Magic(self.team, self.x, self.y, self.target.x, self.target.y, self.damage); magics.push(magic); game.addChild(magic); } else if (self.unitType === 'dragon') { // Change to attack asset for all dragon attacks self.removeChild(graphics); var assetName = 'DragonAttack'; graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Dragon shoots fire with 1 damage var fire = new Fire(self.team, self.x, self.y, self.target.x, self.target.y, 1); fires.push(fire); game.addChild(fire); } else { // Melee attack self.target.health -= self.damage; if (self.target.health <= 0) { // Check if target is a unit or tower if (self.target.unitType) { // Target is a unit self.destroyUnit(); } else { // Target is a tower self.destroyTower(); } } } // Set dragon attack cooldown to 3 frames (0.05 seconds) if (self.unitType === 'dragon') { self.attackCooldown = 3; } else { self.attackCooldown = 60; } LK.getSound('attack').play(); } }; self.destroyUnit = function () { var units = self.team === 'player' ? aiUnits : playerUnits; var index = units.indexOf(self.target); if (index > -1) { units.splice(index, 1); self.target.destroy(); self.target = null; } }; self.destroyTower = function () { var towers = self.team === 'player' ? aiTowers : playerTowers; var index = towers.indexOf(self.target); if (index > -1) { towers.splice(index, 1); self.target.destroy(); LK.getSound('towerDestroyed').play(); self.target = null; } }; self.attackZombies = function () { // Find nearest zombie to target var nearestZombie = null; var nearestDistance = 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 < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } // Move toward and attack nearest zombie if (nearestZombie) { var dx = nearestZombie.x - self.x; var dy = nearestZombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Move toward zombie if not in attack range if (distance > self.attackRange) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } else if (self.attackCooldown <= 0) { // Attack zombie based on unit type if (self.unitType === 'archer') { // Archer shoots arrow at zombie var arrow = new Arrow(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage); arrows.push(arrow); game.addChild(arrow); } else if (self.unitType === 'wizard') { // Wizard shoots magic at zombie var magic = new Magic(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage); magics.push(magic); game.addChild(magic); } else if (self.unitType === 'dragon') { // Change to attack asset for dragon zombie attacks self.removeChild(graphics); var assetName = 'DragonAttack'; graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Dragon shoots fire at zombie var fire = new Fire(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage); fires.push(fire); game.addChild(fire); } else { // Melee attack (warrior) nearestZombie.health -= self.damage; if (nearestZombie.health <= 0) { for (var j = 0; j < zombies.length; j++) { if (zombies[j] === nearestZombie) { zombies.splice(j, 1); nearestZombie.destroy(); break; } } } } self.attackCooldown = 60; // 1 second cooldown LK.getSound('attack').play(); } } }; self.combatNearbyEnemies = function () { if (self.attackCooldown <= 0) { var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; var meleeRange = 60; // Close combat range for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; // Skip workers in combat if (enemy.unitType === 'worker') { continue; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // If enemy unit is within melee range, attack it if (distance <= meleeRange) { enemy.health -= self.damage; self.attackCooldown = 60; // 1 second cooldown LK.getSound('attack').play(); // Check if enemy is destroyed if (enemy.health <= 0) { enemyUnits.splice(i, 1); enemy.destroy(); } break; // Only attack one enemy per cycle } } } }; return self; }); var Fire = Container.expand(function (team, startX, startY, targetX, targetY, damage) { var self = Container.call(this); var graphics = self.attachAsset('Magic', { anchorX: 0.5, anchorY: 0.5 }); self.team = team; self.damage = damage; self.speed = 4; self.target = null; // Calculate direction var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.directionX = dx / distance; self.directionY = dy / distance; // Position fire at start self.x = startX; self.y = startY; // Add fire effect - orange/red tint and scaling graphics.tint = 0xff4500; // Orange-red fire color tween(graphics, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut }); self.update = function () { // Move fire self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Add fire flicker rotation graphics.rotation += 0.15; // Check if fire is off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.destroy(); // Remove from fires array for (var i = fires.length - 1; i >= 0; i--) { if (fires[i] === self) { fires.splice(i, 1); break; } } } // Check collision with enemy units var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; if (self.intersects(enemy)) { enemy.health -= self.damage; if (enemy.health <= 0) { enemyUnits.splice(i, 1); enemy.destroy(); } self.destroy(); // Remove from fires array for (var j = fires.length - 1; j >= 0; j--) { if (fires[j] === self) { fires.splice(j, 1); break; } } return; } } // Check collision with enemy towers var enemyTowers = self.team === 'player' ? aiTowers : playerTowers; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (self.intersects(tower)) { tower.health -= self.damage; if (tower.health <= 0) { enemyTowers.splice(i, 1); tower.destroy(); LK.getSound('towerDestroyed').play(); } self.destroy(); // Remove from fires array for (var j = fires.length - 1; j >= 0; j--) { if (fires[j] === self) { fires.splice(j, 1); break; } } return; } } }; return self; }); var Magic = Container.expand(function (team, startX, startY, targetX, targetY, damage) { var self = Container.call(this); var graphics = self.attachAsset('Magic', { anchorX: 0.5, anchorY: 0.5 }); self.team = team; self.damage = damage; self.speed = 6; self.target = null; // Calculate direction var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.directionX = dx / distance; self.directionY = dy / distance; // Position magic at start self.x = startX; self.y = startY; // Add magical glow effect tween(graphics, { tint: 0x9966ff }, { duration: 500, easing: tween.easeInOut }); tween(graphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); self.update = function () { // Move magic self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; // Add sparkle rotation graphics.rotation += 0.1; // Check if magic is off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.destroy(); // Remove from magics array for (var i = magics.length - 1; i >= 0; i--) { if (magics[i] === self) { magics.splice(i, 1); break; } } } // Check collision with enemy units var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; if (self.intersects(enemy)) { enemy.health -= self.damage; if (enemy.health <= 0) { enemyUnits.splice(i, 1); enemy.destroy(); } self.destroy(); // Remove from magics array for (var j = magics.length - 1; j >= 0; j--) { if (magics[j] === self) { magics.splice(j, 1); break; } } return; } } // Check collision with enemy towers var enemyTowers = self.team === 'player' ? aiTowers : playerTowers; for (var i = 0; i < enemyTowers.length; i++) { var tower = enemyTowers[i]; if (self.intersects(tower)) { tower.health -= self.damage; if (tower.health <= 0) { enemyTowers.splice(i, 1); tower.destroy(); LK.getSound('towerDestroyed').play(); } self.destroy(); // Remove from magics array for (var j = magics.length - 1; j >= 0; j--) { if (magics[j] === self) { magics.splice(j, 1); break; } } return; } } }; return self; }); var Tower = Container.expand(function (team) { var self = Container.call(this); var assetName = team === 'player' ? 'playerTower' : 'aiTower'; var graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 1.0 }); self.team = team; self.health = 500; self.maxHealth = 500; // Create health bar background (black) self.healthBarBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.08, scaleY: 0.625 }); self.healthBarBg.x = 0; self.healthBarBg.y = -262.5; self.healthBarBg.tint = 0x0000ff; self.addChild(self.healthBarBg); // Create health bar (green for player, red for AI) self.healthBar = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.08, scaleY: 0.625 }); self.healthBar.x = 0; self.healthBar.y = -262.5; self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000; self.addChild(self.healthBar); self.healthText = new Text2(self.health.toString(), { size: 42, fill: '#ffffff' }); self.healthText.anchor.set(0.5, 0.5); self.addChild(self.healthText); self.healthText.x = 0; self.healthText.y = -262.5; self.attackCooldown = 0; self.attackRange = 400; // Damage area outline removed as requested self.update = function () { if (self.attackCooldown > 0) { self.attackCooldown--; } self.attackNearbyEnemies(); self.healthText.setText(self.health.toString()); // Update health bar scale based on health percentage var healthPercentage = self.health / self.maxHealth; tween(self.healthBar, { scaleX: 2.08 * healthPercentage }, { duration: 200 }); }; self.attackNearbyEnemies = function () { if (self.attackCooldown <= 0) { var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; var nearestEnemy = null; var nearestDistance = Infinity; // Find nearest enemy unit within attack range for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; var dx = enemy.x - self.x; var dy = enemy.y - (self.y - 131.25); // Adjust for tower center position var distance = Math.sqrt(dx * dx + dy * dy); // Only consider units within the attack range if (distance <= self.attackRange && distance < nearestDistance) { nearestDistance = distance; nearestEnemy = enemy; } } // Attack only the nearest enemy if found if (nearestEnemy) { nearestEnemy.health -= 1; // Deal 1 damage as specified self.attackCooldown = 3; // 0.05 seconds at 60 FPS LK.getSound('attack').play(); if (nearestEnemy.health <= 0) { var index = enemyUnits.indexOf(nearestEnemy); if (index > -1) { self.destroyUnit(nearestEnemy, enemyUnits, index); } } } // In zombie mode, player towers also attack zombies (find nearest zombie) if (zombieMode && self.team === 'player') { var nearestZombie = null; var nearestZombieDistance = 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 - 131.25); var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.attackRange && distance < nearestZombieDistance) { nearestZombieDistance = distance; nearestZombie = zombie; } } // Attack only the nearest zombie if found if (nearestZombie && !nearestEnemy) { // Only attack zombie if no enemy unit was attacked nearestZombie.health -= 1; self.attackCooldown = 3; LK.getSound('attack').play(); if (nearestZombie.health <= 0) { var index = zombies.indexOf(nearestZombie); if (index > -1) { zombies.splice(index, 1); nearestZombie.destroy(); } } } } } }; self.showDamageArea = function () { // No visual effects when towers attack }; self.destroyUnit = function (unit, unitsArray, index) { unitsArray.splice(index, 1); unit.destroy(); }; return self; }); var Worker = Container.expand(function (team) { var self = Container.call(this); var assetName = team === 'player' ? 'worker' : 'aiWorker'; var graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.team = team; self.speed = 4; self.target = null; self.collectRange = 40; self.health = 10; self.maxHealth = 10; // Create health bar background (black) self.healthBarBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.75 }); self.healthBarBg.x = 0; self.healthBarBg.y = -120; self.healthBarBg.tint = 0x000000; self.addChild(self.healthBarBg); // Create health bar (green for player, red for AI) self.healthBar = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.75 }); self.healthBar.x = 0; self.healthBar.y = -120; self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000; self.addChild(self.healthBar); self.healthText = new Text2(self.health.toString(), { size: 50, fill: '#ffffff' }); self.healthText.anchor.set(0.5, 0.5); self.addChild(self.healthText); self.healthText.x = 0; self.healthText.y = -120; self.update = function () { if (!self.target) { self.findNearestCoin(); } else { self.moveToTarget(); } // Workers can defend themselves self.defendSelf(); self.healthText.setText(self.health.toString()); // Update health bar scale based on health percentage var healthPercentage = self.health / self.maxHealth; tween(self.healthBar, { scaleX: 1.5 * healthPercentage }, { duration: 200 }); }; self.findNearestCoin = function () { var targetCoins = self.team === 'player' ? playerCoins_array : aiCoins_array; var teamWorkers = self.team === 'player' ? playerUnits : aiUnits; var availableCoins = []; // First, collect all untargeted coins for (var i = 0; i < targetCoins.length; i++) { var coin = targetCoins[i]; if (!coin.collected) { // Check if another worker is already targeting this coin var isTargeted = false; for (var j = 0; j < teamWorkers.length; j++) { var worker = teamWorkers[j]; if (worker.unitType === 'worker' && worker !== self && worker.target === coin) { isTargeted = true; break; } } if (!isTargeted) { availableCoins.push(coin); } } } // If no available coins, clear target if (availableCoins.length === 0) { self.target = null; return; } // Get worker index to create unique targeting preference var workerIndex = -1; for (var i = 0; i < teamWorkers.length; i++) { if (teamWorkers[i] === self && teamWorkers[i].unitType === 'worker') { workerIndex = i; break; } } // Use worker index to create different targeting preferences var targetCoin = null; if (workerIndex >= 0 && workerIndex < availableCoins.length) { // Each worker gets a different coin based on their index targetCoin = availableCoins[workerIndex]; } else { // If more workers than coins, use modulo to cycle through available coins var coinIndex = workerIndex % availableCoins.length; targetCoin = availableCoins[coinIndex]; } // If targeted approach fails, fall back to nearest coin if (!targetCoin) { var nearestCoin = null; var nearestDistance = Infinity; for (var i = 0; i < availableCoins.length; i++) { var coin = availableCoins[i]; var distance = Math.sqrt(Math.pow(coin.x - self.x, 2) + Math.pow(coin.y - self.y, 2)); if (distance < nearestDistance) { nearestDistance = distance; nearestCoin = coin; } } targetCoin = nearestCoin; } self.target = targetCoin; }; self.moveToTarget = function () { if (!self.target || self.target.collected) { self.target = null; return; } 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) { self.collectCoin(); } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.collectCoin = function () { if (self.target && !self.target.collected) { self.target.collected = true; if (self.team === 'player') { playerCoins += self.target.value; } else { aiCoins += self.target.value; } LK.getSound('coinCollect').play(); self.target = null; } }; self.defendSelf = function () { var enemyUnits = self.team === 'player' ? aiUnits : playerUnits; var defenseRange = 50; for (var i = 0; i < enemyUnits.length; i++) { var enemy = enemyUnits[i]; // Workers only defend against other workers if (enemy.unitType === 'worker') { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= defenseRange) { enemy.health -= 5; // Workers do less damage LK.getSound('attack').play(); if (enemy.health <= 0) { enemyUnits.splice(i, 1); enemy.destroy(); } break; } } } }; return self; }); var Zombie = Container.expand(function (waveNumber) { var self = Container.call(this); var graphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); // Stats increase with wave number var waveMultiplier = waveNumber || 1; self.speed = 1.5 + (waveMultiplier - 1) * 0.2; // Speed increases slightly each wave self.health = 30 + (waveMultiplier - 1) * 15; // Health increases by 15 each wave self.maxHealth = self.health; self.damage = 10 + (waveMultiplier - 1) * 5; // Damage increases by 5 each wave self.waveNumber = waveNumber; self.target = null; self.attackCooldown = 0; // Create health bar background (black) self.healthBarBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.75 }); self.healthBarBg.x = 0; self.healthBarBg.y = -120; self.healthBarBg.tint = 0x000000; self.addChild(self.healthBarBg); // Create health bar (red for zombies) self.healthBar = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 0.75 }); self.healthBar.x = 0; self.healthBar.y = -120; self.healthBar.tint = 0xff0000; self.addChild(self.healthBar); self.healthText = new Text2(self.health.toString(), { size: 50, fill: '#ffffff' }); self.healthText.anchor.set(0.5, 0.5); self.addChild(self.healthText); self.healthText.x = 0; self.healthText.y = -120; self.update = function () { if (self.attackCooldown > 0) { self.attackCooldown--; } if (!self.target) { self.findNearestPlayerTower(); } else { self.moveToTarget(); } // Attack nearby player units self.attackNearbyPlayerUnits(); self.healthText.setText(self.health.toString()); // Update health bar scale based on health percentage var healthPercentage = self.health / self.maxHealth; tween(self.healthBar, { scaleX: 1.5 * healthPercentage }, { duration: 200 }); }; self.findNearestPlayerTower = function () { // Target player tower if (playerTowers.length > 0) { self.target = playerTowers[0]; } }; self.moveToTarget = function () { if (!self.target) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { self.attackTarget(); } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.attackTarget = function () { if (self.attackCooldown <= 0 && self.target) { self.target.health -= self.damage; self.attackCooldown = 60; LK.getSound('attack').play(); if (self.target.health <= 0) { // Tower destroyed var index = playerTowers.indexOf(self.target); if (index > -1) { playerTowers.splice(index, 1); self.target.destroy(); LK.getSound('towerDestroyed').play(); self.target = null; } } } }; self.attackNearbyPlayerUnits = function () { if (self.attackCooldown <= 0) { var meleeRange = 60; for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; var dx = unit.x - self.x; var dy = unit.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= meleeRange) { unit.health -= self.damage; self.attackCooldown = 60; LK.getSound('attack').play(); if (unit.health <= 0) { playerUnits.splice(i, 1); unit.destroy(); } break; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Game state management var gameState = 'menu'; // 'menu' or 'playing' // Add menu background var menuBackground = LK.getAsset('menuBg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(menuBackground); // Create menu overlay var menuOverlay = new Container(); game.addChild(menuOverlay); // Add towers asset at the top of the menu var towersAsset = LK.getAsset('Towers', { anchorX: 0.5, anchorY: 0, scaleX: 4.0, scaleY: 4.0 }); towersAsset.x = 1024; towersAsset.y = 50; menuOverlay.addChild(towersAsset); // Add app banner at top of menu var appBanner = LK.getAsset('Appbanner', { anchorX: 0.5, anchorY: 0, x: 1024, y: 20 }); menuOverlay.addChild(appBanner); // Start button background var startButtonBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 4.0, scaleY: 1.5 }); startButtonBg.x = 1024; startButtonBg.y = 1000; startButtonBg.tint = 0x228b22; menuOverlay.addChild(startButtonBg); // Start button image var startButton = LK.getAsset('StartGameButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 4.0, scaleY: 2.0 }); startButton.x = 1024; startButton.y = 1000; menuOverlay.addChild(startButton); // How to Play button var howToPlayButton = LK.getAsset('Howtoplay22', { anchorX: 0.5, anchorY: 0.5, scaleX: 4.0, scaleY: 4.0 }); howToPlayButton.x = 1024; howToPlayButton.y = 1615; // 1.3x higher (2100 - 485 = 1615) menuOverlay.addChild(howToPlayButton); // Play menu music when game starts LK.playMusic('Menusong'); // How to Play instructions overlay var howToPlayOverlay = new Container(); howToPlayOverlay.visible = false; game.addChild(howToPlayOverlay); // How to Play background var howToPlayBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 18.0, scaleY: 24.0 }); howToPlayBg.x = 1024; howToPlayBg.y = 1366; howToPlayBg.tint = 0x000000; howToPlayBg.alpha = 0.9; howToPlayOverlay.addChild(howToPlayBg); // How to Play title var howToPlayTitle = new Text2('HOW TO PLAY', { size: 100, fill: '#ffffff' }); howToPlayTitle.anchor.set(0.5, 0.5); howToPlayTitle.x = 1024; howToPlayTitle.y = 400; howToPlayOverlay.addChild(howToPlayTitle); // How to Play instructions var instructionsText = new Text2('NORMAL MODE:\n• Collect gold coins with workers\n• Spend gold to recruit units\n• Press Attack to command units to advance\n• Destroy enemy tower to win\n\nUNIT TYPES & STATS:\n• Worker: 5 gold, 10 HP, 5 damage, collects coins\n• Warrior: 10 gold, 20 HP, 10 damage, melee combat\n• Archer: 15 gold, 30 HP, 20 damage, ranged arrows\n• Wizard: 20 gold, 40 HP, 30 damage, magic attacks\n• Dragon: 25 gold, 50 HP, 40 damage, fire breath\n\nTOWERS:\n• Player Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• AI Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• Attack range: 400 pixels\n\nZOMBIE MODE:\n• Survive endless zombie waves\n• Units automatically attack zombies first\n• Zombies spawn 30 seconds after start\n• Wave 1: 5 zombies (30 HP, 10 damage)\n• Each wave: +2 zombies, +15 HP, +5 damage\n• Defend your tower at all costs!', { size: 42, fill: '#ffffff' }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 1024; instructionsText.y = 1300; howToPlayOverlay.addChild(instructionsText); // Back button var backButton = new Text2('BACK TO MENU', { size: 80, fill: '#ffff00' }); backButton.anchor.set(0.5, 0.5); backButton.x = 1024; backButton.y = 2100; howToPlayOverlay.addChild(backButton); // Mode selection overlay var modeSelectionOverlay = new Container(); modeSelectionOverlay.visible = false; game.addChild(modeSelectionOverlay); // Mode selection background var modeSelectionBg = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 18.0, scaleY: 24.0 }); modeSelectionBg.x = 1024; modeSelectionBg.y = 1366; modeSelectionBg.tint = 0x000000; modeSelectionBg.alpha = 0.9; modeSelectionOverlay.addChild(modeSelectionBg); // Mode selection title var modeSelectionTitle = new Text2('SELECT MODE', { size: 100, fill: '#ffffff' }); modeSelectionTitle.anchor.set(0.5, 0.5); modeSelectionTitle.x = 1024; modeSelectionTitle.y = 600; modeSelectionOverlay.addChild(modeSelectionTitle); // Normal mode button var normalModeButton = LK.getAsset('Normalmode', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); normalModeButton.x = 1024; normalModeButton.y = 1000; modeSelectionOverlay.addChild(normalModeButton); // Zombie mode button in mode selection var zombieModeSelectButton = LK.getAsset('Zombiemode', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); zombieModeSelectButton.x = 1024; zombieModeSelectButton.y = 1400; modeSelectionOverlay.addChild(zombieModeSelectButton); // Back to menu button from mode selection var backToMenuButton = new Text2('BACK TO MENU', { size: 80, fill: '#ffff00' }); backToMenuButton.anchor.set(0.5, 0.5); backToMenuButton.x = 1024; backToMenuButton.y = 2000; modeSelectionOverlay.addChild(backToMenuButton); // Start button event handler - now shows mode selection startButton.down = function () { menuOverlay.visible = false; modeSelectionOverlay.visible = true; }; startButtonBg.down = function () { menuOverlay.visible = false; modeSelectionOverlay.visible = true; }; // Normal mode button event handler normalModeButton.down = function () { zombieMode = false; gameState = 'playing'; modeSelectionOverlay.visible = false; LK.stopMusic(); // Stop menu music when starting game initializeGame(); }; // Zombie mode button event handler in mode selection zombieModeSelectButton.down = function () { zombieMode = true; gameState = 'playing'; modeSelectionOverlay.visible = false; LK.stopMusic(); initializeGame(); }; // Back to menu button event handler backToMenuButton.down = function () { modeSelectionOverlay.visible = false; menuOverlay.visible = true; }; // How to Play button event handler howToPlayButton.down = function () { menuOverlay.visible = false; howToPlayOverlay.visible = true; }; // Back button event handler backButton.down = function () { howToPlayOverlay.visible = false; menuOverlay.visible = true; }; // Add game background (initially hidden) var background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); background.visible = false; game.addChild(background); var playerCoins = 0; var aiCoins = 0; var playerTowers = []; var aiTowers = []; var coins = []; var playerCoins_array = []; var aiCoins_array = []; var playerUnits = []; var aiUnits = []; var arrows = []; var magics = []; var fires = []; var coinSpawnTimer = 0; var aiActionTimer = 0; var zombies = []; var zombieSpawnTimer = 0; var zombieMode = false; var zombieWaveNumber = 1; var zombieWaveSpawnTimer = 0; var zombieWaveInProgress = false; var zombiesInCurrentWave = 0; var zombiesToSpawnInWave = 0; // UI Elements var coinDisplay = new Text2('Golds: 0', { size: 80, fill: '#ffff00' }); coinDisplay.anchor.set(0.5, 0.5); coinDisplay.x = 200; coinDisplay.y = -273.75; coinDisplay.visible = false; LK.gui.bottom.addChild(coinDisplay); // Attack button var attackButton = new Text2('Attack', { size: 80, fill: '#ff0000' }); attackButton.anchor.set(0.5, 0.5); attackButton.x = -200; attackButton.y = -273.75; attackButton.visible = false; LK.gui.bottom.addChild(attackButton); var workerButton = new Text2('5', { size: 32, fill: '#ffffff' }); workerButton.anchor.set(0.5, 0.5); workerButton.x = -400; workerButton.y = -45; var warriorButton = new Text2('10', { size: 32, fill: '#ffffff' }); warriorButton.anchor.set(0.5, 0.5); warriorButton.x = -200; warriorButton.y = -45; var wizardButton = new Text2('20', { size: 32, fill: '#ffffff' }); wizardButton.anchor.set(0.5, 0.5); wizardButton.x = 200; wizardButton.y = -45; var archerButton = new Text2('15', { size: 32, fill: '#ffffff' }); archerButton.anchor.set(0.5, 0.5); archerButton.x = 0; archerButton.y = -45; var dragonButton = new Text2('25', { size: 32, fill: '#ffffff' }); dragonButton.anchor.set(0.5, 0.5); dragonButton.x = 400; dragonButton.y = -45; // Add unified black background behind all recruitment buttons and unit images var unifiedBackground = LK.getAsset('blackBackground', { anchorX: 0.5, anchorY: 0.5, scaleX: 22.0, scaleY: 2.8 }); unifiedBackground.x = 0; unifiedBackground.y = -78; unifiedBackground.tint = 0x000000; unifiedBackground.alpha = 0.5; unifiedBackground.visible = false; LK.gui.bottom.addChild(unifiedBackground); // Add unit images above recruitment buttons var workerImage = LK.getAsset('worker', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); workerImage.x = -400; workerImage.y = -110; LK.gui.bottom.addChild(workerImage); var warriorImage = LK.getAsset('warrior', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); warriorImage.x = -200; warriorImage.y = -110; LK.gui.bottom.addChild(warriorImage); var wizardImage = LK.getAsset('wizard', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); wizardImage.x = 200; wizardImage.y = -110; LK.gui.bottom.addChild(wizardImage); var archerImage = LK.getAsset('archer', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); archerImage.x = 0; archerImage.y = -110; LK.gui.bottom.addChild(archerImage); var dragonImage = LK.getAsset('dragon', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); dragonImage.x = 400; dragonImage.y = -110; LK.gui.bottom.addChild(dragonImage); // Wave display for zombie mode var waveDisplay = new Text2('Wave: 1', { size: 80, fill: '#ff0000' }); waveDisplay.anchor.set(0.5, 0.5); waveDisplay.x = 0; waveDisplay.y = 100; waveDisplay.visible = false; LK.gui.top.addChild(waveDisplay); // Hide all UI elements initially (menu state) workerButton.visible = false; warriorButton.visible = false; wizardButton.visible = false; archerButton.visible = false; dragonButton.visible = false; workerImage.visible = false; warriorImage.visible = false; wizardImage.visible = false; archerImage.visible = false; dragonImage.visible = false; // Add texts after backgrounds so they appear on top LK.gui.bottom.addChild(workerButton); LK.gui.bottom.addChild(warriorButton); LK.gui.bottom.addChild(wizardButton); LK.gui.bottom.addChild(archerButton); LK.gui.bottom.addChild(dragonButton); // Initialize towers function initializeTowers() { // Player tower var playerTower = new Tower('player'); playerTower.x = 1024; playerTower.y = 2300; playerTowers.push(playerTower); game.addChild(playerTower); // AI tower (only in normal mode) if (!zombieMode) { var aiTower = new Tower('ai'); aiTower.x = 1024; aiTower.y = 400; aiTowers.push(aiTower); game.addChild(aiTower); } } ; function spawnCoin() { // Spawn player coin var playerCoin = new Coin(); playerCoin.x = Math.random() * 1800 + 124; playerCoin.y = Math.random() * 1000 + 1200; // Lower half for player playerCoins_array.push(playerCoin); coins.push(playerCoin); game.addChild(playerCoin); // Spawn AI coin var aiCoin = new Coin(); aiCoin.x = Math.random() * 1800 + 124; aiCoin.y = Math.random() * 1000 + 400; // Upper half for AI aiCoins_array.push(aiCoin); coins.push(aiCoin); game.addChild(aiCoin); } function spawnUnit(team, unitType) { var unit; var cost = unitType === 'worker' ? 5 : unitType === 'warrior' ? 10 : unitType === 'wizard' ? 20 : unitType === 'archer' ? 15 : 25; if (team === 'player' && playerCoins >= cost) { playerCoins -= cost; if (unitType === 'worker') { unit = new Worker('player'); } else { unit = new CombatUnit('player', unitType); } if (!zombieMode && unitType !== 'worker') { // Position combat units in front of tower in normal mode with better spacing var unitCount = playerUnits.length; var spreadRadius = 300; var angle = unitCount * 0.5 % (Math.PI * 2); unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius; unit.y = playerTowers[0].y - 507 + Math.sin(angle) * 100; } else { var unitCount = playerUnits.length; var spreadRadius = 200; var angle = unitCount * 0.8 % (Math.PI * 2); unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius; unit.y = playerTowers[0].y - 100 + Math.sin(angle) * 50; } playerUnits.push(unit); game.addChild(unit); LK.getSound('unitSpawn').play(); } else if (team === 'ai' && aiCoins >= cost) { aiCoins -= cost; if (unitType === 'worker') { unit = new Worker('ai'); } else { unit = new CombatUnit('ai', unitType); } var unitCount = aiUnits.length; var spreadRadius = 200; var angle = unitCount * 0.8 % (Math.PI * 2); unit.x = aiTowers[0].x + Math.cos(angle) * spreadRadius; unit.y = aiTowers[0].y + 100 + Math.sin(angle) * 50; aiUnits.push(unit); game.addChild(unit); } } function spawnZombie() { var zombie = new Zombie(zombieWaveNumber); zombie.x = Math.random() * 1800 + 124; // Random X position across screen width zombie.y = 50; // Spawn at top of screen zombies.push(zombie); game.addChild(zombie); } function startZombieWave() { zombieWaveInProgress = true; zombiesToSpawnInWave = 3 + zombieWaveNumber * 2; // Wave 1: 5 zombies, Wave 2: 7 zombies, etc. zombiesInCurrentWave = 0; zombieWaveSpawnTimer = 0; // Update wave display if (zombieMode) { waveDisplay.setText('Wave: ' + zombieWaveNumber); } } function checkWaveComplete() { // Check if all zombies in current wave are spawned and destroyed if (zombieWaveInProgress && zombiesInCurrentWave >= zombiesToSpawnInWave && zombies.length === 0) { zombieWaveInProgress = false; zombieWaveNumber++; // Start next wave after 10 seconds (doubled from 5 seconds) zombieWaveSpawnTimer = -600; // 10 seconds delay before next wave } } function aiLogic() { if (aiActionTimer <= 0) { if (aiCoins >= 25 && Math.random() < 0.1) { spawnUnit('ai', 'dragon'); } else if (aiCoins >= 15 && Math.random() < 0.15) { spawnUnit('ai', 'archer'); } else if (aiCoins >= 20 && Math.random() < 0.2) { spawnUnit('ai', 'wizard'); } else if (aiCoins >= 10 && Math.random() < 0.3) { spawnUnit('ai', 'warrior'); } else if (aiCoins >= 5 && Math.random() < 0.4) { spawnUnit('ai', 'worker'); } aiActionTimer = 120; } aiActionTimer--; } function checkGameEnd() { if (playerTowers.length === 0) { LK.showGameOver(); } else if (!zombieMode && aiTowers.length === 0) { LK.showYouWin(); } } function cleanupCollectedCoins() { // Clean up from main coins array for (var i = coins.length - 1; i >= 0; i--) { if (coins[i].collected) { coins[i].destroy(); coins.splice(i, 1); } } // Clean up from player coins array for (var i = playerCoins_array.length - 1; i >= 0; i--) { if (playerCoins_array[i].collected) { playerCoins_array.splice(i, 1); } } // Clean up from AI coins array for (var i = aiCoins_array.length - 1; i >= 0; i--) { if (aiCoins_array[i].collected) { aiCoins_array.splice(i, 1); } } } // Button event handlers workerButton.down = function () { spawnUnit('player', 'worker'); }; warriorButton.down = function () { spawnUnit('player', 'warrior'); }; wizardButton.down = function () { spawnUnit('player', 'wizard'); }; archerButton.down = function () { spawnUnit('player', 'archer'); }; dragonButton.down = function () { spawnUnit('player', 'dragon'); }; // Character image event handlers workerImage.down = function () { spawnUnit('player', 'worker'); }; warriorImage.down = function () { spawnUnit('player', 'warrior'); }; wizardImage.down = function () { spawnUnit('player', 'wizard'); }; archerImage.down = function () { spawnUnit('player', 'archer'); }; dragonImage.down = function () { spawnUnit('player', 'dragon'); }; // Attack button event handler attackButton.down = function () { if (!zombieMode) { // Make all waiting player units start attacking for (var i = 0; i < playerUnits.length; i++) { var unit = playerUnits[i]; if (unit.unitType !== 'worker') { unit.isWaiting = false; } } } }; // Initialize game function function initializeGame() { background.visible = true; // Show UI elements when game starts coinDisplay.visible = true; unifiedBackground.visible = true; workerButton.visible = true; warriorButton.visible = true; wizardButton.visible = true; archerButton.visible = true; dragonButton.visible = true; workerImage.visible = true; warriorImage.visible = true; wizardImage.visible = true; archerImage.visible = true; dragonImage.visible = true; // Show attack button only in normal mode if (!zombieMode) { attackButton.visible = true; } else { // Show wave display in zombie mode waveDisplay.visible = true; waveDisplay.setText('Wave: ' + zombieWaveNumber); } // Reset zombie wave system zombieWaveNumber = 1; zombieWaveSpawnTimer = -1800; // 30 second delay (30 * 60 FPS = 1800 frames) zombieWaveInProgress = false; zombiesInCurrentWave = 0; zombiesToSpawnInWave = 0; // Play game music when match starts LK.playMusic('Song'); initializeTowers(); // Spawn initial workers for both teams var playerWorker = new Worker('player'); playerWorker.x = playerTowers[0].x + 50; playerWorker.y = playerTowers[0].y - 100; playerUnits.push(playerWorker); game.addChild(playerWorker); // AI worker (only in normal mode) if (!zombieMode) { var aiWorker = new Worker('ai'); aiWorker.x = aiTowers[0].x + 50; aiWorker.y = aiTowers[0].y + 100; aiUnits.push(aiWorker); game.addChild(aiWorker); } } // Main game loop game.update = function () { if (gameState === 'playing') { // Update coin display coinDisplay.setText('Golds: ' + playerCoins); // Spawn coins coinSpawnTimer++; if (coinSpawnTimer >= 36) { // 5x more frequent (180/5 = 36) spawnCoin(); coinSpawnTimer = 0; } // AI logic (only in normal mode) if (!zombieMode) { aiLogic(); } // Clean up collected coins cleanupCollectedCoins(); // Zombie mode logic if (zombieMode) { zombieWaveSpawnTimer++; // Start first wave or next wave after delay if (!zombieWaveInProgress && zombieWaveSpawnTimer >= 0) { startZombieWave(); } // Spawn zombies in current wave if (zombieWaveInProgress && zombiesInCurrentWave < zombiesToSpawnInWave) { if (zombieWaveSpawnTimer % 60 === 0) { // Spawn one zombie every second during wave spawnZombie(); zombiesInCurrentWave++; } } // Check if current wave is complete checkWaveComplete(); // Clean up off-screen zombies for (var i = zombies.length - 1; i >= 0; i--) { if (zombies[i].y > 2800) { zombies[i].destroy(); zombies.splice(i, 1); } } } // Check game end conditions checkGameEnd(); } };
/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Arrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 8;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Set rotation to point toward target
self.rotation = Math.atan2(dy, dx);
// Position arrow at start
self.x = startX;
self.y = startY;
self.update = function () {
// Move arrow
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check if arrow is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from arrows array
for (var i = arrows.length - 1; i >= 0; i--) {
if (arrows[i] === self) {
arrows.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from arrows array
for (var j = arrows.length - 1; j >= 0; j--) {
if (arrows[j] === self) {
arrows.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from arrows array
for (var j = arrows.length - 1; j >= 0; j--) {
if (arrows[j] === self) {
arrows.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.collected = false;
return self;
});
var CombatUnit = Container.expand(function (team, unitType) {
var self = Container.call(this);
var assetName = team === 'player' ? unitType : 'ai' + unitType.charAt(0).toUpperCase() + unitType.slice(1);
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.unitType = unitType;
self.speed = unitType === 'dragon' ? 3 : unitType === 'wizard' ? 1.5 : unitType === 'archer' ? 2.5 : 2;
self.damage = unitType === 'dragon' ? 40 : unitType === 'wizard' ? 30 : unitType === 'archer' ? 20 : 10;
self.health = unitType === 'dragon' ? 50 : unitType === 'wizard' ? 40 : unitType === 'archer' ? 30 : unitType === 'warrior' ? 20 : 10;
self.maxHealth = self.health;
self.attackRange = unitType === 'wizard' ? 100 : unitType === 'archer' ? 200 : 50;
self.attackCooldown = 0;
self.target = null;
self.isWaiting = !zombieMode && team === 'player'; // Only player units wait in normal mode
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = team === 'player' ? 0x0000ff : 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// In zombie mode, player units prioritize attacking zombies
if (zombieMode && self.team === 'player' && self.unitType !== 'worker') {
self.attackZombies();
} else if (!zombieMode && self.team === 'player' && self.isWaiting) {
// In normal mode, player units wait unless attack is triggered
// Stay in position and don't move
} else {
if (!self.target) {
self.findNearestEnemyTower();
} else {
self.moveToTarget();
}
// Combat with nearby enemy units
self.combatNearbyEnemies();
}
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestEnemyTower = function () {
// In zombie mode, player units should prioritize attacking zombies
if (zombieMode && self.team === 'player' && self.unitType !== 'worker') {
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// If we found a zombie, target it
if (nearestZombie) {
self.target = nearestZombie;
return;
}
}
// Find nearest target - prioritize ALL enemy units first (including workers), then towers
var nearestTarget = null;
var nearestDistance = Infinity;
// First check all enemy units (including workers)
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Target all enemy units now, including workers
if (self.unitType !== 'worker') {
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = enemy;
}
}
}
// Only target towers if no enemy units remain
if (!nearestTarget) {
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = tower;
}
}
}
self.target = nearestTarget;
};
self.moveToTarget = function () {
if (!self.target) {
return;
}
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.attackRange) {
self.attackTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.attackTarget = function () {
if (self.attackCooldown <= 0 && self.target) {
if (self.unitType === 'archer') {
// Archer shoots arrow
var arrow = new Arrow(self.team, self.x, self.y, self.target.x, self.target.y, self.damage);
arrows.push(arrow);
game.addChild(arrow);
} else if (self.unitType === 'wizard') {
// Wizard shoots magic
var magic = new Magic(self.team, self.x, self.y, self.target.x, self.target.y, self.damage);
magics.push(magic);
game.addChild(magic);
} else if (self.unitType === 'dragon') {
// Change to attack asset for all dragon attacks
self.removeChild(graphics);
var assetName = 'DragonAttack';
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Dragon shoots fire with 1 damage
var fire = new Fire(self.team, self.x, self.y, self.target.x, self.target.y, 1);
fires.push(fire);
game.addChild(fire);
} else {
// Melee attack
self.target.health -= self.damage;
if (self.target.health <= 0) {
// Check if target is a unit or tower
if (self.target.unitType) {
// Target is a unit
self.destroyUnit();
} else {
// Target is a tower
self.destroyTower();
}
}
}
// Set dragon attack cooldown to 3 frames (0.05 seconds)
if (self.unitType === 'dragon') {
self.attackCooldown = 3;
} else {
self.attackCooldown = 60;
}
LK.getSound('attack').play();
}
};
self.destroyUnit = function () {
var units = self.team === 'player' ? aiUnits : playerUnits;
var index = units.indexOf(self.target);
if (index > -1) {
units.splice(index, 1);
self.target.destroy();
self.target = null;
}
};
self.destroyTower = function () {
var towers = self.team === 'player' ? aiTowers : playerTowers;
var index = towers.indexOf(self.target);
if (index > -1) {
towers.splice(index, 1);
self.target.destroy();
LK.getSound('towerDestroyed').play();
self.target = null;
}
};
self.attackZombies = function () {
// Find nearest zombie to target
var nearestZombie = null;
var nearestDistance = 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 < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// Move toward and attack nearest zombie
if (nearestZombie) {
var dx = nearestZombie.x - self.x;
var dy = nearestZombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward zombie if not in attack range
if (distance > self.attackRange) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
} else if (self.attackCooldown <= 0) {
// Attack zombie based on unit type
if (self.unitType === 'archer') {
// Archer shoots arrow at zombie
var arrow = new Arrow(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
arrows.push(arrow);
game.addChild(arrow);
} else if (self.unitType === 'wizard') {
// Wizard shoots magic at zombie
var magic = new Magic(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
magics.push(magic);
game.addChild(magic);
} else if (self.unitType === 'dragon') {
// Change to attack asset for dragon zombie attacks
self.removeChild(graphics);
var assetName = 'DragonAttack';
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Dragon shoots fire at zombie
var fire = new Fire(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
fires.push(fire);
game.addChild(fire);
} else {
// Melee attack (warrior)
nearestZombie.health -= self.damage;
if (nearestZombie.health <= 0) {
for (var j = 0; j < zombies.length; j++) {
if (zombies[j] === nearestZombie) {
zombies.splice(j, 1);
nearestZombie.destroy();
break;
}
}
}
}
self.attackCooldown = 60; // 1 second cooldown
LK.getSound('attack').play();
}
}
};
self.combatNearbyEnemies = function () {
if (self.attackCooldown <= 0) {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var meleeRange = 60; // Close combat range
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Skip workers in combat
if (enemy.unitType === 'worker') {
continue;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If enemy unit is within melee range, attack it
if (distance <= meleeRange) {
enemy.health -= self.damage;
self.attackCooldown = 60; // 1 second cooldown
LK.getSound('attack').play();
// Check if enemy is destroyed
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
break; // Only attack one enemy per cycle
}
}
}
};
return self;
});
var Fire = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Magic', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 4;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Position fire at start
self.x = startX;
self.y = startY;
// Add fire effect - orange/red tint and scaling
graphics.tint = 0xff4500; // Orange-red fire color
tween(graphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Move fire
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Add fire flicker rotation
graphics.rotation += 0.15;
// Check if fire is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from fires array
for (var i = fires.length - 1; i >= 0; i--) {
if (fires[i] === self) {
fires.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from fires array
for (var j = fires.length - 1; j >= 0; j--) {
if (fires[j] === self) {
fires.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from fires array
for (var j = fires.length - 1; j >= 0; j--) {
if (fires[j] === self) {
fires.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Magic = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Magic', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 6;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Position magic at start
self.x = startX;
self.y = startY;
// Add magical glow effect
tween(graphics, {
tint: 0x9966ff
}, {
duration: 500,
easing: tween.easeInOut
});
tween(graphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
self.update = function () {
// Move magic
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Add sparkle rotation
graphics.rotation += 0.1;
// Check if magic is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from magics array
for (var i = magics.length - 1; i >= 0; i--) {
if (magics[i] === self) {
magics.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from magics array
for (var j = magics.length - 1; j >= 0; j--) {
if (magics[j] === self) {
magics.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from magics array
for (var j = magics.length - 1; j >= 0; j--) {
if (magics[j] === self) {
magics.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Tower = Container.expand(function (team) {
var self = Container.call(this);
var assetName = team === 'player' ? 'playerTower' : 'aiTower';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
self.team = team;
self.health = 500;
self.maxHealth = 500;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.08,
scaleY: 0.625
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -262.5;
self.healthBarBg.tint = 0x0000ff;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.08,
scaleY: 0.625
});
self.healthBar.x = 0;
self.healthBar.y = -262.5;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 42,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -262.5;
self.attackCooldown = 0;
self.attackRange = 400;
// Damage area outline removed as requested
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
self.attackNearbyEnemies();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 2.08 * healthPercentage
}, {
duration: 200
});
};
self.attackNearbyEnemies = function () {
if (self.attackCooldown <= 0) {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var nearestEnemy = null;
var nearestDistance = Infinity;
// Find nearest enemy unit within attack range
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
var dx = enemy.x - self.x;
var dy = enemy.y - (self.y - 131.25); // Adjust for tower center position
var distance = Math.sqrt(dx * dx + dy * dy);
// Only consider units within the attack range
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Attack only the nearest enemy if found
if (nearestEnemy) {
nearestEnemy.health -= 1; // Deal 1 damage as specified
self.attackCooldown = 3; // 0.05 seconds at 60 FPS
LK.getSound('attack').play();
if (nearestEnemy.health <= 0) {
var index = enemyUnits.indexOf(nearestEnemy);
if (index > -1) {
self.destroyUnit(nearestEnemy, enemyUnits, index);
}
}
}
// In zombie mode, player towers also attack zombies (find nearest zombie)
if (zombieMode && self.team === 'player') {
var nearestZombie = null;
var nearestZombieDistance = 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 - 131.25);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestZombieDistance) {
nearestZombieDistance = distance;
nearestZombie = zombie;
}
}
// Attack only the nearest zombie if found
if (nearestZombie && !nearestEnemy) {
// Only attack zombie if no enemy unit was attacked
nearestZombie.health -= 1;
self.attackCooldown = 3;
LK.getSound('attack').play();
if (nearestZombie.health <= 0) {
var index = zombies.indexOf(nearestZombie);
if (index > -1) {
zombies.splice(index, 1);
nearestZombie.destroy();
}
}
}
}
}
};
self.showDamageArea = function () {
// No visual effects when towers attack
};
self.destroyUnit = function (unit, unitsArray, index) {
unitsArray.splice(index, 1);
unit.destroy();
};
return self;
});
var Worker = Container.expand(function (team) {
var self = Container.call(this);
var assetName = team === 'player' ? 'worker' : 'aiWorker';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.speed = 4;
self.target = null;
self.collectRange = 40;
self.health = 10;
self.maxHealth = 10;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (!self.target) {
self.findNearestCoin();
} else {
self.moveToTarget();
}
// Workers can defend themselves
self.defendSelf();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestCoin = function () {
var targetCoins = self.team === 'player' ? playerCoins_array : aiCoins_array;
var teamWorkers = self.team === 'player' ? playerUnits : aiUnits;
var availableCoins = [];
// First, collect all untargeted coins
for (var i = 0; i < targetCoins.length; i++) {
var coin = targetCoins[i];
if (!coin.collected) {
// Check if another worker is already targeting this coin
var isTargeted = false;
for (var j = 0; j < teamWorkers.length; j++) {
var worker = teamWorkers[j];
if (worker.unitType === 'worker' && worker !== self && worker.target === coin) {
isTargeted = true;
break;
}
}
if (!isTargeted) {
availableCoins.push(coin);
}
}
}
// If no available coins, clear target
if (availableCoins.length === 0) {
self.target = null;
return;
}
// Get worker index to create unique targeting preference
var workerIndex = -1;
for (var i = 0; i < teamWorkers.length; i++) {
if (teamWorkers[i] === self && teamWorkers[i].unitType === 'worker') {
workerIndex = i;
break;
}
}
// Use worker index to create different targeting preferences
var targetCoin = null;
if (workerIndex >= 0 && workerIndex < availableCoins.length) {
// Each worker gets a different coin based on their index
targetCoin = availableCoins[workerIndex];
} else {
// If more workers than coins, use modulo to cycle through available coins
var coinIndex = workerIndex % availableCoins.length;
targetCoin = availableCoins[coinIndex];
}
// If targeted approach fails, fall back to nearest coin
if (!targetCoin) {
var nearestCoin = null;
var nearestDistance = Infinity;
for (var i = 0; i < availableCoins.length; i++) {
var coin = availableCoins[i];
var distance = Math.sqrt(Math.pow(coin.x - self.x, 2) + Math.pow(coin.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestCoin = coin;
}
}
targetCoin = nearestCoin;
}
self.target = targetCoin;
};
self.moveToTarget = function () {
if (!self.target || self.target.collected) {
self.target = null;
return;
}
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) {
self.collectCoin();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.collectCoin = function () {
if (self.target && !self.target.collected) {
self.target.collected = true;
if (self.team === 'player') {
playerCoins += self.target.value;
} else {
aiCoins += self.target.value;
}
LK.getSound('coinCollect').play();
self.target = null;
}
};
self.defendSelf = function () {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var defenseRange = 50;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Workers only defend against other workers
if (enemy.unitType === 'worker') {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= defenseRange) {
enemy.health -= 5; // Workers do less damage
LK.getSound('attack').play();
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
break;
}
}
}
};
return self;
});
var Zombie = Container.expand(function (waveNumber) {
var self = Container.call(this);
var graphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Stats increase with wave number
var waveMultiplier = waveNumber || 1;
self.speed = 1.5 + (waveMultiplier - 1) * 0.2; // Speed increases slightly each wave
self.health = 30 + (waveMultiplier - 1) * 15; // Health increases by 15 each wave
self.maxHealth = self.health;
self.damage = 10 + (waveMultiplier - 1) * 5; // Damage increases by 5 each wave
self.waveNumber = waveNumber;
self.target = null;
self.attackCooldown = 0;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (red for zombies)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (!self.target) {
self.findNearestPlayerTower();
} else {
self.moveToTarget();
}
// Attack nearby player units
self.attackNearbyPlayerUnits();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestPlayerTower = function () {
// Target player tower
if (playerTowers.length > 0) {
self.target = playerTowers[0];
}
};
self.moveToTarget = function () {
if (!self.target) {
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
self.attackTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.attackTarget = function () {
if (self.attackCooldown <= 0 && self.target) {
self.target.health -= self.damage;
self.attackCooldown = 60;
LK.getSound('attack').play();
if (self.target.health <= 0) {
// Tower destroyed
var index = playerTowers.indexOf(self.target);
if (index > -1) {
playerTowers.splice(index, 1);
self.target.destroy();
LK.getSound('towerDestroyed').play();
self.target = null;
}
}
}
};
self.attackNearbyPlayerUnits = function () {
if (self.attackCooldown <= 0) {
var meleeRange = 60;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var dx = unit.x - self.x;
var dy = unit.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= meleeRange) {
unit.health -= self.damage;
self.attackCooldown = 60;
LK.getSound('attack').play();
if (unit.health <= 0) {
playerUnits.splice(i, 1);
unit.destroy();
}
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu' or 'playing'
// Add menu background
var menuBackground = LK.getAsset('menuBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(menuBackground);
// Create menu overlay
var menuOverlay = new Container();
game.addChild(menuOverlay);
// Add towers asset at the top of the menu
var towersAsset = LK.getAsset('Towers', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4.0,
scaleY: 4.0
});
towersAsset.x = 1024;
towersAsset.y = 50;
menuOverlay.addChild(towersAsset);
// Add app banner at top of menu
var appBanner = LK.getAsset('Appbanner', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 20
});
menuOverlay.addChild(appBanner);
// Start button background
var startButtonBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 1.5
});
startButtonBg.x = 1024;
startButtonBg.y = 1000;
startButtonBg.tint = 0x228b22;
menuOverlay.addChild(startButtonBg);
// Start button image
var startButton = LK.getAsset('StartGameButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 2.0
});
startButton.x = 1024;
startButton.y = 1000;
menuOverlay.addChild(startButton);
// How to Play button
var howToPlayButton = LK.getAsset('Howtoplay22', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 4.0
});
howToPlayButton.x = 1024;
howToPlayButton.y = 1615; // 1.3x higher (2100 - 485 = 1615)
menuOverlay.addChild(howToPlayButton);
// Play menu music when game starts
LK.playMusic('Menusong');
// How to Play instructions overlay
var howToPlayOverlay = new Container();
howToPlayOverlay.visible = false;
game.addChild(howToPlayOverlay);
// How to Play background
var howToPlayBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 18.0,
scaleY: 24.0
});
howToPlayBg.x = 1024;
howToPlayBg.y = 1366;
howToPlayBg.tint = 0x000000;
howToPlayBg.alpha = 0.9;
howToPlayOverlay.addChild(howToPlayBg);
// How to Play title
var howToPlayTitle = new Text2('HOW TO PLAY', {
size: 100,
fill: '#ffffff'
});
howToPlayTitle.anchor.set(0.5, 0.5);
howToPlayTitle.x = 1024;
howToPlayTitle.y = 400;
howToPlayOverlay.addChild(howToPlayTitle);
// How to Play instructions
var instructionsText = new Text2('NORMAL MODE:\n• Collect gold coins with workers\n• Spend gold to recruit units\n• Press Attack to command units to advance\n• Destroy enemy tower to win\n\nUNIT TYPES & STATS:\n• Worker: 5 gold, 10 HP, 5 damage, collects coins\n• Warrior: 10 gold, 20 HP, 10 damage, melee combat\n• Archer: 15 gold, 30 HP, 20 damage, ranged arrows\n• Wizard: 20 gold, 40 HP, 30 damage, magic attacks\n• Dragon: 25 gold, 50 HP, 40 damage, fire breath\n\nTOWERS:\n• Player Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• AI Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• Attack range: 400 pixels\n\nZOMBIE MODE:\n• Survive endless zombie waves\n• Units automatically attack zombies first\n• Zombies spawn 30 seconds after start\n• Wave 1: 5 zombies (30 HP, 10 damage)\n• Each wave: +2 zombies, +15 HP, +5 damage\n• Defend your tower at all costs!', {
size: 42,
fill: '#ffffff'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1300;
howToPlayOverlay.addChild(instructionsText);
// Back button
var backButton = new Text2('BACK TO MENU', {
size: 80,
fill: '#ffff00'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = 2100;
howToPlayOverlay.addChild(backButton);
// Mode selection overlay
var modeSelectionOverlay = new Container();
modeSelectionOverlay.visible = false;
game.addChild(modeSelectionOverlay);
// Mode selection background
var modeSelectionBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 18.0,
scaleY: 24.0
});
modeSelectionBg.x = 1024;
modeSelectionBg.y = 1366;
modeSelectionBg.tint = 0x000000;
modeSelectionBg.alpha = 0.9;
modeSelectionOverlay.addChild(modeSelectionBg);
// Mode selection title
var modeSelectionTitle = new Text2('SELECT MODE', {
size: 100,
fill: '#ffffff'
});
modeSelectionTitle.anchor.set(0.5, 0.5);
modeSelectionTitle.x = 1024;
modeSelectionTitle.y = 600;
modeSelectionOverlay.addChild(modeSelectionTitle);
// Normal mode button
var normalModeButton = LK.getAsset('Normalmode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
normalModeButton.x = 1024;
normalModeButton.y = 1000;
modeSelectionOverlay.addChild(normalModeButton);
// Zombie mode button in mode selection
var zombieModeSelectButton = LK.getAsset('Zombiemode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
zombieModeSelectButton.x = 1024;
zombieModeSelectButton.y = 1400;
modeSelectionOverlay.addChild(zombieModeSelectButton);
// Back to menu button from mode selection
var backToMenuButton = new Text2('BACK TO MENU', {
size: 80,
fill: '#ffff00'
});
backToMenuButton.anchor.set(0.5, 0.5);
backToMenuButton.x = 1024;
backToMenuButton.y = 2000;
modeSelectionOverlay.addChild(backToMenuButton);
// Start button event handler - now shows mode selection
startButton.down = function () {
menuOverlay.visible = false;
modeSelectionOverlay.visible = true;
};
startButtonBg.down = function () {
menuOverlay.visible = false;
modeSelectionOverlay.visible = true;
};
// Normal mode button event handler
normalModeButton.down = function () {
zombieMode = false;
gameState = 'playing';
modeSelectionOverlay.visible = false;
LK.stopMusic(); // Stop menu music when starting game
initializeGame();
};
// Zombie mode button event handler in mode selection
zombieModeSelectButton.down = function () {
zombieMode = true;
gameState = 'playing';
modeSelectionOverlay.visible = false;
LK.stopMusic();
initializeGame();
};
// Back to menu button event handler
backToMenuButton.down = function () {
modeSelectionOverlay.visible = false;
menuOverlay.visible = true;
};
// How to Play button event handler
howToPlayButton.down = function () {
menuOverlay.visible = false;
howToPlayOverlay.visible = true;
};
// Back button event handler
backButton.down = function () {
howToPlayOverlay.visible = false;
menuOverlay.visible = true;
};
// Add game background (initially hidden)
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
background.visible = false;
game.addChild(background);
var playerCoins = 0;
var aiCoins = 0;
var playerTowers = [];
var aiTowers = [];
var coins = [];
var playerCoins_array = [];
var aiCoins_array = [];
var playerUnits = [];
var aiUnits = [];
var arrows = [];
var magics = [];
var fires = [];
var coinSpawnTimer = 0;
var aiActionTimer = 0;
var zombies = [];
var zombieSpawnTimer = 0;
var zombieMode = false;
var zombieWaveNumber = 1;
var zombieWaveSpawnTimer = 0;
var zombieWaveInProgress = false;
var zombiesInCurrentWave = 0;
var zombiesToSpawnInWave = 0;
// UI Elements
var coinDisplay = new Text2('Golds: 0', {
size: 80,
fill: '#ffff00'
});
coinDisplay.anchor.set(0.5, 0.5);
coinDisplay.x = 200;
coinDisplay.y = -273.75;
coinDisplay.visible = false;
LK.gui.bottom.addChild(coinDisplay);
// Attack button
var attackButton = new Text2('Attack', {
size: 80,
fill: '#ff0000'
});
attackButton.anchor.set(0.5, 0.5);
attackButton.x = -200;
attackButton.y = -273.75;
attackButton.visible = false;
LK.gui.bottom.addChild(attackButton);
var workerButton = new Text2('5', {
size: 32,
fill: '#ffffff'
});
workerButton.anchor.set(0.5, 0.5);
workerButton.x = -400;
workerButton.y = -45;
var warriorButton = new Text2('10', {
size: 32,
fill: '#ffffff'
});
warriorButton.anchor.set(0.5, 0.5);
warriorButton.x = -200;
warriorButton.y = -45;
var wizardButton = new Text2('20', {
size: 32,
fill: '#ffffff'
});
wizardButton.anchor.set(0.5, 0.5);
wizardButton.x = 200;
wizardButton.y = -45;
var archerButton = new Text2('15', {
size: 32,
fill: '#ffffff'
});
archerButton.anchor.set(0.5, 0.5);
archerButton.x = 0;
archerButton.y = -45;
var dragonButton = new Text2('25', {
size: 32,
fill: '#ffffff'
});
dragonButton.anchor.set(0.5, 0.5);
dragonButton.x = 400;
dragonButton.y = -45;
// Add unified black background behind all recruitment buttons and unit images
var unifiedBackground = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 22.0,
scaleY: 2.8
});
unifiedBackground.x = 0;
unifiedBackground.y = -78;
unifiedBackground.tint = 0x000000;
unifiedBackground.alpha = 0.5;
unifiedBackground.visible = false;
LK.gui.bottom.addChild(unifiedBackground);
// Add unit images above recruitment buttons
var workerImage = LK.getAsset('worker', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
workerImage.x = -400;
workerImage.y = -110;
LK.gui.bottom.addChild(workerImage);
var warriorImage = LK.getAsset('warrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
warriorImage.x = -200;
warriorImage.y = -110;
LK.gui.bottom.addChild(warriorImage);
var wizardImage = LK.getAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
wizardImage.x = 200;
wizardImage.y = -110;
LK.gui.bottom.addChild(wizardImage);
var archerImage = LK.getAsset('archer', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
archerImage.x = 0;
archerImage.y = -110;
LK.gui.bottom.addChild(archerImage);
var dragonImage = LK.getAsset('dragon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
dragonImage.x = 400;
dragonImage.y = -110;
LK.gui.bottom.addChild(dragonImage);
// Wave display for zombie mode
var waveDisplay = new Text2('Wave: 1', {
size: 80,
fill: '#ff0000'
});
waveDisplay.anchor.set(0.5, 0.5);
waveDisplay.x = 0;
waveDisplay.y = 100;
waveDisplay.visible = false;
LK.gui.top.addChild(waveDisplay);
// Hide all UI elements initially (menu state)
workerButton.visible = false;
warriorButton.visible = false;
wizardButton.visible = false;
archerButton.visible = false;
dragonButton.visible = false;
workerImage.visible = false;
warriorImage.visible = false;
wizardImage.visible = false;
archerImage.visible = false;
dragonImage.visible = false;
// Add texts after backgrounds so they appear on top
LK.gui.bottom.addChild(workerButton);
LK.gui.bottom.addChild(warriorButton);
LK.gui.bottom.addChild(wizardButton);
LK.gui.bottom.addChild(archerButton);
LK.gui.bottom.addChild(dragonButton);
// Initialize towers
function initializeTowers() {
// Player tower
var playerTower = new Tower('player');
playerTower.x = 1024;
playerTower.y = 2300;
playerTowers.push(playerTower);
game.addChild(playerTower);
// AI tower (only in normal mode)
if (!zombieMode) {
var aiTower = new Tower('ai');
aiTower.x = 1024;
aiTower.y = 400;
aiTowers.push(aiTower);
game.addChild(aiTower);
}
}
;
function spawnCoin() {
// Spawn player coin
var playerCoin = new Coin();
playerCoin.x = Math.random() * 1800 + 124;
playerCoin.y = Math.random() * 1000 + 1200; // Lower half for player
playerCoins_array.push(playerCoin);
coins.push(playerCoin);
game.addChild(playerCoin);
// Spawn AI coin
var aiCoin = new Coin();
aiCoin.x = Math.random() * 1800 + 124;
aiCoin.y = Math.random() * 1000 + 400; // Upper half for AI
aiCoins_array.push(aiCoin);
coins.push(aiCoin);
game.addChild(aiCoin);
}
function spawnUnit(team, unitType) {
var unit;
var cost = unitType === 'worker' ? 5 : unitType === 'warrior' ? 10 : unitType === 'wizard' ? 20 : unitType === 'archer' ? 15 : 25;
if (team === 'player' && playerCoins >= cost) {
playerCoins -= cost;
if (unitType === 'worker') {
unit = new Worker('player');
} else {
unit = new CombatUnit('player', unitType);
}
if (!zombieMode && unitType !== 'worker') {
// Position combat units in front of tower in normal mode with better spacing
var unitCount = playerUnits.length;
var spreadRadius = 300;
var angle = unitCount * 0.5 % (Math.PI * 2);
unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = playerTowers[0].y - 507 + Math.sin(angle) * 100;
} else {
var unitCount = playerUnits.length;
var spreadRadius = 200;
var angle = unitCount * 0.8 % (Math.PI * 2);
unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = playerTowers[0].y - 100 + Math.sin(angle) * 50;
}
playerUnits.push(unit);
game.addChild(unit);
LK.getSound('unitSpawn').play();
} else if (team === 'ai' && aiCoins >= cost) {
aiCoins -= cost;
if (unitType === 'worker') {
unit = new Worker('ai');
} else {
unit = new CombatUnit('ai', unitType);
}
var unitCount = aiUnits.length;
var spreadRadius = 200;
var angle = unitCount * 0.8 % (Math.PI * 2);
unit.x = aiTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = aiTowers[0].y + 100 + Math.sin(angle) * 50;
aiUnits.push(unit);
game.addChild(unit);
}
}
function spawnZombie() {
var zombie = new Zombie(zombieWaveNumber);
zombie.x = Math.random() * 1800 + 124; // Random X position across screen width
zombie.y = 50; // Spawn at top of screen
zombies.push(zombie);
game.addChild(zombie);
}
function startZombieWave() {
zombieWaveInProgress = true;
zombiesToSpawnInWave = 3 + zombieWaveNumber * 2; // Wave 1: 5 zombies, Wave 2: 7 zombies, etc.
zombiesInCurrentWave = 0;
zombieWaveSpawnTimer = 0;
// Update wave display
if (zombieMode) {
waveDisplay.setText('Wave: ' + zombieWaveNumber);
}
}
function checkWaveComplete() {
// Check if all zombies in current wave are spawned and destroyed
if (zombieWaveInProgress && zombiesInCurrentWave >= zombiesToSpawnInWave && zombies.length === 0) {
zombieWaveInProgress = false;
zombieWaveNumber++;
// Start next wave after 10 seconds (doubled from 5 seconds)
zombieWaveSpawnTimer = -600; // 10 seconds delay before next wave
}
}
function aiLogic() {
if (aiActionTimer <= 0) {
if (aiCoins >= 25 && Math.random() < 0.1) {
spawnUnit('ai', 'dragon');
} else if (aiCoins >= 15 && Math.random() < 0.15) {
spawnUnit('ai', 'archer');
} else if (aiCoins >= 20 && Math.random() < 0.2) {
spawnUnit('ai', 'wizard');
} else if (aiCoins >= 10 && Math.random() < 0.3) {
spawnUnit('ai', 'warrior');
} else if (aiCoins >= 5 && Math.random() < 0.4) {
spawnUnit('ai', 'worker');
}
aiActionTimer = 120;
}
aiActionTimer--;
}
function checkGameEnd() {
if (playerTowers.length === 0) {
LK.showGameOver();
} else if (!zombieMode && aiTowers.length === 0) {
LK.showYouWin();
}
}
function cleanupCollectedCoins() {
// Clean up from main coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i].collected) {
coins[i].destroy();
coins.splice(i, 1);
}
}
// Clean up from player coins array
for (var i = playerCoins_array.length - 1; i >= 0; i--) {
if (playerCoins_array[i].collected) {
playerCoins_array.splice(i, 1);
}
}
// Clean up from AI coins array
for (var i = aiCoins_array.length - 1; i >= 0; i--) {
if (aiCoins_array[i].collected) {
aiCoins_array.splice(i, 1);
}
}
}
// Button event handlers
workerButton.down = function () {
spawnUnit('player', 'worker');
};
warriorButton.down = function () {
spawnUnit('player', 'warrior');
};
wizardButton.down = function () {
spawnUnit('player', 'wizard');
};
archerButton.down = function () {
spawnUnit('player', 'archer');
};
dragonButton.down = function () {
spawnUnit('player', 'dragon');
};
// Character image event handlers
workerImage.down = function () {
spawnUnit('player', 'worker');
};
warriorImage.down = function () {
spawnUnit('player', 'warrior');
};
wizardImage.down = function () {
spawnUnit('player', 'wizard');
};
archerImage.down = function () {
spawnUnit('player', 'archer');
};
dragonImage.down = function () {
spawnUnit('player', 'dragon');
};
// Attack button event handler
attackButton.down = function () {
if (!zombieMode) {
// Make all waiting player units start attacking
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (unit.unitType !== 'worker') {
unit.isWaiting = false;
}
}
}
};
// Initialize game function
function initializeGame() {
background.visible = true;
// Show UI elements when game starts
coinDisplay.visible = true;
unifiedBackground.visible = true;
workerButton.visible = true;
warriorButton.visible = true;
wizardButton.visible = true;
archerButton.visible = true;
dragonButton.visible = true;
workerImage.visible = true;
warriorImage.visible = true;
wizardImage.visible = true;
archerImage.visible = true;
dragonImage.visible = true;
// Show attack button only in normal mode
if (!zombieMode) {
attackButton.visible = true;
} else {
// Show wave display in zombie mode
waveDisplay.visible = true;
waveDisplay.setText('Wave: ' + zombieWaveNumber);
}
// Reset zombie wave system
zombieWaveNumber = 1;
zombieWaveSpawnTimer = -1800; // 30 second delay (30 * 60 FPS = 1800 frames)
zombieWaveInProgress = false;
zombiesInCurrentWave = 0;
zombiesToSpawnInWave = 0;
// Play game music when match starts
LK.playMusic('Song');
initializeTowers();
// Spawn initial workers for both teams
var playerWorker = new Worker('player');
playerWorker.x = playerTowers[0].x + 50;
playerWorker.y = playerTowers[0].y - 100;
playerUnits.push(playerWorker);
game.addChild(playerWorker);
// AI worker (only in normal mode)
if (!zombieMode) {
var aiWorker = new Worker('ai');
aiWorker.x = aiTowers[0].x + 50;
aiWorker.y = aiTowers[0].y + 100;
aiUnits.push(aiWorker);
game.addChild(aiWorker);
}
}
// Main game loop
game.update = function () {
if (gameState === 'playing') {
// Update coin display
coinDisplay.setText('Golds: ' + playerCoins);
// Spawn coins
coinSpawnTimer++;
if (coinSpawnTimer >= 36) {
// 5x more frequent (180/5 = 36)
spawnCoin();
coinSpawnTimer = 0;
}
// AI logic (only in normal mode)
if (!zombieMode) {
aiLogic();
}
// Clean up collected coins
cleanupCollectedCoins();
// Zombie mode logic
if (zombieMode) {
zombieWaveSpawnTimer++;
// Start first wave or next wave after delay
if (!zombieWaveInProgress && zombieWaveSpawnTimer >= 0) {
startZombieWave();
}
// Spawn zombies in current wave
if (zombieWaveInProgress && zombiesInCurrentWave < zombiesToSpawnInWave) {
if (zombieWaveSpawnTimer % 60 === 0) {
// Spawn one zombie every second during wave
spawnZombie();
zombiesInCurrentWave++;
}
}
// Check if current wave is complete
checkWaveComplete();
// Clean up off-screen zombies
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].y > 2800) {
zombies[i].destroy();
zombies.splice(i, 1);
}
}
}
// Check game end conditions
checkGameEnd();
}
};
Worker. In-Game asset. 2d. High contrast. No shadows
Wizard. In-Game asset. 2d. High contrast. No shadows
Dragon. In-Game asset. 2d. High contrast. No shadows
Warrior. In-Game asset. 2d. High contrast. No shadows
etrafı kırmızı işçi. In-Game asset. 2d. High contrast. No shadows
Archer. In-Game asset. 2d. High contrast. No shadows
Magic. In-Game asset. 2d. High contrast. No shadows
Sadece 1 Altın parçacığı. In-Game asset. 2d. High contrast. No shadows
Köşelerdeki taş yerleri kaldır
Çözünürlüğü artır ve kırmızı şeyleri kaldır. In-Game asset. 2d. High contrast. No shadows
Ağzından ateş püskürtsün
Tam yukarıdan bakılan bir kule clash royaledeki gibi. In-Game asset. 2d. High contrast. No shadows
Mavi yerler kırmızı olsun
Sol tarafında kılıçlar olan start buton. In-Game asset. 2d. High contrast. No shadows
How to play button. In-Game asset. 2d. High contrast. No shadows
Tower defence menü background. In-Game asset. 2d. High contrast. No shadows
Towers On Attack yazısı. In-Game asset. 2d. High contrast. No shadows
Zombie. In-Game asset. 2d. High contrast. No shadows
Normal Mode button. In-Game asset. 2d. High contrast. No shadows
Zombie Mode buton. In-Game asset. 2d. High contrast. No shadows