/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 1; self.targetX = 0; self.targetY = 0; self.update = function () { 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 = self.targetX; self.y = self.targetY; } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Add bullet glow animation if (!self.glowInitialized) { self.glowInitialized = true; tween(graphics, { alpha: 0.3 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(graphics, { alpha: 1 }, { duration: 300, easing: tween.easeInOut }); } }); } }; return self; }); var DefensePlant = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('sunpetalSentinel', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 5; self.maxHp = 5; self.shootTimer = 0; self.shootCooldown = 60; self.range = 150; self.update = function () { self.shootTimer--; if (self.shootTimer <= 0) { var nearestEnemy = self.findNearestEnemy(); if (nearestEnemy) { self.shoot(nearestEnemy); self.shootTimer = self.shootCooldown; } } }; self.findNearestEnemy = function () { var nearest = null; var nearestDistance = 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 < nearestDistance) { nearest = enemy; nearestDistance = distance; } } return nearest; }; self.shoot = function (target) { var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); // Add plant shooting animation texture tween(self, { scaleX: 1.2, scaleY: 1.2, tint: 0xffff88 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1, tint: 0xffffff }, { duration: 200, easing: tween.easeIn }); } }); // Add muzzle flash effect var muzzleFlash = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 2, scaleY: 2, alpha: 0.7, tint: 0xffff00 }); game.addChild(muzzleFlash); tween(muzzleFlash, { scaleX: 0.5, scaleY: 0.5, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { muzzleFlash.destroy(); } }); }; self.takeDamage = function (damage) { self.hp -= damage; if (self.hp <= 0) { return true; } return false; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('slimeSnail', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 3; self.maxHp = 3; self.speed = 1; self.targetX = 1024; self.targetY = 1366; self.isStasised = false; self.stasisTimer = 0; self.update = function () { if (self.isStasised) { self.stasisTimer--; if (self.stasisTimer <= 0) { self.isStasised = false; graphics.tint = 0xFFFFFF; } return; } var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 10) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; self.takeDamage = function (damage) { self.hp -= damage; // Check for low health state if (self.hp <= self.maxHp * 0.33 && self.hp > 0 && !self.lowHealthEffect) { self.lowHealthEffect = true; // Start pulsing red effect for low health self.pulseHealthEffect(); } if (self.hp <= 0) { chronoEssence += 5; LK.getSound('enemy_death').play(); return true; } return false; }; self.pulseHealthEffect = function () { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xff3333, scaleX: 1.1, scaleY: 1.1 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xffffff, scaleX: 1, scaleY: 1 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { // Continue pulsing if still alive and low health if (self.hp > 0 && self.hp <= self.maxHp * 0.33) { self.pulseHealthEffect(); } else { self.lowHealthEffect = false; } } }); } } }); } }; self.applyStasis = function (duration) { self.isStasised = true; self.stasisTimer = duration; graphics.tint = 0x2196F3; }; return self; }); var FastEnemy = Enemy.expand(function () { var self = Enemy.call(this); var graphics = self.attachAsset('sporeRunner', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 2; self.maxHp = 2; self.speed = 2.5; self.pulseHealthEffect = function () { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xff3333, scaleX: 1.15, scaleY: 1.15 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xffffff, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.hp <= self.maxHp * 0.33) { self.pulseHealthEffect(); } else { self.lowHealthEffect = false; } } }); } } }); } }; return self; }); var BossEnemy = Enemy.expand(function () { var self = Enemy.call(this); var graphics = self.attachAsset('invaderWeed', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 8; self.maxHp = 8; self.speed = 0.8; self.takeDamage = function (damage) { self.hp -= damage; // Check for low health state with different threshold for boss if (self.hp <= self.maxHp * 0.5 && self.hp > 0 && !self.lowHealthEffect) { self.lowHealthEffect = true; self.pulseHealthEffect(); } if (self.hp <= 0) { chronoEssence += 20; LK.getSound('enemy_death').play(); return true; } return false; }; self.pulseHealthEffect = function () { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xff1111, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xffffff, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.hp <= self.maxHp * 0.5) { self.pulseHealthEffect(); } else { self.lowHealthEffect = false; } } }); } } }); } }; return self; }); var PlantingZone = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('plantingZone', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); self.occupied = false; self.plantType = null; self.down = function (x, y, obj) { if (!self.occupied && gameState === 'planning') { showPlantMenu(self); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('playerGardener', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 100; self.maxHp = 100; self.rewindCooldown = 0; self.stasisCooldown = 0; self.bloomCooldown = 0; self.moveTarget = null; self.isMoving = false; self.isAttacking = false; self.attackTarget = null; self.attackTimer = 0; self.moveSpeed = 4; self.lowHealthEffect = false; self.invulnerabilityTimer = 0; self.update = function () { if (self.rewindCooldown > 0) self.rewindCooldown--; if (self.stasisCooldown > 0) self.stasisCooldown--; if (self.bloomCooldown > 0) self.bloomCooldown--; // Handle movement if (self.isMoving && self.moveTarget) { var dx = self.moveTarget.x - self.x; var dy = self.moveTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.moveSpeed) { self.x = self.moveTarget.x; self.y = self.moveTarget.y; self.isMoving = false; self.moveTarget = null; } else { self.x += dx / distance * self.moveSpeed; self.y += dy / distance * self.moveSpeed; } } // Handle attacking if (self.isAttacking && self.attackTimer > 0) { self.attackTimer--; if (self.attackTimer <= 0) { if (self.attackTarget && enemies.indexOf(self.attackTarget) !== -1) { // Deal damage to enemy with visual feedback if (self.attackTarget && self.attackTarget.parent && !self.attackTarget.destroyed) { var currentTarget = self.attackTarget; // Store reference to avoid null issues tween(currentTarget, { tint: 0xff0000, scaleX: 0.8, scaleY: 0.8 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { if (currentTarget && currentTarget.parent && !currentTarget.destroyed) { tween(currentTarget, { tint: 0xffffff, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } } }); } if (self.attackTarget.takeDamage(2)) { var index = enemies.indexOf(self.attackTarget); if (index !== -1) { // Add destruction effect LK.effects.flashObject(self.attackTarget, 0xffffff, 200); self.attackTarget.destroy(); enemies.splice(index, 1); } } } self.isAttacking = false; self.attackTarget = null; } } }; self.useRewind = function () { if (self.rewindCooldown <= 0 && chronoEssence >= 15) { chronoEssence -= 15; self.rewindCooldown = 300; // Heal all plants with visual effect for (var i = 0; i < defensePlants.length; i++) { defensePlants[i].hp = defensePlants[i].maxHp; LK.effects.flashObject(defensePlants[i], 0x4CAF50, 300); } // Push back enemies with visual effect for (var i = 0; i < enemies.length; i++) { enemies[i].x -= 100; enemies[i].y -= 50; LK.effects.flashObject(enemies[i], 0xFFFFFF, 200); } LK.getSound('rewind').play(); LK.effects.flashScreen(0x4CAF50, 500); return true; } return false; }; self.useStasis = function (x, y) { if (self.stasisCooldown <= 0 && chronoEssence >= 10) { chronoEssence -= 10; self.stasisCooldown = 240; var stasisRadius = 120; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - x; var dy = enemy.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < stasisRadius) { enemy.applyStasis(180); } } var stasisEffect = LK.getAsset('stasisField', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, alpha: 0.3 }); game.addChild(stasisEffect); tween(stasisEffect, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { stasisEffect.destroy(); } }); LK.getSound('stasis').play(); return true; } return false; }; self.takeDamage = function (damage) { if (self.invulnerabilityTimer > 0) return false; self.hp -= damage; self.invulnerabilityTimer = 60; // 1 second of invulnerability // Visual damage feedback tween(graphics, { tint: 0xff3333, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(graphics, { tint: 0xffffff, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); // Check for low health state if (self.hp <= self.maxHp * 0.25 && self.hp > 0 && !self.lowHealthEffect) { self.lowHealthEffect = true; self.pulseHealthEffect(); } // Flash screen red LK.effects.flashScreen(0xff0000, 300); if (self.hp <= 0) { self.hp = 0; // Ensure hp doesn't go below zero gameState = 'game_over'; LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return true; } return false; }; self.pulseHealthEffect = function () { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xff6666, alpha: 0.7 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.lowHealthEffect) { tween(graphics, { tint: 0xffffff, alpha: 1 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (self.hp > 0 && self.hp <= self.maxHp * 0.25) { self.pulseHealthEffect(); } else { self.lowHealthEffect = false; } } }); } } }); } }; return self; }); var VinePlant = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('stickyVineSnare', { anchorX: 0.5, anchorY: 0.5 }); self.hp = 3; self.maxHp = 3; self.slowEffect = 0.5; self.range = 100; self.update = function () { 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 < self.range && !enemy.isStasised) { enemy.speed = Math.max(0.3, enemy.speed * self.slowEffect); } } }; self.takeDamage = function (damage) { self.hp -= damage; if (self.hp <= 0) { return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2E7D32 }); /**** * Game Code ****/ // Game variables // Game assets - automatically created by LK engine var gameState = 'planning'; // 'planning', 'combat', 'wave_complete' var currentWave = 1; var maxWaves = 5; var chronoEssence = 50; var waveTimer = 0; var enemySpawnTimer = 0; var wavesPerformed = 0; // Game objects var player = null; var enemies = []; var bullets = []; var defensePlants = []; var plantingZones = []; // UI elements var essenceText = null; var healthText = null; var waveText = null; var stateText = null; var rewindButton = null; var stasisButton = null; var plantMenuVisible = false; var selectedZone = null; // Wave configurations var waveConfigs = [{ enemies: [{ type: 'basic', count: 5 }], delay: 60 }, { enemies: [{ type: 'basic', count: 7 }, { type: 'fast', count: 2 }], delay: 45 }, { enemies: [{ type: 'basic', count: 5 }, { type: 'fast', count: 4 }], delay: 40 }, { enemies: [{ type: 'basic', count: 8 }, { type: 'fast', count: 3 }, { type: 'boss', count: 1 }], delay: 35 }, { enemies: [{ type: 'basic', count: 10 }, { type: 'fast', count: 5 }, { type: 'boss', count: 2 }], delay: 30 }]; // Initialize UI function initializeUI() { essenceText = new Text2('Essence: 50', { size: 50, fill: '#9C27B0' }); essenceText.anchor.set(0, 0); LK.gui.topLeft.addChild(essenceText); essenceText.x = 120; essenceText.y = 20; healthText = new Text2('Health: 100/100', { size: 50, fill: '#4CAF50' }); healthText.anchor.set(0, 0); LK.gui.topLeft.addChild(healthText); healthText.x = 120; healthText.y = 80; waveText = new Text2('Wave: 1/5', { size: 50, fill: '#FFFFFF' }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); waveText.y = 20; stateText = new Text2('Planning Phase - Place your defenses!', { size: 40, fill: '#4CAF50' }); stateText.anchor.set(0.5, 0); LK.gui.top.addChild(stateText); stateText.y = 80; // Ability buttons rewindButton = new Text2('Rewind (15)', { size: 35, fill: '#4CAF50' }); rewindButton.anchor.set(0, 1); LK.gui.bottomLeft.addChild(rewindButton); rewindButton.x = 20; rewindButton.y = -20; stasisButton = new Text2('Stasis (10)', { size: 35, fill: '#2196F3' }); stasisButton.anchor.set(0, 1); LK.gui.bottomLeft.addChild(stasisButton); stasisButton.x = 20; stasisButton.y = -70; } // Initialize game objects function initializeGame() { // Create player player = new Player(); player.x = 1024; player.y = 2400; game.addChild(player); // Create planting zones for (var row = 0; row < 6; row++) { for (var col = 0; col < 4; col++) { var zone = new PlantingZone(); zone.x = 400 + col * 200; zone.y = 400 + row * 200; plantingZones.push(zone); game.addChild(zone); } } initializeUI(); } // Plant menu functions function showPlantMenu(zone) { if (plantMenuVisible) return; selectedZone = zone; plantMenuVisible = true; // Create plant options createPlantOption('Sunpetal (20)', 1024, 1800, 'sunpetal', 20); createPlantOption('Vine Snare (15)', 1024, 1900, 'vine', 15); } function createPlantOption(text, x, y, plantType, cost) { var option = new Text2(text, { size: 40, fill: chronoEssence >= cost ? '#FFFFFF' : '#888888' }); option.anchor.set(0.5, 0.5); option.x = x; option.y = y; option.plantType = plantType; option.cost = cost; option.down = function () { if (chronoEssence >= option.cost && selectedZone) { plantDefense(selectedZone, option.plantType, option.cost); hidePlantMenu(); } }; game.addChild(option); return option; } function hidePlantMenu() { plantMenuVisible = false; selectedZone = null; // Remove plant option UI elements for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child.plantType !== undefined) { child.destroy(); } } } function plantDefense(zone, plantType, cost) { if (zone.occupied || chronoEssence < cost) return; chronoEssence -= cost; zone.occupied = true; var plant = null; if (plantType === 'sunpetal') { plant = new DefensePlant(); } else if (plantType === 'vine') { plant = new VinePlant(); } if (plant) { plant.x = zone.x; plant.y = zone.y; plant.zone = zone; defensePlants.push(plant); game.addChild(plant); LK.getSound('plant').play(); } } // Wave management function startWave() { if (currentWave > maxWaves) { gameState = 'victory'; stateText.setText('Victory! All waves defeated!'); return; } gameState = 'combat'; stateText.setText('Wave ' + currentWave + ' - Defend your garden!'); waveTimer = 0; enemySpawnTimer = 0; wavesPerformed = 0; } function spawnEnemies() { var config = waveConfigs[currentWave - 1]; if (!config) return; for (var i = 0; i < config.enemies.length; i++) { var enemyConfig = config.enemies[i]; for (var j = 0; j < enemyConfig.count; j++) { var enemy = null; if (enemyConfig.type === 'basic') { enemy = new Enemy(); } else if (enemyConfig.type === 'fast') { enemy = new FastEnemy(); } else if (enemyConfig.type === 'boss') { enemy = new BossEnemy(); } if (enemy) { enemy.x = Math.random() * 2048; enemy.y = -100 - j * 50; enemies.push(enemy); game.addChild(enemy); } } } } function updateGame() { // Check game over condition first if (gameState === 'game_over') { return; // Stop all game logic when game is over } // Check victory condition first if (currentWave > maxWaves && enemies.length === 0 && gameState !== 'victory') { gameState = 'victory'; stateText.setText('Victory! All waves defeated!'); LK.effects.flashScreen(0x4CAF50, 2000); LK.setTimeout(function () { LK.showYouWin(); }, 1000); return; } // Update UI essenceText.setText('Essence: ' + chronoEssence); healthText.setText('Health: ' + player.hp + '/' + player.maxHp); // Update health text color based on health percentage var healthPercent = player.hp / player.maxHp; if (healthPercent > 0.5) { healthText.fill = '#4CAF50'; // Green } else if (healthPercent > 0.25) { healthText.fill = '#FF9800'; // Orange } else { healthText.fill = '#F44336'; // Red } waveText.setText('Wave: ' + currentWave + '/' + maxWaves); // Update ability button colors rewindButton.fill = player.rewindCooldown <= 0 && chronoEssence >= 15 ? '#4CAF50' : '#888888'; stasisButton.fill = player.stasisCooldown <= 0 && chronoEssence >= 10 ? '#2196F3' : '#888888'; // Add texture effect to rewind button when cooling down if (player.rewindCooldown > 0) { var cooldownPercent = player.rewindCooldown / 300; rewindButton.alpha = 0.5 + cooldownPercent * 0.5; rewindButton.setText('Rewind (' + Math.ceil(player.rewindCooldown / 60) + 's)'); } else { rewindButton.alpha = 1.0; rewindButton.setText('Rewind (15)'); } // Add texture effect to stasis button when cooling down if (player.stasisCooldown > 0) { var cooldownPercent = player.stasisCooldown / 240; stasisButton.alpha = 0.5 + cooldownPercent * 0.5; stasisButton.setText('Stasis (' + Math.ceil(player.stasisCooldown / 60) + 's)'); } else { stasisButton.alpha = 1.0; stasisButton.setText('Stasis (10)'); } // Game state logic if (gameState === 'planning') { // Planning phase logic } else if (gameState === 'combat') { waveTimer++; // Spawn enemies if (enemySpawnTimer <= 0 && wavesPerformed === 0) { spawnEnemies(); wavesPerformed = 1; enemySpawnTimer = 300; } // Check if wave is complete if (enemies.length === 0 && wavesPerformed > 0) { gameState = 'wave_complete'; currentWave++; chronoEssence += 25; if (currentWave > maxWaves) { gameState = 'victory'; stateText.setText('Victory! All waves defeated!'); LK.effects.flashScreen(0x4CAF50, 2000); LK.setTimeout(function () { LK.showYouWin(); }, 1500); } else { stateText.setText('Wave Complete! Prepare for next wave...'); LK.setTimeout(function () { gameState = 'planning'; stateText.setText('Planning Phase - Place your defenses!'); }, 2000); } } // Update player invulnerability timer if (player.invulnerabilityTimer > 0) { player.invulnerabilityTimer--; } // Check enemy collision with player for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - player.x; var dy = enemy.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60 && player.invulnerabilityTimer <= 0) { player.takeDamage(10); } } // Check game over condition for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.y > 2500) { gameState = 'game_over'; LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } } } // Bullet collision detection for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; var hit = false; for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (bullet.intersects(enemy)) { // Create impact effect at collision point var impactEffect = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, x: bullet.x, y: bullet.y, scaleX: 2, scaleY: 2, alpha: 0.9, tint: 0xffaa00 }); game.addChild(impactEffect); // Animate impact effect tween(impactEffect, { scaleX: 0.2, scaleY: 0.2, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { impactEffect.destroy(); } }); // Apply damage with visual feedback tween(enemy, { tint: 0xff4444, scaleX: 1.1, scaleY: 1.1 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(enemy, { tint: 0xffffff, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); if (enemy.takeDamage(bullet.damage)) { // Add death explosion effect var explosionEffect = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y, scaleX: 3, scaleY: 3, alpha: 0.8, tint: 0xff0000 }); game.addChild(explosionEffect); tween(explosionEffect, { scaleX: 0.1, scaleY: 0.1, alpha: 0, rotation: Math.PI * 2 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { explosionEffect.destroy(); } }); enemy.destroy(); enemies.splice(j, 1); } bullet.destroy(); bullets.splice(i, 1); hit = true; break; } } if (!hit && (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782)) { bullet.destroy(); bullets.splice(i, 1); } } // Remove destroyed plants for (var i = defensePlants.length - 1; i >= 0; i--) { var plant = defensePlants[i]; if (plant.hp <= 0) { if (plant.zone) { plant.zone.occupied = false; } plant.destroy(); defensePlants.splice(i, 1); } } } // Event handlers game.down = function (x, y, obj) { if (plantMenuVisible) { hidePlantMenu(); return; } if (gameState === 'planning') { // Check if clicked on start wave button area if (x > 1700 && x < 2000 && y > 2400 && y < 2500) { startWave(); } } else if (gameState === 'combat') { // Check ability usage in bottom left corner if (x < 300 && y > 2500) { if (y > 2600) { player.useRewind(); } else { player.useStasis(x, y); } } else { // Player movement and attack control var dx = x - player.x; var dy = y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if there's an enemy nearby to attack var targetEnemy = null; var minDistance = 300; // Increased attack range for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var enemyDx = enemy.x - x; var enemyDy = enemy.y - y; var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy); if (enemyDistance < minDistance) { targetEnemy = enemy; minDistance = enemyDistance; } } if (targetEnemy && distance < 400) { // Check if enemy is within melee range (close combat) if (minDistance < 100) { // Melee attack for close enemies player.attackTarget = targetEnemy; player.isAttacking = true; player.attackTimer = 30; // Create attack slash effect var attackEffect = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, x: player.x, y: player.y, scaleX: 3, scaleY: 0.5, alpha: 0.8, tint: 0xffff00 }); game.addChild(attackEffect); // Animate attack effect towards enemy tween(attackEffect, { x: targetEnemy.x, y: targetEnemy.y, rotation: Math.PI * 2, scaleX: 1, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { attackEffect.destroy(); } }); LK.effects.flashObject(targetEnemy, 0xff0000, 300); } else { // Ranged attack for distant enemies var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; bullet.targetX = targetEnemy.x; bullet.targetY = targetEnemy.y; bullet.damage = 1; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); // Enhanced shooting animation for player tween(player, { scaleX: 1.1, scaleY: 1.1, tint: 0x44ff44 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1, tint: 0xffffff }, { duration: 200, easing: tween.easeIn }); } }); } // Player attack animation for both melee and ranged tween(player, { scaleX: 1.3, scaleY: 1.3, tint: 0xff4444 }, { duration: 150, easing: tween.easeInOut, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1, tint: 0xffffff }, { duration: 150, easing: tween.easeInOut }); } }); } else { // Move player towards tap location player.moveTarget = { x: x, y: y }; player.isMoving = true; } } } }; // Create start wave button var startWaveButton = new Text2('Start Wave', { size: 60, fill: '#FF5722' }); startWaveButton.anchor.set(0.5, 0.5); startWaveButton.x = 1850; startWaveButton.y = 2450; game.addChild(startWaveButton); // Main game update loop game.update = function () { updateGame(); }; // Initialize everything initializeGame(); LK.playMusic('garden_theme');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 1;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
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 = self.targetX;
self.y = self.targetY;
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Add bullet glow animation
if (!self.glowInitialized) {
self.glowInitialized = true;
tween(graphics, {
alpha: 0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(graphics, {
alpha: 1
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
};
return self;
});
var DefensePlant = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('sunpetalSentinel', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 5;
self.maxHp = 5;
self.shootTimer = 0;
self.shootCooldown = 60;
self.range = 150;
self.update = function () {
self.shootTimer--;
if (self.shootTimer <= 0) {
var nearestEnemy = self.findNearestEnemy();
if (nearestEnemy) {
self.shoot(nearestEnemy);
self.shootTimer = self.shootCooldown;
}
}
};
self.findNearestEnemy = function () {
var nearest = null;
var nearestDistance = 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 < nearestDistance) {
nearest = enemy;
nearestDistance = distance;
}
}
return nearest;
};
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
// Add plant shooting animation texture
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xffff88
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Add muzzle flash effect
var muzzleFlash = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 2,
scaleY: 2,
alpha: 0.7,
tint: 0xffff00
});
game.addChild(muzzleFlash);
tween(muzzleFlash, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
muzzleFlash.destroy();
}
});
};
self.takeDamage = function (damage) {
self.hp -= damage;
if (self.hp <= 0) {
return true;
}
return false;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('slimeSnail', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 3;
self.maxHp = 3;
self.speed = 1;
self.targetX = 1024;
self.targetY = 1366;
self.isStasised = false;
self.stasisTimer = 0;
self.update = function () {
if (self.isStasised) {
self.stasisTimer--;
if (self.stasisTimer <= 0) {
self.isStasised = false;
graphics.tint = 0xFFFFFF;
}
return;
}
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.takeDamage = function (damage) {
self.hp -= damage;
// Check for low health state
if (self.hp <= self.maxHp * 0.33 && self.hp > 0 && !self.lowHealthEffect) {
self.lowHealthEffect = true;
// Start pulsing red effect for low health
self.pulseHealthEffect();
}
if (self.hp <= 0) {
chronoEssence += 5;
LK.getSound('enemy_death').play();
return true;
}
return false;
};
self.pulseHealthEffect = function () {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xff3333,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue pulsing if still alive and low health
if (self.hp > 0 && self.hp <= self.maxHp * 0.33) {
self.pulseHealthEffect();
} else {
self.lowHealthEffect = false;
}
}
});
}
}
});
}
};
self.applyStasis = function (duration) {
self.isStasised = true;
self.stasisTimer = duration;
graphics.tint = 0x2196F3;
};
return self;
});
var FastEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
var graphics = self.attachAsset('sporeRunner', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 2;
self.maxHp = 2;
self.speed = 2.5;
self.pulseHealthEffect = function () {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xff3333,
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.hp <= self.maxHp * 0.33) {
self.pulseHealthEffect();
} else {
self.lowHealthEffect = false;
}
}
});
}
}
});
}
};
return self;
});
var BossEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
var graphics = self.attachAsset('invaderWeed', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 8;
self.maxHp = 8;
self.speed = 0.8;
self.takeDamage = function (damage) {
self.hp -= damage;
// Check for low health state with different threshold for boss
if (self.hp <= self.maxHp * 0.5 && self.hp > 0 && !self.lowHealthEffect) {
self.lowHealthEffect = true;
self.pulseHealthEffect();
}
if (self.hp <= 0) {
chronoEssence += 20;
LK.getSound('enemy_death').play();
return true;
}
return false;
};
self.pulseHealthEffect = function () {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xff1111,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.hp <= self.maxHp * 0.5) {
self.pulseHealthEffect();
} else {
self.lowHealthEffect = false;
}
}
});
}
}
});
}
};
return self;
});
var PlantingZone = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('plantingZone', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
self.occupied = false;
self.plantType = null;
self.down = function (x, y, obj) {
if (!self.occupied && gameState === 'planning') {
showPlantMenu(self);
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('playerGardener', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 100;
self.maxHp = 100;
self.rewindCooldown = 0;
self.stasisCooldown = 0;
self.bloomCooldown = 0;
self.moveTarget = null;
self.isMoving = false;
self.isAttacking = false;
self.attackTarget = null;
self.attackTimer = 0;
self.moveSpeed = 4;
self.lowHealthEffect = false;
self.invulnerabilityTimer = 0;
self.update = function () {
if (self.rewindCooldown > 0) self.rewindCooldown--;
if (self.stasisCooldown > 0) self.stasisCooldown--;
if (self.bloomCooldown > 0) self.bloomCooldown--;
// Handle movement
if (self.isMoving && self.moveTarget) {
var dx = self.moveTarget.x - self.x;
var dy = self.moveTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.moveSpeed) {
self.x = self.moveTarget.x;
self.y = self.moveTarget.y;
self.isMoving = false;
self.moveTarget = null;
} else {
self.x += dx / distance * self.moveSpeed;
self.y += dy / distance * self.moveSpeed;
}
}
// Handle attacking
if (self.isAttacking && self.attackTimer > 0) {
self.attackTimer--;
if (self.attackTimer <= 0) {
if (self.attackTarget && enemies.indexOf(self.attackTarget) !== -1) {
// Deal damage to enemy with visual feedback
if (self.attackTarget && self.attackTarget.parent && !self.attackTarget.destroyed) {
var currentTarget = self.attackTarget; // Store reference to avoid null issues
tween(currentTarget, {
tint: 0xff0000,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (currentTarget && currentTarget.parent && !currentTarget.destroyed) {
tween(currentTarget, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
}
});
}
if (self.attackTarget.takeDamage(2)) {
var index = enemies.indexOf(self.attackTarget);
if (index !== -1) {
// Add destruction effect
LK.effects.flashObject(self.attackTarget, 0xffffff, 200);
self.attackTarget.destroy();
enemies.splice(index, 1);
}
}
}
self.isAttacking = false;
self.attackTarget = null;
}
}
};
self.useRewind = function () {
if (self.rewindCooldown <= 0 && chronoEssence >= 15) {
chronoEssence -= 15;
self.rewindCooldown = 300;
// Heal all plants with visual effect
for (var i = 0; i < defensePlants.length; i++) {
defensePlants[i].hp = defensePlants[i].maxHp;
LK.effects.flashObject(defensePlants[i], 0x4CAF50, 300);
}
// Push back enemies with visual effect
for (var i = 0; i < enemies.length; i++) {
enemies[i].x -= 100;
enemies[i].y -= 50;
LK.effects.flashObject(enemies[i], 0xFFFFFF, 200);
}
LK.getSound('rewind').play();
LK.effects.flashScreen(0x4CAF50, 500);
return true;
}
return false;
};
self.useStasis = function (x, y) {
if (self.stasisCooldown <= 0 && chronoEssence >= 10) {
chronoEssence -= 10;
self.stasisCooldown = 240;
var stasisRadius = 120;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - x;
var dy = enemy.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < stasisRadius) {
enemy.applyStasis(180);
}
}
var stasisEffect = LK.getAsset('stasisField', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.3
});
game.addChild(stasisEffect);
tween(stasisEffect, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
stasisEffect.destroy();
}
});
LK.getSound('stasis').play();
return true;
}
return false;
};
self.takeDamage = function (damage) {
if (self.invulnerabilityTimer > 0) return false;
self.hp -= damage;
self.invulnerabilityTimer = 60; // 1 second of invulnerability
// Visual damage feedback
tween(graphics, {
tint: 0xff3333,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Check for low health state
if (self.hp <= self.maxHp * 0.25 && self.hp > 0 && !self.lowHealthEffect) {
self.lowHealthEffect = true;
self.pulseHealthEffect();
}
// Flash screen red
LK.effects.flashScreen(0xff0000, 300);
if (self.hp <= 0) {
self.hp = 0; // Ensure hp doesn't go below zero
gameState = 'game_over';
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return true;
}
return false;
};
self.pulseHealthEffect = function () {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xff6666,
alpha: 0.7
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.lowHealthEffect) {
tween(graphics, {
tint: 0xffffff,
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.hp > 0 && self.hp <= self.maxHp * 0.25) {
self.pulseHealthEffect();
} else {
self.lowHealthEffect = false;
}
}
});
}
}
});
}
};
return self;
});
var VinePlant = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('stickyVineSnare', {
anchorX: 0.5,
anchorY: 0.5
});
self.hp = 3;
self.maxHp = 3;
self.slowEffect = 0.5;
self.range = 100;
self.update = function () {
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 < self.range && !enemy.isStasised) {
enemy.speed = Math.max(0.3, enemy.speed * self.slowEffect);
}
}
};
self.takeDamage = function (damage) {
self.hp -= damage;
if (self.hp <= 0) {
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E7D32
});
/****
* Game Code
****/
// Game variables
// Game assets - automatically created by LK engine
var gameState = 'planning'; // 'planning', 'combat', 'wave_complete'
var currentWave = 1;
var maxWaves = 5;
var chronoEssence = 50;
var waveTimer = 0;
var enemySpawnTimer = 0;
var wavesPerformed = 0;
// Game objects
var player = null;
var enemies = [];
var bullets = [];
var defensePlants = [];
var plantingZones = [];
// UI elements
var essenceText = null;
var healthText = null;
var waveText = null;
var stateText = null;
var rewindButton = null;
var stasisButton = null;
var plantMenuVisible = false;
var selectedZone = null;
// Wave configurations
var waveConfigs = [{
enemies: [{
type: 'basic',
count: 5
}],
delay: 60
}, {
enemies: [{
type: 'basic',
count: 7
}, {
type: 'fast',
count: 2
}],
delay: 45
}, {
enemies: [{
type: 'basic',
count: 5
}, {
type: 'fast',
count: 4
}],
delay: 40
}, {
enemies: [{
type: 'basic',
count: 8
}, {
type: 'fast',
count: 3
}, {
type: 'boss',
count: 1
}],
delay: 35
}, {
enemies: [{
type: 'basic',
count: 10
}, {
type: 'fast',
count: 5
}, {
type: 'boss',
count: 2
}],
delay: 30
}];
// Initialize UI
function initializeUI() {
essenceText = new Text2('Essence: 50', {
size: 50,
fill: '#9C27B0'
});
essenceText.anchor.set(0, 0);
LK.gui.topLeft.addChild(essenceText);
essenceText.x = 120;
essenceText.y = 20;
healthText = new Text2('Health: 100/100', {
size: 50,
fill: '#4CAF50'
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120;
healthText.y = 80;
waveText = new Text2('Wave: 1/5', {
size: 50,
fill: '#FFFFFF'
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
waveText.y = 20;
stateText = new Text2('Planning Phase - Place your defenses!', {
size: 40,
fill: '#4CAF50'
});
stateText.anchor.set(0.5, 0);
LK.gui.top.addChild(stateText);
stateText.y = 80;
// Ability buttons
rewindButton = new Text2('Rewind (15)', {
size: 35,
fill: '#4CAF50'
});
rewindButton.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(rewindButton);
rewindButton.x = 20;
rewindButton.y = -20;
stasisButton = new Text2('Stasis (10)', {
size: 35,
fill: '#2196F3'
});
stasisButton.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(stasisButton);
stasisButton.x = 20;
stasisButton.y = -70;
}
// Initialize game objects
function initializeGame() {
// Create player
player = new Player();
player.x = 1024;
player.y = 2400;
game.addChild(player);
// Create planting zones
for (var row = 0; row < 6; row++) {
for (var col = 0; col < 4; col++) {
var zone = new PlantingZone();
zone.x = 400 + col * 200;
zone.y = 400 + row * 200;
plantingZones.push(zone);
game.addChild(zone);
}
}
initializeUI();
}
// Plant menu functions
function showPlantMenu(zone) {
if (plantMenuVisible) return;
selectedZone = zone;
plantMenuVisible = true;
// Create plant options
createPlantOption('Sunpetal (20)', 1024, 1800, 'sunpetal', 20);
createPlantOption('Vine Snare (15)', 1024, 1900, 'vine', 15);
}
function createPlantOption(text, x, y, plantType, cost) {
var option = new Text2(text, {
size: 40,
fill: chronoEssence >= cost ? '#FFFFFF' : '#888888'
});
option.anchor.set(0.5, 0.5);
option.x = x;
option.y = y;
option.plantType = plantType;
option.cost = cost;
option.down = function () {
if (chronoEssence >= option.cost && selectedZone) {
plantDefense(selectedZone, option.plantType, option.cost);
hidePlantMenu();
}
};
game.addChild(option);
return option;
}
function hidePlantMenu() {
plantMenuVisible = false;
selectedZone = null;
// Remove plant option UI elements
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child.plantType !== undefined) {
child.destroy();
}
}
}
function plantDefense(zone, plantType, cost) {
if (zone.occupied || chronoEssence < cost) return;
chronoEssence -= cost;
zone.occupied = true;
var plant = null;
if (plantType === 'sunpetal') {
plant = new DefensePlant();
} else if (plantType === 'vine') {
plant = new VinePlant();
}
if (plant) {
plant.x = zone.x;
plant.y = zone.y;
plant.zone = zone;
defensePlants.push(plant);
game.addChild(plant);
LK.getSound('plant').play();
}
}
// Wave management
function startWave() {
if (currentWave > maxWaves) {
gameState = 'victory';
stateText.setText('Victory! All waves defeated!');
return;
}
gameState = 'combat';
stateText.setText('Wave ' + currentWave + ' - Defend your garden!');
waveTimer = 0;
enemySpawnTimer = 0;
wavesPerformed = 0;
}
function spawnEnemies() {
var config = waveConfigs[currentWave - 1];
if (!config) return;
for (var i = 0; i < config.enemies.length; i++) {
var enemyConfig = config.enemies[i];
for (var j = 0; j < enemyConfig.count; j++) {
var enemy = null;
if (enemyConfig.type === 'basic') {
enemy = new Enemy();
} else if (enemyConfig.type === 'fast') {
enemy = new FastEnemy();
} else if (enemyConfig.type === 'boss') {
enemy = new BossEnemy();
}
if (enemy) {
enemy.x = Math.random() * 2048;
enemy.y = -100 - j * 50;
enemies.push(enemy);
game.addChild(enemy);
}
}
}
}
function updateGame() {
// Check game over condition first
if (gameState === 'game_over') {
return; // Stop all game logic when game is over
}
// Check victory condition first
if (currentWave > maxWaves && enemies.length === 0 && gameState !== 'victory') {
gameState = 'victory';
stateText.setText('Victory! All waves defeated!');
LK.effects.flashScreen(0x4CAF50, 2000);
LK.setTimeout(function () {
LK.showYouWin();
}, 1000);
return;
}
// Update UI
essenceText.setText('Essence: ' + chronoEssence);
healthText.setText('Health: ' + player.hp + '/' + player.maxHp);
// Update health text color based on health percentage
var healthPercent = player.hp / player.maxHp;
if (healthPercent > 0.5) {
healthText.fill = '#4CAF50'; // Green
} else if (healthPercent > 0.25) {
healthText.fill = '#FF9800'; // Orange
} else {
healthText.fill = '#F44336'; // Red
}
waveText.setText('Wave: ' + currentWave + '/' + maxWaves);
// Update ability button colors
rewindButton.fill = player.rewindCooldown <= 0 && chronoEssence >= 15 ? '#4CAF50' : '#888888';
stasisButton.fill = player.stasisCooldown <= 0 && chronoEssence >= 10 ? '#2196F3' : '#888888';
// Add texture effect to rewind button when cooling down
if (player.rewindCooldown > 0) {
var cooldownPercent = player.rewindCooldown / 300;
rewindButton.alpha = 0.5 + cooldownPercent * 0.5;
rewindButton.setText('Rewind (' + Math.ceil(player.rewindCooldown / 60) + 's)');
} else {
rewindButton.alpha = 1.0;
rewindButton.setText('Rewind (15)');
}
// Add texture effect to stasis button when cooling down
if (player.stasisCooldown > 0) {
var cooldownPercent = player.stasisCooldown / 240;
stasisButton.alpha = 0.5 + cooldownPercent * 0.5;
stasisButton.setText('Stasis (' + Math.ceil(player.stasisCooldown / 60) + 's)');
} else {
stasisButton.alpha = 1.0;
stasisButton.setText('Stasis (10)');
}
// Game state logic
if (gameState === 'planning') {
// Planning phase logic
} else if (gameState === 'combat') {
waveTimer++;
// Spawn enemies
if (enemySpawnTimer <= 0 && wavesPerformed === 0) {
spawnEnemies();
wavesPerformed = 1;
enemySpawnTimer = 300;
}
// Check if wave is complete
if (enemies.length === 0 && wavesPerformed > 0) {
gameState = 'wave_complete';
currentWave++;
chronoEssence += 25;
if (currentWave > maxWaves) {
gameState = 'victory';
stateText.setText('Victory! All waves defeated!');
LK.effects.flashScreen(0x4CAF50, 2000);
LK.setTimeout(function () {
LK.showYouWin();
}, 1500);
} else {
stateText.setText('Wave Complete! Prepare for next wave...');
LK.setTimeout(function () {
gameState = 'planning';
stateText.setText('Planning Phase - Place your defenses!');
}, 2000);
}
}
// Update player invulnerability timer
if (player.invulnerabilityTimer > 0) {
player.invulnerabilityTimer--;
}
// Check enemy collision with player
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60 && player.invulnerabilityTimer <= 0) {
player.takeDamage(10);
}
}
// Check game over condition
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.y > 2500) {
gameState = 'game_over';
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
}
}
// Bullet collision detection
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
var hit = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Create impact effect at collision point
var impactEffect = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
x: bullet.x,
y: bullet.y,
scaleX: 2,
scaleY: 2,
alpha: 0.9,
tint: 0xffaa00
});
game.addChild(impactEffect);
// Animate impact effect
tween(impactEffect, {
scaleX: 0.2,
scaleY: 0.2,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
impactEffect.destroy();
}
});
// Apply damage with visual feedback
tween(enemy, {
tint: 0xff4444,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
if (enemy.takeDamage(bullet.damage)) {
// Add death explosion effect
var explosionEffect = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y,
scaleX: 3,
scaleY: 3,
alpha: 0.8,
tint: 0xff0000
});
game.addChild(explosionEffect);
tween(explosionEffect, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
enemy.destroy();
enemies.splice(j, 1);
}
bullet.destroy();
bullets.splice(i, 1);
hit = true;
break;
}
}
if (!hit && (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782)) {
bullet.destroy();
bullets.splice(i, 1);
}
}
// Remove destroyed plants
for (var i = defensePlants.length - 1; i >= 0; i--) {
var plant = defensePlants[i];
if (plant.hp <= 0) {
if (plant.zone) {
plant.zone.occupied = false;
}
plant.destroy();
defensePlants.splice(i, 1);
}
}
}
// Event handlers
game.down = function (x, y, obj) {
if (plantMenuVisible) {
hidePlantMenu();
return;
}
if (gameState === 'planning') {
// Check if clicked on start wave button area
if (x > 1700 && x < 2000 && y > 2400 && y < 2500) {
startWave();
}
} else if (gameState === 'combat') {
// Check ability usage in bottom left corner
if (x < 300 && y > 2500) {
if (y > 2600) {
player.useRewind();
} else {
player.useStasis(x, y);
}
} else {
// Player movement and attack control
var dx = x - player.x;
var dy = y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if there's an enemy nearby to attack
var targetEnemy = null;
var minDistance = 300; // Increased attack range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = enemy.x - x;
var enemyDy = enemy.y - y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
if (enemyDistance < minDistance) {
targetEnemy = enemy;
minDistance = enemyDistance;
}
}
if (targetEnemy && distance < 400) {
// Check if enemy is within melee range (close combat)
if (minDistance < 100) {
// Melee attack for close enemies
player.attackTarget = targetEnemy;
player.isAttacking = true;
player.attackTimer = 30;
// Create attack slash effect
var attackEffect = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
x: player.x,
y: player.y,
scaleX: 3,
scaleY: 0.5,
alpha: 0.8,
tint: 0xffff00
});
game.addChild(attackEffect);
// Animate attack effect towards enemy
tween(attackEffect, {
x: targetEnemy.x,
y: targetEnemy.y,
rotation: Math.PI * 2,
scaleX: 1,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
attackEffect.destroy();
}
});
LK.effects.flashObject(targetEnemy, 0xff0000, 300);
} else {
// Ranged attack for distant enemies
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
bullet.targetX = targetEnemy.x;
bullet.targetY = targetEnemy.y;
bullet.damage = 1;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
// Enhanced shooting animation for player
tween(player, {
scaleX: 1.1,
scaleY: 1.1,
tint: 0x44ff44
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
// Player attack animation for both melee and ranged
tween(player, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xff4444
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
} else {
// Move player towards tap location
player.moveTarget = {
x: x,
y: y
};
player.isMoving = true;
}
}
}
};
// Create start wave button
var startWaveButton = new Text2('Start Wave', {
size: 60,
fill: '#FF5722'
});
startWaveButton.anchor.set(0.5, 0.5);
startWaveButton.x = 1850;
startWaveButton.y = 2450;
game.addChild(startWaveButton);
// Main game update loop
game.update = function () {
updateGame();
};
// Initialize everything
initializeGame();
LK.playMusic('garden_theme');
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Temporal Bloom" and with the description "A whimsical garden defense game where you manipulate time to protect magical plants from waves of fantastical pests using abilities like local time rewind, accelerated growth, and temporal stasis fields.". No text on banner!
Make a image that were a crop on grass. 2d. High contrast. No shadows
Bullet image. 2d. High contrast. No shadows
sporeRunner image. 2d. High contrast. No shadows
invaderWeed. 2d. High contrast. No shadows
stasisField. In-Game asset. 2d. High contrast. No shadows
manaBloom. 2d. High contrast. No shadows
stickyVineSnare. 2d. High contrast. No shadows
thornWhipWeaver. 2d. High contrast. No shadows
sunpetalSentinel. 2d. High contrast. No shadows
slimeSnail. 2d. High contrast. No shadows