User prompt
Has que el girasol se pueda fusionar con el girasol y de 15 puntos cada 5.5 segundos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Has que los wallnut tengan 200 de vida y cuesten 20 puntos
User prompt
Has que los zombies tengan 215 de vida
User prompt
Has que el pointShooter dispare un punto por cada 4.5 segundos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Has que el pointShooter tenga 65 de vida
User prompt
Has que el girasol se pueda fusionar con el lanza guisantes y que dispare puntos y de puntos 1al impactar con un zombie y ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Has que el zombie común, el corredor tengan 250 de vida
User prompt
Has que el lanza guisantes doble solo tire 2 volas por zombie
User prompt
Has que si tú fusionas un lanza guisantes con otro lanza guisantes el lanza guisantes sea un lanza guisantes doble ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Has que si un zombie cruza hacia el otro lado pierdas automáticamente el juego
User prompt
Has que pierdas el juego si no tienes ni un giralos si tienes un girasol no pierdas
User prompt
Has que si te quedas sin puntos y tienes menos de 1 girasol pierdas el juego
User prompt
Has que las cherry bomb exploten y hagan 200 de daño a los zombis en un radio de 3 slot, y has que cuesten 50 puntos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Has que los zombies no te den puntos
User prompt
Has que los girasoles solo den 5 puntos en 20 segundos, has que si no tienes puntos no puedas poner plantas, has que al principio tengas 100 puntos, y que el girasol cueste 15 puntos
User prompt
Has que los lanza guisantes hagan una animación al disparar, añade a el girasol que te dé 10 puntos cada 5 segundos, el lanza guisantes cuesta 10 puntos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
HAS que los zombies agan una animación al caminar y el zombie rapido haga una animación mas rápida, has que los lanza guisantes hagan una animación al disparar ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Has que los lanza guisantes solo disparen recto en una línea recta
User prompt
Has que el sonido zombie se reprodusca en los zombies
User prompt
Has que el selector de personajes este abajo de el mapa
User prompt
Has que todo el videojuego este para toda la pantalla osea que no estén en una esquina
Code edit (1 edits merged)
Please save this source code
User prompt
Garden Defense: Plants vs Zombies
Initial prompt
Hasme un videojuego de plantas vs zombies
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var ExplosiveProjectile = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('pea', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF4444, scaleX: 1.3, scaleY: 1.3 }); self.x = startX; self.y = startY; self.target = target; self.damage = 25; self.speed = 8; self.explosionRadius = 150; // Shoot straight horizontally to the right self.dirX = 1; self.dirY = 0; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Pulsing red animation while flying if (LK.ticks % 8 === 0) { tween(self.graphics, { tint: 0xFF0000 }, { duration: 80 }); tween(self.graphics, { tint: 0xFF4444 }, { duration: 80 }); } // Check collision with any zombie for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (Math.abs(zombie.x - self.x) < 30 && Math.abs(zombie.y - self.y) < 30) { // Create explosion visual effect tween(self.graphics, { tint: 0xFFFFFF, scaleX: 4, scaleY: 4, alpha: 0.7 }, { duration: 300 }); // Immediate explosion - damage all zombies in radius for (var j = zombies.length - 1; j >= 0; j--) { var targetZombie = zombies[j]; var dx = targetZombie.x - self.x; var dy = targetZombie.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explosionRadius) { targetZombie.takeDamage(self.damage); } } LK.getSound('explosion').play(); self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); return; } } // Remove if off-screen if (self.x > 2100 || self.x < -50 || self.y > 2800 || self.y < -50) { self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); } }; self.explode = function () { // Explosion animation tween(self.graphics, { tint: 0xFF0000, scaleX: 4, scaleY: 4 }, { duration: 200, onFinish: function onFinish() { // Damage all zombies in explosion radius for (var i = zombies.length - 1; i >= 0; 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 <= self.explosionRadius) { zombie.takeDamage(self.damage); } } LK.getSound('explosion').play(); self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); } }); }; return self; }); var Plant = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.health = 100; self.maxHealth = 100; self.shootTimer = 0; self.shootCooldown = 60; // 1 second at 60fps if (type === 'peashooter') { self.graphics = self.attachAsset('peashooter', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 25; self.range = 400; self.shootCooldown = 228; // 3.8 seconds at 60fps (3.8 * 60 = 228) } else if (type === 'doublepeashooter') { self.graphics = self.attachAsset('doublepeashooter', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 50; self.range = 400; self.shootCooldown = 210; // Total cycle time: 3.5 seconds (3.5 * 60 = 210) self.firstShotTime = 60; // First shot at 1 second (1 * 60 = 60) self.secondShotTime = 114; // Second shot at 1.9 seconds (1.9 * 60 = 114) self.hasFiredFirst = false; self.hasFiredSecond = false; } else if (type === 'wallnut') { self.graphics = self.attachAsset('wallnut', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1500; self.maxHealth = 1500; self.damage = 0; } else if (type === 'cherrybomb') { self.graphics = self.attachAsset('cherrybomb', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 200; self.explosionRadius = 540; // 3 slots * 180px per slot self.fuseTimer = 180; // 3 seconds } else if (type === 'sunflower') { self.graphics = self.attachAsset('sunflower', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 0; self.sunTimer = 0; self.sunInterval = 1200; // 20 seconds at 60fps } else if (type === 'pointshooter') { self.graphics = self.attachAsset('pointShooter', { anchorX: 0.5, anchorY: 0.5 }); self.health = 65; self.maxHealth = 65; self.damage = 0; // Doesn't damage, gives points instead self.range = 400; self.shootCooldown = 270; // Shoots every 4.5 seconds (270 frames at 60fps) } else if (type === 'doublesunflower') { self.graphics = self.attachAsset('doublesunflower', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 0; self.sunTimer = 0; self.sunInterval = 330; // 5.5 seconds at 60fps (5.5 * 60 = 330) } else if (type === 'armoredshooter') { self.graphics = self.attachAsset('armoredshooter', { anchorX: 0.5, anchorY: 0.5 }); self.health = 350; // High health like wallnut but less self.maxHealth = 350; self.damage = 35; // More damage than regular peashooter self.range = 400; self.shootCooldown = 240; // 4 seconds at 60fps (4 * 60 = 240) } else if (type === 'papapum') { self.graphics = self.attachAsset('papapum', { anchorX: 0.5, anchorY: 0.5 }); self.health = 80; // Lower health since it's a mine self.maxHealth = 80; self.damage = 250; // High explosive damage self.explosionRadius = 300; // Explosion range self.isTriggered = false; // Track if mine has been triggered self.cooldownTimer = 0; // Add cooldown timer starting at 0 self.cooldownDuration = 540; // 9 seconds at 60fps (9 * 60 = 540) } else if (type === 'explosivewallnut') { self.graphics = self.attachAsset('explosivewallnut', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1200; // High health like wallnut but slightly less self.maxHealth = 1200; self.damage = 300; // Explosive damage when destroyed self.explosionRadius = 400; // Large explosion range } else if (type === 'explosivepeashooter') { self.graphics = self.attachAsset('explosivepeashooter', { anchorX: 0.5, anchorY: 0.5 }); self.health = 120; // Slightly more than regular peashooter self.maxHealth = 120; self.damage = 40; // Higher damage than regular peashooter self.range = 400; self.shootCooldown = 180; // 3 seconds at 60fps (3 * 60 = 180) } else if (type === 'triplesunflower') { self.graphics = self.attachAsset('triplesunflower', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFD700 }); self.damage = 0; self.sunTimer = 0; self.sunInterval = 180; // 3 seconds at 60fps (3 * 60 = 180) } else if (type === 'burstpeashooter') { self.graphics = self.attachAsset('burstpeashooter', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFFFFF }); self.health = 150; self.maxHealth = 150; self.damage = 8; // Each projectile does 8 damage self.range = 400; self.shootCooldown = 234; // 3.9 seconds at 60fps (3.9 * 60 = 234) self.burstCount = 15; // Number of projectiles in burst self.burstDelay = 6; // Frames between each projectile in burst (0.1 seconds) self.burstProgress = 0; // Track current burst progress self.isBursting = false; // Track if currently in burst mode } else if (type === 'piranaplant') { self.graphics = self.attachAsset('piranaplant', { anchorX: 0.5, anchorY: 0.5 }); self.health = 200; self.maxHealth = 200; self.damage = 0; // Doesn't damage, eats zombies self.eatRange = 100; // Range to eat zombies self.eatCooldown = 60; // 1 second cooldown between eating self.eatTimer = 0; self.isEating = false; self.digestionTimer = 0; self.digestionTime = 900; // 15 seconds to digest (much longer chewing) } self.update = function () { if (self.type === 'peashooter' || self.type === 'doublepeashooter' || self.type === 'pointshooter' || self.type === 'armoredshooter' || self.type === 'explosivepeashooter' || self.type === 'burstpeashooter') { self.shootTimer++; if (self.type === 'doublepeashooter') { var target = self.findZombieInLane(); if (target) { // First shot at 1 second if (self.shootTimer >= self.firstShotTime && !self.hasFiredFirst) { self.shoot(target); self.hasFiredFirst = true; } // Second shot at 1.9 seconds if (self.shootTimer >= self.secondShotTime && !self.hasFiredSecond) { self.shoot(target); self.hasFiredSecond = true; } } // Reset cycle after total cooldown (3.5 seconds) if (self.shootTimer >= self.shootCooldown) { self.shootTimer = 0; self.hasFiredFirst = false; self.hasFiredSecond = false; } } else if (self.type === 'burstpeashooter') { var target = self.findZombieInLane(); if (target && !self.isBursting) { // Start burst sequence if (self.shootTimer >= self.shootCooldown) { self.isBursting = true; self.burstProgress = 0; self.shootTimer = 0; } } // Handle burst firing if (self.isBursting) { if (self.shootTimer % self.burstDelay === 0 && self.burstProgress < self.burstCount) { var currentTarget = self.findZombieInLane(); if (currentTarget) { // Create projectile directly without animation var projectile = new Projectile(self.x, self.y, currentTarget, self.damage); projectiles.push(projectile); game.addChild(projectile); LK.getSound('shoot').play(); } self.burstProgress++; } // End burst sequence if (self.burstProgress >= self.burstCount) { self.isBursting = false; self.burstProgress = 0; } } } else if (self.shootTimer >= self.shootCooldown) { var target = self.findZombieInLane(); if (target) { if (self.type === 'pointshooter') { self.shootPoints(target); } else if (self.type === 'explosivepeashooter') { self.shootExplosive(target); } else { self.shoot(target); } } self.shootTimer = 0; } } else if (self.type === 'cherrybomb') { self.fuseTimer--; // Check if any zombie is within very close range (80px) before exploding var zombieNearby = false; for (var z = 0; z < zombies.length; z++) { var zombie = zombies[z]; if (Math.abs(zombie.x - self.x) < 80 && Math.abs(zombie.y - self.y) < 80) { zombieNearby = true; break; } } // Only explode if fuse timer is done AND zombie is very close if (self.fuseTimer <= 0 && zombieNearby) { self.explode(); } } else if (self.type === 'sunflower') { self.sunTimer++; if (self.sunTimer >= self.sunInterval) { LK.setScore(LK.getScore() + 5); scoreText.setText('Girasoles: ' + LK.getScore()); self.sunTimer = 0; } } else if (self.type === 'doublesunflower') { self.sunTimer++; if (self.sunTimer >= self.sunInterval) { LK.setScore(LK.getScore() + 15); scoreText.setText('Girasoles: ' + LK.getScore()); self.sunTimer = 0; } } else if (self.type === 'triplesunflower') { self.sunTimer++; if (self.sunTimer >= self.sunInterval) { LK.setScore(LK.getScore() + 35); scoreText.setText('Girasoles: ' + LK.getScore()); self.sunTimer = 0; // Special golden glow animation every time it generates sun tween(self.graphics, { scaleX: 1.3, scaleY: 1.3, tint: 0xFFFF00 }, { duration: 200, onFinish: function onFinish() { tween(self.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFD700 }, { duration: 200 }); } }); } } else if (self.type === 'papapum' && !self.isTriggered) { // Increment cooldown timer self.cooldownTimer++; // Ready animation when cooldown just completes if (self.cooldownTimer === self.cooldownDuration) { // Papapum ready animation - pulsing green glow tween(self.graphics, { tint: 0x00FF00, scaleX: 1.2, scaleY: 1.2 }, { duration: 300, onFinish: function onFinish() { tween(self.graphics, { tint: 0xFFFFFF, scaleX: 1, scaleY: 1 }, { duration: 300 }); } }); } // Only become active after cooldown period if (self.cooldownTimer >= self.cooldownDuration) { // Check if any zombie is stepping on the papapum for (var z = 0; z < zombies.length; z++) { var zombie = zombies[z]; if (Math.abs(zombie.x - self.x) < 90 && Math.abs(zombie.y - self.y) < 90) { self.isTriggered = true; self.explode(); break; } } } } else if (self.type === 'piranaplant') { self.eatTimer++; if (self.isEating) { self.digestionTimer++; if (self.digestionTimer >= self.digestionTime) { self.isEating = false; self.digestionTimer = 0; // Check if this is an exploding piranha plant if (self.isExploding) { // Explode after finishing eating self.explodeAfterEating(); } else { // Return to normal appearance tween(self.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 300 }); } } } else if (self.eatTimer >= self.eatCooldown && !self.isEating) { // Look for zombies in eating range - only if not already eating for (var z = 0; z < zombies.length; z++) { var zombie = zombies[z]; if (Math.abs(zombie.x - self.x) < self.eatRange && Math.abs(zombie.y - self.y) < self.eatRange) { // Eat the zombie self.eatZombie(zombie); break; } } self.eatTimer = 0; } } }; self.findZombieInLane = function () { for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; // Check if zombie is in same lane (within 100px vertically) and to the right if (Math.abs(zombie.y - self.y) < 100 && zombie.x > self.x) { return zombie; } } return null; }; self.getDistanceTo = function (target) { var dx = target.x - self.x; var dy = target.y - self.y; return Math.sqrt(dx * dx + dy * dy); }; self.shoot = function (target) { // Shooting animation - scale and recoil effect tween(self.graphics, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100 }); tween(self.graphics, { scaleX: 1, scaleY: 1 }, { duration: 200 }); tween(self.graphics, { x: -10 }, { duration: 100 }); tween(self.graphics, { x: 0 }, { duration: 200 }); var projectile = new Projectile(self.x, self.y, target, self.damage); projectiles.push(projectile); game.addChild(projectile); LK.getSound('shoot').play(); }; self.shootPoints = function (target) { // Golden shooting animation tween(self.graphics, { scaleX: 1.2, scaleY: 1.2, tint: 0xFFD700 }, { duration: 150 }); tween(self.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 250 }); var pointProjectile = new PointProjectile(self.x, self.y, target); projectiles.push(pointProjectile); game.addChild(pointProjectile); LK.getSound('shoot').play(); }; self.shootExplosive = function (target) { // Red explosive shooting animation tween(self.graphics, { scaleX: 1.4, scaleY: 1.4, tint: 0xFF0000 }, { duration: 120 }); tween(self.graphics, { scaleX: 1, scaleY: 1, tint: 0xFF4444 }, { duration: 200 }); tween(self.graphics, { x: -15 }, { duration: 120 }); tween(self.graphics, { x: 0 }, { duration: 200 }); var explosiveProjectile = new ExplosiveProjectile(self.x, self.y, target, self.damage); projectiles.push(explosiveProjectile); game.addChild(explosiveProjectile); LK.getSound('shoot').play(); }; self.eatZombie = function (zombie) { // Start eating animation self.isEating = true; self.digestionTimer = 0; // Eating animation - grow larger and redder tween(self.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xFF0000 }, { duration: 200, onFinish: function onFinish() { // Chomp sound and remove zombie LK.getSound('zombieHit').play(); // Award points based on zombie type if (zombie.type === 'tank') { LK.setScore(LK.getScore() + 450); zombiesKilled++; } else if (zombie.type === 'strong') { zombiesKilled++; } else { zombiesKilled++; } scoreText.setText('Girasoles: ' + LK.getScore()); // Remove zombie from game zombie.destroy(); zombies.splice(zombies.indexOf(zombie), 1); } }); }; self.explodeAfterEating = function () { // Create explosion sprite var explosionSprite = self.attachAsset('explosionSprite', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, alpha: 0.8 }); // Play explosion sound immediately LK.getSound('explosion').play(); // Damage all zombies in explosion radius immediately for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; if (self.getDistanceTo(zombie) <= self.explosionRadius) { zombie.takeDamage(self.explosionDamage); } } // Special explosion for piranha plant after eating tween(self.graphics, { tint: 0xFF0000, scaleX: 2.5, scaleY: 2.5 }, { duration: 400, onFinish: function onFinish() { // Animate explosion sprite tween(explosionSprite, { scaleX: 3, scaleY: 3, alpha: 1, tint: 0xFFFF00 }, { duration: 300, onFinish: function onFinish() { // Second explosion wave tween(explosionSprite, { scaleX: 5, scaleY: 5, alpha: 0, tint: 0xFF0000 }, { duration: 400, onFinish: function onFinish() { // Free up the grid cell for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { var cell = gridCells[row][col]; if (Math.abs(cell.x - self.x) < 40 && Math.abs(cell.y - self.y) < 40) { cell.occupied = false; } } } self.destroy(); plants.splice(plants.indexOf(self), 1); } }); } }); // Create large explosion effect on main graphics tween(self.graphics, { tint: 0xFFFFFF, scaleX: 4, scaleY: 4, alpha: 0.3 }, { duration: 200 }); } }); }; self.explode = function () { // Explosion animation - flash and scale up tween(self.graphics, { tint: 0xFF4444, scaleX: 3, scaleY: 3 }, { duration: 300, onFinish: function onFinish() { // Damage all zombies in explosion radius for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; if (self.getDistanceTo(zombie) <= self.explosionRadius) { zombie.takeDamage(self.damage); } } // Free up the grid cell for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { var cell = gridCells[row][col]; if (Math.abs(cell.x - self.x) < 40 && Math.abs(cell.y - self.y) < 40) { cell.occupied = false; } } } LK.getSound('explosion').play(); self.destroy(); plants.splice(plants.indexOf(self), 1); } }); }; self.takeDamage = function (damage) { self.health -= damage; // Change wallnut sprite when health drops to 670 or below if (self.type === 'wallnut' && self.health <= 670 && self.health > 0) { // Remove current graphics self.removeChild(self.graphics); // Add damaged wallnut graphics self.graphics = self.attachAsset('wallnutDamaged', { anchorX: 0.5, anchorY: 0.5 }); } // Change armoredshooter sprite when health drops to 175 or below (half of 350) if (self.type === 'armoredshooter' && self.health <= 175 && self.health > 0) { // Remove current graphics self.removeChild(self.graphics); // Add damaged wallnut graphics (reuse damaged wallnut sprite) self.graphics = self.attachAsset('wallnutDamaged', { anchorX: 0.5, anchorY: 0.5 }); } // Change explosivewallnut sprite when health drops to 600 or below (half of 1200) if (self.type === 'explosivewallnut' && self.health <= 600 && self.health > 0) { // Remove current graphics self.removeChild(self.graphics); // Add damaged wallnut graphics self.graphics = self.attachAsset('wallnutDamaged', { anchorX: 0.5, anchorY: 0.5 }); } if (self.health <= 0) { // If explosivewallnut is destroyed, explode if (self.type === 'explosivewallnut') { self.explode(); } else { // Free up the grid cell for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { var cell = gridCells[row][col]; if (Math.abs(cell.x - self.x) < 40 && Math.abs(cell.y - self.y) < 40) { cell.occupied = false; } } } self.destroy(); plants.splice(plants.indexOf(self), 1); } } }; return self; }); var PlantButton = Container.expand(function (plantType, index) { var self = Container.call(this); self.plantType = plantType; self.button = self.attachAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var icon; if (plantType === 'peashooter') { icon = self.attachAsset('peashooter', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } else if (plantType === 'wallnut') { icon = self.attachAsset('wallnut', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } else if (plantType === 'cherrybomb') { icon = self.attachAsset('cherrybomb', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } else if (plantType === 'sunflower') { icon = self.attachAsset('sunflower', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } else if (plantType === 'papapum') { icon = self.attachAsset('papapum', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } else if (plantType === 'piranaplant') { icon = self.attachAsset('piranaplant', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } else if (plantType === 'shovel') { icon = self.attachAsset('shovel', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); } self.x = 1024 - plantTypes.length * 200 / 2 + index * 200; self.y = 2600; self.down = function (x, y, obj) { // Check if plant is on cooldown if (plantCooldowns[self.plantType] && plantCooldowns[self.plantType] > 0) { return; // Don't allow selection if on cooldown } selectedPlantType = self.plantType; // Visual feedback for (var i = 0; i < plantButtons.length; i++) { var button = plantButtons[i]; var plantType = button.plantType; if (plantCooldowns[plantType] && plantCooldowns[plantType] > 0) { // Keep cooldown plants darkened button.button.tint = 0x666666; } else { // Reset other buttons to normal button.button.tint = 0xFFFFFF; } } self.button.tint = 0x00FF00; }; return self; }); var PointProjectile = Container.expand(function (startX, startY, target) { var self = Container.call(this); self.graphics = self.attachAsset('pointPea', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.speed = 6; // Shoot straight horizontally to the right self.dirX = 1; self.dirY = 0; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Golden sparkle animation if (LK.ticks % 10 === 0) { tween(self.graphics, { tint: 0xFFFF00 }, { duration: 100 }); tween(self.graphics, { tint: 0xFFD700 }, { duration: 100 }); } // Check collision with any zombie for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (Math.abs(zombie.x - self.x) < 30 && Math.abs(zombie.y - self.y) < 30) { // Give 1 point instead of damage LK.setScore(LK.getScore() + 1); scoreText.setText('Girasoles: ' + LK.getScore()); // Visual effect on hit tween(zombie.graphics, { tint: 0xFFD700 }, { duration: 200 }); tween(zombie.graphics, { tint: 0xFFFFFF }, { duration: 200 }); self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); return; } } // Remove if off-screen if (self.x > 2100 || self.x < -50 || self.y > 2800 || self.y < -50) { self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); } }; return self; }); var Projectile = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('pea', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 8; // Shoot straight horizontally to the right self.dirX = 1; self.dirY = 0; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Check collision with target or any zombie for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; if (Math.abs(zombie.x - self.x) < 30 && Math.abs(zombie.y - self.y) < 30) { zombie.takeDamage(self.damage); self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); return; } } // Remove if off-screen if (self.x > 2100 || self.x < -50 || self.y > 2800 || self.y < -50) { self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); } }; return self; }); var TankProjectile = Container.expand(function (startX, startY, target) { var self = Container.call(this); self.graphics = self.attachAsset('pea', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, tint: 0xFF4444 }); self.x = startX; self.y = startY; self.target = target; self.damage = 100; self.speed = 4; self.explosionRadius = 200; // Shoot straight horizontally to the left self.dirX = -1; self.dirY = 0; self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Check collision with any plant for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (Math.abs(plant.x - self.x) < 40 && Math.abs(plant.y - self.y) < 40) { self.explode(); return; } } // Remove if off-screen if (self.x < -50 || self.x > 2100 || self.y > 2800 || self.y < -50) { self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); } }; self.explode = function () { // Explosion animation tween(self.graphics, { tint: 0xFF0000, scaleX: 4, scaleY: 4 }, { duration: 300, onFinish: function onFinish() { // Damage all plants in explosion radius for (var i = plants.length - 1; i >= 0; i--) { var plant = plants[i]; var dx = plant.x - self.x; var dy = plant.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.explosionRadius) { plant.takeDamage(self.damage); } } LK.getSound('explosion').play(); self.destroy(); projectiles.splice(projectiles.indexOf(self), 1); } }); }; return self; }); var Zombie = Container.expand(function (type, lane) { var self = Container.call(this); self.type = type; self.lane = lane; self.id = Math.random().toString(36).substr(2, 9); // Generate unique ID self.speed = 1; self.health = 100; self.maxHealth = 100; self.damage = 25; self.attackCooldown = 60; self.attackTimer = 0; self.isAttacking = false; if (type === 'basic') { self.graphics = self.attachAsset('basicZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.3; self.health = 215; self.maxHealth = 215; } else if (type === 'fast') { self.graphics = self.attachAsset('fastZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.3; self.health = 215; self.maxHealth = 215; } else if (type === 'strong') { self.graphics = self.attachAsset('strongZombie', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.6, scaleY: 1.6 }); self.speed = 0.4; self.health = 500; self.maxHealth = 500; self.damage = 45; } else if (type === 'tank') { self.graphics = self.attachAsset('tankZombie', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.2; self.health = 777; self.maxHealth = 777; self.damage = 50; self.shootTimer = 0; self.shootCooldown = 300; // 5 seconds at 60fps } self.x = 2048 + 50; // Start off-screen right self.y = 300 + lane * 250; // Position in lane // Play zombie sound when spawned LK.getSound('zombie').play(); self.update = function () { if (!self.isAttacking) { self.x -= self.speed; // Walking animation - bob up and down if (self.type === 'basic') { // Slower walking animation for basic zombie if (LK.ticks % 40 < 20) { tween(self.graphics, { y: 5 }, { duration: 200 }); } else { tween(self.graphics, { y: -5 }, { duration: 200 }); } } else if (self.type === 'fast') { // Faster walking animation for fast zombie if (LK.ticks % 20 < 10) { tween(self.graphics, { y: 8 }, { duration: 100 }); } else { tween(self.graphics, { y: -8 }, { duration: 100 }); } } else if (self.type === 'strong') { // Medium walking animation for strong zombie if (LK.ticks % 30 < 15) { tween(self.graphics, { y: 6 }, { duration: 150 }); } else { tween(self.graphics, { y: -6 }, { duration: 150 }); } } else if (self.type === 'tank') { // Slow heavy walking animation for tank zombie if (LK.ticks % 60 < 30) { tween(self.graphics, { y: 3 }, { duration: 300 }); } else { tween(self.graphics, { y: -3 }, { duration: 300 }); } } // Tank zombie shooting logic if (self.type === 'tank') { self.shootTimer++; if (self.shootTimer >= self.shootCooldown) { var target = self.findPlantInLane(); if (target) { var tankProjectile = new TankProjectile(self.x, self.y, target); projectiles.push(tankProjectile); game.addChild(tankProjectile); LK.getSound('shoot').play(); } self.shootTimer = 0; } } // Check for plant collision var plantInWay = self.findPlantInWay(); if (plantInWay) { self.isAttacking = true; self.attackTimer = 0; } } else { self.attackTimer++; if (self.attackTimer >= self.attackCooldown) { var plantInWay = self.findPlantInWay(); if (plantInWay) { plantInWay.takeDamage(self.damage); self.attackTimer = 0; } else { self.isAttacking = false; } } } // Check if reached end if (self.x < -50) { zombiesReachedEnd++; self.destroy(); zombies.splice(zombies.indexOf(self), 1); } }; self.findPlantInWay = function () { for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (Math.abs(plant.x - self.x) < 80 && Math.abs(plant.y - self.y) < 80) { return plant; } } return null; }; self.findPlantInLane = function () { for (var i = 0; i < plants.length; i++) { var plant = plants[i]; // Check if plant is in same lane (within 100px vertically) and to the left if (Math.abs(plant.y - self.y) < 100 && plant.x < self.x) { return plant; } } return null; }; self.takeDamage = function (damage) { self.health -= damage; LK.getSound('zombieHit').play(); // Visual feedback tween(self.graphics, { tint: 0xFF0000 }, { duration: 200 }); tween(self.graphics, { tint: 0xFFFFFF }, { duration: 200 }); if (self.health <= 0) { // Give points based on zombie type if (self.type === 'tank') { LK.setScore(LK.getScore() + 450); zombiesKilled++; } else if (self.type === 'strong') { // Strong zombie gives no points but still counts as killed zombiesKilled++; } else { zombiesKilled++; } scoreText.setText('Girasoles: ' + LK.getScore()); // Clean up shots tracking for all plants for (var p = 0; p < plants.length; p++) { if (plants[p].shotsFired && plants[p].shotsFired[self.id]) { delete plants[p].shotsFired[self.id]; } } self.destroy(); zombies.splice(zombies.indexOf(self), 1); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2E7D32 }); /**** * Game Code ****/ // Sounds // Grid and UI // Projectiles // Zombies // Plants // Game variables var plants = []; var zombies = []; var projectiles = []; var plantButtons = []; var gridCells = []; var selectedPlantType = null; var waveNumber = 1; var zombiesInWave = 5; var zombieSpawnTimer = 0; var zombieSpawnInterval = 120; // 2 seconds var zombiesSpawned = 0; var zombiesReachedEnd = 0; var maxZombiesAllowed = 5; var gameState = 'menu'; // 'menu', 'playing', 'gameOver', 'victory', 'tutorial' var autoPointTimer = 0; var autoPointInterval = 420; // 7 seconds at 60fps (7 * 60 = 420) var zombiesKilled = 0; var selectedLanguage = storage.selectedLanguage || 'spanish'; // Default to Spanish var fusionMode = storage.fusionMode !== undefined ? storage.fusionMode : true; // Default to fusion enabled // Plant cooldown system var plantCooldowns = { sunflower: 0, peashooter: 0, wallnut: 0, cherrybomb: 0, piranaplant: 0 }; var plantCooldownDurations = { sunflower: 90, // 1.5 seconds at 60fps peashooter: 60, // 1 second at 60fps wallnut: 150, // 2.5 seconds at 60fps cherrybomb: 210, // 3.5 seconds at 60fps piranaplant: 240 // 4 seconds at 60fps }; // Menu elements var menuContainer = new Container(); game.addChild(menuContainer); // Menu title var titleText = new Text2('Plants vs Zombies', { size: 80, fill: '#00FF00' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 600; menuContainer.addChild(titleText); // Menu buttons var idiomasButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); idiomasButton.x = 1024; idiomasButton.y = 1000; menuContainer.addChild(idiomasButton); var idiomasText = new Text2('IDIOMAS', { size: 50, fill: '#FFFFFF' }); idiomasText.anchor.set(0.5, 0.5); idiomasText.x = 1024; idiomasText.y = 1000; menuContainer.addChild(idiomasText); var jugarButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); jugarButton.x = 1024; jugarButton.y = 1300; menuContainer.addChild(jugarButton); var jugarText = new Text2('JUGAR', { size: 50, fill: '#FFFFFF' }); jugarText.anchor.set(0.5, 0.5); jugarText.x = 1024; jugarText.y = 1300; menuContainer.addChild(jugarText); var ajustesButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); ajustesButton.x = 1024; ajustesButton.y = 1600; menuContainer.addChild(ajustesButton); var ajustesText = new Text2('AJUSTES', { size: 50, fill: '#FFFFFF' }); ajustesText.anchor.set(0.5, 0.5); ajustesText.x = 1024; ajustesText.y = 1600; menuContainer.addChild(ajustesText); var tutorialButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); tutorialButton.x = 1024; tutorialButton.y = 1900; menuContainer.addChild(tutorialButton); var tutorialText = new Text2('TUTORIAL', { size: 50, fill: '#FFFFFF' }); tutorialText.anchor.set(0.5, 0.5); tutorialText.x = 1024; tutorialText.y = 1900; menuContainer.addChild(tutorialText); // Language selection menu (initially hidden) var languageMenuContainer = new Container(); game.addChild(languageMenuContainer); languageMenuContainer.visible = false; // Language menu title var languageTitleText = new Text2('Select Language', { size: 60, fill: '#00FF00' }); languageTitleText.anchor.set(0.5, 0.5); languageTitleText.x = 1024; languageTitleText.y = 700; languageMenuContainer.addChild(languageTitleText); // English button var englishButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); englishButton.x = 1024; englishButton.y = 1000; languageMenuContainer.addChild(englishButton); var englishText = new Text2('ENGLISH', { size: 50, fill: '#FFFFFF' }); englishText.anchor.set(0.5, 0.5); englishText.x = 1024; englishText.y = 1000; languageMenuContainer.addChild(englishText); // Spanish button var spanishButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); spanishButton.x = 1024; spanishButton.y = 1300; languageMenuContainer.addChild(spanishButton); var spanishText = new Text2('ESPAÑOL', { size: 50, fill: '#FFFFFF' }); spanishText.anchor.set(0.5, 0.5); spanishText.x = 1024; spanishText.y = 1300; languageMenuContainer.addChild(spanishText); // Back button var backButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.0 }); backButton.x = 1024; backButton.y = 1600; languageMenuContainer.addChild(backButton); var backText = new Text2('BACK', { size: 40, fill: '#FFFFFF' }); backText.anchor.set(0.5, 0.5); backText.x = 1024; backText.y = 1600; languageMenuContainer.addChild(backText); // Function to update menu texts based on selected language function updateMenuTexts() { if (selectedLanguage === 'english') { idiomasText.setText('LANGUAGES'); jugarText.setText('PLAY'); ajustesText.setText('SETTINGS'); backText.setText('BACK'); tutorialText.setText('TUTORIAL'); } else { idiomasText.setText('IDIOMAS'); jugarText.setText('JUGAR'); ajustesText.setText('AJUSTES'); backText.setText('VOLVER'); tutorialText.setText('TUTORIAL'); } } // Settings menu container (initially hidden) var settingsContainer = new Container(); game.addChild(settingsContainer); settingsContainer.visible = false; // Settings menu title var settingsTitleText = new Text2('SETTINGS', { size: 60, fill: '#00FF00' }); settingsTitleText.anchor.set(0.5, 0.5); settingsTitleText.x = 1024; settingsTitleText.y = 700; settingsContainer.addChild(settingsTitleText); // Fusion mode toggle button var fusionModeButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 1.2 }); fusionModeButton.x = 1024; fusionModeButton.y = 1200; settingsContainer.addChild(fusionModeButton); var fusionModeText = new Text2('FUSION: ON', { size: 45, fill: '#FFFFFF' }); fusionModeText.anchor.set(0.5, 0.5); fusionModeText.x = 1024; fusionModeText.y = 1200; settingsContainer.addChild(fusionModeText); // Settings back button var settingsBackButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.2 }); settingsBackButton.x = 1024; settingsBackButton.y = 1600; settingsContainer.addChild(settingsBackButton); var settingsBackText = new Text2('BACK', { size: 50, fill: '#FFFFFF' }); settingsBackText.anchor.set(0.5, 0.5); settingsBackText.x = 1024; settingsBackText.y = 1600; settingsContainer.addChild(settingsBackText); // Function to update fusion mode text function updateFusionModeText() { if (selectedLanguage === 'english') { fusionModeText.setText('FUSION: ' + (fusionMode ? 'ON' : 'OFF')); settingsTitleText.setText('SETTINGS'); settingsBackText.setText('BACK'); } else { fusionModeText.setText('FUSIÓN: ' + (fusionMode ? 'SÍ' : 'NO')); settingsTitleText.setText('AJUSTES'); settingsBackText.setText('VOLVER'); } } // Initialize fusion mode text updateFusionModeText(); // Update menu texts after all elements are created updateMenuTexts(); // Tutorial container (initially hidden) var tutorialContainer = new Container(); game.addChild(tutorialContainer); tutorialContainer.visible = false; // Crazy Dave character var crazyDave = LK.getAsset('crazyDave', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); crazyDave.x = 300; crazyDave.y = 800; tutorialContainer.addChild(crazyDave); // Tutorial text var tutorialText = new Text2('¡Hola! Soy Crazy Dave.\n\nBienvenido a Plants vs Zombies!\n\nColoca plantas para defender\ntu jardín de los zombis.\n\nPresiona CONTINUAR para seguir.', { size: 50, fill: '#FFFFFF' }); tutorialText.anchor.set(0.5, 0.5); tutorialText.x = 1024; tutorialText.y = 1400; tutorialContainer.addChild(tutorialText); // Tutorial continue button var tutorialContinueButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.0 }); tutorialContinueButton.x = 1024; tutorialContinueButton.y = 2200; tutorialContainer.addChild(tutorialContinueButton); var tutorialContinueText = new Text2('CONTINUAR', { size: 40, fill: '#FFFFFF' }); tutorialContinueText.anchor.set(0.5, 0.5); tutorialContinueText.x = 1024; tutorialContinueText.y = 2200; tutorialContainer.addChild(tutorialContinueText); // Tutorial step tracking var tutorialStep = 0; var tutorialMessages = { spanish: ['¡Hola! Soy Crazy Dave.\n\nBienvenido a Plants vs Zombies!\n\nColoca plantas para defender\ntu jardín de los zombis.\n\nPresiona CONTINUAR para seguir.', 'Los GIRASOLES generan puntos\npara comprar más plantas.\n\nLos LANZAGUISANTES disparan\na los zombis.\n\nPresiona CONTINUAR.', 'Las NUECES bloquean a los zombis\ny tienen mucha vida.\n\nLas CEREZAS BOMBA explotan\ny dañan a muchos zombis.\n\nPresiona CONTINUAR.', 'Puedes FUSIONAR plantas del\nmismo tipo para crear plantas\nmás poderosas.\n\nPresiona JUGAR para empezar.'], english: ['Hello! I am Crazy Dave.\n\nWelcome to Plants vs Zombies!\n\nPlace plants to defend\nyour garden from zombies.\n\nPress CONTINUE to proceed.', 'SUNFLOWERS generate points\nto buy more plants.\n\nPEASHOOTERS shoot at zombies.\n\nPress CONTINUE.', 'WALLNUTS block zombies\nand have lots of health.\n\nCHERRY BOMBS explode\nand damage many zombies.\n\nPress CONTINUE.', 'You can FUSE plants of the\nsame type to create more\npowerful plants.\n\nPress PLAY to start.'] }; // Function to update tutorial text function updateTutorialText() { var messages = tutorialMessages[selectedLanguage]; tutorialText.setText(messages[tutorialStep]); // Update continue button text if (tutorialStep === messages.length - 1) { if (selectedLanguage === 'english') { tutorialContinueText.setText('PLAY'); } else { tutorialContinueText.setText('JUGAR'); } } else { if (selectedLanguage === 'english') { tutorialContinueText.setText('CONTINUE'); } else { tutorialContinueText.setText('CONTINUAR'); } } } // Animate Crazy Dave function animateCrazyDave() { tween(crazyDave, { scaleX: 2.2, scaleY: 2.2, rotation: 0.1 }, { duration: 800, onFinish: function onFinish() { tween(crazyDave, { scaleX: 2, scaleY: 2, rotation: -0.1 }, { duration: 800, onFinish: function onFinish() { if (gameState === 'tutorial') { animateCrazyDave(); } } }); } }); } // UI Elements // Set initial score to 100 LK.setScore(100); var scoreText = new Text2('Girasoles: ' + LK.getScore(), { size: 40, fill: '#FFFFFF' }); scoreText.anchor.set(0, 0); scoreText.x = 150; scoreText.y = 20; LK.gui.topLeft.addChild(scoreText); scoreText.visible = false; // Hide during menu var waveText = new Text2('Wave: 1', { size: 40, fill: '#FFFFFF' }); waveText.anchor.set(0.5, 0); waveText.x = 1024; waveText.y = 20; LK.gui.top.addChild(waveText); waveText.visible = false; // Hide during menu var livesText = new Text2('Lives: 5', { size: 40, fill: '#FF0000' }); livesText.anchor.set(1, 0); livesText.x = 1900; livesText.y = 20; LK.gui.topRight.addChild(livesText); livesText.visible = false; // Hide during menu function startGame() { gameState = 'playing'; menuContainer.visible = false; scoreText.visible = true; waveText.visible = true; livesText.visible = true; // Show plant buttons for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].visible = true; } // Show grid cells for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { gridCells[row][col].visible = true; } } // Music will start 7.5 seconds after clicking play button } // Create plant selection buttons var plantTypes = ['sunflower', 'peashooter', 'wallnut', 'cherrybomb', 'papapum', 'piranaplant', 'shovel']; for (var i = 0; i < plantTypes.length; i++) { var button = new PlantButton(plantTypes[i], i); plantButtons.push(button); game.addChild(button); button.visible = false; // Hide during menu } // Create grid - scale to use full screen var gridStartX = 150; var gridStartY = 300; var gridRows = 5; var gridCols = 8; for (var row = 0; row < gridRows; row++) { gridCells[row] = []; for (var col = 0; col < gridCols; col++) { var cell = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); cell.x = gridStartX + col * 200; cell.y = gridStartY + row * 250; cell.row = row; cell.col = col; cell.occupied = false; cell.visible = false; // Hide during menu gridCells[row][col] = cell; game.addChild(cell); } } // Game event handlers game.down = function (x, y, obj) { // Handle settings menu interactions if (settingsContainer.visible) { // Check if clicking on fusion mode button if (Math.abs(x - fusionModeButton.x) < 150 && Math.abs(y - fusionModeButton.y) < 60) { // Toggle fusion mode fusionMode = !fusionMode; storage.fusionMode = fusionMode; updateFusionModeText(); // Fusion mode button animation tween(fusionModeButton, { scaleX: 2.2, scaleY: 1.4, tint: fusionMode ? 0x00FF00 : 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(fusionModeButton, { scaleX: 2.0, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); return; } // Check if clicking on settings back button if (Math.abs(x - settingsBackButton.x) < 135 && Math.abs(y - settingsBackButton.y) < 60) { // Settings back button animation tween(settingsBackButton, { scaleX: 1.7, scaleY: 1.4, tint: 0x00CCFF }, { duration: 150, onFinish: function onFinish() { tween(settingsBackButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Hide settings menu and show main menu settingsContainer.visible = false; menuContainer.visible = true; return; } return; } // Handle language menu interactions if (languageMenuContainer.visible) { // Check if clicking on English button if (Math.abs(x - englishButton.x) < 135 && Math.abs(y - englishButton.y) < 60) { selectedLanguage = 'english'; storage.selectedLanguage = 'english'; // Update menu texts to English updateMenuTexts(); // English button animation tween(englishButton, { scaleX: 1.7, scaleY: 1.4, tint: 0x00FF00 }, { duration: 150, onFinish: function onFinish() { tween(englishButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Hide language menu and show main menu languageMenuContainer.visible = false; menuContainer.visible = true; return; } // Check if clicking on Spanish button if (Math.abs(x - spanishButton.x) < 135 && Math.abs(y - spanishButton.y) < 60) { selectedLanguage = 'spanish'; storage.selectedLanguage = 'spanish'; // Update menu texts to Spanish updateMenuTexts(); // Spanish button animation tween(spanishButton, { scaleX: 1.7, scaleY: 1.4, tint: 0x00FF00 }, { duration: 150, onFinish: function onFinish() { tween(spanishButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Hide language menu and show main menu languageMenuContainer.visible = false; menuContainer.visible = true; return; } // Check if clicking on Back button if (Math.abs(x - backButton.x) < 90 && Math.abs(y - backButton.y) < 50) { // Back button animation tween(backButton, { scaleX: 1.4, scaleY: 1.2, tint: 0xFFCC00 }, { duration: 150, onFinish: function onFinish() { tween(backButton, { scaleX: 1.2, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 150 }); } }); // Hide language menu and show main menu languageMenuContainer.visible = false; menuContainer.visible = true; return; } return; } // Handle menu interactions if (gameState === 'menu') { // Check if clicking on Jugar button if (Math.abs(x - jugarButton.x) < 135 && Math.abs(y - jugarButton.y) < 60) { // Jugar button animation tween(jugarButton, { scaleX: 1.7, scaleY: 1.4, tint: 0x00FF00 }, { duration: 150, onFinish: function onFinish() { tween(jugarButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Stop music and play sound based on selected language LK.stopMusic(); if (selectedLanguage === 'english') { LK.getSound('english').play(); } else { LK.getSound('spanish').play(); } // Start music 7.5 seconds after clicking play LK.setTimeout(function () { if (gameState === 'playing') { LK.playMusic('Musixa'); } }, 7500); startGame(); return; } // Check if clicking on Idiomas button if (Math.abs(x - idiomasButton.x) < 135 && Math.abs(y - idiomasButton.y) < 60) { // Idiomas button animation tween(idiomasButton, { scaleX: 1.7, scaleY: 1.4, tint: 0x0088FF }, { duration: 150, onFinish: function onFinish() { tween(idiomasButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Show language menu and hide main menu menuContainer.visible = false; languageMenuContainer.visible = true; return; } // Check if clicking on Ajustes button if (Math.abs(x - ajustesButton.x) < 135 && Math.abs(y - ajustesButton.y) < 60) { // Ajustes button animation tween(ajustesButton, { scaleX: 1.7, scaleY: 1.4, tint: 0xFF8800 }, { duration: 150, onFinish: function onFinish() { tween(ajustesButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Show settings menu and hide main menu menuContainer.visible = false; settingsContainer.visible = true; return; } // Check if clicking on Tutorial button if (Math.abs(x - tutorialButton.x) < 135 && Math.abs(y - tutorialButton.y) < 60) { // Tutorial button animation tween(tutorialButton, { scaleX: 1.7, scaleY: 1.4, tint: 0x00FFFF }, { duration: 150, onFinish: function onFinish() { tween(tutorialButton, { scaleX: 1.5, scaleY: 1.2, tint: 0xFFFFFF }, { duration: 150 }); } }); // Start tutorial gameState = 'tutorial'; tutorialStep = 0; menuContainer.visible = false; tutorialContainer.visible = true; updateTutorialText(); animateCrazyDave(); return; } return; } // Handle tutorial interactions if (gameState === 'tutorial') { // Check if clicking on continue button if (Math.abs(x - tutorialContinueButton.x) < 117 && Math.abs(y - tutorialContinueButton.y) < 50) { // Continue button animation tween(tutorialContinueButton, { scaleX: 1.5, scaleY: 1.2, tint: 0x00FF00 }, { duration: 150, onFinish: function onFinish() { tween(tutorialContinueButton, { scaleX: 1.3, scaleY: 1.0, tint: 0xFFFFFF }, { duration: 150 }); } }); var maxSteps = tutorialMessages[selectedLanguage].length; if (tutorialStep < maxSteps - 1) { tutorialStep++; updateTutorialText(); } else { // End tutorial and start game gameState = 'playing'; tutorialContainer.visible = false; // Stop music and play sound based on selected language LK.stopMusic(); if (selectedLanguage === 'english') { LK.getSound('english').play(); } else { LK.getSound('spanish').play(); } // Start music 7.5 seconds after clicking play LK.setTimeout(function () { if (gameState === 'playing') { LK.playMusic('Musixa'); } }, 7500); startGame(); } return; } return; } if (gameState !== 'playing') return; if (!selectedPlantType) return; // Check if clicking on grid for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { var cell = gridCells[row][col]; if (Math.abs(x - cell.x) < 80 && Math.abs(y - cell.y) < 80) { // Check for fusion - peashooter + peashooter or sunflower + peashooter or papapum + wallnut if (fusionMode && cell.occupied && (selectedPlantType === 'peashooter' || selectedPlantType === 'sunflower' || selectedPlantType === 'wallnut' || selectedPlantType === 'papapum')) { // Find existing plant at this position var existingPlant = null; for (var p = 0; p < plants.length; p++) { if (Math.abs(plants[p].x - cell.x) < 40 && Math.abs(plants[p].y - cell.y) < 40) { existingPlant = plants[p]; break; } } // Peashooter + Peashooter = Double Peashooter if (existingPlant && existingPlant.type === 'peashooter' && selectedPlantType === 'peashooter') { var cost = 10; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing peashooter existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create double peashooter var fusedPlant = new Plant('doublepeashooter'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Fusion animation tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0x00FF00 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Double Peashooter + Peashooter = Burst Peashooter else if (existingPlant && (existingPlant.type === 'doublepeashooter' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'doublepeashooter')) { var cost = selectedPlantType === 'peashooter' ? 10 : 20; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create burst peashooter var fusedPlant = new Plant('burstpeashooter'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Special burst fusion animation with white tint tween(fusedPlant.graphics, { scaleX: 1.6, scaleY: 1.6, tint: 0xFFFFFF }, { duration: 400, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 250 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Sunflower + Sunflower = Double Sunflower else if (existingPlant && existingPlant.type === 'sunflower' && selectedPlantType === 'sunflower') { var cost = 15; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing sunflower existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create double sunflower var fusedPlant = new Plant('doublesunflower'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Golden fusion animation for double sun production with sprite transformation tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xFFFF00 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFD700 }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Sunflower + Double Sunflower = Triple Sunflower else if (existingPlant && (existingPlant.type === 'sunflower' && selectedPlantType === 'doublesunflower' || existingPlant.type === 'doublesunflower' && selectedPlantType === 'sunflower')) { var cost = selectedPlantType === 'sunflower' ? 15 : 35; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create triple sunflower var fusedPlant = new Plant('triplesunflower'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Special golden fusion animation for triple sun production tween(fusedPlant.graphics, { scaleX: 1.8, scaleY: 1.8, tint: 0xFFFF00 }, { duration: 400, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFD700 }, { duration: 300 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Sunflower + Peashooter = Point Shooter else if (existingPlant && (existingPlant.type === 'sunflower' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'sunflower')) { var cost = selectedPlantType === 'peashooter' ? 10 : 15; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create point shooter var fusedPlant = new Plant('pointshooter'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Golden fusion animation tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xFFD700 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Wallnut + Peashooter = Armored Shooter else if (existingPlant && (existingPlant.type === 'wallnut' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'wallnut')) { var cost = selectedPlantType === 'peashooter' ? 10 : 20; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create armored shooter var fusedPlant = new Plant('armoredshooter'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Defensive fusion animation with green tint tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0x00AA00 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Papapum + Wallnut = Explosive Wallnut else if (existingPlant && (existingPlant.type === 'papapum' && selectedPlantType === 'wallnut' || existingPlant.type === 'wallnut' && selectedPlantType === 'papapum')) { var cost = selectedPlantType === 'papapum' ? 25 : 20; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create explosive wallnut var fusedPlant = new Plant('explosivewallnut'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Explosive fusion animation with red tint tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xFF4444 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFFFFFF }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Papapum + Peashooter = Explosive Peashooter else if (existingPlant && (existingPlant.type === 'papapum' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'papapum')) { var cost = selectedPlantType === 'papapum' ? 25 : 10; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create explosive peashooter var fusedPlant = new Plant('explosivepeashooter'); fusedPlant.x = cell.x; fusedPlant.y = cell.y; plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Explosive fusion animation with red tint tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xFF4444 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFF4444 }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } // Piranha Plant + Cherry Bomb = Exploding Piranha Plant else if (existingPlant && (existingPlant.type === 'piranaplant' && selectedPlantType === 'cherrybomb' || existingPlant.type === 'cherrybomb' && selectedPlantType === 'piranaplant')) { var cost = selectedPlantType === 'cherrybomb' ? 50 : 30; if (LK.getScore() < cost) { return; // Not enough points } // Remove existing plant existingPlant.destroy(); plants.splice(plants.indexOf(existingPlant), 1); // Create exploding piranha plant (special piranha that explodes after eating) var fusedPlant = new Plant('piranaplant'); // Replace the graphics with exploding variant sprite fusedPlant.removeChild(fusedPlant.graphics); fusedPlant.graphics = fusedPlant.attachAsset('explodingpiranaplant', { anchorX: 0.5, anchorY: 0.5 }); fusedPlant.x = cell.x; fusedPlant.y = cell.y; fusedPlant.isExploding = true; // Mark as exploding variant fusedPlant.explosionRadius = 360; // 2 slots radius (180px per slot) fusedPlant.explosionDamage = 200; // Strong explosion damage plants.push(fusedPlant); game.addChild(fusedPlant); LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); LK.getSound('plantPlace').play(); // Exploding piranha fusion animation with red-purple tint tween(fusedPlant.graphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xFF0080 }, { duration: 300, onFinish: function onFinish() { tween(fusedPlant.graphics, { scaleX: 1, scaleY: 1, tint: 0xFF4444 }, { duration: 200 }); } }); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } } // Handle shovel selection - remove existing plant if (selectedPlantType === 'shovel' && cell.occupied) { // Find and remove the plant at this position for (var p = plants.length - 1; p >= 0; p--) { if (Math.abs(plants[p].x - cell.x) < 40 && Math.abs(plants[p].y - cell.y) < 40) { plants[p].destroy(); plants.splice(p, 1); // Make sure to update the grid cell's occupied status gridCells[row][col].occupied = false; break; } } // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } if (!cell.occupied && selectedPlantType !== 'shovel') { var cost = 0; if (selectedPlantType === 'peashooter') cost = 10; if (selectedPlantType === 'sunflower') cost = 15; if (selectedPlantType === 'wallnut') cost = 20; if (selectedPlantType === 'cherrybomb') cost = 50; if (selectedPlantType === 'papapum') cost = 25; if (selectedPlantType === 'piranaplant') cost = 30; // Check if plant is on cooldown if (plantCooldowns[selectedPlantType] && plantCooldowns[selectedPlantType] > 0) { return; // Plant is on cooldown } if (LK.getScore() < cost) { return; // Not enough points } var plant = new Plant(selectedPlantType); plant.x = cell.x; plant.y = cell.y; plants.push(plant); game.addChild(plant); // Update grid cell occupied status using row/col indices gridCells[row][col].occupied = true; if (cost > 0) { LK.setScore(LK.getScore() - cost); scoreText.setText('Girasoles: ' + LK.getScore()); } // Start cooldown for this plant type if (plantCooldownDurations[selectedPlantType]) { plantCooldowns[selectedPlantType] = plantCooldownDurations[selectedPlantType]; } LK.getSound('plantPlace').play(); // Reset selection selectedPlantType = null; for (var i = 0; i < plantButtons.length; i++) { plantButtons[i].button.tint = 0xFFFFFF; } return; } } } } }; function spawnZombie() { if (zombiesSpawned >= zombiesInWave) return; var lane = Math.floor(Math.random() * gridRows); var zombieTypes = ['basic', 'fast', 'strong']; var type = zombieTypes[Math.floor(Math.random() * zombieTypes.length)]; // Tank zombies appear after 50 zombies killed if (zombiesKilled >= 50) { zombieTypes = ['basic', 'fast', 'strong', 'tank']; } // Increase difficulty with wave number if (waveNumber > 3) { type = zombieTypes[Math.floor(Math.random() * zombieTypes.length)]; } else if (waveNumber > 1) { type = Math.random() < 0.7 ? 'basic' : Math.random() < 0.5 ? 'fast' : 'strong'; } else { type = Math.random() < 0.6 ? 'basic' : 'strong'; } var zombie = new Zombie(type, lane); zombies.push(zombie); game.addChild(zombie); zombiesSpawned++; } function checkWaveComplete() { if (zombiesSpawned >= zombiesInWave && zombies.length === 0) { // Wave complete waveNumber++; zombiesInWave += 2; zombiesSpawned = 0; waveText.setText('Wave: ' + waveNumber); if (waveNumber > 10) { gameState = 'victory'; LK.showYouWin(); } } } function checkGameOver() { if (zombiesReachedEnd >= 1) { gameState = 'gameOver'; LK.showGameOver(); } livesText.setText('Lives: ' + (maxZombiesAllowed - zombiesReachedEnd)); } // Main game loop game.update = function () { if (gameState !== 'playing') return; // Auto point generation - 7 points every 7 seconds autoPointTimer++; if (autoPointTimer >= autoPointInterval) { LK.setScore(LK.getScore() + 7); scoreText.setText('Girasoles: ' + LK.getScore()); autoPointTimer = 0; } // Update plant cooldowns for (var plantType in plantCooldowns) { if (plantCooldowns[plantType] > 0) { plantCooldowns[plantType]--; } } // Update plant button visual states based on cooldown for (var i = 0; i < plantButtons.length; i++) { var button = plantButtons[i]; var plantType = button.plantType; if (plantCooldowns[plantType] && plantCooldowns[plantType] > 0) { // Plant is on cooldown - darken button button.button.tint = 0x666666; } else if (selectedPlantType !== plantType) { // Plant is ready and not selected - normal color button.button.tint = 0xFFFFFF; } // Keep selected plant highlighted in green if (selectedPlantType === plantType) { button.button.tint = 0x00FF00; } } // Spawn zombies zombieSpawnTimer++; if (zombieSpawnTimer >= zombieSpawnInterval) { spawnZombie(); zombieSpawnTimer = 0; } // Update all objects for (var i = plants.length - 1; i >= 0; i--) { if (plants[i].update) { plants[i].update(); } } for (var i = zombies.length - 1; i >= 0; i--) { if (zombies[i].update) { zombies[i].update(); } } for (var i = projectiles.length - 1; i >= 0; i--) { if (projectiles[i].update) { projectiles[i].update(); } } checkWaveComplete(); checkGameOver(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var ExplosiveProjectile = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF4444,
scaleX: 1.3,
scaleY: 1.3
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = 25;
self.speed = 8;
self.explosionRadius = 150;
// Shoot straight horizontally to the right
self.dirX = 1;
self.dirY = 0;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Pulsing red animation while flying
if (LK.ticks % 8 === 0) {
tween(self.graphics, {
tint: 0xFF0000
}, {
duration: 80
});
tween(self.graphics, {
tint: 0xFF4444
}, {
duration: 80
});
}
// Check collision with any zombie
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (Math.abs(zombie.x - self.x) < 30 && Math.abs(zombie.y - self.y) < 30) {
// Create explosion visual effect
tween(self.graphics, {
tint: 0xFFFFFF,
scaleX: 4,
scaleY: 4,
alpha: 0.7
}, {
duration: 300
});
// Immediate explosion - damage all zombies in radius
for (var j = zombies.length - 1; j >= 0; j--) {
var targetZombie = zombies[j];
var dx = targetZombie.x - self.x;
var dy = targetZombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
targetZombie.takeDamage(self.damage);
}
}
LK.getSound('explosion').play();
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
return;
}
}
// Remove if off-screen
if (self.x > 2100 || self.x < -50 || self.y > 2800 || self.y < -50) {
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
}
};
self.explode = function () {
// Explosion animation
tween(self.graphics, {
tint: 0xFF0000,
scaleX: 4,
scaleY: 4
}, {
duration: 200,
onFinish: function onFinish() {
// Damage all zombies in explosion radius
for (var i = zombies.length - 1; i >= 0; 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 <= self.explosionRadius) {
zombie.takeDamage(self.damage);
}
}
LK.getSound('explosion').play();
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
}
});
};
return self;
});
var Plant = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootCooldown = 60; // 1 second at 60fps
if (type === 'peashooter') {
self.graphics = self.attachAsset('peashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 25;
self.range = 400;
self.shootCooldown = 228; // 3.8 seconds at 60fps (3.8 * 60 = 228)
} else if (type === 'doublepeashooter') {
self.graphics = self.attachAsset('doublepeashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 50;
self.range = 400;
self.shootCooldown = 210; // Total cycle time: 3.5 seconds (3.5 * 60 = 210)
self.firstShotTime = 60; // First shot at 1 second (1 * 60 = 60)
self.secondShotTime = 114; // Second shot at 1.9 seconds (1.9 * 60 = 114)
self.hasFiredFirst = false;
self.hasFiredSecond = false;
} else if (type === 'wallnut') {
self.graphics = self.attachAsset('wallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1500;
self.maxHealth = 1500;
self.damage = 0;
} else if (type === 'cherrybomb') {
self.graphics = self.attachAsset('cherrybomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 200;
self.explosionRadius = 540; // 3 slots * 180px per slot
self.fuseTimer = 180; // 3 seconds
} else if (type === 'sunflower') {
self.graphics = self.attachAsset('sunflower', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 0;
self.sunTimer = 0;
self.sunInterval = 1200; // 20 seconds at 60fps
} else if (type === 'pointshooter') {
self.graphics = self.attachAsset('pointShooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 65;
self.maxHealth = 65;
self.damage = 0; // Doesn't damage, gives points instead
self.range = 400;
self.shootCooldown = 270; // Shoots every 4.5 seconds (270 frames at 60fps)
} else if (type === 'doublesunflower') {
self.graphics = self.attachAsset('doublesunflower', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 0;
self.sunTimer = 0;
self.sunInterval = 330; // 5.5 seconds at 60fps (5.5 * 60 = 330)
} else if (type === 'armoredshooter') {
self.graphics = self.attachAsset('armoredshooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 350; // High health like wallnut but less
self.maxHealth = 350;
self.damage = 35; // More damage than regular peashooter
self.range = 400;
self.shootCooldown = 240; // 4 seconds at 60fps (4 * 60 = 240)
} else if (type === 'papapum') {
self.graphics = self.attachAsset('papapum', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 80; // Lower health since it's a mine
self.maxHealth = 80;
self.damage = 250; // High explosive damage
self.explosionRadius = 300; // Explosion range
self.isTriggered = false; // Track if mine has been triggered
self.cooldownTimer = 0; // Add cooldown timer starting at 0
self.cooldownDuration = 540; // 9 seconds at 60fps (9 * 60 = 540)
} else if (type === 'explosivewallnut') {
self.graphics = self.attachAsset('explosivewallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1200; // High health like wallnut but slightly less
self.maxHealth = 1200;
self.damage = 300; // Explosive damage when destroyed
self.explosionRadius = 400; // Large explosion range
} else if (type === 'explosivepeashooter') {
self.graphics = self.attachAsset('explosivepeashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 120; // Slightly more than regular peashooter
self.maxHealth = 120;
self.damage = 40; // Higher damage than regular peashooter
self.range = 400;
self.shootCooldown = 180; // 3 seconds at 60fps (3 * 60 = 180)
} else if (type === 'triplesunflower') {
self.graphics = self.attachAsset('triplesunflower', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFD700
});
self.damage = 0;
self.sunTimer = 0;
self.sunInterval = 180; // 3 seconds at 60fps (3 * 60 = 180)
} else if (type === 'burstpeashooter') {
self.graphics = self.attachAsset('burstpeashooter', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF
});
self.health = 150;
self.maxHealth = 150;
self.damage = 8; // Each projectile does 8 damage
self.range = 400;
self.shootCooldown = 234; // 3.9 seconds at 60fps (3.9 * 60 = 234)
self.burstCount = 15; // Number of projectiles in burst
self.burstDelay = 6; // Frames between each projectile in burst (0.1 seconds)
self.burstProgress = 0; // Track current burst progress
self.isBursting = false; // Track if currently in burst mode
} else if (type === 'piranaplant') {
self.graphics = self.attachAsset('piranaplant', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 200;
self.maxHealth = 200;
self.damage = 0; // Doesn't damage, eats zombies
self.eatRange = 100; // Range to eat zombies
self.eatCooldown = 60; // 1 second cooldown between eating
self.eatTimer = 0;
self.isEating = false;
self.digestionTimer = 0;
self.digestionTime = 900; // 15 seconds to digest (much longer chewing)
}
self.update = function () {
if (self.type === 'peashooter' || self.type === 'doublepeashooter' || self.type === 'pointshooter' || self.type === 'armoredshooter' || self.type === 'explosivepeashooter' || self.type === 'burstpeashooter') {
self.shootTimer++;
if (self.type === 'doublepeashooter') {
var target = self.findZombieInLane();
if (target) {
// First shot at 1 second
if (self.shootTimer >= self.firstShotTime && !self.hasFiredFirst) {
self.shoot(target);
self.hasFiredFirst = true;
}
// Second shot at 1.9 seconds
if (self.shootTimer >= self.secondShotTime && !self.hasFiredSecond) {
self.shoot(target);
self.hasFiredSecond = true;
}
}
// Reset cycle after total cooldown (3.5 seconds)
if (self.shootTimer >= self.shootCooldown) {
self.shootTimer = 0;
self.hasFiredFirst = false;
self.hasFiredSecond = false;
}
} else if (self.type === 'burstpeashooter') {
var target = self.findZombieInLane();
if (target && !self.isBursting) {
// Start burst sequence
if (self.shootTimer >= self.shootCooldown) {
self.isBursting = true;
self.burstProgress = 0;
self.shootTimer = 0;
}
}
// Handle burst firing
if (self.isBursting) {
if (self.shootTimer % self.burstDelay === 0 && self.burstProgress < self.burstCount) {
var currentTarget = self.findZombieInLane();
if (currentTarget) {
// Create projectile directly without animation
var projectile = new Projectile(self.x, self.y, currentTarget, self.damage);
projectiles.push(projectile);
game.addChild(projectile);
LK.getSound('shoot').play();
}
self.burstProgress++;
}
// End burst sequence
if (self.burstProgress >= self.burstCount) {
self.isBursting = false;
self.burstProgress = 0;
}
}
} else if (self.shootTimer >= self.shootCooldown) {
var target = self.findZombieInLane();
if (target) {
if (self.type === 'pointshooter') {
self.shootPoints(target);
} else if (self.type === 'explosivepeashooter') {
self.shootExplosive(target);
} else {
self.shoot(target);
}
}
self.shootTimer = 0;
}
} else if (self.type === 'cherrybomb') {
self.fuseTimer--;
// Check if any zombie is within very close range (80px) before exploding
var zombieNearby = false;
for (var z = 0; z < zombies.length; z++) {
var zombie = zombies[z];
if (Math.abs(zombie.x - self.x) < 80 && Math.abs(zombie.y - self.y) < 80) {
zombieNearby = true;
break;
}
}
// Only explode if fuse timer is done AND zombie is very close
if (self.fuseTimer <= 0 && zombieNearby) {
self.explode();
}
} else if (self.type === 'sunflower') {
self.sunTimer++;
if (self.sunTimer >= self.sunInterval) {
LK.setScore(LK.getScore() + 5);
scoreText.setText('Girasoles: ' + LK.getScore());
self.sunTimer = 0;
}
} else if (self.type === 'doublesunflower') {
self.sunTimer++;
if (self.sunTimer >= self.sunInterval) {
LK.setScore(LK.getScore() + 15);
scoreText.setText('Girasoles: ' + LK.getScore());
self.sunTimer = 0;
}
} else if (self.type === 'triplesunflower') {
self.sunTimer++;
if (self.sunTimer >= self.sunInterval) {
LK.setScore(LK.getScore() + 35);
scoreText.setText('Girasoles: ' + LK.getScore());
self.sunTimer = 0;
// Special golden glow animation every time it generates sun
tween(self.graphics, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFFF00
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFD700
}, {
duration: 200
});
}
});
}
} else if (self.type === 'papapum' && !self.isTriggered) {
// Increment cooldown timer
self.cooldownTimer++;
// Ready animation when cooldown just completes
if (self.cooldownTimer === self.cooldownDuration) {
// Papapum ready animation - pulsing green glow
tween(self.graphics, {
tint: 0x00FF00,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.graphics, {
tint: 0xFFFFFF,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
});
}
// Only become active after cooldown period
if (self.cooldownTimer >= self.cooldownDuration) {
// Check if any zombie is stepping on the papapum
for (var z = 0; z < zombies.length; z++) {
var zombie = zombies[z];
if (Math.abs(zombie.x - self.x) < 90 && Math.abs(zombie.y - self.y) < 90) {
self.isTriggered = true;
self.explode();
break;
}
}
}
} else if (self.type === 'piranaplant') {
self.eatTimer++;
if (self.isEating) {
self.digestionTimer++;
if (self.digestionTimer >= self.digestionTime) {
self.isEating = false;
self.digestionTimer = 0;
// Check if this is an exploding piranha plant
if (self.isExploding) {
// Explode after finishing eating
self.explodeAfterEating();
} else {
// Return to normal appearance
tween(self.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 300
});
}
}
} else if (self.eatTimer >= self.eatCooldown && !self.isEating) {
// Look for zombies in eating range - only if not already eating
for (var z = 0; z < zombies.length; z++) {
var zombie = zombies[z];
if (Math.abs(zombie.x - self.x) < self.eatRange && Math.abs(zombie.y - self.y) < self.eatRange) {
// Eat the zombie
self.eatZombie(zombie);
break;
}
}
self.eatTimer = 0;
}
}
};
self.findZombieInLane = function () {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
// Check if zombie is in same lane (within 100px vertically) and to the right
if (Math.abs(zombie.y - self.y) < 100 && zombie.x > self.x) {
return zombie;
}
}
return null;
};
self.getDistanceTo = function (target) {
var dx = target.x - self.x;
var dy = target.y - self.y;
return Math.sqrt(dx * dx + dy * dy);
};
self.shoot = function (target) {
// Shooting animation - scale and recoil effect
tween(self.graphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100
});
tween(self.graphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
tween(self.graphics, {
x: -10
}, {
duration: 100
});
tween(self.graphics, {
x: 0
}, {
duration: 200
});
var projectile = new Projectile(self.x, self.y, target, self.damage);
projectiles.push(projectile);
game.addChild(projectile);
LK.getSound('shoot').play();
};
self.shootPoints = function (target) {
// Golden shooting animation
tween(self.graphics, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFFD700
}, {
duration: 150
});
tween(self.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 250
});
var pointProjectile = new PointProjectile(self.x, self.y, target);
projectiles.push(pointProjectile);
game.addChild(pointProjectile);
LK.getSound('shoot').play();
};
self.shootExplosive = function (target) {
// Red explosive shooting animation
tween(self.graphics, {
scaleX: 1.4,
scaleY: 1.4,
tint: 0xFF0000
}, {
duration: 120
});
tween(self.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFF4444
}, {
duration: 200
});
tween(self.graphics, {
x: -15
}, {
duration: 120
});
tween(self.graphics, {
x: 0
}, {
duration: 200
});
var explosiveProjectile = new ExplosiveProjectile(self.x, self.y, target, self.damage);
projectiles.push(explosiveProjectile);
game.addChild(explosiveProjectile);
LK.getSound('shoot').play();
};
self.eatZombie = function (zombie) {
// Start eating animation
self.isEating = true;
self.digestionTimer = 0;
// Eating animation - grow larger and redder
tween(self.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
// Chomp sound and remove zombie
LK.getSound('zombieHit').play();
// Award points based on zombie type
if (zombie.type === 'tank') {
LK.setScore(LK.getScore() + 450);
zombiesKilled++;
} else if (zombie.type === 'strong') {
zombiesKilled++;
} else {
zombiesKilled++;
}
scoreText.setText('Girasoles: ' + LK.getScore());
// Remove zombie from game
zombie.destroy();
zombies.splice(zombies.indexOf(zombie), 1);
}
});
};
self.explodeAfterEating = function () {
// Create explosion sprite
var explosionSprite = self.attachAsset('explosionSprite', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
// Play explosion sound immediately
LK.getSound('explosion').play();
// Damage all zombies in explosion radius immediately
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
if (self.getDistanceTo(zombie) <= self.explosionRadius) {
zombie.takeDamage(self.explosionDamage);
}
}
// Special explosion for piranha plant after eating
tween(self.graphics, {
tint: 0xFF0000,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 400,
onFinish: function onFinish() {
// Animate explosion sprite
tween(explosionSprite, {
scaleX: 3,
scaleY: 3,
alpha: 1,
tint: 0xFFFF00
}, {
duration: 300,
onFinish: function onFinish() {
// Second explosion wave
tween(explosionSprite, {
scaleX: 5,
scaleY: 5,
alpha: 0,
tint: 0xFF0000
}, {
duration: 400,
onFinish: function onFinish() {
// Free up the grid cell
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var cell = gridCells[row][col];
if (Math.abs(cell.x - self.x) < 40 && Math.abs(cell.y - self.y) < 40) {
cell.occupied = false;
}
}
}
self.destroy();
plants.splice(plants.indexOf(self), 1);
}
});
}
});
// Create large explosion effect on main graphics
tween(self.graphics, {
tint: 0xFFFFFF,
scaleX: 4,
scaleY: 4,
alpha: 0.3
}, {
duration: 200
});
}
});
};
self.explode = function () {
// Explosion animation - flash and scale up
tween(self.graphics, {
tint: 0xFF4444,
scaleX: 3,
scaleY: 3
}, {
duration: 300,
onFinish: function onFinish() {
// Damage all zombies in explosion radius
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
if (self.getDistanceTo(zombie) <= self.explosionRadius) {
zombie.takeDamage(self.damage);
}
}
// Free up the grid cell
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var cell = gridCells[row][col];
if (Math.abs(cell.x - self.x) < 40 && Math.abs(cell.y - self.y) < 40) {
cell.occupied = false;
}
}
}
LK.getSound('explosion').play();
self.destroy();
plants.splice(plants.indexOf(self), 1);
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
// Change wallnut sprite when health drops to 670 or below
if (self.type === 'wallnut' && self.health <= 670 && self.health > 0) {
// Remove current graphics
self.removeChild(self.graphics);
// Add damaged wallnut graphics
self.graphics = self.attachAsset('wallnutDamaged', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Change armoredshooter sprite when health drops to 175 or below (half of 350)
if (self.type === 'armoredshooter' && self.health <= 175 && self.health > 0) {
// Remove current graphics
self.removeChild(self.graphics);
// Add damaged wallnut graphics (reuse damaged wallnut sprite)
self.graphics = self.attachAsset('wallnutDamaged', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Change explosivewallnut sprite when health drops to 600 or below (half of 1200)
if (self.type === 'explosivewallnut' && self.health <= 600 && self.health > 0) {
// Remove current graphics
self.removeChild(self.graphics);
// Add damaged wallnut graphics
self.graphics = self.attachAsset('wallnutDamaged', {
anchorX: 0.5,
anchorY: 0.5
});
}
if (self.health <= 0) {
// If explosivewallnut is destroyed, explode
if (self.type === 'explosivewallnut') {
self.explode();
} else {
// Free up the grid cell
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var cell = gridCells[row][col];
if (Math.abs(cell.x - self.x) < 40 && Math.abs(cell.y - self.y) < 40) {
cell.occupied = false;
}
}
}
self.destroy();
plants.splice(plants.indexOf(self), 1);
}
}
};
return self;
});
var PlantButton = Container.expand(function (plantType, index) {
var self = Container.call(this);
self.plantType = plantType;
self.button = self.attachAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var icon;
if (plantType === 'peashooter') {
icon = self.attachAsset('peashooter', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
} else if (plantType === 'wallnut') {
icon = self.attachAsset('wallnut', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
} else if (plantType === 'cherrybomb') {
icon = self.attachAsset('cherrybomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
} else if (plantType === 'sunflower') {
icon = self.attachAsset('sunflower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
} else if (plantType === 'papapum') {
icon = self.attachAsset('papapum', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
} else if (plantType === 'piranaplant') {
icon = self.attachAsset('piranaplant', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
} else if (plantType === 'shovel') {
icon = self.attachAsset('shovel', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
}
self.x = 1024 - plantTypes.length * 200 / 2 + index * 200;
self.y = 2600;
self.down = function (x, y, obj) {
// Check if plant is on cooldown
if (plantCooldowns[self.plantType] && plantCooldowns[self.plantType] > 0) {
return; // Don't allow selection if on cooldown
}
selectedPlantType = self.plantType;
// Visual feedback
for (var i = 0; i < plantButtons.length; i++) {
var button = plantButtons[i];
var plantType = button.plantType;
if (plantCooldowns[plantType] && plantCooldowns[plantType] > 0) {
// Keep cooldown plants darkened
button.button.tint = 0x666666;
} else {
// Reset other buttons to normal
button.button.tint = 0xFFFFFF;
}
}
self.button.tint = 0x00FF00;
};
return self;
});
var PointProjectile = Container.expand(function (startX, startY, target) {
var self = Container.call(this);
self.graphics = self.attachAsset('pointPea', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.speed = 6;
// Shoot straight horizontally to the right
self.dirX = 1;
self.dirY = 0;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Golden sparkle animation
if (LK.ticks % 10 === 0) {
tween(self.graphics, {
tint: 0xFFFF00
}, {
duration: 100
});
tween(self.graphics, {
tint: 0xFFD700
}, {
duration: 100
});
}
// Check collision with any zombie
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (Math.abs(zombie.x - self.x) < 30 && Math.abs(zombie.y - self.y) < 30) {
// Give 1 point instead of damage
LK.setScore(LK.getScore() + 1);
scoreText.setText('Girasoles: ' + LK.getScore());
// Visual effect on hit
tween(zombie.graphics, {
tint: 0xFFD700
}, {
duration: 200
});
tween(zombie.graphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
return;
}
}
// Remove if off-screen
if (self.x > 2100 || self.x < -50 || self.y > 2800 || self.y < -50) {
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
}
};
return self;
});
var Projectile = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
// Shoot straight horizontally to the right
self.dirX = 1;
self.dirY = 0;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Check collision with target or any zombie
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (Math.abs(zombie.x - self.x) < 30 && Math.abs(zombie.y - self.y) < 30) {
zombie.takeDamage(self.damage);
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
return;
}
}
// Remove if off-screen
if (self.x > 2100 || self.x < -50 || self.y > 2800 || self.y < -50) {
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
}
};
return self;
});
var TankProjectile = Container.expand(function (startX, startY, target) {
var self = Container.call(this);
self.graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
tint: 0xFF4444
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = 100;
self.speed = 4;
self.explosionRadius = 200;
// Shoot straight horizontally to the left
self.dirX = -1;
self.dirY = 0;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Check collision with any plant
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (Math.abs(plant.x - self.x) < 40 && Math.abs(plant.y - self.y) < 40) {
self.explode();
return;
}
}
// Remove if off-screen
if (self.x < -50 || self.x > 2100 || self.y > 2800 || self.y < -50) {
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
}
};
self.explode = function () {
// Explosion animation
tween(self.graphics, {
tint: 0xFF0000,
scaleX: 4,
scaleY: 4
}, {
duration: 300,
onFinish: function onFinish() {
// Damage all plants in explosion radius
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
var dx = plant.x - self.x;
var dy = plant.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
plant.takeDamage(self.damage);
}
}
LK.getSound('explosion').play();
self.destroy();
projectiles.splice(projectiles.indexOf(self), 1);
}
});
};
return self;
});
var Zombie = Container.expand(function (type, lane) {
var self = Container.call(this);
self.type = type;
self.lane = lane;
self.id = Math.random().toString(36).substr(2, 9); // Generate unique ID
self.speed = 1;
self.health = 100;
self.maxHealth = 100;
self.damage = 25;
self.attackCooldown = 60;
self.attackTimer = 0;
self.isAttacking = false;
if (type === 'basic') {
self.graphics = self.attachAsset('basicZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.3;
self.health = 215;
self.maxHealth = 215;
} else if (type === 'fast') {
self.graphics = self.attachAsset('fastZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.3;
self.health = 215;
self.maxHealth = 215;
} else if (type === 'strong') {
self.graphics = self.attachAsset('strongZombie', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.6,
scaleY: 1.6
});
self.speed = 0.4;
self.health = 500;
self.maxHealth = 500;
self.damage = 45;
} else if (type === 'tank') {
self.graphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.2;
self.health = 777;
self.maxHealth = 777;
self.damage = 50;
self.shootTimer = 0;
self.shootCooldown = 300; // 5 seconds at 60fps
}
self.x = 2048 + 50; // Start off-screen right
self.y = 300 + lane * 250; // Position in lane
// Play zombie sound when spawned
LK.getSound('zombie').play();
self.update = function () {
if (!self.isAttacking) {
self.x -= self.speed;
// Walking animation - bob up and down
if (self.type === 'basic') {
// Slower walking animation for basic zombie
if (LK.ticks % 40 < 20) {
tween(self.graphics, {
y: 5
}, {
duration: 200
});
} else {
tween(self.graphics, {
y: -5
}, {
duration: 200
});
}
} else if (self.type === 'fast') {
// Faster walking animation for fast zombie
if (LK.ticks % 20 < 10) {
tween(self.graphics, {
y: 8
}, {
duration: 100
});
} else {
tween(self.graphics, {
y: -8
}, {
duration: 100
});
}
} else if (self.type === 'strong') {
// Medium walking animation for strong zombie
if (LK.ticks % 30 < 15) {
tween(self.graphics, {
y: 6
}, {
duration: 150
});
} else {
tween(self.graphics, {
y: -6
}, {
duration: 150
});
}
} else if (self.type === 'tank') {
// Slow heavy walking animation for tank zombie
if (LK.ticks % 60 < 30) {
tween(self.graphics, {
y: 3
}, {
duration: 300
});
} else {
tween(self.graphics, {
y: -3
}, {
duration: 300
});
}
}
// Tank zombie shooting logic
if (self.type === 'tank') {
self.shootTimer++;
if (self.shootTimer >= self.shootCooldown) {
var target = self.findPlantInLane();
if (target) {
var tankProjectile = new TankProjectile(self.x, self.y, target);
projectiles.push(tankProjectile);
game.addChild(tankProjectile);
LK.getSound('shoot').play();
}
self.shootTimer = 0;
}
}
// Check for plant collision
var plantInWay = self.findPlantInWay();
if (plantInWay) {
self.isAttacking = true;
self.attackTimer = 0;
}
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
var plantInWay = self.findPlantInWay();
if (plantInWay) {
plantInWay.takeDamage(self.damage);
self.attackTimer = 0;
} else {
self.isAttacking = false;
}
}
}
// Check if reached end
if (self.x < -50) {
zombiesReachedEnd++;
self.destroy();
zombies.splice(zombies.indexOf(self), 1);
}
};
self.findPlantInWay = function () {
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (Math.abs(plant.x - self.x) < 80 && Math.abs(plant.y - self.y) < 80) {
return plant;
}
}
return null;
};
self.findPlantInLane = function () {
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
// Check if plant is in same lane (within 100px vertically) and to the left
if (Math.abs(plant.y - self.y) < 100 && plant.x < self.x) {
return plant;
}
}
return null;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.getSound('zombieHit').play();
// Visual feedback
tween(self.graphics, {
tint: 0xFF0000
}, {
duration: 200
});
tween(self.graphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
if (self.health <= 0) {
// Give points based on zombie type
if (self.type === 'tank') {
LK.setScore(LK.getScore() + 450);
zombiesKilled++;
} else if (self.type === 'strong') {
// Strong zombie gives no points but still counts as killed
zombiesKilled++;
} else {
zombiesKilled++;
}
scoreText.setText('Girasoles: ' + LK.getScore());
// Clean up shots tracking for all plants
for (var p = 0; p < plants.length; p++) {
if (plants[p].shotsFired && plants[p].shotsFired[self.id]) {
delete plants[p].shotsFired[self.id];
}
}
self.destroy();
zombies.splice(zombies.indexOf(self), 1);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E7D32
});
/****
* Game Code
****/
// Sounds
// Grid and UI
// Projectiles
// Zombies
// Plants
// Game variables
var plants = [];
var zombies = [];
var projectiles = [];
var plantButtons = [];
var gridCells = [];
var selectedPlantType = null;
var waveNumber = 1;
var zombiesInWave = 5;
var zombieSpawnTimer = 0;
var zombieSpawnInterval = 120; // 2 seconds
var zombiesSpawned = 0;
var zombiesReachedEnd = 0;
var maxZombiesAllowed = 5;
var gameState = 'menu'; // 'menu', 'playing', 'gameOver', 'victory', 'tutorial'
var autoPointTimer = 0;
var autoPointInterval = 420; // 7 seconds at 60fps (7 * 60 = 420)
var zombiesKilled = 0;
var selectedLanguage = storage.selectedLanguage || 'spanish'; // Default to Spanish
var fusionMode = storage.fusionMode !== undefined ? storage.fusionMode : true; // Default to fusion enabled
// Plant cooldown system
var plantCooldowns = {
sunflower: 0,
peashooter: 0,
wallnut: 0,
cherrybomb: 0,
piranaplant: 0
};
var plantCooldownDurations = {
sunflower: 90,
// 1.5 seconds at 60fps
peashooter: 60,
// 1 second at 60fps
wallnut: 150,
// 2.5 seconds at 60fps
cherrybomb: 210,
// 3.5 seconds at 60fps
piranaplant: 240 // 4 seconds at 60fps
};
// Menu elements
var menuContainer = new Container();
game.addChild(menuContainer);
// Menu title
var titleText = new Text2('Plants vs Zombies', {
size: 80,
fill: '#00FF00'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 600;
menuContainer.addChild(titleText);
// Menu buttons
var idiomasButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
idiomasButton.x = 1024;
idiomasButton.y = 1000;
menuContainer.addChild(idiomasButton);
var idiomasText = new Text2('IDIOMAS', {
size: 50,
fill: '#FFFFFF'
});
idiomasText.anchor.set(0.5, 0.5);
idiomasText.x = 1024;
idiomasText.y = 1000;
menuContainer.addChild(idiomasText);
var jugarButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
jugarButton.x = 1024;
jugarButton.y = 1300;
menuContainer.addChild(jugarButton);
var jugarText = new Text2('JUGAR', {
size: 50,
fill: '#FFFFFF'
});
jugarText.anchor.set(0.5, 0.5);
jugarText.x = 1024;
jugarText.y = 1300;
menuContainer.addChild(jugarText);
var ajustesButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
ajustesButton.x = 1024;
ajustesButton.y = 1600;
menuContainer.addChild(ajustesButton);
var ajustesText = new Text2('AJUSTES', {
size: 50,
fill: '#FFFFFF'
});
ajustesText.anchor.set(0.5, 0.5);
ajustesText.x = 1024;
ajustesText.y = 1600;
menuContainer.addChild(ajustesText);
var tutorialButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
tutorialButton.x = 1024;
tutorialButton.y = 1900;
menuContainer.addChild(tutorialButton);
var tutorialText = new Text2('TUTORIAL', {
size: 50,
fill: '#FFFFFF'
});
tutorialText.anchor.set(0.5, 0.5);
tutorialText.x = 1024;
tutorialText.y = 1900;
menuContainer.addChild(tutorialText);
// Language selection menu (initially hidden)
var languageMenuContainer = new Container();
game.addChild(languageMenuContainer);
languageMenuContainer.visible = false;
// Language menu title
var languageTitleText = new Text2('Select Language', {
size: 60,
fill: '#00FF00'
});
languageTitleText.anchor.set(0.5, 0.5);
languageTitleText.x = 1024;
languageTitleText.y = 700;
languageMenuContainer.addChild(languageTitleText);
// English button
var englishButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
englishButton.x = 1024;
englishButton.y = 1000;
languageMenuContainer.addChild(englishButton);
var englishText = new Text2('ENGLISH', {
size: 50,
fill: '#FFFFFF'
});
englishText.anchor.set(0.5, 0.5);
englishText.x = 1024;
englishText.y = 1000;
languageMenuContainer.addChild(englishText);
// Spanish button
var spanishButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
spanishButton.x = 1024;
spanishButton.y = 1300;
languageMenuContainer.addChild(spanishButton);
var spanishText = new Text2('ESPAÑOL', {
size: 50,
fill: '#FFFFFF'
});
spanishText.anchor.set(0.5, 0.5);
spanishText.x = 1024;
spanishText.y = 1300;
languageMenuContainer.addChild(spanishText);
// Back button
var backButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.0
});
backButton.x = 1024;
backButton.y = 1600;
languageMenuContainer.addChild(backButton);
var backText = new Text2('BACK', {
size: 40,
fill: '#FFFFFF'
});
backText.anchor.set(0.5, 0.5);
backText.x = 1024;
backText.y = 1600;
languageMenuContainer.addChild(backText);
// Function to update menu texts based on selected language
function updateMenuTexts() {
if (selectedLanguage === 'english') {
idiomasText.setText('LANGUAGES');
jugarText.setText('PLAY');
ajustesText.setText('SETTINGS');
backText.setText('BACK');
tutorialText.setText('TUTORIAL');
} else {
idiomasText.setText('IDIOMAS');
jugarText.setText('JUGAR');
ajustesText.setText('AJUSTES');
backText.setText('VOLVER');
tutorialText.setText('TUTORIAL');
}
}
// Settings menu container (initially hidden)
var settingsContainer = new Container();
game.addChild(settingsContainer);
settingsContainer.visible = false;
// Settings menu title
var settingsTitleText = new Text2('SETTINGS', {
size: 60,
fill: '#00FF00'
});
settingsTitleText.anchor.set(0.5, 0.5);
settingsTitleText.x = 1024;
settingsTitleText.y = 700;
settingsContainer.addChild(settingsTitleText);
// Fusion mode toggle button
var fusionModeButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.2
});
fusionModeButton.x = 1024;
fusionModeButton.y = 1200;
settingsContainer.addChild(fusionModeButton);
var fusionModeText = new Text2('FUSION: ON', {
size: 45,
fill: '#FFFFFF'
});
fusionModeText.anchor.set(0.5, 0.5);
fusionModeText.x = 1024;
fusionModeText.y = 1200;
settingsContainer.addChild(fusionModeText);
// Settings back button
var settingsBackButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.2
});
settingsBackButton.x = 1024;
settingsBackButton.y = 1600;
settingsContainer.addChild(settingsBackButton);
var settingsBackText = new Text2('BACK', {
size: 50,
fill: '#FFFFFF'
});
settingsBackText.anchor.set(0.5, 0.5);
settingsBackText.x = 1024;
settingsBackText.y = 1600;
settingsContainer.addChild(settingsBackText);
// Function to update fusion mode text
function updateFusionModeText() {
if (selectedLanguage === 'english') {
fusionModeText.setText('FUSION: ' + (fusionMode ? 'ON' : 'OFF'));
settingsTitleText.setText('SETTINGS');
settingsBackText.setText('BACK');
} else {
fusionModeText.setText('FUSIÓN: ' + (fusionMode ? 'SÍ' : 'NO'));
settingsTitleText.setText('AJUSTES');
settingsBackText.setText('VOLVER');
}
}
// Initialize fusion mode text
updateFusionModeText();
// Update menu texts after all elements are created
updateMenuTexts();
// Tutorial container (initially hidden)
var tutorialContainer = new Container();
game.addChild(tutorialContainer);
tutorialContainer.visible = false;
// Crazy Dave character
var crazyDave = LK.getAsset('crazyDave', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
crazyDave.x = 300;
crazyDave.y = 800;
tutorialContainer.addChild(crazyDave);
// Tutorial text
var tutorialText = new Text2('¡Hola! Soy Crazy Dave.\n\nBienvenido a Plants vs Zombies!\n\nColoca plantas para defender\ntu jardín de los zombis.\n\nPresiona CONTINUAR para seguir.', {
size: 50,
fill: '#FFFFFF'
});
tutorialText.anchor.set(0.5, 0.5);
tutorialText.x = 1024;
tutorialText.y = 1400;
tutorialContainer.addChild(tutorialText);
// Tutorial continue button
var tutorialContinueButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.0
});
tutorialContinueButton.x = 1024;
tutorialContinueButton.y = 2200;
tutorialContainer.addChild(tutorialContinueButton);
var tutorialContinueText = new Text2('CONTINUAR', {
size: 40,
fill: '#FFFFFF'
});
tutorialContinueText.anchor.set(0.5, 0.5);
tutorialContinueText.x = 1024;
tutorialContinueText.y = 2200;
tutorialContainer.addChild(tutorialContinueText);
// Tutorial step tracking
var tutorialStep = 0;
var tutorialMessages = {
spanish: ['¡Hola! Soy Crazy Dave.\n\nBienvenido a Plants vs Zombies!\n\nColoca plantas para defender\ntu jardín de los zombis.\n\nPresiona CONTINUAR para seguir.', 'Los GIRASOLES generan puntos\npara comprar más plantas.\n\nLos LANZAGUISANTES disparan\na los zombis.\n\nPresiona CONTINUAR.', 'Las NUECES bloquean a los zombis\ny tienen mucha vida.\n\nLas CEREZAS BOMBA explotan\ny dañan a muchos zombis.\n\nPresiona CONTINUAR.', 'Puedes FUSIONAR plantas del\nmismo tipo para crear plantas\nmás poderosas.\n\nPresiona JUGAR para empezar.'],
english: ['Hello! I am Crazy Dave.\n\nWelcome to Plants vs Zombies!\n\nPlace plants to defend\nyour garden from zombies.\n\nPress CONTINUE to proceed.', 'SUNFLOWERS generate points\nto buy more plants.\n\nPEASHOOTERS shoot at zombies.\n\nPress CONTINUE.', 'WALLNUTS block zombies\nand have lots of health.\n\nCHERRY BOMBS explode\nand damage many zombies.\n\nPress CONTINUE.', 'You can FUSE plants of the\nsame type to create more\npowerful plants.\n\nPress PLAY to start.']
};
// Function to update tutorial text
function updateTutorialText() {
var messages = tutorialMessages[selectedLanguage];
tutorialText.setText(messages[tutorialStep]);
// Update continue button text
if (tutorialStep === messages.length - 1) {
if (selectedLanguage === 'english') {
tutorialContinueText.setText('PLAY');
} else {
tutorialContinueText.setText('JUGAR');
}
} else {
if (selectedLanguage === 'english') {
tutorialContinueText.setText('CONTINUE');
} else {
tutorialContinueText.setText('CONTINUAR');
}
}
}
// Animate Crazy Dave
function animateCrazyDave() {
tween(crazyDave, {
scaleX: 2.2,
scaleY: 2.2,
rotation: 0.1
}, {
duration: 800,
onFinish: function onFinish() {
tween(crazyDave, {
scaleX: 2,
scaleY: 2,
rotation: -0.1
}, {
duration: 800,
onFinish: function onFinish() {
if (gameState === 'tutorial') {
animateCrazyDave();
}
}
});
}
});
}
// UI Elements
// Set initial score to 100
LK.setScore(100);
var scoreText = new Text2('Girasoles: ' + LK.getScore(), {
size: 40,
fill: '#FFFFFF'
});
scoreText.anchor.set(0, 0);
scoreText.x = 150;
scoreText.y = 20;
LK.gui.topLeft.addChild(scoreText);
scoreText.visible = false; // Hide during menu
var waveText = new Text2('Wave: 1', {
size: 40,
fill: '#FFFFFF'
});
waveText.anchor.set(0.5, 0);
waveText.x = 1024;
waveText.y = 20;
LK.gui.top.addChild(waveText);
waveText.visible = false; // Hide during menu
var livesText = new Text2('Lives: 5', {
size: 40,
fill: '#FF0000'
});
livesText.anchor.set(1, 0);
livesText.x = 1900;
livesText.y = 20;
LK.gui.topRight.addChild(livesText);
livesText.visible = false; // Hide during menu
function startGame() {
gameState = 'playing';
menuContainer.visible = false;
scoreText.visible = true;
waveText.visible = true;
livesText.visible = true;
// Show plant buttons
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].visible = true;
}
// Show grid cells
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
gridCells[row][col].visible = true;
}
}
// Music will start 7.5 seconds after clicking play button
}
// Create plant selection buttons
var plantTypes = ['sunflower', 'peashooter', 'wallnut', 'cherrybomb', 'papapum', 'piranaplant', 'shovel'];
for (var i = 0; i < plantTypes.length; i++) {
var button = new PlantButton(plantTypes[i], i);
plantButtons.push(button);
game.addChild(button);
button.visible = false; // Hide during menu
}
// Create grid - scale to use full screen
var gridStartX = 150;
var gridStartY = 300;
var gridRows = 5;
var gridCols = 8;
for (var row = 0; row < gridRows; row++) {
gridCells[row] = [];
for (var col = 0; col < gridCols; col++) {
var cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
cell.x = gridStartX + col * 200;
cell.y = gridStartY + row * 250;
cell.row = row;
cell.col = col;
cell.occupied = false;
cell.visible = false; // Hide during menu
gridCells[row][col] = cell;
game.addChild(cell);
}
}
// Game event handlers
game.down = function (x, y, obj) {
// Handle settings menu interactions
if (settingsContainer.visible) {
// Check if clicking on fusion mode button
if (Math.abs(x - fusionModeButton.x) < 150 && Math.abs(y - fusionModeButton.y) < 60) {
// Toggle fusion mode
fusionMode = !fusionMode;
storage.fusionMode = fusionMode;
updateFusionModeText();
// Fusion mode button animation
tween(fusionModeButton, {
scaleX: 2.2,
scaleY: 1.4,
tint: fusionMode ? 0x00FF00 : 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(fusionModeButton, {
scaleX: 2.0,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
return;
}
// Check if clicking on settings back button
if (Math.abs(x - settingsBackButton.x) < 135 && Math.abs(y - settingsBackButton.y) < 60) {
// Settings back button animation
tween(settingsBackButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0x00CCFF
}, {
duration: 150,
onFinish: function onFinish() {
tween(settingsBackButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Hide settings menu and show main menu
settingsContainer.visible = false;
menuContainer.visible = true;
return;
}
return;
}
// Handle language menu interactions
if (languageMenuContainer.visible) {
// Check if clicking on English button
if (Math.abs(x - englishButton.x) < 135 && Math.abs(y - englishButton.y) < 60) {
selectedLanguage = 'english';
storage.selectedLanguage = 'english';
// Update menu texts to English
updateMenuTexts();
// English button animation
tween(englishButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0x00FF00
}, {
duration: 150,
onFinish: function onFinish() {
tween(englishButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Hide language menu and show main menu
languageMenuContainer.visible = false;
menuContainer.visible = true;
return;
}
// Check if clicking on Spanish button
if (Math.abs(x - spanishButton.x) < 135 && Math.abs(y - spanishButton.y) < 60) {
selectedLanguage = 'spanish';
storage.selectedLanguage = 'spanish';
// Update menu texts to Spanish
updateMenuTexts();
// Spanish button animation
tween(spanishButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0x00FF00
}, {
duration: 150,
onFinish: function onFinish() {
tween(spanishButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Hide language menu and show main menu
languageMenuContainer.visible = false;
menuContainer.visible = true;
return;
}
// Check if clicking on Back button
if (Math.abs(x - backButton.x) < 90 && Math.abs(y - backButton.y) < 50) {
// Back button animation
tween(backButton, {
scaleX: 1.4,
scaleY: 1.2,
tint: 0xFFCC00
}, {
duration: 150,
onFinish: function onFinish() {
tween(backButton, {
scaleX: 1.2,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Hide language menu and show main menu
languageMenuContainer.visible = false;
menuContainer.visible = true;
return;
}
return;
}
// Handle menu interactions
if (gameState === 'menu') {
// Check if clicking on Jugar button
if (Math.abs(x - jugarButton.x) < 135 && Math.abs(y - jugarButton.y) < 60) {
// Jugar button animation
tween(jugarButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0x00FF00
}, {
duration: 150,
onFinish: function onFinish() {
tween(jugarButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Stop music and play sound based on selected language
LK.stopMusic();
if (selectedLanguage === 'english') {
LK.getSound('english').play();
} else {
LK.getSound('spanish').play();
}
// Start music 7.5 seconds after clicking play
LK.setTimeout(function () {
if (gameState === 'playing') {
LK.playMusic('Musixa');
}
}, 7500);
startGame();
return;
}
// Check if clicking on Idiomas button
if (Math.abs(x - idiomasButton.x) < 135 && Math.abs(y - idiomasButton.y) < 60) {
// Idiomas button animation
tween(idiomasButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0x0088FF
}, {
duration: 150,
onFinish: function onFinish() {
tween(idiomasButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Show language menu and hide main menu
menuContainer.visible = false;
languageMenuContainer.visible = true;
return;
}
// Check if clicking on Ajustes button
if (Math.abs(x - ajustesButton.x) < 135 && Math.abs(y - ajustesButton.y) < 60) {
// Ajustes button animation
tween(ajustesButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0xFF8800
}, {
duration: 150,
onFinish: function onFinish() {
tween(ajustesButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Show settings menu and hide main menu
menuContainer.visible = false;
settingsContainer.visible = true;
return;
}
// Check if clicking on Tutorial button
if (Math.abs(x - tutorialButton.x) < 135 && Math.abs(y - tutorialButton.y) < 60) {
// Tutorial button animation
tween(tutorialButton, {
scaleX: 1.7,
scaleY: 1.4,
tint: 0x00FFFF
}, {
duration: 150,
onFinish: function onFinish() {
tween(tutorialButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
// Start tutorial
gameState = 'tutorial';
tutorialStep = 0;
menuContainer.visible = false;
tutorialContainer.visible = true;
updateTutorialText();
animateCrazyDave();
return;
}
return;
}
// Handle tutorial interactions
if (gameState === 'tutorial') {
// Check if clicking on continue button
if (Math.abs(x - tutorialContinueButton.x) < 117 && Math.abs(y - tutorialContinueButton.y) < 50) {
// Continue button animation
tween(tutorialContinueButton, {
scaleX: 1.5,
scaleY: 1.2,
tint: 0x00FF00
}, {
duration: 150,
onFinish: function onFinish() {
tween(tutorialContinueButton, {
scaleX: 1.3,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 150
});
}
});
var maxSteps = tutorialMessages[selectedLanguage].length;
if (tutorialStep < maxSteps - 1) {
tutorialStep++;
updateTutorialText();
} else {
// End tutorial and start game
gameState = 'playing';
tutorialContainer.visible = false;
// Stop music and play sound based on selected language
LK.stopMusic();
if (selectedLanguage === 'english') {
LK.getSound('english').play();
} else {
LK.getSound('spanish').play();
}
// Start music 7.5 seconds after clicking play
LK.setTimeout(function () {
if (gameState === 'playing') {
LK.playMusic('Musixa');
}
}, 7500);
startGame();
}
return;
}
return;
}
if (gameState !== 'playing') return;
if (!selectedPlantType) return;
// Check if clicking on grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var cell = gridCells[row][col];
if (Math.abs(x - cell.x) < 80 && Math.abs(y - cell.y) < 80) {
// Check for fusion - peashooter + peashooter or sunflower + peashooter or papapum + wallnut
if (fusionMode && cell.occupied && (selectedPlantType === 'peashooter' || selectedPlantType === 'sunflower' || selectedPlantType === 'wallnut' || selectedPlantType === 'papapum')) {
// Find existing plant at this position
var existingPlant = null;
for (var p = 0; p < plants.length; p++) {
if (Math.abs(plants[p].x - cell.x) < 40 && Math.abs(plants[p].y - cell.y) < 40) {
existingPlant = plants[p];
break;
}
}
// Peashooter + Peashooter = Double Peashooter
if (existingPlant && existingPlant.type === 'peashooter' && selectedPlantType === 'peashooter') {
var cost = 10;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing peashooter
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create double peashooter
var fusedPlant = new Plant('doublepeashooter');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Fusion animation
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0x00FF00
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Double Peashooter + Peashooter = Burst Peashooter
else if (existingPlant && (existingPlant.type === 'doublepeashooter' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'doublepeashooter')) {
var cost = selectedPlantType === 'peashooter' ? 10 : 20;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create burst peashooter
var fusedPlant = new Plant('burstpeashooter');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Special burst fusion animation with white tint
tween(fusedPlant.graphics, {
scaleX: 1.6,
scaleY: 1.6,
tint: 0xFFFFFF
}, {
duration: 400,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 250
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Sunflower + Sunflower = Double Sunflower
else if (existingPlant && existingPlant.type === 'sunflower' && selectedPlantType === 'sunflower') {
var cost = 15;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing sunflower
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create double sunflower
var fusedPlant = new Plant('doublesunflower');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Golden fusion animation for double sun production with sprite transformation
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFFFF00
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFD700
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Sunflower + Double Sunflower = Triple Sunflower
else if (existingPlant && (existingPlant.type === 'sunflower' && selectedPlantType === 'doublesunflower' || existingPlant.type === 'doublesunflower' && selectedPlantType === 'sunflower')) {
var cost = selectedPlantType === 'sunflower' ? 15 : 35;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create triple sunflower
var fusedPlant = new Plant('triplesunflower');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Special golden fusion animation for triple sun production
tween(fusedPlant.graphics, {
scaleX: 1.8,
scaleY: 1.8,
tint: 0xFFFF00
}, {
duration: 400,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFD700
}, {
duration: 300
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Sunflower + Peashooter = Point Shooter
else if (existingPlant && (existingPlant.type === 'sunflower' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'sunflower')) {
var cost = selectedPlantType === 'peashooter' ? 10 : 15;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create point shooter
var fusedPlant = new Plant('pointshooter');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Golden fusion animation
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFFD700
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Wallnut + Peashooter = Armored Shooter
else if (existingPlant && (existingPlant.type === 'wallnut' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'wallnut')) {
var cost = selectedPlantType === 'peashooter' ? 10 : 20;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create armored shooter
var fusedPlant = new Plant('armoredshooter');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Defensive fusion animation with green tint
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0x00AA00
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Papapum + Wallnut = Explosive Wallnut
else if (existingPlant && (existingPlant.type === 'papapum' && selectedPlantType === 'wallnut' || existingPlant.type === 'wallnut' && selectedPlantType === 'papapum')) {
var cost = selectedPlantType === 'papapum' ? 25 : 20;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create explosive wallnut
var fusedPlant = new Plant('explosivewallnut');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Explosive fusion animation with red tint
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF4444
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Papapum + Peashooter = Explosive Peashooter
else if (existingPlant && (existingPlant.type === 'papapum' && selectedPlantType === 'peashooter' || existingPlant.type === 'peashooter' && selectedPlantType === 'papapum')) {
var cost = selectedPlantType === 'papapum' ? 25 : 10;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create explosive peashooter
var fusedPlant = new Plant('explosivepeashooter');
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Explosive fusion animation with red tint
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF4444
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFF4444
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
// Piranha Plant + Cherry Bomb = Exploding Piranha Plant
else if (existingPlant && (existingPlant.type === 'piranaplant' && selectedPlantType === 'cherrybomb' || existingPlant.type === 'cherrybomb' && selectedPlantType === 'piranaplant')) {
var cost = selectedPlantType === 'cherrybomb' ? 50 : 30;
if (LK.getScore() < cost) {
return; // Not enough points
}
// Remove existing plant
existingPlant.destroy();
plants.splice(plants.indexOf(existingPlant), 1);
// Create exploding piranha plant (special piranha that explodes after eating)
var fusedPlant = new Plant('piranaplant');
// Replace the graphics with exploding variant sprite
fusedPlant.removeChild(fusedPlant.graphics);
fusedPlant.graphics = fusedPlant.attachAsset('explodingpiranaplant', {
anchorX: 0.5,
anchorY: 0.5
});
fusedPlant.x = cell.x;
fusedPlant.y = cell.y;
fusedPlant.isExploding = true; // Mark as exploding variant
fusedPlant.explosionRadius = 360; // 2 slots radius (180px per slot)
fusedPlant.explosionDamage = 200; // Strong explosion damage
plants.push(fusedPlant);
game.addChild(fusedPlant);
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
LK.getSound('plantPlace').play();
// Exploding piranha fusion animation with red-purple tint
tween(fusedPlant.graphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xFF0080
}, {
duration: 300,
onFinish: function onFinish() {
tween(fusedPlant.graphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFF4444
}, {
duration: 200
});
}
});
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
}
// Handle shovel selection - remove existing plant
if (selectedPlantType === 'shovel' && cell.occupied) {
// Find and remove the plant at this position
for (var p = plants.length - 1; p >= 0; p--) {
if (Math.abs(plants[p].x - cell.x) < 40 && Math.abs(plants[p].y - cell.y) < 40) {
plants[p].destroy();
plants.splice(p, 1);
// Make sure to update the grid cell's occupied status
gridCells[row][col].occupied = false;
break;
}
}
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
if (!cell.occupied && selectedPlantType !== 'shovel') {
var cost = 0;
if (selectedPlantType === 'peashooter') cost = 10;
if (selectedPlantType === 'sunflower') cost = 15;
if (selectedPlantType === 'wallnut') cost = 20;
if (selectedPlantType === 'cherrybomb') cost = 50;
if (selectedPlantType === 'papapum') cost = 25;
if (selectedPlantType === 'piranaplant') cost = 30;
// Check if plant is on cooldown
if (plantCooldowns[selectedPlantType] && plantCooldowns[selectedPlantType] > 0) {
return; // Plant is on cooldown
}
if (LK.getScore() < cost) {
return; // Not enough points
}
var plant = new Plant(selectedPlantType);
plant.x = cell.x;
plant.y = cell.y;
plants.push(plant);
game.addChild(plant);
// Update grid cell occupied status using row/col indices
gridCells[row][col].occupied = true;
if (cost > 0) {
LK.setScore(LK.getScore() - cost);
scoreText.setText('Girasoles: ' + LK.getScore());
}
// Start cooldown for this plant type
if (plantCooldownDurations[selectedPlantType]) {
plantCooldowns[selectedPlantType] = plantCooldownDurations[selectedPlantType];
}
LK.getSound('plantPlace').play();
// Reset selection
selectedPlantType = null;
for (var i = 0; i < plantButtons.length; i++) {
plantButtons[i].button.tint = 0xFFFFFF;
}
return;
}
}
}
}
};
function spawnZombie() {
if (zombiesSpawned >= zombiesInWave) return;
var lane = Math.floor(Math.random() * gridRows);
var zombieTypes = ['basic', 'fast', 'strong'];
var type = zombieTypes[Math.floor(Math.random() * zombieTypes.length)];
// Tank zombies appear after 50 zombies killed
if (zombiesKilled >= 50) {
zombieTypes = ['basic', 'fast', 'strong', 'tank'];
}
// Increase difficulty with wave number
if (waveNumber > 3) {
type = zombieTypes[Math.floor(Math.random() * zombieTypes.length)];
} else if (waveNumber > 1) {
type = Math.random() < 0.7 ? 'basic' : Math.random() < 0.5 ? 'fast' : 'strong';
} else {
type = Math.random() < 0.6 ? 'basic' : 'strong';
}
var zombie = new Zombie(type, lane);
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
}
function checkWaveComplete() {
if (zombiesSpawned >= zombiesInWave && zombies.length === 0) {
// Wave complete
waveNumber++;
zombiesInWave += 2;
zombiesSpawned = 0;
waveText.setText('Wave: ' + waveNumber);
if (waveNumber > 10) {
gameState = 'victory';
LK.showYouWin();
}
}
}
function checkGameOver() {
if (zombiesReachedEnd >= 1) {
gameState = 'gameOver';
LK.showGameOver();
}
livesText.setText('Lives: ' + (maxZombiesAllowed - zombiesReachedEnd));
}
// Main game loop
game.update = function () {
if (gameState !== 'playing') return;
// Auto point generation - 7 points every 7 seconds
autoPointTimer++;
if (autoPointTimer >= autoPointInterval) {
LK.setScore(LK.getScore() + 7);
scoreText.setText('Girasoles: ' + LK.getScore());
autoPointTimer = 0;
}
// Update plant cooldowns
for (var plantType in plantCooldowns) {
if (plantCooldowns[plantType] > 0) {
plantCooldowns[plantType]--;
}
}
// Update plant button visual states based on cooldown
for (var i = 0; i < plantButtons.length; i++) {
var button = plantButtons[i];
var plantType = button.plantType;
if (plantCooldowns[plantType] && plantCooldowns[plantType] > 0) {
// Plant is on cooldown - darken button
button.button.tint = 0x666666;
} else if (selectedPlantType !== plantType) {
// Plant is ready and not selected - normal color
button.button.tint = 0xFFFFFF;
}
// Keep selected plant highlighted in green
if (selectedPlantType === plantType) {
button.button.tint = 0x00FF00;
}
}
// Spawn zombies
zombieSpawnTimer++;
if (zombieSpawnTimer >= zombieSpawnInterval) {
spawnZombie();
zombieSpawnTimer = 0;
}
// Update all objects
for (var i = plants.length - 1; i >= 0; i--) {
if (plants[i].update) {
plants[i].update();
}
}
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].update) {
zombies[i].update();
}
}
for (var i = projectiles.length - 1; i >= 0; i--) {
if (projectiles[i].update) {
projectiles[i].update();
}
}
checkWaveComplete();
checkGameOver();
};
Zombie básico del videojuego plantas vs zombies. In-Game asset. No shadows
Un lanza guisantes del videojuego plantas vs zombies. In-Game asset. No shadows
Fast zombie del videojuego plantas vs zombies. In-Game asset. No shadows
Cherrybomb del videojuego plantas vs zombies. In-Game asset. No shadows
Girasol de juego plantas vs zombies. In-Game asset
doublepeashooter del juego plantas vs zombies fusion. In-Game asset. No shadows
Un lanza guisantes fusionado cun un girasol de plantas vs zombies fusion. In-Game asset. No shadows
Cuadro rojo con azul para seleccionar algo. In-Game asset. No shadows
Wallnut del videojuego plantas vs zombies. In-Game asset. No shadows
Tank zombie del juego plantas vs zombies. In-Game asset. No shadows
Cuadrado de cesped con flores visto desde arriba. In-Game asset. No shadows
Shovel de plantas vs zombies. In-Game asset. 2d. No shadows
wallnutDamaged del videojuego de plantas vs zombies. In-Game asset. 2d. No shadows
doublesunflower de plantas vs zombies fusion. In-Game asset. 2d. No shadows
Una fusion del wallnut de plantas vs zombies con el lanza guisantes. In-Game asset. No shadows
Papapum de plantas vs zombies que sea tierna. In-Game asset. No shadows
explosivewallnut de plantas vs zombies fusion. In-Game asset. 2d. High contrast
explosivepeashooter de plantas vs zombie fusion. In-Game asset. 2d. High contrast
Triple girasol de plantas vs zombies. In-Game asset. 2d. High contrast
Triple lanza guisantes del videojuego plantas vs zombies fusion con cascos militares y boca de metralletas. In-Game asset. 2d. High contrast
Strong zombie de plantas vs zombies. In-Game asset. 2d. High contrast. No shadows
Crazy Dave del juego plantas vs zombies con cara alegre y con una pelota desinflada en la cabeza. In-Game asset. 2d. High contrast
Planta piraña de plantas vs zombies. In-Game asset. 2d. High contrast. No shadows
Piraña explosiva del plantas vs zombies. In-Game asset. 2d. High contrast