/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, pearls: 0, currentLevel: 1 }); /**** * Classes ****/ var BuildSpot = Container.expand(function () { var self = Container.call(this); var buildGraphics = self.attachAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); self.occupied = false; self.tower = null; self.down = function (x, y, obj) { if (!self.occupied && gameState.pearls >= 10) { // Check if any Megalodon is blocking tower building var towerBlocked = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].constructor.name === "Megalodon" && enemies[i].blockingTowerBuilding) { towerBlocked = true; break; } } if (towerBlocked) { // Show warning message var blockedText = new Text2("TOWER BUILDING BLOCKED BY MEGALODON!", { size: 40, fill: 0xFF0000 }); blockedText.anchor.set(0.5, 0.5); LK.gui.center.addChild(blockedText); // Remove warning after 2 seconds LK.setTimeout(function () { blockedText.destroy(); }, 2000); } else { openBuildMenu(self); } } }; return self; }); var CoralReef = Container.expand(function () { var self = Container.call(this); var coralGraphics = self.attachAsset('coral', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 500); if (self.health <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.speed = 1.5; self.damage = 10; self.pathIndex = 0; self.pearls = 5; self.boss = false; // Default enemies are not bosses // Health bar var healthBar = LK.getAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 10, scaleX: 1, scaleY: 0.2, y: -40, tint: 0x00ff00 }); self.addChild(healthBar); self.update = function () { // Initialize stunned property if not set if (self.stunned === undefined) { self.stunned = false; } if (self.pathIndex < gamePath.length && !self.stunned) { var targetPos = gamePath[self.pathIndex]; var dx = targetPos.x - self.x; var dy = targetPos.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.speed) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Face the direction of movement if (dx < 0) { enemyGraphics.scaleX = -1; } else { enemyGraphics.scaleX = 1; } } else { self.pathIndex++; } } else if (self.pathIndex >= gamePath.length) { // Reached the end, damage coral coralReef.takeDamage(self.damage); self.destroy(); var index = enemies.indexOf(self); if (index !== -1) { enemies.splice(index, 1); } } healthBar.scaleX = self.health / self.maxHealth; // Visual indicator for stunned state if (self.stunned) { // Pulsate alpha when stunned if (LK.ticks % 15 === 0) { enemyGraphics.alpha = enemyGraphics.alpha === 1 ? 0.5 : 1; } } else { enemyGraphics.alpha = 1; } }; self.takeDamage = function (damage) { self.health -= damage; LK.getSound('enemyHit').play(); if (self.health <= 0) { gameState.pearls += self.pearls; pearlText.setText("Pearls: " + gameState.pearls); LK.getSound('enemyDefeat').play(); // Add to score LK.setScore(LK.getScore() + 10); scoreText.setText("Score: " + LK.getScore()); self.destroy(); var index = enemies.indexOf(self); if (index !== -1) { enemies.splice(index, 1); } } }; return self; }); var PlasticBag = Enemy.expand(function () { var self = Enemy.call(this); // Replace with plastic bag graphic self.removeChild(self.children[1]); // Remove regular enemy graphic var plasticBagGraphics = self.attachAsset('PlasticBag', { anchorX: 0.5, anchorY: 0.5 }); // Plastic bag properties self.health = 80; self.maxHealth = 80; self.speed = 2.0; // Faster than regular enemies self.damage = 5; // Less damage self.pearls = 3; // Special ability: Sometimes changes direction randomly self.update = function () { // Call parent's update logic Enemy.prototype.update.call(self); // 2% chance per frame to temporarily speed up if (Math.random() < 0.02 && !self.speedBoost) { self.speedBoost = true; self.originalSpeed = self.speed; self.speed *= 1.5; // Reset speed after a short duration LK.setTimeout(function () { if (self && self.parent) { self.speed = self.originalSpeed; self.speedBoost = false; } }, 1000); } }; return self; }); var Megalodon = Enemy.expand(function () { var self = Enemy.call(this); // Replace with megalodon graphic self.removeChild(self.children[1]); // Remove regular enemy graphic var megalGraphics = self.attachAsset('Megalodon', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); // Megalodon properties self.health = 500; self.maxHealth = 500; self.speed = 1.0; self.damage = 30; self.pearls = 25; self.boss = true; // Special ability: regenerate health self.lastRegenTick = 0; self.lastSpecialAttackTick = 0; self.blockingTowerBuilding = false; // Override update method to add regeneration var parentUpdate = self.update; self.update = function () { // Call parent update first parentUpdate.call(self); // Regenerate health every 3 seconds (180 frames) if (LK.ticks - self.lastRegenTick > 180) { self.lastRegenTick = LK.ticks; if (self.health < self.maxHealth) { self.health = Math.min(self.health + 20, self.maxHealth); // Show healing effect LK.effects.flashObject(self, 0x00ff00, 500); } } // Special attack: block tower building every 15 seconds (900 frames) if (LK.ticks - self.lastSpecialAttackTick > 900) { // 30% chance to activate special attack when time is ready if (Math.random() < 0.3) { self.lastSpecialAttackTick = LK.ticks; self.blockingTowerBuilding = true; // Create warning text var warningText = new Text2("MEGALODON BLOCKS TOWERS!", { size: 60, fill: 0xFF0000 }); warningText.anchor.set(0.5, 0.5); LK.gui.center.addChild(warningText); // Flash megalodon in red LK.effects.flashObject(self, 0xff0000, 1000); // Duration of blocking: 10 seconds (600 frames) LK.setTimeout(function () { self.blockingTowerBuilding = false; // Create end warning text var endWarningText = new Text2("TOWER BLOCKING ENDED", { size: 50, fill: 0x00FF00 }); endWarningText.anchor.set(0.5, 0.5); LK.gui.center.addChild(endWarningText); // Remove text after 2 seconds LK.setTimeout(function () { endWarningText.destroy(); }, 2000); }, 10000); // Remove warning text after 3 seconds LK.setTimeout(function () { warningText.destroy(); }, 3000); } } }; return self; }); var FloppyFish = Container.expand(function () { var self = Container.call(this); var fishGraphics = self.attachAsset('floppyFish', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 6; self.targetX = null; self.targetY = null; self.update = function () { if (self.targetX !== null && self.targetY !== null) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.speed) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Face the direction of movement if (dx < 0) { fishGraphics.scaleX = -1; } else { fishGraphics.scaleX = 1; } } else { self.x = self.targetX; self.y = self.targetY; self.targetX = null; self.targetY = null; } } }; return self; }); var PathTile = Container.expand(function () { var self = Container.call(this); var pathGraphics = self.attachAsset('path', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); self.buildable = false; return self; }); var Tower = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'bubbleCannon'; self.level = 1; self.range = 250; self.damage = 25; self.fireRate = 60; // frames between shots self.fireCounter = 0; self.cost = 10; self.upgradesCost = 15; var towerGraphics = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5 }); // Range indicator (only shown when selected) var rangeIndicator = LK.getAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, alpha: 0.2, tint: 0x3498db }); // Scale the range indicator to match the tower's range rangeIndicator.scaleX = self.range / 40; rangeIndicator.scaleY = self.range / 40; rangeIndicator.visible = false; self.addChild(rangeIndicator); self.down = function (x, y, obj) { // Deselect previous tower if (selectedTower && selectedTower !== self) { selectedTower.deselect(); } selectedTower = self; self.select(); // Show upgrade UI with cost upgradeButton.visible = true; stopUpgradeButton.visible = true; upgradeText.setText("Upgrade: " + self.upgradesCost + " pearls"); }; self.select = function () { rangeIndicator.visible = true; towerGraphics.tint = 0xffff00; }; self.deselect = function () { rangeIndicator.visible = false; towerGraphics.tint = 0xffffff; }; self.upgrade = function () { if (gameState.pearls >= self.upgradesCost) { gameState.pearls -= self.upgradesCost; pearlText.setText("Pearls: " + gameState.pearls); self.level++; self.damage += 15; self.range += 50; self.fireRate = Math.max(30, self.fireRate - 10); self.upgradesCost = Math.floor(self.upgradesCost * 1.5); // Update range indicator rangeIndicator.scaleX = self.range / 40; rangeIndicator.scaleY = self.range / 40; // Update upgrade button text upgradeText.setText("Upgrade: " + self.upgradesCost + " pearls"); // Scale up tower slightly to show upgrade tween(towerGraphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, onFinish: function onFinish() { tween(towerGraphics, { scaleX: 1, scaleY: 1 }, { duration: 300 }); } }); } }; self.findTarget = function () { var closestEnemy = null; var closestDistance = self.range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestEnemy = enemy; closestDistance = distance; } } return closestEnemy; }; self.shoot = function (target) { var bubble = LK.getAsset('bubble', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y }); game.addChild(bubble); projectiles.push({ sprite: bubble, target: target, damage: self.damage, speed: 7 }); LK.getSound('bubble').play(); }; self.update = function () { self.fireCounter++; if (self.fireCounter >= self.fireRate) { var target = self.findTarget(); if (target) { self.shoot(target); self.fireCounter = 0; } } }; return self; }); var TurtleShell = Tower.expand(function () { var self = Tower.call(this, 'TurtleShell'); // Override properties for turtle shell self.range = 300; self.damage = 30; self.fireRate = 60; // Override shoot method for attack self.shoot = function (target) { // Create turtle shell projectile var shell = LK.getAsset('TurtleShellAttack', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y }); game.addChild(shell); // Create special projectile that will do damage projectiles.push({ sprite: shell, target: target, damage: self.damage, speed: 8 }); // Play sound (using bubble sound for now) LK.getSound('bubble').play(); }; return self; }); var SharkTower = Tower.expand(function () { var self = Tower.call(this, 'Sharky'); // Override properties for shark tower self.range = 250; self.damage = 100; self.fireRate = 90; // Slower than bubble cannon but high damage // Override shoot method for instant kill functionality self.shoot = function (target) { // Create bite effect (using the bubble as a placeholder for the bite animation) var bite = LK.getAsset('bubble', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y }); game.addChild(bite); // Move bite effect to target projectiles.push({ sprite: bite, target: target, damage: target.boss ? self.damage : target.health, // Instant kill for non-boss enemies speed: 10 // Faster projectile to simulate bite }); // Flash target red to indicate it's being bitten LK.effects.flashObject(target, 0xff0000, 200); // Play bite sound (using bubble sound for now) LK.getSound('bubble').play(); }; return self; }); var SeaweedTrap = Tower.expand(function () { var self = Tower.call(this, 'seaweedTrap'); // Override properties for seaweed trap self.range = 200; self.damage = 5; self.fireRate = 60; self.stunDuration = 60; // 1 second stun (60 frames) // Override shoot method for stun functionality self.shoot = function (target) { // Apply stun effect to the target if (!target.stunned) { target.stunned = true; target.originalSpeed = target.speed; target.speed = 0; // Make the seaweed trap "stare" at the enemy by rotating to face it var dx = target.x - self.x; var dy = target.y - self.y; var angle = Math.atan2(dy, dx); // Use tween to rotate the tower to face the enemy tween(self.children[0], { rotation: angle }, { duration: 300, easing: tween.easeOut }); // Flash the target to indicate stun LK.effects.flashObject(target, 0x00ff00, 300); // Set a timer to remove the stun after duration LK.setTimeout(function () { if (target && target.parent) { target.speed = target.originalSpeed; target.stunned = false; } }, self.stunDuration * 16.67); // Convert frames to milliseconds (60fps ≈ 16.67ms per frame) } target.takeDamage(self.damage); LK.getSound('bubble').play(); }; return self; }); var JellyStinger = Tower.expand(function () { var self = Tower.call(this, 'JellyStinger'); // Override properties for jelly stinger self.range = 230; self.damage = 15; self.fireRate = 90; self.stunDuration = 1800; // 30 seconds (60fps * 30) // Override shoot method for electricity stunning self.shoot = function (target) { // Create electricity effect var electricity = LK.getAsset('Electricity', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.8 }); game.addChild(electricity); // Create special projectile that will apply electricity stun projectiles.push({ sprite: electricity, target: target, damage: self.damage, speed: 8, isElectric: true, stunDuration: self.stunDuration }); // Play sound (using bubble sound for now) LK.getSound('bubble').play(); }; return self; }); var ClamMine = Tower.expand(function () { var self = Tower.call(this, 'clamMine'); // Override properties for clam mine self.range = 200; self.damage = 40; self.fireRate = 120; // Slower fire rate but area damage self.areaRadius = 150; // Radius of explosion // Override shoot method for area damage self.shoot = function (target) { // Create pearl projectile var pearl = LK.getAsset('pearl', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y }); game.addChild(pearl); // Create special projectile that will explode projectiles.push({ sprite: pearl, target: target, damage: self.damage, speed: 5, isAreaDamage: true, areaRadius: self.areaRadius }); LK.getSound('bubble').play(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a5276 }); /**** * Game Code ****/ // Music // Sounds // Items and effects // Towers and defenses // Base characters // Play music LK.playMusic('underwaterTheme'); // Game variables var gameState = { pearls: 100, currentWave: 1, maxWaves: 100, waveInProgress: false, enemiesLeft: 0 }; var selectedTower = null; var buildMenuOpen = false; var gamePath = []; var buildSpots = []; var enemies = []; var towers = []; var projectiles = []; var coralReef; var floppyFish; // UI Elements var scoreText = new Text2("Score: 0", { size: 50, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.topRight.addChild(scoreText); scoreText.x = -scoreText.width - 20; scoreText.y = 20; var pearlText = new Text2("Pearls: " + gameState.pearls, { size: 50, fill: 0xFFFFFF }); pearlText.anchor.set(0, 0); LK.gui.topRight.addChild(pearlText); pearlText.x = -pearlText.width - 20; pearlText.y = 80; var waveText = new Text2("Wave: " + gameState.currentWave + "/" + gameState.maxWaves, { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); waveText.y = 20; // Upgrade button (hidden initially) var upgradeButton = LK.getAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 80, tint: 0x2ecc71 }); upgradeButton.visible = false; LK.gui.bottom.addChild(upgradeButton); upgradeButton.y = -100; var upgradeText = new Text2("Upgrade: 15 pearls", { size: 40, fill: 0xFFFFFF }); upgradeText.anchor.set(0.5, 0.5); upgradeButton.addChild(upgradeText); // Stop upgrading button (hidden initially) var stopUpgradeButton = LK.getAsset('Stopupgrade', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, tint: 0xff0000 }); stopUpgradeButton.visible = false; LK.gui.bottom.addChild(stopUpgradeButton); stopUpgradeButton.x = 200; stopUpgradeButton.y = -100; // Start wave button var startWaveButton = LK.getAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 80, tint: 0x3498db }); LK.gui.bottom.addChild(startWaveButton); startWaveButton.y = -200; var startWaveText = new Text2("Start Wave", { size: 40, fill: 0xFFFFFF }); startWaveText.anchor.set(0.5, 0.5); startWaveButton.addChild(startWaveText); // Build menu (hidden initially) var buildMenu = new Container(); buildMenu.visible = false; LK.gui.center.addChild(buildMenu); var buildMenuBackground = LK.getAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 500, tint: 0x34495e, alpha: 0.9 }); buildMenu.addChild(buildMenuBackground); var buildMenuTitle = new Text2("Build Tower", { size: 50, fill: 0xFFFFFF }); buildMenuTitle.anchor.set(0.5, 0); buildMenuTitle.y = -200; buildMenu.addChild(buildMenuTitle); // Tower options in build menu var bubbleOption = LK.getAsset('bubbleCannon', { anchorX: 0.5, anchorY: 0.5, x: -100, y: -100 }); buildMenu.addChild(bubbleOption); var bubbleText = new Text2("Bubble\nCannon\n10 pearls", { size: 30, fill: 0xFFFFFF }); bubbleText.anchor.set(0.5, 0); bubbleText.y = 50; bubbleOption.addChild(bubbleText); var seaweedOption = LK.getAsset('seaweedTrap', { anchorX: 0.5, anchorY: 0.5, x: 100, y: -100 }); buildMenu.addChild(seaweedOption); var seaweedText = new Text2("Seaweed\nTrap\n10 pearls", { size: 30, fill: 0xFFFFFF }); seaweedText.anchor.set(0.5, 0); seaweedText.y = 50; seaweedOption.addChild(seaweedText); var clamOption = LK.getAsset('clamMine', { anchorX: 0.5, anchorY: 0.5, x: -100, y: 50 }); buildMenu.addChild(clamOption); var clamText = new Text2("Clam\nMine\n10 pearls", { size: 30, fill: 0xFFFFFF }); clamText.anchor.set(0.5, 0); clamText.y = 50; clamOption.addChild(clamText); var sharkyOption = LK.getAsset('Sharky', { anchorX: 0.5, anchorY: 0.5, x: 100, y: 50 }); buildMenu.addChild(sharkyOption); var sharkyText = new Text2("Sharky\nBiter\n10 pearls", { size: 30, fill: 0xFFFFFF }); sharkyText.anchor.set(0.5, 0); sharkyText.y = 50; sharkyOption.addChild(sharkyText); var jellyStingerOption = LK.getAsset('JellyStinger', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 150 }); buildMenu.addChild(jellyStingerOption); var jellyStingerText = new Text2("Jelly\nStinger\n10 pearls", { size: 30, fill: 0xFFFFFF }); jellyStingerText.anchor.set(0.5, 0); jellyStingerText.y = 50; jellyStingerOption.addChild(jellyStingerText); var cancelBuildButton = LK.getAsset('buildPlaceholder', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 60, y: 150, tint: 0xe74c3c }); buildMenu.addChild(cancelBuildButton); var cancelText = new Text2("Cancel", { size: 40, fill: 0xFFFFFF }); cancelText.anchor.set(0.5, 0.5); cancelBuildButton.addChild(cancelText); // Initialize the map function initializeMap() { // Create path var pathPoints = [{ x: 0, y: 600 }, { x: 400, y: 600 }, { x: 400, y: 1000 }, { x: 800, y: 1000 }, { x: 800, y: 500 }, { x: 1200, y: 500 }, { x: 1200, y: 1200 }, { x: 1600, y: 1200 }, { x: 1600, y: 800 }, { x: 2000, y: 800 }]; // Create path tiles for (var i = 0; i < pathPoints.length; i++) { gamePath.push(pathPoints[i]); var pathTile = new PathTile(); pathTile.x = pathPoints[i].x; pathTile.y = pathPoints[i].y; game.addChild(pathTile); // Create build spots along the path (offset from path) if (i < pathPoints.length - 1) { var dx = pathPoints[i + 1].x - pathPoints[i].x; var dy = pathPoints[i + 1].y - pathPoints[i].y; var len = Math.sqrt(dx * dx + dy * dy); // Normalize and rotate 90 degrees for perpendicular var perpX = -dy / len; var perpY = dx / len; // Create build spots along segments var segments = Math.floor(len / 200); for (var j = 0; j <= segments; j++) { var segX = pathPoints[i].x + dx * j / segments; var segY = pathPoints[i].y + dy * j / segments; // Create build spots on both sides of path var buildSpot1 = new BuildSpot(); buildSpot1.x = segX + perpX * 120; buildSpot1.y = segY + perpY * 120; game.addChild(buildSpot1); buildSpots.push(buildSpot1); var buildSpot2 = new BuildSpot(); buildSpot2.x = segX - perpX * 120; buildSpot2.y = segY - perpY * 120; game.addChild(buildSpot2); buildSpots.push(buildSpot2); } } } // Add coral reef at the end of the path coralReef = new CoralReef(); coralReef.x = 2048 - 100; coralReef.y = 800; game.addChild(coralReef); // Add floppy fish floppyFish = new FloppyFish(); floppyFish.x = 1024; floppyFish.y = 1366; game.addChild(floppyFish); } // Initialize the game initializeMap(); // Functions function openBuildMenu(buildSpot) { buildMenuOpen = true; buildMenu.visible = true; buildMenu.currentBuildSpot = buildSpot; // If the player has insufficient pearls, disable options bubbleOption.alpha = gameState.pearls >= 10 ? 1 : 0.5; seaweedOption.alpha = gameState.pearls >= 10 ? 1 : 0.5; clamOption.alpha = gameState.pearls >= 10 ? 1 : 0.5; sharkyOption.alpha = gameState.pearls >= 10 ? 1 : 0.5; jellyStingerOption.alpha = gameState.pearls >= 10 ? 1 : 0.5; } function closeBuildMenu() { buildMenuOpen = false; buildMenu.visible = false; buildMenu.currentBuildSpot = null; } function buildTower(type) { if (gameState.pearls < 10) { return; } var buildSpot = buildMenu.currentBuildSpot; if (!buildSpot || buildSpot.occupied) { return; } // Check if any Megalodon is blocking tower building var towerBlocked = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].constructor.name === "Megalodon" && enemies[i].blockingTowerBuilding) { towerBlocked = true; break; } } if (towerBlocked) { // Show warning message var blockedText = new Text2("TOWER BUILDING BLOCKED BY MEGALODON!", { size: 40, fill: 0xFF0000 }); blockedText.anchor.set(0.5, 0.5); LK.gui.center.addChild(blockedText); // Remove warning after 2 seconds LK.setTimeout(function () { blockedText.destroy(); }, 2000); // Close build menu without building tower closeBuildMenu(); return; } gameState.pearls -= 10; pearlText.setText("Pearls: " + gameState.pearls); var tower; if (type === 'seaweedTrap') { tower = new SeaweedTrap(); } else if (type === 'Sharky') { tower = new SharkTower(); } else if (type === 'clamMine') { tower = new ClamMine(); } else if (type === 'JellyStinger') { tower = new JellyStinger(); } else { tower = new Tower(type); } tower.x = buildSpot.x; tower.y = buildSpot.y; game.addChild(tower); buildSpot.occupied = true; buildSpot.tower = tower; towers.push(tower); LK.getSound('buildTower').play(); // Spawn an enemy if a wave is in progress if (gameState.waveInProgress) { // Immediate spawn of enemy after tower placement spawnEnemy(); } closeBuildMenu(); } function spawnEnemy() { if (gameState.enemiesLeft <= 0) { return; } // Determine if this should be a Megalodon boss (at wave 20 or every 3rd wave, and last enemy of the wave) var isMegalodon = (gameState.currentWave === 20 || gameState.currentWave % 3 === 0) && gameState.enemiesLeft === 1 && enemies.length === 0; var enemy; if (isMegalodon) { // Create Megalodon enemy = new Megalodon(); // Announce Megalodon arrival var bossText = new Text2("MEGALODON INCOMING!", { size: 80, fill: 0xFF0000 }); bossText.anchor.set(0.5, 0.5); LK.gui.center.addChild(bossText); // Flash screen and remove announcement after delay LK.effects.flashScreen(0xFF0000, 500); LK.setTimeout(function () { bossText.destroy(); }, 2000); } else { // Create regular enemy // 25% chance to create a plastic bag instead of regular enemy if (Math.random() < 0.25) { enemy = new Enemy(); // Override with plastic bag graphics enemy.removeChild(enemy.children[1]); // Remove regular enemy graphic var plasticBagGraphics = enemy.attachAsset('PlasticBag', { anchorX: 0.5, anchorY: 0.5 }); } else { enemy = new Enemy(); } // Randomly make some regular enemies bosses var isBoss = Math.random() < 0.2; // 20% chance for boss enemy.boss = isBoss; // Increase enemy stats based on wave enemy.health = 100 + (gameState.currentWave - 1) * 20; // Boss enemies have double health if (enemy.boss) { enemy.health *= 2; enemy.scaleX = 1.5; enemy.scaleY = 1.5; enemy.pearls *= 2; } } // Check if this is a plastic bag (has PlasticBag as child) var isPlasticBag = enemy.children.some(function (child) { return child.assetId === 'PlasticBag'; }); var spawnX, spawnY, pathIndex; // Always spawn at start of track (first path point) spawnX = gamePath[0].x; spawnY = gamePath[0].y; pathIndex = 0; // If not a plastic bag, we can spawn at different locations if (!isPlasticBag) { // Spawn near a tower if there are towers (for non-plastic bag enemies) var spawnNearTower = towers.length > 0; if (spawnNearTower) { // Choose a random tower to spawn near var randomTower = towers[Math.floor(Math.random() * towers.length)]; // Find the closest path segment to this tower var minDistance = Infinity; var closestSegment = 0; for (var i = 0; i < gamePath.length - 1; i++) { var dx = gamePath[i].x - randomTower.x; var dy = gamePath[i].y - randomTower.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minDistance) { minDistance = distance; closestSegment = i; } } // Use the closest path segment spawnX = gamePath[closestSegment].x; spawnY = gamePath[closestSegment].y; pathIndex = closestSegment; } else { // No towers, use random path segment as before var randomPathSegment = Math.floor(Math.random() * (gamePath.length - 1)); spawnX = gamePath[randomPathSegment].x; spawnY = gamePath[randomPathSegment].y; pathIndex = randomPathSegment; } } enemy.x = spawnX; enemy.y = spawnY; enemy.pathIndex = pathIndex; // Set the path index to start from the chosen segment // Adjust regular enemy stats based on wave if (!isMegalodon) { enemy.maxHealth = enemy.health; enemy.speed = 1.5 + (gameState.currentWave - 1) * 0.1; enemy.pearls = 5 + Math.floor((gameState.currentWave - 1) / 2); } game.addChild(enemy); enemies.push(enemy); gameState.enemiesLeft--; // Schedule next enemy if (gameState.enemiesLeft > 0) { LK.setTimeout(spawnEnemy, 1000); } } function startWave() { if (gameState.waveInProgress) { return; } gameState.waveInProgress = true; gameState.enemiesLeft = 5 + gameState.currentWave * 2; startWaveButton.tint = 0x95a5a6; // Gray out button // Update wave text waveText.setText("Wave: " + gameState.currentWave + "/" + gameState.maxWaves); // Start spawning enemies spawnEnemy(); } function endWave() { gameState.waveInProgress = false; gameState.currentWave++; if (gameState.currentWave > gameState.maxWaves) { // Game won LK.showYouWin(); return; } // Update wave text waveText.setText("Wave: " + gameState.currentWave + "/" + gameState.maxWaves); startWaveButton.tint = 0x3498db; // Blue button } // Event handlers startWaveButton.down = function () { if (!gameState.waveInProgress) { startWave(); } }; upgradeButton.down = function () { if (selectedTower) { selectedTower.upgrade(); } }; stopUpgradeButton.down = function () { if (selectedTower) { selectedTower.deselect(); selectedTower = null; upgradeButton.visible = false; stopUpgradeButton.visible = false; } }; cancelBuildButton.down = function () { closeBuildMenu(); }; bubbleOption.down = function () { buildTower('bubbleCannon'); }; seaweedOption.down = function () { buildTower('seaweedTrap'); }; clamOption.down = function () { buildTower('clamMine'); }; sharkyOption.down = function () { buildTower('Sharky'); }; jellyStingerOption.down = function () { buildTower('JellyStinger'); }; game.down = function (x, y, obj) { // Deselect tower when clicking elsewhere if (selectedTower && !upgradeButton.visible) { selectedTower.deselect(); selectedTower = null; upgradeButton.visible = false; stopUpgradeButton.visible = false; } if (!buildMenuOpen) { floppyFish.targetX = x; floppyFish.targetY = y; } }; game.up = function (x, y, obj) { // Nothing specific needed for up events }; // Update logic game.update = function () { // Update floppy fish if (floppyFish) { // Check if floppy fish is colliding with any enemies for (var i = 0; i < enemies.length; i++) { if (floppyFish.intersects(enemies[i])) { enemies[i].takeDamage(5); LK.effects.flashObject(floppyFish, 0xff0000, 200); break; } } } // Update towers for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); } // Update projectiles for (var i = projectiles.length - 1; i >= 0; i--) { var p = projectiles[i]; // Check if target still exists if (!p.target || !p.target.parent) { p.sprite.destroy(); projectiles.splice(i, 1); continue; } // Move towards target var dx = p.target.x - p.sprite.x; var dy = p.target.y - p.sprite.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > p.speed) { p.sprite.x += dx / distance * p.speed; p.sprite.y += dy / distance * p.speed; } else { // Hit target if (p.isAreaDamage) { // Create explosion effect var explosion = LK.getAsset('pearl', { anchorX: 0.5, anchorY: 0.5, x: p.target.x, y: p.target.y, scaleX: 0.5, scaleY: 0.5 }); game.addChild(explosion); // Scale up and fade out for explosion effect tween(explosion, { scaleX: 5, scaleY: 5, alpha: 0 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Deal damage to all enemies in area for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; var dxe = enemy.x - p.target.x; var dye = enemy.y - p.target.y; var dist = Math.sqrt(dxe * dxe + dye * dye); if (dist <= p.areaRadius) { // Deal damage based on distance (more damage closer to center) var damageFactor = 1 - dist / p.areaRadius; var actualDamage = Math.floor(p.damage * damageFactor); enemy.takeDamage(actualDamage); } } } else if (p.isElectric) { // Apply electricity stun effect to the target p.target.takeDamage(p.damage); // Apply stunning effect p.target.stunned = true; p.target.originalSpeed = p.target.speed; p.target.speed = 0; // Create electricity visual effect around the target var electricEffect = LK.getAsset('Electricity', { anchorX: 0.5, anchorY: 0.5, x: p.target.x, y: p.target.y, scaleX: 0.8, scaleY: 0.8, alpha: 0.7 }); game.addChild(electricEffect); p.target.electricEffect = electricEffect; // Make the electricity follow the target var updateElectricity = function updateElectricity() { if (p.target && p.target.parent && electricEffect && electricEffect.parent) { electricEffect.x = p.target.x; electricEffect.y = p.target.y; // Pulsate the effect if (LK.ticks % 10 === 0) { tween(electricEffect, { alpha: electricEffect.alpha > 0.4 ? 0.4 : 0.7 }, { duration: 300 }); } } }; // Store the original update function safely before reassigning it var originalUpdate = p.target.update; // Create a new update function that doesn't cause recursion p.target.update = function () { // Call the original update ONLY if it's not the current function (avoid recursion) if (typeof originalUpdate === 'function' && originalUpdate !== this.update) { originalUpdate.call(this); } updateElectricity(); }; // Apply a tint to show the enemy is electrified tween(p.target, { tint: 0x00ffff }, { duration: 300 }); // Set a timer to remove the stun after duration LK.setTimeout(function () { if (p.target && p.target.parent) { p.target.speed = p.target.originalSpeed; p.target.stunned = false; // Remove the electricity effect if (electricEffect && electricEffect.parent) { electricEffect.destroy(); } // Reset the tint tween(p.target, { tint: 0xffffff }, { duration: 300 }); // Safely restore original update function if (typeof originalUpdate === 'function') { p.target.update = originalUpdate; } else { // Create a fallback update if original was not available p.target.update = function () { // Basic enemy update logic if (p.target.pathIndex < gamePath.length && !p.target.stunned) { var targetPos = gamePath[p.target.pathIndex]; var dx = targetPos.x - p.target.x; var dy = targetPos.y - p.target.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > p.target.speed) { p.target.x += dx / distance * p.target.speed; p.target.y += dy / distance * p.target.speed; } else { p.target.pathIndex++; } } }; } } }, p.stunDuration * 16.67); // Convert frames to milliseconds } else { // Regular projectile just hits target p.target.takeDamage(p.damage); } p.sprite.destroy(); projectiles.splice(i, 1); } } // Check if wave is over if (gameState.waveInProgress && gameState.enemiesLeft <= 0 && enemies.length === 0) { endWave(); } // Update storage data storage.pearls = gameState.pearls; if (gameState.currentWave > storage.currentLevel) { storage.currentLevel = gameState.currentWave; } if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
pearls: 0,
currentLevel: 1
});
/****
* Classes
****/
var BuildSpot = Container.expand(function () {
var self = Container.call(this);
var buildGraphics = self.attachAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
self.occupied = false;
self.tower = null;
self.down = function (x, y, obj) {
if (!self.occupied && gameState.pearls >= 10) {
// Check if any Megalodon is blocking tower building
var towerBlocked = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].constructor.name === "Megalodon" && enemies[i].blockingTowerBuilding) {
towerBlocked = true;
break;
}
}
if (towerBlocked) {
// Show warning message
var blockedText = new Text2("TOWER BUILDING BLOCKED BY MEGALODON!", {
size: 40,
fill: 0xFF0000
});
blockedText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(blockedText);
// Remove warning after 2 seconds
LK.setTimeout(function () {
blockedText.destroy();
}, 2000);
} else {
openBuildMenu(self);
}
}
};
return self;
});
var CoralReef = Container.expand(function () {
var self = Container.call(this);
var coralGraphics = self.attachAsset('coral', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 500);
if (self.health <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.speed = 1.5;
self.damage = 10;
self.pathIndex = 0;
self.pearls = 5;
self.boss = false; // Default enemies are not bosses
// Health bar
var healthBar = LK.getAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 10,
scaleX: 1,
scaleY: 0.2,
y: -40,
tint: 0x00ff00
});
self.addChild(healthBar);
self.update = function () {
// Initialize stunned property if not set
if (self.stunned === undefined) {
self.stunned = false;
}
if (self.pathIndex < gamePath.length && !self.stunned) {
var targetPos = gamePath[self.pathIndex];
var dx = targetPos.x - self.x;
var dy = targetPos.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.speed) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Face the direction of movement
if (dx < 0) {
enemyGraphics.scaleX = -1;
} else {
enemyGraphics.scaleX = 1;
}
} else {
self.pathIndex++;
}
} else if (self.pathIndex >= gamePath.length) {
// Reached the end, damage coral
coralReef.takeDamage(self.damage);
self.destroy();
var index = enemies.indexOf(self);
if (index !== -1) {
enemies.splice(index, 1);
}
}
healthBar.scaleX = self.health / self.maxHealth;
// Visual indicator for stunned state
if (self.stunned) {
// Pulsate alpha when stunned
if (LK.ticks % 15 === 0) {
enemyGraphics.alpha = enemyGraphics.alpha === 1 ? 0.5 : 1;
}
} else {
enemyGraphics.alpha = 1;
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.getSound('enemyHit').play();
if (self.health <= 0) {
gameState.pearls += self.pearls;
pearlText.setText("Pearls: " + gameState.pearls);
LK.getSound('enemyDefeat').play();
// Add to score
LK.setScore(LK.getScore() + 10);
scoreText.setText("Score: " + LK.getScore());
self.destroy();
var index = enemies.indexOf(self);
if (index !== -1) {
enemies.splice(index, 1);
}
}
};
return self;
});
var PlasticBag = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with plastic bag graphic
self.removeChild(self.children[1]); // Remove regular enemy graphic
var plasticBagGraphics = self.attachAsset('PlasticBag', {
anchorX: 0.5,
anchorY: 0.5
});
// Plastic bag properties
self.health = 80;
self.maxHealth = 80;
self.speed = 2.0; // Faster than regular enemies
self.damage = 5; // Less damage
self.pearls = 3;
// Special ability: Sometimes changes direction randomly
self.update = function () {
// Call parent's update logic
Enemy.prototype.update.call(self);
// 2% chance per frame to temporarily speed up
if (Math.random() < 0.02 && !self.speedBoost) {
self.speedBoost = true;
self.originalSpeed = self.speed;
self.speed *= 1.5;
// Reset speed after a short duration
LK.setTimeout(function () {
if (self && self.parent) {
self.speed = self.originalSpeed;
self.speedBoost = false;
}
}, 1000);
}
};
return self;
});
var Megalodon = Enemy.expand(function () {
var self = Enemy.call(this);
// Replace with megalodon graphic
self.removeChild(self.children[1]); // Remove regular enemy graphic
var megalGraphics = self.attachAsset('Megalodon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// Megalodon properties
self.health = 500;
self.maxHealth = 500;
self.speed = 1.0;
self.damage = 30;
self.pearls = 25;
self.boss = true;
// Special ability: regenerate health
self.lastRegenTick = 0;
self.lastSpecialAttackTick = 0;
self.blockingTowerBuilding = false;
// Override update method to add regeneration
var parentUpdate = self.update;
self.update = function () {
// Call parent update first
parentUpdate.call(self);
// Regenerate health every 3 seconds (180 frames)
if (LK.ticks - self.lastRegenTick > 180) {
self.lastRegenTick = LK.ticks;
if (self.health < self.maxHealth) {
self.health = Math.min(self.health + 20, self.maxHealth);
// Show healing effect
LK.effects.flashObject(self, 0x00ff00, 500);
}
}
// Special attack: block tower building every 15 seconds (900 frames)
if (LK.ticks - self.lastSpecialAttackTick > 900) {
// 30% chance to activate special attack when time is ready
if (Math.random() < 0.3) {
self.lastSpecialAttackTick = LK.ticks;
self.blockingTowerBuilding = true;
// Create warning text
var warningText = new Text2("MEGALODON BLOCKS TOWERS!", {
size: 60,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(warningText);
// Flash megalodon in red
LK.effects.flashObject(self, 0xff0000, 1000);
// Duration of blocking: 10 seconds (600 frames)
LK.setTimeout(function () {
self.blockingTowerBuilding = false;
// Create end warning text
var endWarningText = new Text2("TOWER BLOCKING ENDED", {
size: 50,
fill: 0x00FF00
});
endWarningText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endWarningText);
// Remove text after 2 seconds
LK.setTimeout(function () {
endWarningText.destroy();
}, 2000);
}, 10000);
// Remove warning text after 3 seconds
LK.setTimeout(function () {
warningText.destroy();
}, 3000);
}
}
};
return self;
});
var FloppyFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('floppyFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.targetX = null;
self.targetY = null;
self.update = function () {
if (self.targetX !== null && self.targetY !== null) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.speed) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Face the direction of movement
if (dx < 0) {
fishGraphics.scaleX = -1;
} else {
fishGraphics.scaleX = 1;
}
} else {
self.x = self.targetX;
self.y = self.targetY;
self.targetX = null;
self.targetY = null;
}
}
};
return self;
});
var PathTile = Container.expand(function () {
var self = Container.call(this);
var pathGraphics = self.attachAsset('path', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.buildable = false;
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'bubbleCannon';
self.level = 1;
self.range = 250;
self.damage = 25;
self.fireRate = 60; // frames between shots
self.fireCounter = 0;
self.cost = 10;
self.upgradesCost = 15;
var towerGraphics = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Range indicator (only shown when selected)
var rangeIndicator = LK.getAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2,
tint: 0x3498db
});
// Scale the range indicator to match the tower's range
rangeIndicator.scaleX = self.range / 40;
rangeIndicator.scaleY = self.range / 40;
rangeIndicator.visible = false;
self.addChild(rangeIndicator);
self.down = function (x, y, obj) {
// Deselect previous tower
if (selectedTower && selectedTower !== self) {
selectedTower.deselect();
}
selectedTower = self;
self.select();
// Show upgrade UI with cost
upgradeButton.visible = true;
stopUpgradeButton.visible = true;
upgradeText.setText("Upgrade: " + self.upgradesCost + " pearls");
};
self.select = function () {
rangeIndicator.visible = true;
towerGraphics.tint = 0xffff00;
};
self.deselect = function () {
rangeIndicator.visible = false;
towerGraphics.tint = 0xffffff;
};
self.upgrade = function () {
if (gameState.pearls >= self.upgradesCost) {
gameState.pearls -= self.upgradesCost;
pearlText.setText("Pearls: " + gameState.pearls);
self.level++;
self.damage += 15;
self.range += 50;
self.fireRate = Math.max(30, self.fireRate - 10);
self.upgradesCost = Math.floor(self.upgradesCost * 1.5);
// Update range indicator
rangeIndicator.scaleX = self.range / 40;
rangeIndicator.scaleY = self.range / 40;
// Update upgrade button text
upgradeText.setText("Upgrade: " + self.upgradesCost + " pearls");
// Scale up tower slightly to show upgrade
tween(towerGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
});
}
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
return closestEnemy;
};
self.shoot = function (target) {
var bubble = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
});
game.addChild(bubble);
projectiles.push({
sprite: bubble,
target: target,
damage: self.damage,
speed: 7
});
LK.getSound('bubble').play();
};
self.update = function () {
self.fireCounter++;
if (self.fireCounter >= self.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.fireCounter = 0;
}
}
};
return self;
});
var TurtleShell = Tower.expand(function () {
var self = Tower.call(this, 'TurtleShell');
// Override properties for turtle shell
self.range = 300;
self.damage = 30;
self.fireRate = 60;
// Override shoot method for attack
self.shoot = function (target) {
// Create turtle shell projectile
var shell = LK.getAsset('TurtleShellAttack', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
});
game.addChild(shell);
// Create special projectile that will do damage
projectiles.push({
sprite: shell,
target: target,
damage: self.damage,
speed: 8
});
// Play sound (using bubble sound for now)
LK.getSound('bubble').play();
};
return self;
});
var SharkTower = Tower.expand(function () {
var self = Tower.call(this, 'Sharky');
// Override properties for shark tower
self.range = 250;
self.damage = 100;
self.fireRate = 90; // Slower than bubble cannon but high damage
// Override shoot method for instant kill functionality
self.shoot = function (target) {
// Create bite effect (using the bubble as a placeholder for the bite animation)
var bite = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
});
game.addChild(bite);
// Move bite effect to target
projectiles.push({
sprite: bite,
target: target,
damage: target.boss ? self.damage : target.health,
// Instant kill for non-boss enemies
speed: 10 // Faster projectile to simulate bite
});
// Flash target red to indicate it's being bitten
LK.effects.flashObject(target, 0xff0000, 200);
// Play bite sound (using bubble sound for now)
LK.getSound('bubble').play();
};
return self;
});
var SeaweedTrap = Tower.expand(function () {
var self = Tower.call(this, 'seaweedTrap');
// Override properties for seaweed trap
self.range = 200;
self.damage = 5;
self.fireRate = 60;
self.stunDuration = 60; // 1 second stun (60 frames)
// Override shoot method for stun functionality
self.shoot = function (target) {
// Apply stun effect to the target
if (!target.stunned) {
target.stunned = true;
target.originalSpeed = target.speed;
target.speed = 0;
// Make the seaweed trap "stare" at the enemy by rotating to face it
var dx = target.x - self.x;
var dy = target.y - self.y;
var angle = Math.atan2(dy, dx);
// Use tween to rotate the tower to face the enemy
tween(self.children[0], {
rotation: angle
}, {
duration: 300,
easing: tween.easeOut
});
// Flash the target to indicate stun
LK.effects.flashObject(target, 0x00ff00, 300);
// Set a timer to remove the stun after duration
LK.setTimeout(function () {
if (target && target.parent) {
target.speed = target.originalSpeed;
target.stunned = false;
}
}, self.stunDuration * 16.67); // Convert frames to milliseconds (60fps ≈ 16.67ms per frame)
}
target.takeDamage(self.damage);
LK.getSound('bubble').play();
};
return self;
});
var JellyStinger = Tower.expand(function () {
var self = Tower.call(this, 'JellyStinger');
// Override properties for jelly stinger
self.range = 230;
self.damage = 15;
self.fireRate = 90;
self.stunDuration = 1800; // 30 seconds (60fps * 30)
// Override shoot method for electricity stunning
self.shoot = function (target) {
// Create electricity effect
var electricity = LK.getAsset('Electricity', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
alpha: 0.8
});
game.addChild(electricity);
// Create special projectile that will apply electricity stun
projectiles.push({
sprite: electricity,
target: target,
damage: self.damage,
speed: 8,
isElectric: true,
stunDuration: self.stunDuration
});
// Play sound (using bubble sound for now)
LK.getSound('bubble').play();
};
return self;
});
var ClamMine = Tower.expand(function () {
var self = Tower.call(this, 'clamMine');
// Override properties for clam mine
self.range = 200;
self.damage = 40;
self.fireRate = 120; // Slower fire rate but area damage
self.areaRadius = 150; // Radius of explosion
// Override shoot method for area damage
self.shoot = function (target) {
// Create pearl projectile
var pearl = LK.getAsset('pearl', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
});
game.addChild(pearl);
// Create special projectile that will explode
projectiles.push({
sprite: pearl,
target: target,
damage: self.damage,
speed: 5,
isAreaDamage: true,
areaRadius: self.areaRadius
});
LK.getSound('bubble').play();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a5276
});
/****
* Game Code
****/
// Music
// Sounds
// Items and effects
// Towers and defenses
// Base characters
// Play music
LK.playMusic('underwaterTheme');
// Game variables
var gameState = {
pearls: 100,
currentWave: 1,
maxWaves: 100,
waveInProgress: false,
enemiesLeft: 0
};
var selectedTower = null;
var buildMenuOpen = false;
var gamePath = [];
var buildSpots = [];
var enemies = [];
var towers = [];
var projectiles = [];
var coralReef;
var floppyFish;
// UI Elements
var scoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -scoreText.width - 20;
scoreText.y = 20;
var pearlText = new Text2("Pearls: " + gameState.pearls, {
size: 50,
fill: 0xFFFFFF
});
pearlText.anchor.set(0, 0);
LK.gui.topRight.addChild(pearlText);
pearlText.x = -pearlText.width - 20;
pearlText.y = 80;
var waveText = new Text2("Wave: " + gameState.currentWave + "/" + gameState.maxWaves, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
waveText.y = 20;
// Upgrade button (hidden initially)
var upgradeButton = LK.getAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 80,
tint: 0x2ecc71
});
upgradeButton.visible = false;
LK.gui.bottom.addChild(upgradeButton);
upgradeButton.y = -100;
var upgradeText = new Text2("Upgrade: 15 pearls", {
size: 40,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeButton.addChild(upgradeText);
// Stop upgrading button (hidden initially)
var stopUpgradeButton = LK.getAsset('Stopupgrade', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
tint: 0xff0000
});
stopUpgradeButton.visible = false;
LK.gui.bottom.addChild(stopUpgradeButton);
stopUpgradeButton.x = 200;
stopUpgradeButton.y = -100;
// Start wave button
var startWaveButton = LK.getAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 80,
tint: 0x3498db
});
LK.gui.bottom.addChild(startWaveButton);
startWaveButton.y = -200;
var startWaveText = new Text2("Start Wave", {
size: 40,
fill: 0xFFFFFF
});
startWaveText.anchor.set(0.5, 0.5);
startWaveButton.addChild(startWaveText);
// Build menu (hidden initially)
var buildMenu = new Container();
buildMenu.visible = false;
LK.gui.center.addChild(buildMenu);
var buildMenuBackground = LK.getAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 500,
tint: 0x34495e,
alpha: 0.9
});
buildMenu.addChild(buildMenuBackground);
var buildMenuTitle = new Text2("Build Tower", {
size: 50,
fill: 0xFFFFFF
});
buildMenuTitle.anchor.set(0.5, 0);
buildMenuTitle.y = -200;
buildMenu.addChild(buildMenuTitle);
// Tower options in build menu
var bubbleOption = LK.getAsset('bubbleCannon', {
anchorX: 0.5,
anchorY: 0.5,
x: -100,
y: -100
});
buildMenu.addChild(bubbleOption);
var bubbleText = new Text2("Bubble\nCannon\n10 pearls", {
size: 30,
fill: 0xFFFFFF
});
bubbleText.anchor.set(0.5, 0);
bubbleText.y = 50;
bubbleOption.addChild(bubbleText);
var seaweedOption = LK.getAsset('seaweedTrap', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: -100
});
buildMenu.addChild(seaweedOption);
var seaweedText = new Text2("Seaweed\nTrap\n10 pearls", {
size: 30,
fill: 0xFFFFFF
});
seaweedText.anchor.set(0.5, 0);
seaweedText.y = 50;
seaweedOption.addChild(seaweedText);
var clamOption = LK.getAsset('clamMine', {
anchorX: 0.5,
anchorY: 0.5,
x: -100,
y: 50
});
buildMenu.addChild(clamOption);
var clamText = new Text2("Clam\nMine\n10 pearls", {
size: 30,
fill: 0xFFFFFF
});
clamText.anchor.set(0.5, 0);
clamText.y = 50;
clamOption.addChild(clamText);
var sharkyOption = LK.getAsset('Sharky', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 50
});
buildMenu.addChild(sharkyOption);
var sharkyText = new Text2("Sharky\nBiter\n10 pearls", {
size: 30,
fill: 0xFFFFFF
});
sharkyText.anchor.set(0.5, 0);
sharkyText.y = 50;
sharkyOption.addChild(sharkyText);
var jellyStingerOption = LK.getAsset('JellyStinger', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 150
});
buildMenu.addChild(jellyStingerOption);
var jellyStingerText = new Text2("Jelly\nStinger\n10 pearls", {
size: 30,
fill: 0xFFFFFF
});
jellyStingerText.anchor.set(0.5, 0);
jellyStingerText.y = 50;
jellyStingerOption.addChild(jellyStingerText);
var cancelBuildButton = LK.getAsset('buildPlaceholder', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 60,
y: 150,
tint: 0xe74c3c
});
buildMenu.addChild(cancelBuildButton);
var cancelText = new Text2("Cancel", {
size: 40,
fill: 0xFFFFFF
});
cancelText.anchor.set(0.5, 0.5);
cancelBuildButton.addChild(cancelText);
// Initialize the map
function initializeMap() {
// Create path
var pathPoints = [{
x: 0,
y: 600
}, {
x: 400,
y: 600
}, {
x: 400,
y: 1000
}, {
x: 800,
y: 1000
}, {
x: 800,
y: 500
}, {
x: 1200,
y: 500
}, {
x: 1200,
y: 1200
}, {
x: 1600,
y: 1200
}, {
x: 1600,
y: 800
}, {
x: 2000,
y: 800
}];
// Create path tiles
for (var i = 0; i < pathPoints.length; i++) {
gamePath.push(pathPoints[i]);
var pathTile = new PathTile();
pathTile.x = pathPoints[i].x;
pathTile.y = pathPoints[i].y;
game.addChild(pathTile);
// Create build spots along the path (offset from path)
if (i < pathPoints.length - 1) {
var dx = pathPoints[i + 1].x - pathPoints[i].x;
var dy = pathPoints[i + 1].y - pathPoints[i].y;
var len = Math.sqrt(dx * dx + dy * dy);
// Normalize and rotate 90 degrees for perpendicular
var perpX = -dy / len;
var perpY = dx / len;
// Create build spots along segments
var segments = Math.floor(len / 200);
for (var j = 0; j <= segments; j++) {
var segX = pathPoints[i].x + dx * j / segments;
var segY = pathPoints[i].y + dy * j / segments;
// Create build spots on both sides of path
var buildSpot1 = new BuildSpot();
buildSpot1.x = segX + perpX * 120;
buildSpot1.y = segY + perpY * 120;
game.addChild(buildSpot1);
buildSpots.push(buildSpot1);
var buildSpot2 = new BuildSpot();
buildSpot2.x = segX - perpX * 120;
buildSpot2.y = segY - perpY * 120;
game.addChild(buildSpot2);
buildSpots.push(buildSpot2);
}
}
}
// Add coral reef at the end of the path
coralReef = new CoralReef();
coralReef.x = 2048 - 100;
coralReef.y = 800;
game.addChild(coralReef);
// Add floppy fish
floppyFish = new FloppyFish();
floppyFish.x = 1024;
floppyFish.y = 1366;
game.addChild(floppyFish);
}
// Initialize the game
initializeMap();
// Functions
function openBuildMenu(buildSpot) {
buildMenuOpen = true;
buildMenu.visible = true;
buildMenu.currentBuildSpot = buildSpot;
// If the player has insufficient pearls, disable options
bubbleOption.alpha = gameState.pearls >= 10 ? 1 : 0.5;
seaweedOption.alpha = gameState.pearls >= 10 ? 1 : 0.5;
clamOption.alpha = gameState.pearls >= 10 ? 1 : 0.5;
sharkyOption.alpha = gameState.pearls >= 10 ? 1 : 0.5;
jellyStingerOption.alpha = gameState.pearls >= 10 ? 1 : 0.5;
}
function closeBuildMenu() {
buildMenuOpen = false;
buildMenu.visible = false;
buildMenu.currentBuildSpot = null;
}
function buildTower(type) {
if (gameState.pearls < 10) {
return;
}
var buildSpot = buildMenu.currentBuildSpot;
if (!buildSpot || buildSpot.occupied) {
return;
}
// Check if any Megalodon is blocking tower building
var towerBlocked = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].constructor.name === "Megalodon" && enemies[i].blockingTowerBuilding) {
towerBlocked = true;
break;
}
}
if (towerBlocked) {
// Show warning message
var blockedText = new Text2("TOWER BUILDING BLOCKED BY MEGALODON!", {
size: 40,
fill: 0xFF0000
});
blockedText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(blockedText);
// Remove warning after 2 seconds
LK.setTimeout(function () {
blockedText.destroy();
}, 2000);
// Close build menu without building tower
closeBuildMenu();
return;
}
gameState.pearls -= 10;
pearlText.setText("Pearls: " + gameState.pearls);
var tower;
if (type === 'seaweedTrap') {
tower = new SeaweedTrap();
} else if (type === 'Sharky') {
tower = new SharkTower();
} else if (type === 'clamMine') {
tower = new ClamMine();
} else if (type === 'JellyStinger') {
tower = new JellyStinger();
} else {
tower = new Tower(type);
}
tower.x = buildSpot.x;
tower.y = buildSpot.y;
game.addChild(tower);
buildSpot.occupied = true;
buildSpot.tower = tower;
towers.push(tower);
LK.getSound('buildTower').play();
// Spawn an enemy if a wave is in progress
if (gameState.waveInProgress) {
// Immediate spawn of enemy after tower placement
spawnEnemy();
}
closeBuildMenu();
}
function spawnEnemy() {
if (gameState.enemiesLeft <= 0) {
return;
}
// Determine if this should be a Megalodon boss (at wave 20 or every 3rd wave, and last enemy of the wave)
var isMegalodon = (gameState.currentWave === 20 || gameState.currentWave % 3 === 0) && gameState.enemiesLeft === 1 && enemies.length === 0;
var enemy;
if (isMegalodon) {
// Create Megalodon
enemy = new Megalodon();
// Announce Megalodon arrival
var bossText = new Text2("MEGALODON INCOMING!", {
size: 80,
fill: 0xFF0000
});
bossText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(bossText);
// Flash screen and remove announcement after delay
LK.effects.flashScreen(0xFF0000, 500);
LK.setTimeout(function () {
bossText.destroy();
}, 2000);
} else {
// Create regular enemy
// 25% chance to create a plastic bag instead of regular enemy
if (Math.random() < 0.25) {
enemy = new Enemy();
// Override with plastic bag graphics
enemy.removeChild(enemy.children[1]); // Remove regular enemy graphic
var plasticBagGraphics = enemy.attachAsset('PlasticBag', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
enemy = new Enemy();
}
// Randomly make some regular enemies bosses
var isBoss = Math.random() < 0.2; // 20% chance for boss
enemy.boss = isBoss;
// Increase enemy stats based on wave
enemy.health = 100 + (gameState.currentWave - 1) * 20;
// Boss enemies have double health
if (enemy.boss) {
enemy.health *= 2;
enemy.scaleX = 1.5;
enemy.scaleY = 1.5;
enemy.pearls *= 2;
}
}
// Check if this is a plastic bag (has PlasticBag as child)
var isPlasticBag = enemy.children.some(function (child) {
return child.assetId === 'PlasticBag';
});
var spawnX, spawnY, pathIndex;
// Always spawn at start of track (first path point)
spawnX = gamePath[0].x;
spawnY = gamePath[0].y;
pathIndex = 0;
// If not a plastic bag, we can spawn at different locations
if (!isPlasticBag) {
// Spawn near a tower if there are towers (for non-plastic bag enemies)
var spawnNearTower = towers.length > 0;
if (spawnNearTower) {
// Choose a random tower to spawn near
var randomTower = towers[Math.floor(Math.random() * towers.length)];
// Find the closest path segment to this tower
var minDistance = Infinity;
var closestSegment = 0;
for (var i = 0; i < gamePath.length - 1; i++) {
var dx = gamePath[i].x - randomTower.x;
var dy = gamePath[i].y - randomTower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
closestSegment = i;
}
}
// Use the closest path segment
spawnX = gamePath[closestSegment].x;
spawnY = gamePath[closestSegment].y;
pathIndex = closestSegment;
} else {
// No towers, use random path segment as before
var randomPathSegment = Math.floor(Math.random() * (gamePath.length - 1));
spawnX = gamePath[randomPathSegment].x;
spawnY = gamePath[randomPathSegment].y;
pathIndex = randomPathSegment;
}
}
enemy.x = spawnX;
enemy.y = spawnY;
enemy.pathIndex = pathIndex; // Set the path index to start from the chosen segment
// Adjust regular enemy stats based on wave
if (!isMegalodon) {
enemy.maxHealth = enemy.health;
enemy.speed = 1.5 + (gameState.currentWave - 1) * 0.1;
enemy.pearls = 5 + Math.floor((gameState.currentWave - 1) / 2);
}
game.addChild(enemy);
enemies.push(enemy);
gameState.enemiesLeft--;
// Schedule next enemy
if (gameState.enemiesLeft > 0) {
LK.setTimeout(spawnEnemy, 1000);
}
}
function startWave() {
if (gameState.waveInProgress) {
return;
}
gameState.waveInProgress = true;
gameState.enemiesLeft = 5 + gameState.currentWave * 2;
startWaveButton.tint = 0x95a5a6; // Gray out button
// Update wave text
waveText.setText("Wave: " + gameState.currentWave + "/" + gameState.maxWaves);
// Start spawning enemies
spawnEnemy();
}
function endWave() {
gameState.waveInProgress = false;
gameState.currentWave++;
if (gameState.currentWave > gameState.maxWaves) {
// Game won
LK.showYouWin();
return;
}
// Update wave text
waveText.setText("Wave: " + gameState.currentWave + "/" + gameState.maxWaves);
startWaveButton.tint = 0x3498db; // Blue button
}
// Event handlers
startWaveButton.down = function () {
if (!gameState.waveInProgress) {
startWave();
}
};
upgradeButton.down = function () {
if (selectedTower) {
selectedTower.upgrade();
}
};
stopUpgradeButton.down = function () {
if (selectedTower) {
selectedTower.deselect();
selectedTower = null;
upgradeButton.visible = false;
stopUpgradeButton.visible = false;
}
};
cancelBuildButton.down = function () {
closeBuildMenu();
};
bubbleOption.down = function () {
buildTower('bubbleCannon');
};
seaweedOption.down = function () {
buildTower('seaweedTrap');
};
clamOption.down = function () {
buildTower('clamMine');
};
sharkyOption.down = function () {
buildTower('Sharky');
};
jellyStingerOption.down = function () {
buildTower('JellyStinger');
};
game.down = function (x, y, obj) {
// Deselect tower when clicking elsewhere
if (selectedTower && !upgradeButton.visible) {
selectedTower.deselect();
selectedTower = null;
upgradeButton.visible = false;
stopUpgradeButton.visible = false;
}
if (!buildMenuOpen) {
floppyFish.targetX = x;
floppyFish.targetY = y;
}
};
game.up = function (x, y, obj) {
// Nothing specific needed for up events
};
// Update logic
game.update = function () {
// Update floppy fish
if (floppyFish) {
// Check if floppy fish is colliding with any enemies
for (var i = 0; i < enemies.length; i++) {
if (floppyFish.intersects(enemies[i])) {
enemies[i].takeDamage(5);
LK.effects.flashObject(floppyFish, 0xff0000, 200);
break;
}
}
}
// Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var p = projectiles[i];
// Check if target still exists
if (!p.target || !p.target.parent) {
p.sprite.destroy();
projectiles.splice(i, 1);
continue;
}
// Move towards target
var dx = p.target.x - p.sprite.x;
var dy = p.target.y - p.sprite.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > p.speed) {
p.sprite.x += dx / distance * p.speed;
p.sprite.y += dy / distance * p.speed;
} else {
// Hit target
if (p.isAreaDamage) {
// Create explosion effect
var explosion = LK.getAsset('pearl', {
anchorX: 0.5,
anchorY: 0.5,
x: p.target.x,
y: p.target.y,
scaleX: 0.5,
scaleY: 0.5
});
game.addChild(explosion);
// Scale up and fade out for explosion effect
tween(explosion, {
scaleX: 5,
scaleY: 5,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Deal damage to all enemies in area
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
var dxe = enemy.x - p.target.x;
var dye = enemy.y - p.target.y;
var dist = Math.sqrt(dxe * dxe + dye * dye);
if (dist <= p.areaRadius) {
// Deal damage based on distance (more damage closer to center)
var damageFactor = 1 - dist / p.areaRadius;
var actualDamage = Math.floor(p.damage * damageFactor);
enemy.takeDamage(actualDamage);
}
}
} else if (p.isElectric) {
// Apply electricity stun effect to the target
p.target.takeDamage(p.damage);
// Apply stunning effect
p.target.stunned = true;
p.target.originalSpeed = p.target.speed;
p.target.speed = 0;
// Create electricity visual effect around the target
var electricEffect = LK.getAsset('Electricity', {
anchorX: 0.5,
anchorY: 0.5,
x: p.target.x,
y: p.target.y,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.7
});
game.addChild(electricEffect);
p.target.electricEffect = electricEffect;
// Make the electricity follow the target
var updateElectricity = function updateElectricity() {
if (p.target && p.target.parent && electricEffect && electricEffect.parent) {
electricEffect.x = p.target.x;
electricEffect.y = p.target.y;
// Pulsate the effect
if (LK.ticks % 10 === 0) {
tween(electricEffect, {
alpha: electricEffect.alpha > 0.4 ? 0.4 : 0.7
}, {
duration: 300
});
}
}
};
// Store the original update function safely before reassigning it
var originalUpdate = p.target.update;
// Create a new update function that doesn't cause recursion
p.target.update = function () {
// Call the original update ONLY if it's not the current function (avoid recursion)
if (typeof originalUpdate === 'function' && originalUpdate !== this.update) {
originalUpdate.call(this);
}
updateElectricity();
};
// Apply a tint to show the enemy is electrified
tween(p.target, {
tint: 0x00ffff
}, {
duration: 300
});
// Set a timer to remove the stun after duration
LK.setTimeout(function () {
if (p.target && p.target.parent) {
p.target.speed = p.target.originalSpeed;
p.target.stunned = false;
// Remove the electricity effect
if (electricEffect && electricEffect.parent) {
electricEffect.destroy();
}
// Reset the tint
tween(p.target, {
tint: 0xffffff
}, {
duration: 300
});
// Safely restore original update function
if (typeof originalUpdate === 'function') {
p.target.update = originalUpdate;
} else {
// Create a fallback update if original was not available
p.target.update = function () {
// Basic enemy update logic
if (p.target.pathIndex < gamePath.length && !p.target.stunned) {
var targetPos = gamePath[p.target.pathIndex];
var dx = targetPos.x - p.target.x;
var dy = targetPos.y - p.target.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > p.target.speed) {
p.target.x += dx / distance * p.target.speed;
p.target.y += dy / distance * p.target.speed;
} else {
p.target.pathIndex++;
}
}
};
}
}
}, p.stunDuration * 16.67); // Convert frames to milliseconds
} else {
// Regular projectile just hits target
p.target.takeDamage(p.damage);
}
p.sprite.destroy();
projectiles.splice(i, 1);
}
}
// Check if wave is over
if (gameState.waveInProgress && gameState.enemiesLeft <= 0 && enemies.length === 0) {
endWave();
}
// Update storage data
storage.pearls = gameState.pearls;
if (gameState.currentWave > storage.currentLevel) {
storage.currentLevel = gameState.currentWave;
}
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
};
Floppy Fish the fish. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows. Floppy Fish
Coral with seeweed inside of it. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Seeweed with eyes. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Bubbles. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A cannon made out of a bubble. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A clam. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A shark. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A pearl. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A sleeping fish. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Friendly shark. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Megalodon shark. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Jellyfish. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Electricity. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Button that says, stop upgrading. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Plastic bag. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Turtle. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Turtle shell. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows