/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Boss = Container.expand(function (generation) { var self = Container.call(this); var bossGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); // Assign random color to boss but make it initially dark var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF]; var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)]; // Store the original color for later use self.originalColor = randomColor; // Make boss initially dark bossGraphics.tint = 0x404040; self.speed = 1; self.generation = generation || 1; self.health = (2 + self.generation) * 3; // First boss has 9 health, second has 12, etc. self.maxHealth = (2 + self.generation) * 3; self.isDying = false; self.direction = { x: 0, y: 0 }; // Each generation doubles in base size: 1.5 * (2^(generation-1)) self.baseScale = 1.5 * Math.pow(2, self.generation - 1); self.maxScale = self.baseScale * 2.67; // Keep same ratio as original (4/1.5) self.lastDistanceFromPlayer = 0; // Health bar setup var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.y = -80; // Position above boss var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5 }); healthBarFill.x = healthBarBg.x - healthBarBg.width / 2; healthBarFill.y = healthBarBg.y; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBarFill.width = 120 * healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; // Initialize health bar self.updateHealthBar(); self.startDeathSequence = function () { if (self.isDying) return; self.isDying = true; self.speed = 0; // Stop movement // Change color to red tween(bossGraphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { // Wait one second then destroy tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }); }; self.update = function () { if (self.isDying) return; // Don't move if dying // Calculate direction toward player every frame var dx = player.x - self.x; var dy = player.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); // Track distance for scaling self.lastDistanceFromPlayer = length; if (length > 0) { self.direction.x = dx / length; self.direction.y = dy / length; } // Keep boss at constant base scale bossGraphics.scaleX = self.baseScale; bossGraphics.scaleY = self.baseScale; self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Update health bar self.updateHealthBar(); }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 80; self.direction = { x: 0, y: 0 }; self.isGuided = false; self.setDirection = function (dirX, dirY) { self.direction.x = dirX; self.direction.y = dirY; // Calculate rotation angle based on direction and rotate the bullet graphics var angle = Math.atan2(dirY, dirX); bulletGraphics.rotation = angle; }; self.update = function () { if (self.isGuided) { // Find closest enemy var closestEnemy = null; var closestDistance = Infinity; // Check regular enemies 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) { closestDistance = distance; closestEnemy = enemy; } } // Check golden enemies for (var i = 0; i < goldenEnemies.length; i++) { var goldenEnemy = goldenEnemies[i]; var dx = goldenEnemy.x - self.x; var dy = goldenEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = goldenEnemy; } } // Check explosive enemies for (var i = 0; i < explosiveEnemies.length; i++) { var explosiveEnemy = explosiveEnemies[i]; var dx = explosiveEnemy.x - self.x; var dy = explosiveEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = explosiveEnemy; } } // Check stone enemies for (var i = 0; i < stoneEnemies.length; i++) { var stoneEnemy = stoneEnemies[i]; var dx = stoneEnemy.x - self.x; var dy = stoneEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = stoneEnemy; } } // Check bosses for (var i = 0; i < bosses.length; i++) { var boss = bosses[i]; var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < closestDistance) { closestDistance = distance; closestEnemy = boss; } } // Update direction towards closest enemy if (closestEnemy && closestDistance > 0) { var dx = closestEnemy.x - self.x; var dy = closestEnemy.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); self.direction.x = dx / length; self.direction.y = dy / length; // Update bullet rotation var angle = Math.atan2(dy, dx); bulletGraphics.rotation = angle; } } self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Make enemy initially dark enemyGraphics.tint = 0x404040; self.speed = 2; self.health = 1; self.isDying = false; self.direction = { x: 0, y: 0 }; self.startDeathSequence = function () { if (self.isDying) return; self.isDying = true; self.speed = 0; // Stop movement // Change color to red tween(enemyGraphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { // Wait one second then destroy tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }); }; self.update = function () { if (self.isDying) return; // Don't move if dying // Calculate direction toward player every frame var dx = player.x - self.x; var dy = player.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { self.direction.x = dx / length; self.direction.y = dy / length; } self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; }; return self; }); var ExplosiveEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('explosiveEnemy', { anchorX: 0.5, anchorY: 0.5 }); // Assign random color to explosive enemy but make it initially dark var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF]; var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)]; // Store the original color for later use self.originalColor = randomColor; // Make enemy initially dark enemyGraphics.tint = 0x404040; self.speed = 1.8; self.health = 5; self.maxHealth = 5; self.isDying = false; self.direction = { x: 0, y: 0 }; self.hasExploded = false; self.startDeathSequence = function () { if (self.isDying || self.hasExploded) return; self.isDying = true; self.speed = 0; // Stop movement // Change color to red tween(enemyGraphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { // Wait one second then destroy tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }); }; // Create explosion damage zone self.explode = function () { if (self.hasExploded) return; self.hasExploded = true; // Create explosion visual effect LK.effects.flashObject(self, 0xFF0000, 500); LK.effects.flashScreen(0xFF4444, 300); // Check for damage to nearby enemies and player var explosionRadius = 500; // Damage player if in range var playerDx = player.x - self.x; var playerDy = player.y - self.y; var playerDistance = Math.sqrt(playerDx * playerDx + playerDy * playerDy); if (playerDistance <= explosionRadius) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Damage nearby enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius && enemy !== self) { enemy.destroy(); enemies.splice(i, 1); LK.setScore(LK.getScore() + 5); playRandomEnemyKillSound(); } } // Damage nearby golden enemies for (var i = goldenEnemies.length - 1; i >= 0; i--) { var goldenEnemy = goldenEnemies[i]; var dx = goldenEnemy.x - self.x; var dy = goldenEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { goldenEnemy.destroy(); goldenEnemies.splice(i, 1); LK.setScore(LK.getScore() + 25); playRandomEnemyKillSound(); } } // Damage nearby explosive enemies (including self) for (var i = explosiveEnemies.length - 1; i >= 0; i--) { var explosiveEnemy = explosiveEnemies[i]; var dx = explosiveEnemy.x - self.x; var dy = explosiveEnemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { explosiveEnemy.destroy(); explosiveEnemies.splice(i, 1); LK.setScore(LK.getScore() + 15); playRandomEnemyKillSound(); } } // Damage nearby bosses for (var i = 0; i < bosses.length; i++) { var boss = bosses[i]; var dx = boss.x - self.x; var dy = boss.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= explosionRadius) { boss.health -= 2; boss.updateHealthBar(); LK.effects.flashObject(boss, 0xFF0000, 300); LK.setScore(LK.getScore() + 10); } } // Create visual explosion zone var explosionZone = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); explosionZone.x = self.x; explosionZone.y = self.y; explosionZone.scaleX = explosionRadius / 200; explosionZone.scaleY = explosionRadius / 200; explosionZone.alpha = 0.8; game.addChild(explosionZone); // Animate explosion zone tween(explosionZone, { scaleX: explosionZone.scaleX * 1.5, scaleY: explosionZone.scaleY * 1.5, alpha: 0 }, { duration: 500, onFinish: function onFinish() { explosionZone.destroy(); } }); }; self.update = function () { if (self.isDying) return; // Don't move if dying // Calculate direction toward player every frame var dx = player.x - self.x; var dy = player.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { self.direction.x = dx / length; self.direction.y = dy / length; } self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if health reached 1 and explode if (self.health <= 1 && !self.hasExploded) { self.explode(); self.startDeathSequence(); // Start death sequence after exploding } }; return self; }); var GoldenEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('goldenEnemy', { anchorX: 0.5, anchorY: 0.5 }); // Make golden enemy initially dark enemyGraphics.tint = 0x404040; self.speed = 1.5; self.health = 2; self.isDying = false; self.direction = { x: 0, y: 0 }; self.startDeathSequence = function () { if (self.isDying) return; self.isDying = true; self.speed = 0; // Stop movement // Change color to red tween(enemyGraphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { // Wait one second then destroy tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }); }; self.update = function () { if (self.isDying) return; // Don't move if dying // Calculate direction toward player every frame var dx = player.x - self.x; var dy = player.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { self.direction.x = dx / length; self.direction.y = dy / length; } self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Make player initially dark playerGraphics.tint = 0x404040; self.canShoot = false; return self; }); var PowerupCrate = Container.expand(function () { var self = Container.call(this); var crateGraphics = self.attachAsset('powerupCrate', { anchorX: 0.5, anchorY: 0.5 }); self.bobDirection = 1; self.bobSpeed = 0.5; self.baseY = 0; self.isDestroying = false; // Start 5 second countdown to destruction tween(self, {}, { duration: 5000, onFinish: function onFinish() { if (!self.isDestroying) { self.isDestroying = true; // Fade out animation before destruction tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.destroy(); } }); } } }); self.update = function () { if (self.isDestroying) return; // Simple bobbing animation self.y += self.bobDirection * self.bobSpeed; var distanceFromBase = Math.abs(self.y - self.baseY); if (distanceFromBase > 10) { self.bobDirection *= -1; } }; return self; }); var RhythmIndicator = Container.expand(function () { var self = Container.call(this); var rhythmBar = self.attachAsset('rhythmBar', { anchorX: 0.5, anchorY: 0.5 }); var perfectZone = self.attachAsset('perfectZone', { anchorX: 0.5, anchorY: 0.5 }); perfectZone.alpha = 0.7; var indicator = self.attachAsset('rhythmIndicator', { anchorX: 0.5, anchorY: 0.5 }); indicator.x = -200; self.beatDuration = 1000; self.startTime = 0; self.update = function () { // Don't move rhythm indicator during tutorial if (!gameStarted) { return; } var elapsed = LK.ticks * (1000 / 60) - self.startTime; var progress = elapsed % self.beatDuration / self.beatDuration; indicator.x = -200 + progress * 400; var perfectZoneStart = 150; var perfectZoneEnd = 250; var indicatorPos = 200 + indicator.x; if (indicatorPos >= perfectZoneStart && indicatorPos <= perfectZoneEnd) { player.canShoot = true; perfectZone.tint = 0x27AE60; // Make ground fully visible when in perfect zone tween(ground, { alpha: 1 }, { duration: 100 }); // Light up all tables when in perfect zone for (var t = 0; t < tables.length; t++) { var table = tables[t]; if (!table.isDestroyed) { tween(table, { tint: 0xFFFFFF }, { duration: 100 }); } } // Light up all enemies when in perfect zone for (var e = 0; e < enemies.length; e++) { var enemy = enemies[e]; if (!enemy.isDying) { tween(enemy, { tint: 0xFFFFFF }, { duration: 100 }); } } for (var ge = 0; ge < goldenEnemies.length; ge++) { var goldenEnemy = goldenEnemies[ge]; if (!goldenEnemy.isDying) { tween(goldenEnemy, { tint: 0xFFD700 }, { duration: 100 }); } } for (var ee = 0; ee < explosiveEnemies.length; ee++) { var explosiveEnemy = explosiveEnemies[ee]; if (!explosiveEnemy.isDying) { tween(explosiveEnemy, { tint: explosiveEnemy.originalColor }, { duration: 100 }); } } for (var se = 0; se < stoneEnemies.length; se++) { var stoneEnemy = stoneEnemies[se]; if (!stoneEnemy.isDying) { tween(stoneEnemy, { tint: stoneEnemy.originalColor }, { duration: 100 }); } } for (var b = 0; b < bosses.length; b++) { var boss = bosses[b]; if (!boss.isDying) { tween(boss, { tint: boss.originalColor }, { duration: 100 }); } } // Light up all trap boxes when in perfect zone for (var tb = 0; tb < trapBoxes.length; tb++) { var trapBox = trapBoxes[tb]; if (!trapBox.isDying) { tween(trapBox, { tint: 0xFFFFFF }, { duration: 100 }); } } // Light up player when in perfect zone tween(player, { tint: 0xFFFFFF }, { duration: 100 }); // Light up weapon when in perfect zone tween(weapon, { tint: 0xFFFFFF }, { duration: 100 }); // Light up all LED borders when in perfect zone tween(ledTop, { tint: 0x00FF00, alpha: 1 }, { duration: 100 }); tween(ledBottom, { tint: 0x00FF00, alpha: 1 }, { duration: 100 }); tween(ledLeft, { tint: 0x00FF00, alpha: 1 }, { duration: 100 }); tween(ledRight, { tint: 0x00FF00, alpha: 1 }, { duration: 100 }); } else { player.canShoot = false; perfectZone.tint = 0xE67E22; // Make ground semi-transparent when not in perfect zone tween(ground, { alpha: 0.3 }, { duration: 100 }); // Make tables dark when not in perfect zone for (var t = 0; t < tables.length; t++) { var table = tables[t]; if (!table.isDestroyed) { tween(table, { tint: 0x404040 }, { duration: 100 }); } } // Make enemies dark when not in perfect zone for (var e = 0; e < enemies.length; e++) { var enemy = enemies[e]; if (!enemy.isDying) { tween(enemy, { tint: 0x404040 }, { duration: 100 }); } } for (var ge = 0; ge < goldenEnemies.length; ge++) { var goldenEnemy = goldenEnemies[ge]; if (!goldenEnemy.isDying) { tween(goldenEnemy, { tint: 0x404040 }, { duration: 100 }); } } for (var ee = 0; ee < explosiveEnemies.length; ee++) { var explosiveEnemy = explosiveEnemies[ee]; if (!explosiveEnemy.isDying) { tween(explosiveEnemy, { tint: 0x404040 }, { duration: 100 }); } } for (var se = 0; se < stoneEnemies.length; se++) { var stoneEnemy = stoneEnemies[se]; if (!stoneEnemy.isDying) { tween(stoneEnemy, { tint: 0x404040 }, { duration: 100 }); } } for (var b = 0; b < bosses.length; b++) { var boss = bosses[b]; if (!boss.isDying) { tween(boss, { tint: 0x404040 }, { duration: 100 }); } } // Make trap boxes dark when not in perfect zone for (var tb = 0; tb < trapBoxes.length; tb++) { var trapBox = trapBoxes[tb]; if (!trapBox.isDying) { tween(trapBox, { tint: 0x404040 }, { duration: 100 }); } } // Make player dark when not in perfect zone tween(player, { tint: 0x404040 }, { duration: 100 }); // Make weapon dark when not in perfect zone tween(weapon, { tint: 0x404040 }, { duration: 100 }); // Dim all LED borders when not in perfect zone tween(ledTop, { tint: 0x404040, alpha: 0.3 }, { duration: 100 }); tween(ledBottom, { tint: 0x404040, alpha: 0.3 }, { duration: 100 }); tween(ledLeft, { tint: 0x404040, alpha: 0.3 }, { duration: 100 }); tween(ledRight, { tint: 0x404040, alpha: 0.3 }, { duration: 100 }); } }; return self; }); var StoneEnemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('stoneEnemy', { anchorX: 0.5, anchorY: 0.5 }); // Give stone enemy a gray stone-like tint but make it initially dark self.originalColor = 0x808080; // Make enemy initially dark enemyGraphics.tint = 0x404040; // Make stone enemy semi-transparent enemyGraphics.alpha = 0.5; self.speed = 0.8; self.health = 7; self.maxHealth = 7; self.isDying = false; self.direction = { x: 0, y: 0 }; // Health bar setup var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.y = -80; // Position above stone enemy var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5 }); healthBarFill.x = healthBarBg.x - healthBarBg.width / 2; healthBarFill.y = healthBarBg.y; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBarFill.width = 120 * healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; // Initialize health bar self.updateHealthBar(); self.startDeathSequence = function () { if (self.isDying) return; self.isDying = true; self.speed = 0; // Stop movement // Change color to red tween(enemyGraphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { // Wait one second then destroy tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }); }; self.update = function () { if (self.isDying) return; // Don't move if dying // Calculate direction toward player every frame var dx = player.x - self.x; var dy = player.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { self.direction.x = dx / length; self.direction.y = dy / length; } self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Update health bar self.updateHealthBar(); }; return self; }); var Table = Container.expand(function () { var self = Container.call(this); var tableGraphics = self.attachAsset('table', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.isDestroyed = false; self.destroy = function () { if (self.isDestroyed) return; self.isDestroyed = true; // Visual feedback for table destruction LK.effects.flashObject(self, 0xFF0000, 300); tween(self, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { Container.prototype.destroy.call(self); } }); }; return self; }); var TrapBox = Container.expand(function () { var self = Container.call(this); var trapBoxGraphics = self.attachAsset('trapBox', { anchorX: 0.5, anchorY: 0.5 }); // Make trap box initially dark trapBoxGraphics.tint = 0x404040; self.health = 10; self.maxHealth = 10; self.isDying = false; self.spawnTimer = 0; self.spawnedEnemy = null; // Track the currently spawned enemy // Health bar setup var healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); healthBarBg.y = -80; // Position above trap box var healthBarFill = self.attachAsset('healthBarFill', { anchorX: 0, anchorY: 0.5 }); healthBarFill.x = healthBarBg.x - healthBarBg.width / 2; healthBarFill.y = healthBarBg.y; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; healthBarFill.width = 120 * healthPercent; // Change color based on health if (healthPercent > 0.6) { healthBarFill.tint = 0x00ff00; // Green } else if (healthPercent > 0.3) { healthBarFill.tint = 0xffff00; // Yellow } else { healthBarFill.tint = 0xff0000; // Red } }; // Initialize health bar self.updateHealthBar(); self.startDeathSequence = function () { if (self.isDying) return; self.isDying = true; self.speed = 0; // Stop movement // Change color to red tween(trapBoxGraphics, { tint: 0xFF0000 }, { duration: 100, onFinish: function onFinish() { // Wait one second then destroy tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }); }; self.spawnRandomEnemy = function () { // Only spawn if no enemy is currently spawned or if the spawned enemy is destroyed if (self.spawnedEnemy && self.spawnedEnemy.parent) { return; // Don't spawn if there's already an active enemy } var enemyTypes = ['normal', 'golden', 'explosive']; var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; var newEnemy; if (randomType === 'normal') { newEnemy = new Enemy(); enemies.push(newEnemy); } else if (randomType === 'golden') { newEnemy = new GoldenEnemy(); goldenEnemies.push(newEnemy); } else if (randomType === 'explosive') { newEnemy = new ExplosiveEnemy(); explosiveEnemies.push(newEnemy); } // Position enemy at trap box location newEnemy.x = self.x; newEnemy.y = self.y; self.spawnedEnemy = newEnemy; // Track this enemy game.addChild(newEnemy); }; self.update = function () { if (self.isDying) return; // Don't move or spawn if dying // Zigzag movement pattern if (!self.zigzagDirection) self.zigzagDirection = 1; // Initialize zigzag direction if (!self.zigzagSpeed) self.zigzagSpeed = 2; // Zigzag horizontal speed if (!self.zigzagTimer) self.zigzagTimer = 0; // Timer for zigzag changes // Change zigzag direction every 60 ticks (1 second) self.zigzagTimer++; if (self.zigzagTimer >= 60) { self.zigzagDirection *= -1; self.zigzagTimer = 0; } // Apply zigzag movement self.x += self.zigzagDirection * self.zigzagSpeed; // Fall down slowly only if above middle of screen if (self.y < 1000) { // Stop falling when reaching above middle (2732/2) self.y += 1; // Much slower speed (was 3, now 1) } // Clean up reference to destroyed enemy if (self.spawnedEnemy && !self.spawnedEnemy.parent) { self.spawnedEnemy = null; } // Spawn enemy every 3 seconds (180 ticks at 60 FPS) self.spawnTimer++; if (self.spawnTimer >= 180) { self.spawnRandomEnemy(); self.spawnTimer = 0; } // Update health bar self.updateHealthBar(); }; return self; }); var Weapon = Container.expand(function () { var self = Container.call(this); var weaponGraphics = self.attachAsset('weapon', { anchorX: 0.5, anchorY: 0.5 }); // Set initial weapon properties - start dark weaponGraphics.tint = 0x404040; // Start dark like other elements self.lastShotDirection = { x: 0, y: -1 }; // Default pointing up self.rotateToDirection = function (dirX, dirY) { // Calculate rotation angle based on direction var angle = Math.atan2(dirY, dirX); // Smooth rotation using tween tween(weaponGraphics, { rotation: angle }, { duration: 150, easing: tween.easeOut }); // Store last shot direction self.lastShotDirection.x = dirX; self.lastShotDirection.y = dirY; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1A1A2E }); /**** * Game Code ****/ // Create LED border lights var ledTop = LK.getAsset('borderLedTop', { anchorX: 0.5, anchorY: 0 }); ledTop.x = 1024; // Center horizontally ledTop.y = 0; // Top of screen ledTop.alpha = 0.3; // Start dim game.addChild(ledTop); var ledBottom = LK.getAsset('borderLedBottom', { anchorX: 0.5, anchorY: 1 }); ledBottom.x = 1024; // Center horizontally ledBottom.y = 2732; // Bottom of screen ledBottom.alpha = 0.3; // Start dim game.addChild(ledBottom); var ledLeft = LK.getAsset('borderLedLeft', { anchorX: 0, anchorY: 0.5 }); ledLeft.x = 0; // Left edge ledLeft.y = 1366; // Center vertically ledLeft.alpha = 0.3; // Start dim game.addChild(ledLeft); var ledRight = LK.getAsset('borderLedRight', { anchorX: 1, anchorY: 0.5 }); ledRight.x = 2048; // Right edge ledRight.y = 1366; // Center vertically ledRight.alpha = 0.3; // Start dim game.addChild(ledRight); // Create ground texture first to render below enemies var ground = LK.getAsset('ground', { anchorX: 0.5, anchorY: 1 }); ground.x = 1024; // Center horizontally ground.y = 2732; // Position at bottom of screen // Scale ground to ensure it covers the full screen width ground.scaleX = 2048 / ground.width; // Scale to cover full width ground.scaleY = Math.max(1, 2732 / ground.height); // Scale to cover full height if needed // Make ground semi-transparent initially ground.alpha = 0.3; game.addChild(ground); var player = game.addChild(new Player()); player.x = 1024; player.y = 2400; // Create and attach weapon to game var weapon = new Weapon(); weapon.x = player.x; // Position at player location weapon.y = player.y - 100; // Position weapon above player game.addChild(weapon); var rhythmIndicator = new RhythmIndicator(); rhythmIndicator.x = 1024; rhythmIndicator.y = 200; rhythmIndicator.beatDuration = 600; // 100 BPM = 60000ms / 100 beats = 600ms per beat (synchronized with bgmusic) rhythmIndicator.startTime = LK.ticks * (1000 / 60); var enemies = []; var goldenEnemies = []; var explosiveEnemies = []; var bullets = []; var powerupCrates = []; var spawnTimer = 0; var goldenSpawnTimer = 0; var explosiveSpawnTimer = 0; var stoneEnemies = []; var stoneSpawnTimer = 0; var gameSpeed = 1; var bulletCount = 10; var maxBullets = 10; var isReloading = false; var reloadTimer = 0; var shotgunMode = false; var shotgunBulletCount = 1; var bosses = []; var trapBoxes = []; var trapBoxSpawnTimer = 0; var comboCount = 0; var lastShotWasPerfect = false; var tables = []; // Create tables in 2x3 grid formation in center of map var tableStartX = 1024 - 400; // Center horizontally accounting for table width and spacing var tableStartY = 766; // Position so second row is centered vertically var tableSpacingX = 800; // 400 pixels distance between tables (400 table width + 400 spacing) var tableSpacingY = 600; // 400 pixels distance between tables (200 table height + 400 spacing) for (var row = 0; row < 3; row++) { for (var col = 0; col < 2; col++) { var table = new Table(); table.x = tableStartX + col * tableSpacingX; table.y = tableStartY + row * tableSpacingY; // Flip right column tables (col === 1) to face left if (col === 1) { table.scaleX = -1; } // Make tables initially dark table.tint = 0x404040; tables.push(table); game.addChild(table); } } var scoreTxt = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var rhythmTxt = new Text2('Hit the beat!', { size: 40, fill: 0x27AE60, stroke: 0x000000, strokeThickness: 4, font: "'Arial Black', Impact, Arial, sans-serif", dropShadow: true, dropShadowColor: 0x000000, dropShadowBlur: 6, dropShadowAngle: Math.PI / 4, dropShadowDistance: 4 }); rhythmTxt.anchor.set(0.5, 0); rhythmTxt.y = 40; LK.gui.top.addChild(rhythmTxt); // Add texture background for ammo counter var ammoTexture = LK.getAsset('feedbackTexture', { anchorX: 0.5, anchorY: 1, scaleX: 1.2, scaleY: 2, alpha: 0.8 }); ammoTexture.x = 180; ammoTexture.y = -10; LK.gui.bottomLeft.addChild(ammoTexture); var ammoTxt = new Text2('Ammo: 10', { size: 80, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 6, font: "'Arial Black', Impact, Arial, sans-serif", dropShadow: true, dropShadowColor: 0x000000, dropShadowBlur: 4, dropShadowAngle: Math.PI / 4, dropShadowDistance: 3 }); ammoTxt.anchor.set(0, 1); LK.gui.bottomLeft.addChild(ammoTxt); var comboTxt = new Text2('Combo: 0', { size: 60, fill: 0xFFD700 }); comboTxt.anchor.set(1, 1); LK.gui.bottomRight.addChild(comboTxt); function playRandomEnemyKillSound() { var killSounds = ['enemyKill', 'enemyKill2', 'enemyKill3', 'enemyKill4']; var randomSound = killSounds[Math.floor(Math.random() * killSounds.length)]; LK.getSound(randomSound).play(); } function spawnEnemy() { // Check if we've reached the enemy limit if (enemies.length >= 10) { return; // Don't spawn if we have 10 or more enemies } var enemy = new Enemy(); // Only spawn from top enemy.x = Math.random() * 2048; enemy.y = -50; // Direction will be calculated in enemy.update() to continuously track player enemies.push(enemy); game.addChild(enemy); } function spawnGoldenEnemy() { var goldenEnemy = new GoldenEnemy(); // Only spawn from top goldenEnemy.x = Math.random() * 2048; goldenEnemy.y = -50; // Direction will be calculated in goldenEnemy.update() to continuously track player goldenEnemies.push(goldenEnemy); game.addChild(goldenEnemy); } function spawnExplosiveEnemy() { var explosiveEnemy = new ExplosiveEnemy(); // Only spawn from top explosiveEnemy.x = Math.random() * 2048; explosiveEnemy.y = -50; explosiveEnemies.push(explosiveEnemy); game.addChild(explosiveEnemy); } function spawnStoneEnemy() { var stoneEnemy = new StoneEnemy(); // Only spawn from top stoneEnemy.x = Math.random() * 2048; stoneEnemy.y = -50; stoneEnemies.push(stoneEnemy); game.addChild(stoneEnemy); } function spawnBoss() { // Starting from generation 5, spawn double bosses var bossesToSpawn = bossGeneration >= 5 ? 2 : 1; for (var b = 0; b < bossesToSpawn; b++) { var boss = new Boss(bossGeneration); // Spawn from top with some horizontal spacing for double bosses if (bossesToSpawn === 2) { boss.x = Math.random() * 1024 + b * 1024; // Split screen into two halves } else { boss.x = Math.random() * 2048; } boss.y = -500; bosses.push(boss); game.addChild(boss); } // Visual feedback for boss spawn LK.effects.flashScreen(0x8B0000, 1000); if (bossesToSpawn === 2) { rhythmTxt.setText('DOUBLE BOSS LV' + bossGeneration + ' INCOMING!'); } else { rhythmTxt.setText('BOSS LV' + bossGeneration + ' INCOMING!'); } rhythmTxt.tint = 0x8B0000; // Increase enemy spawn rate when boss appears gameSpeed += 0.5; // Increment boss generation for next spawn bossGeneration++; } function shootBullet(targetX, targetY) { if (bulletCount <= 0 || isReloading) { rhythmTxt.setText('Reloading...'); rhythmTxt.tint = 0xFFFF00; return; } if (!player.canShoot) { rhythmTxt.setText('Wrong timing!'); rhythmTxt.tint = 0xE74C3C; // Reset combo on wrong timing comboCount = 0; comboTxt.setText('Combo: ' + comboCount); lastShotWasPerfect = false; // Consume bullet even on wrong timing bulletCount--; ammoTxt.setText('Ammo: ' + bulletCount); // Add failure effect LK.effects.flashScreen(0xFF4444, 300); // Play miss sound LK.getSound('miss').play(); // Check if need to reload if (bulletCount <= 0) { isReloading = true; reloadTimer = 120; // 2 seconds at 60 FPS ammoTxt.setText('Reloading...'); ammoTxt.tint = 0xFFFF00; } return; } rhythmTxt.setText('Perfect!'); rhythmTxt.tint = 0x27AE60; // Increment combo count on perfect shot comboCount++; comboTxt.setText('Combo: ' + comboCount); lastShotWasPerfect = true; bulletCount--; ammoTxt.setText('Ammo: ' + bulletCount); if (bulletCount <= 0) { isReloading = true; reloadTimer = 120; // 2 seconds at 60 FPS ammoTxt.setText('Reloading...'); ammoTxt.tint = 0xFFFF00; } // Calculate base direction var dx = targetX - player.x; var dy = targetY - player.y; var length = Math.sqrt(dx * dx + dy * dy); var baseAngle = Math.atan2(dy, dx); // Rotate weapon to face shooting direction if (length > 0) { weapon.rotateToDirection(dx / length, dy / length); } // Check if we should create a guided bullet (every 5 combos) or redirect all bullets (every 10 combos) var shouldCreateGuided = comboCount > 0 && comboCount % 5 === 0 && lastShotWasPerfect; var shouldRedirectAll = comboCount > 0 && comboCount % 10 === 0 && lastShotWasPerfect; if (shotgunMode) { // Fire bullets based on accumulated shotgun count var totalBullets = shotgunBulletCount; var spreadRange = 0.8; // Total spread range in radians var angleStep = totalBullets > 1 ? spreadRange / (totalBullets - 1) : 0; var startAngle = baseAngle - spreadRange / 2; for (var s = 0; s < totalBullets; s++) { var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; var angle = totalBullets > 1 ? startAngle + s * angleStep : baseAngle; bullet.setDirection(Math.cos(angle), Math.sin(angle)); // Assign random color to bullet var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF]; var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)]; bullet.tint = randomColor; // Make first bullet guided if combo condition is met, or all bullets if combo 10 if (s === 0 && shouldCreateGuided || shouldRedirectAll) { bullet.isGuided = true; // Visual feedback for guided bullet var bulletColor = shouldRedirectAll ? 0xFFD700 : 0x00FFFF; // Gold for combo 10, cyan for combo 5 tween(bullet, { tint: bulletColor }, { duration: 200 }); } bullets.push(bullet); game.addChild(bullet); } } else { // Fire single bullet var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; bullet.setDirection(dx / length, dy / length); // Assign random color to bullet var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF]; var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)]; bullet.tint = randomColor; // Make bullet guided if combo condition is met if (shouldCreateGuided || shouldRedirectAll) { bullet.isGuided = true; // Visual feedback for guided bullet var bulletColor = shouldRedirectAll ? 0xFFD700 : 0x00FFFF; // Gold for combo 10, cyan for combo 5 tween(bullet, { tint: bulletColor }, { duration: 200 }); } bullets.push(bullet); game.addChild(bullet); } // Show special feedback for combo milestones if (shouldRedirectAll) { rhythmTxt.setText('ALL SHOTS GUIDED! Combo x' + comboCount); rhythmTxt.tint = 0xFFD700; LK.effects.flashScreen(0xFFD700, 500); } else if (shouldCreateGuided) { rhythmTxt.setText('GUIDED SHOT! Combo x' + comboCount); rhythmTxt.tint = 0x00FFFF; LK.effects.flashScreen(0x00FFFF, 300); } LK.getSound('shoot').play(); // Add camera shake when shooting var shakeIntensity = shotgunMode ? shotgunBulletCount * 8 : 20; tween(game, { x: game.x + shakeIntensity }, { duration: 50, onFinish: function onFinish() { tween(game, { x: game.x - shakeIntensity * 2 }, { duration: 50, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 50 }); } }); } }); } game.down = function (x, y, obj) { shootBullet(x, y); }; game.update = function () { spawnTimer++; goldenSpawnTimer++; explosiveSpawnTimer++; if (spawnTimer >= 120 / gameSpeed) { spawnEnemy(); spawnTimer = 0; } // Spawn golden enemy every 10 seconds (600 ticks at 60 FPS) if (goldenSpawnTimer >= 600) { spawnGoldenEnemy(); goldenSpawnTimer = 0; } // Spawn explosive enemy every 8 seconds (480 ticks at 60 FPS) if (explosiveSpawnTimer >= 480) { spawnExplosiveEnemy(); explosiveSpawnTimer = 0; } // Spawn stone enemy every 12 seconds (720 ticks at 60 FPS) stoneSpawnTimer++; if (stoneSpawnTimer >= 720) { spawnStoneEnemy(); stoneSpawnTimer = 0; } // Spawn trap box every 25 seconds (1500 ticks at 60 FPS) trapBoxSpawnTimer++; if (trapBoxSpawnTimer >= 1500) { var trapBox = new TrapBox(); trapBox.x = Math.random() * 2048; trapBox.y = -50; trapBoxes.push(trapBox); game.addChild(trapBox); trapBoxSpawnTimer = 0; } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check bullet-enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (bullet.intersects(enemy) && !enemy.isDying) { enemy.health--; if (enemy.health <= 0) { LK.setScore(LK.getScore() + 10); scoreTxt.setText('Score: ' + LK.getScore()); // Add combo point for hitting enemy comboCount++; comboTxt.setText('Combo: ' + comboCount); LK.getSound('hit').play(); playRandomEnemyKillSound(); LK.effects.flashObject(enemy, 0xFFFFFF, 200); // Start death sequence enemy.startDeathSequence(); // Remove from array after starting death sequence enemies.splice(j, 1); // Add camera shake when hitting enemy tween(game, { x: game.x + 12 }, { duration: 30, onFinish: function onFinish() { tween(game, { x: game.x - 24 }, { duration: 30, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 30 }); } }); } }); } bullet.destroy(); bullets.splice(i, 1); break; } } // Check bullet-golden enemy collisions for (var j = goldenEnemies.length - 1; j >= 0; j--) { var goldenEnemy = goldenEnemies[j]; if (bullet.intersects(goldenEnemy)) { goldenEnemy.health--; // Add combo point for hitting golden enemy comboCount++; comboTxt.setText('Combo: ' + comboCount); LK.getSound('hit').play(); LK.effects.flashObject(goldenEnemy, 0xFFFFFF, 200); // Add camera shake when hitting golden enemy tween(game, { x: game.x + 16 }, { duration: 35, onFinish: function onFinish() { tween(game, { x: game.x - 32 }, { duration: 35, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 35 }); } }); } }); bullet.destroy(); bullets.splice(i, 1); if (goldenEnemy.health <= 0) { LK.setScore(LK.getScore() + 50); scoreTxt.setText('Score: ' + LK.getScore()); playRandomEnemyKillSound(); // Drop powerup crate at golden enemy position var crate = new PowerupCrate(); crate.x = goldenEnemy.x; crate.y = goldenEnemy.y; crate.baseY = goldenEnemy.y; powerupCrates.push(crate); game.addChild(crate); // Start death sequence goldenEnemy.startDeathSequence(); goldenEnemies.splice(j, 1); } else { LK.setScore(LK.getScore() + 10); scoreTxt.setText('Score: ' + LK.getScore()); } break; } } // Check bullet-explosive enemy collisions for (var j = explosiveEnemies.length - 1; j >= 0; j--) { var explosiveEnemy = explosiveEnemies[j]; if (bullet.intersects(explosiveEnemy)) { explosiveEnemy.health--; // Add combo point for hitting explosive enemy comboCount++; comboTxt.setText('Combo: ' + comboCount); LK.getSound('hit').play(); LK.effects.flashObject(explosiveEnemy, 0xFFFFFF, 200); // Add camera shake when hitting explosive enemy tween(game, { x: game.x + 18 }, { duration: 35, onFinish: function onFinish() { tween(game, { x: game.x - 36 }, { duration: 35, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 35 }); } }); } }); bullet.destroy(); bullets.splice(i, 1); if (explosiveEnemy.health <= 0) { LK.setScore(LK.getScore() + 30); scoreTxt.setText('Score: ' + LK.getScore()); playRandomEnemyKillSound(); // Start death sequence explosiveEnemy.startDeathSequence(); explosiveEnemies.splice(j, 1); } else { LK.setScore(LK.getScore() + 15); scoreTxt.setText('Score: ' + LK.getScore()); } break; } } // Check bullet-boss collisions for (var j = bosses.length - 1; j >= 0; j--) { var boss = bosses[j]; if (bullet.intersects(boss)) { boss.health--; boss.updateHealthBar(); // Update health bar immediately // Add combo point for hitting boss comboCount++; comboTxt.setText('Combo: ' + comboCount); LK.getSound('hit').play(); LK.effects.flashObject(boss, 0xFFFFFF, 200); // Add camera shake when hitting boss tween(game, { x: game.x + 24 }, { duration: 40, onFinish: function onFinish() { tween(game, { x: game.x - 48 }, { duration: 40, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 40 }); } }); } }); bullet.destroy(); bullets.splice(i, 1); if (boss.health <= 0) { LK.setScore(LK.getScore() + 100); scoreTxt.setText('Score: ' + LK.getScore()); playRandomEnemyKillSound(); // Visual feedback for boss defeat LK.effects.flashScreen(0x00FF00, 800); rhythmTxt.setText('BOSS DEFEATED!'); rhythmTxt.tint = 0x00FF00; // Start death sequence boss.startDeathSequence(); bosses.splice(j, 1); } else { LK.setScore(LK.getScore() + 20); scoreTxt.setText('Score: ' + LK.getScore()); } break; } } // Check bullet-powerup crate collisions for (var j = powerupCrates.length - 1; j >= 0; j--) { var crate = powerupCrates[j]; if (bullet.intersects(crate) && !crate.isDestroying) { // Check if we haven't reached the maximum of 2 crates var maxShotgunBullets = 9; // 3^2 = 9 (maximum 2 crates accumulated) if (shotgunBulletCount < maxShotgunBullets) { // Enable shotgun mode and triple bullet count shotgunMode = true; shotgunBulletCount *= 3; // Visual feedback LK.effects.flashScreen(0x8e44ad, 500); rhythmTxt.setText('Shotgun x' + shotgunBulletCount + '!'); rhythmTxt.tint = 0x8e44ad; } else { // Maximum reached, show different feedback LK.effects.flashScreen(0xFFD700, 300); rhythmTxt.setText('Max Shotgun!'); rhythmTxt.tint = 0xFFD700; } // Remove crate and bullet bullet.destroy(); bullets.splice(i, 1); crate.destroy(); powerupCrates.splice(j, 1); break; } } // Check bullet-stone enemy collisions for (var j = stoneEnemies.length - 1; j >= 0; j--) { var stoneEnemy = stoneEnemies[j]; if (bullet.intersects(stoneEnemy) && !stoneEnemy.isDying) { stoneEnemy.health--; // Add combo point for hitting stone enemy comboCount++; comboTxt.setText('Combo: ' + comboCount); LK.getSound('hit').play(); LK.effects.flashObject(stoneEnemy, 0xFFFFFF, 200); // Add camera shake when hitting stone enemy tween(game, { x: game.x + 14 }, { duration: 35, onFinish: function onFinish() { tween(game, { x: game.x - 28 }, { duration: 35, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 35 }); } }); } }); bullet.destroy(); bullets.splice(i, 1); if (stoneEnemy.health <= 0) { LK.setScore(LK.getScore() + 35); scoreTxt.setText('Score: ' + LK.getScore()); playRandomEnemyKillSound(); // Start death sequence stoneEnemy.startDeathSequence(); stoneEnemies.splice(j, 1); } else { LK.setScore(LK.getScore() + 5); scoreTxt.setText('Score: ' + LK.getScore()); } break; } } // Check bullet-trap box collisions for (var j = trapBoxes.length - 1; j >= 0; j--) { var trapBox = trapBoxes[j]; if (bullet.intersects(trapBox) && !trapBox.isDying) { trapBox.health--; // Add combo point for hitting trap box comboCount++; comboTxt.setText('Combo: ' + comboCount); LK.getSound('hit').play(); LK.effects.flashObject(trapBox, 0xFFFFFF, 200); bullet.destroy(); bullets.splice(i, 1); if (trapBox.health <= 0) { LK.setScore(LK.getScore() + 20); scoreTxt.setText('Score: ' + LK.getScore()); playRandomEnemyKillSound(); // Start death sequence trapBox.startDeathSequence(); trapBoxes.splice(j, 1); } else { LK.setScore(LK.getScore() + 5); scoreTxt.setText('Score: ' + LK.getScore()); } break; } } } // Update enemies for (var k = enemies.length - 1; k >= 0; k--) { var enemy = enemies[k]; if (enemy.intersects(player)) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check enemy-table collisions for (var t = tables.length - 1; t >= 0; t--) { var table = tables[t]; if (!table.isDestroyed && enemy.intersects(table)) { table.destroy(); tables.splice(t, 1); break; } } if (enemy.x < -100 || enemy.x > 2148 || enemy.y < -100 || enemy.y > 2832) { enemy.destroy(); enemies.splice(k, 1); } } // Update golden enemies for (var k = goldenEnemies.length - 1; k >= 0; k--) { var goldenEnemy = goldenEnemies[k]; if (goldenEnemy.intersects(player)) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check golden enemy-table collisions for (var t = tables.length - 1; t >= 0; t--) { var table = tables[t]; if (!table.isDestroyed && goldenEnemy.intersects(table)) { table.destroy(); tables.splice(t, 1); break; } } if (goldenEnemy.x < -100 || goldenEnemy.x > 2148 || goldenEnemy.y < -100 || goldenEnemy.y > 2832) { goldenEnemy.destroy(); goldenEnemies.splice(k, 1); } } // Update bosses for (var k = bosses.length - 1; k >= 0; k--) { var boss = bosses[k]; if (boss.intersects(player)) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check boss-table collisions for (var t = tables.length - 1; t >= 0; t--) { var table = tables[t]; if (!table.isDestroyed && boss.intersects(table)) { table.destroy(); tables.splice(t, 1); break; } } if (boss.x < -200 || boss.x > 2248 || boss.y < -200 || boss.y > 2932) { boss.destroy(); bosses.splice(k, 1); } } // Update explosive enemies for (var k = explosiveEnemies.length - 1; k >= 0; k--) { var explosiveEnemy = explosiveEnemies[k]; if (explosiveEnemy.intersects(player)) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check explosive enemy-table collisions for (var t = tables.length - 1; t >= 0; t--) { var table = tables[t]; if (!table.isDestroyed && explosiveEnemy.intersects(table)) { table.destroy(); tables.splice(t, 1); break; } } if (explosiveEnemy.x < -100 || explosiveEnemy.x > 2148 || explosiveEnemy.y < -100 || explosiveEnemy.y > 2832) { explosiveEnemy.destroy(); explosiveEnemies.splice(k, 1); } } // Update and cleanup powerup crates for (var k = powerupCrates.length - 1; k >= 0; k--) { var crate = powerupCrates[k]; // Check if crate was destroyed (by timer or going off screen) if (!crate.parent) { powerupCrates.splice(k, 1); continue; } if (crate.x < -100 || crate.x > 2148 || crate.y < -100 || crate.y > 2832) { crate.destroy(); powerupCrates.splice(k, 1); } } // Update stone enemies for (var k = stoneEnemies.length - 1; k >= 0; k--) { var stoneEnemy = stoneEnemies[k]; if (stoneEnemy.intersects(player)) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check stone enemy-table collisions for (var t = tables.length - 1; t >= 0; t--) { var table = tables[t]; if (!table.isDestroyed && stoneEnemy.intersects(table)) { table.destroy(); tables.splice(t, 1); break; } } if (stoneEnemy.x < -150 || stoneEnemy.x > 2198 || stoneEnemy.y < -150 || stoneEnemy.y > 2882) { stoneEnemy.destroy(); stoneEnemies.splice(k, 1); } } // Update trap boxes for (var k = trapBoxes.length - 1; k >= 0; k--) { var trapBox = trapBoxes[k]; if (trapBox.intersects(player)) { LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check trap box-table collisions for (var t = tables.length - 1; t >= 0; t--) { var table = tables[t]; if (!table.isDestroyed && trapBox.intersects(table)) { table.destroy(); tables.splice(t, 1); break; } } if (trapBox.x < -200 || trapBox.x > 2248 || trapBox.y < -200 || trapBox.y > 2932) { trapBox.destroy(); trapBoxes.splice(k, 1); } } // Handle reloading if (isReloading) { reloadTimer--; if (reloadTimer <= 0) { bulletCount = maxBullets; isReloading = false; ammoTxt.setText('Ammo: ' + bulletCount); ammoTxt.tint = 0xFFFFFF; } } // Update weapon position to follow player weapon.x = player.x; weapon.y = player.y - 100; // Ensure player renders above weapon game.setChildIndex(weapon, game.getChildIndex(player) - 1); // Ensure rhythm indicator stays on top by moving it to the end of children array if (rhythmIndicator.parent) { game.setChildIndex(rhythmIndicator, game.children.length - 1); } // Increase difficulty over time if (LK.getScore() > 0 && LK.getScore() % 100 === 0 && LK.ticks % 60 === 0) { gameSpeed += 0.1; rhythmIndicator.beatDuration = Math.max(600, rhythmIndicator.beatDuration - 50); } }; // Add rhythm indicator last to ensure it renders above all other elements game.addChild(rhythmIndicator); // Ensure rhythm indicator stays on top of all enemies and ground game.setChildIndex(rhythmIndicator, game.children.length - 1); // Tutorial overlay setup var tutorialOverlay = new Container(); tutorialOverlay.x = 0; tutorialOverlay.y = 0; // Semi-transparent background var tutorialBg = LK.getAsset('borderLedTop', { anchorX: 0, anchorY: 0, scaleX: 1, scaleY: 91.07 // Scale to cover full screen height (2732/30) }); tutorialBg.tint = 0x000000; tutorialBg.alpha = 0.8; tutorialOverlay.addChild(tutorialBg); // Tutorial title var tutorialTitle = new Text2('RHYTHM SHOOTER', { size: 120, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 6, font: "'Arial Black', Impact, Arial, sans-serif" }); tutorialTitle.anchor.set(0.5, 0.5); tutorialTitle.x = 1024; tutorialTitle.y = 400; tutorialOverlay.addChild(tutorialTitle); // Tutorial instructions var tutorialText1 = new Text2('🎵 TIME YOUR SHOTS TO THE BEAT! 🎵', { size: 70, fill: 0x27AE60, stroke: 0x000000, strokeThickness: 4, font: "'Arial Black', Impact, Arial, sans-serif" }); tutorialText1.anchor.set(0.5, 0.5); tutorialText1.x = 1024; tutorialText1.y = 600; tutorialOverlay.addChild(tutorialText1); var tutorialText2 = new Text2('• Watch the green rhythm bar', { size: 60, fill: 0xFFFFFF }); tutorialText2.anchor.set(0.5, 0.5); tutorialText2.x = 1024; tutorialText2.y = 800; tutorialOverlay.addChild(tutorialText2); var tutorialText3 = new Text2('• Shoot ONLY when in orange zone', { size: 60, fill: 0xFFFFFF }); tutorialText3.anchor.set(0.5, 0.5); tutorialText3.x = 1024; tutorialText3.y = 900; tutorialOverlay.addChild(tutorialText3); var tutorialText4 = new Text2('• Wrong timing = wasted ammo!', { size: 60, fill: 0xE74C3C }); tutorialText4.anchor.set(0.5, 0.5); tutorialText4.x = 1024; tutorialText4.y = 1000; tutorialOverlay.addChild(tutorialText4); var tutorialText5 = new Text2('• Perfect shots build combos', { size: 60, fill: 0xFFD700 }); tutorialText5.anchor.set(0.5, 0.5); tutorialText5.x = 1024; tutorialText5.y = 1100; tutorialOverlay.addChild(tutorialText5); var tutorialText6 = new Text2('• Destroy enemies before they reach you', { size: 60, fill: 0xFFFFFF }); tutorialText6.anchor.set(0.5, 0.5); tutorialText6.x = 1024; tutorialText6.y = 1200; tutorialOverlay.addChild(tutorialText6); // Start button var startButton = new Text2('🎯 TAP TO START 🎯', { size: 90, fill: 0x00FF00, stroke: 0x000000, strokeThickness: 5, font: "'Arial Black', Impact, Arial, sans-serif" }); startButton.anchor.set(0.5, 0.5); startButton.x = 1024; startButton.y = 1600; tutorialOverlay.addChild(startButton); // Add pulsing effect to start button tween(startButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 800, yoyo: true, repeat: Infinity }); // Game state management var gameStarted = false; // Override game.down to handle tutorial var originalGameDown = game.down; game.down = function (x, y, obj) { if (!gameStarted) { // Start the game gameStarted = true; // Remove tutorial overlay tutorialOverlay.destroy(); // Start music LK.playMusic('bgmusic'); return; } // Call original shoot function originalGameDown(x, y, obj); }; // Override game.update to pause game mechanics until started var originalGameUpdate = game.update; game.update = function () { if (!gameStarted) { return; // Don't run game logic until started } // Call original update function originalGameUpdate(); }; // Add tutorial overlay to game game.addChild(tutorialOverlay); ; ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Boss = Container.expand(function (generation) {
var self = Container.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign random color to boss but make it initially dark
var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF];
var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)];
// Store the original color for later use
self.originalColor = randomColor;
// Make boss initially dark
bossGraphics.tint = 0x404040;
self.speed = 1;
self.generation = generation || 1;
self.health = (2 + self.generation) * 3; // First boss has 9 health, second has 12, etc.
self.maxHealth = (2 + self.generation) * 3;
self.isDying = false;
self.direction = {
x: 0,
y: 0
};
// Each generation doubles in base size: 1.5 * (2^(generation-1))
self.baseScale = 1.5 * Math.pow(2, self.generation - 1);
self.maxScale = self.baseScale * 2.67; // Keep same ratio as original (4/1.5)
self.lastDistanceFromPlayer = 0;
// Health bar setup
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.y = -80; // Position above boss
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5
});
healthBarFill.x = healthBarBg.x - healthBarBg.width / 2;
healthBarFill.y = healthBarBg.y;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBarFill.width = 120 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
// Initialize health bar
self.updateHealthBar();
self.startDeathSequence = function () {
if (self.isDying) return;
self.isDying = true;
self.speed = 0; // Stop movement
// Change color to red
tween(bossGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
// Wait one second then destroy
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
};
self.update = function () {
if (self.isDying) return; // Don't move if dying
// Calculate direction toward player every frame
var dx = player.x - self.x;
var dy = player.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
// Track distance for scaling
self.lastDistanceFromPlayer = length;
if (length > 0) {
self.direction.x = dx / length;
self.direction.y = dy / length;
}
// Keep boss at constant base scale
bossGraphics.scaleX = self.baseScale;
bossGraphics.scaleY = self.baseScale;
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Update health bar
self.updateHealthBar();
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 80;
self.direction = {
x: 0,
y: 0
};
self.isGuided = false;
self.setDirection = function (dirX, dirY) {
self.direction.x = dirX;
self.direction.y = dirY;
// Calculate rotation angle based on direction and rotate the bullet graphics
var angle = Math.atan2(dirY, dirX);
bulletGraphics.rotation = angle;
};
self.update = function () {
if (self.isGuided) {
// Find closest enemy
var closestEnemy = null;
var closestDistance = Infinity;
// Check regular enemies
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) {
closestDistance = distance;
closestEnemy = enemy;
}
}
// Check golden enemies
for (var i = 0; i < goldenEnemies.length; i++) {
var goldenEnemy = goldenEnemies[i];
var dx = goldenEnemy.x - self.x;
var dy = goldenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = goldenEnemy;
}
}
// Check explosive enemies
for (var i = 0; i < explosiveEnemies.length; i++) {
var explosiveEnemy = explosiveEnemies[i];
var dx = explosiveEnemy.x - self.x;
var dy = explosiveEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = explosiveEnemy;
}
}
// Check stone enemies
for (var i = 0; i < stoneEnemies.length; i++) {
var stoneEnemy = stoneEnemies[i];
var dx = stoneEnemy.x - self.x;
var dy = stoneEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = stoneEnemy;
}
}
// Check bosses
for (var i = 0; i < bosses.length; i++) {
var boss = bosses[i];
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = boss;
}
}
// Update direction towards closest enemy
if (closestEnemy && closestDistance > 0) {
var dx = closestEnemy.x - self.x;
var dy = closestEnemy.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
self.direction.x = dx / length;
self.direction.y = dy / length;
// Update bullet rotation
var angle = Math.atan2(dy, dx);
bulletGraphics.rotation = angle;
}
}
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Make enemy initially dark
enemyGraphics.tint = 0x404040;
self.speed = 2;
self.health = 1;
self.isDying = false;
self.direction = {
x: 0,
y: 0
};
self.startDeathSequence = function () {
if (self.isDying) return;
self.isDying = true;
self.speed = 0; // Stop movement
// Change color to red
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
// Wait one second then destroy
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
};
self.update = function () {
if (self.isDying) return; // Don't move if dying
// Calculate direction toward player every frame
var dx = player.x - self.x;
var dy = player.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
self.direction.x = dx / length;
self.direction.y = dy / length;
}
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
};
return self;
});
var ExplosiveEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('explosiveEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign random color to explosive enemy but make it initially dark
var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF];
var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)];
// Store the original color for later use
self.originalColor = randomColor;
// Make enemy initially dark
enemyGraphics.tint = 0x404040;
self.speed = 1.8;
self.health = 5;
self.maxHealth = 5;
self.isDying = false;
self.direction = {
x: 0,
y: 0
};
self.hasExploded = false;
self.startDeathSequence = function () {
if (self.isDying || self.hasExploded) return;
self.isDying = true;
self.speed = 0; // Stop movement
// Change color to red
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
// Wait one second then destroy
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
};
// Create explosion damage zone
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Create explosion visual effect
LK.effects.flashObject(self, 0xFF0000, 500);
LK.effects.flashScreen(0xFF4444, 300);
// Check for damage to nearby enemies and player
var explosionRadius = 500;
// Damage player if in range
var playerDx = player.x - self.x;
var playerDy = player.y - self.y;
var playerDistance = Math.sqrt(playerDx * playerDx + playerDy * playerDy);
if (playerDistance <= explosionRadius) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Damage nearby enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius && enemy !== self) {
enemy.destroy();
enemies.splice(i, 1);
LK.setScore(LK.getScore() + 5);
playRandomEnemyKillSound();
}
}
// Damage nearby golden enemies
for (var i = goldenEnemies.length - 1; i >= 0; i--) {
var goldenEnemy = goldenEnemies[i];
var dx = goldenEnemy.x - self.x;
var dy = goldenEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
goldenEnemy.destroy();
goldenEnemies.splice(i, 1);
LK.setScore(LK.getScore() + 25);
playRandomEnemyKillSound();
}
}
// Damage nearby explosive enemies (including self)
for (var i = explosiveEnemies.length - 1; i >= 0; i--) {
var explosiveEnemy = explosiveEnemies[i];
var dx = explosiveEnemy.x - self.x;
var dy = explosiveEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
explosiveEnemy.destroy();
explosiveEnemies.splice(i, 1);
LK.setScore(LK.getScore() + 15);
playRandomEnemyKillSound();
}
}
// Damage nearby bosses
for (var i = 0; i < bosses.length; i++) {
var boss = bosses[i];
var dx = boss.x - self.x;
var dy = boss.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
boss.health -= 2;
boss.updateHealthBar();
LK.effects.flashObject(boss, 0xFF0000, 300);
LK.setScore(LK.getScore() + 10);
}
}
// Create visual explosion zone
var explosionZone = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosionZone.x = self.x;
explosionZone.y = self.y;
explosionZone.scaleX = explosionRadius / 200;
explosionZone.scaleY = explosionRadius / 200;
explosionZone.alpha = 0.8;
game.addChild(explosionZone);
// Animate explosion zone
tween(explosionZone, {
scaleX: explosionZone.scaleX * 1.5,
scaleY: explosionZone.scaleY * 1.5,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
explosionZone.destroy();
}
});
};
self.update = function () {
if (self.isDying) return; // Don't move if dying
// Calculate direction toward player every frame
var dx = player.x - self.x;
var dy = player.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
self.direction.x = dx / length;
self.direction.y = dy / length;
}
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Check if health reached 1 and explode
if (self.health <= 1 && !self.hasExploded) {
self.explode();
self.startDeathSequence(); // Start death sequence after exploding
}
};
return self;
});
var GoldenEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('goldenEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Make golden enemy initially dark
enemyGraphics.tint = 0x404040;
self.speed = 1.5;
self.health = 2;
self.isDying = false;
self.direction = {
x: 0,
y: 0
};
self.startDeathSequence = function () {
if (self.isDying) return;
self.isDying = true;
self.speed = 0; // Stop movement
// Change color to red
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
// Wait one second then destroy
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
};
self.update = function () {
if (self.isDying) return; // Don't move if dying
// Calculate direction toward player every frame
var dx = player.x - self.x;
var dy = player.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
self.direction.x = dx / length;
self.direction.y = dy / length;
}
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Make player initially dark
playerGraphics.tint = 0x404040;
self.canShoot = false;
return self;
});
var PowerupCrate = Container.expand(function () {
var self = Container.call(this);
var crateGraphics = self.attachAsset('powerupCrate', {
anchorX: 0.5,
anchorY: 0.5
});
self.bobDirection = 1;
self.bobSpeed = 0.5;
self.baseY = 0;
self.isDestroying = false;
// Start 5 second countdown to destruction
tween(self, {}, {
duration: 5000,
onFinish: function onFinish() {
if (!self.isDestroying) {
self.isDestroying = true;
// Fade out animation before destruction
tween(self, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.destroy();
}
});
}
}
});
self.update = function () {
if (self.isDestroying) return;
// Simple bobbing animation
self.y += self.bobDirection * self.bobSpeed;
var distanceFromBase = Math.abs(self.y - self.baseY);
if (distanceFromBase > 10) {
self.bobDirection *= -1;
}
};
return self;
});
var RhythmIndicator = Container.expand(function () {
var self = Container.call(this);
var rhythmBar = self.attachAsset('rhythmBar', {
anchorX: 0.5,
anchorY: 0.5
});
var perfectZone = self.attachAsset('perfectZone', {
anchorX: 0.5,
anchorY: 0.5
});
perfectZone.alpha = 0.7;
var indicator = self.attachAsset('rhythmIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.x = -200;
self.beatDuration = 1000;
self.startTime = 0;
self.update = function () {
// Don't move rhythm indicator during tutorial
if (!gameStarted) {
return;
}
var elapsed = LK.ticks * (1000 / 60) - self.startTime;
var progress = elapsed % self.beatDuration / self.beatDuration;
indicator.x = -200 + progress * 400;
var perfectZoneStart = 150;
var perfectZoneEnd = 250;
var indicatorPos = 200 + indicator.x;
if (indicatorPos >= perfectZoneStart && indicatorPos <= perfectZoneEnd) {
player.canShoot = true;
perfectZone.tint = 0x27AE60;
// Make ground fully visible when in perfect zone
tween(ground, {
alpha: 1
}, {
duration: 100
});
// Light up all tables when in perfect zone
for (var t = 0; t < tables.length; t++) {
var table = tables[t];
if (!table.isDestroyed) {
tween(table, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
}
// Light up all enemies when in perfect zone
for (var e = 0; e < enemies.length; e++) {
var enemy = enemies[e];
if (!enemy.isDying) {
tween(enemy, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
}
for (var ge = 0; ge < goldenEnemies.length; ge++) {
var goldenEnemy = goldenEnemies[ge];
if (!goldenEnemy.isDying) {
tween(goldenEnemy, {
tint: 0xFFD700
}, {
duration: 100
});
}
}
for (var ee = 0; ee < explosiveEnemies.length; ee++) {
var explosiveEnemy = explosiveEnemies[ee];
if (!explosiveEnemy.isDying) {
tween(explosiveEnemy, {
tint: explosiveEnemy.originalColor
}, {
duration: 100
});
}
}
for (var se = 0; se < stoneEnemies.length; se++) {
var stoneEnemy = stoneEnemies[se];
if (!stoneEnemy.isDying) {
tween(stoneEnemy, {
tint: stoneEnemy.originalColor
}, {
duration: 100
});
}
}
for (var b = 0; b < bosses.length; b++) {
var boss = bosses[b];
if (!boss.isDying) {
tween(boss, {
tint: boss.originalColor
}, {
duration: 100
});
}
}
// Light up all trap boxes when in perfect zone
for (var tb = 0; tb < trapBoxes.length; tb++) {
var trapBox = trapBoxes[tb];
if (!trapBox.isDying) {
tween(trapBox, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
}
// Light up player when in perfect zone
tween(player, {
tint: 0xFFFFFF
}, {
duration: 100
});
// Light up weapon when in perfect zone
tween(weapon, {
tint: 0xFFFFFF
}, {
duration: 100
});
// Light up all LED borders when in perfect zone
tween(ledTop, {
tint: 0x00FF00,
alpha: 1
}, {
duration: 100
});
tween(ledBottom, {
tint: 0x00FF00,
alpha: 1
}, {
duration: 100
});
tween(ledLeft, {
tint: 0x00FF00,
alpha: 1
}, {
duration: 100
});
tween(ledRight, {
tint: 0x00FF00,
alpha: 1
}, {
duration: 100
});
} else {
player.canShoot = false;
perfectZone.tint = 0xE67E22;
// Make ground semi-transparent when not in perfect zone
tween(ground, {
alpha: 0.3
}, {
duration: 100
});
// Make tables dark when not in perfect zone
for (var t = 0; t < tables.length; t++) {
var table = tables[t];
if (!table.isDestroyed) {
tween(table, {
tint: 0x404040
}, {
duration: 100
});
}
}
// Make enemies dark when not in perfect zone
for (var e = 0; e < enemies.length; e++) {
var enemy = enemies[e];
if (!enemy.isDying) {
tween(enemy, {
tint: 0x404040
}, {
duration: 100
});
}
}
for (var ge = 0; ge < goldenEnemies.length; ge++) {
var goldenEnemy = goldenEnemies[ge];
if (!goldenEnemy.isDying) {
tween(goldenEnemy, {
tint: 0x404040
}, {
duration: 100
});
}
}
for (var ee = 0; ee < explosiveEnemies.length; ee++) {
var explosiveEnemy = explosiveEnemies[ee];
if (!explosiveEnemy.isDying) {
tween(explosiveEnemy, {
tint: 0x404040
}, {
duration: 100
});
}
}
for (var se = 0; se < stoneEnemies.length; se++) {
var stoneEnemy = stoneEnemies[se];
if (!stoneEnemy.isDying) {
tween(stoneEnemy, {
tint: 0x404040
}, {
duration: 100
});
}
}
for (var b = 0; b < bosses.length; b++) {
var boss = bosses[b];
if (!boss.isDying) {
tween(boss, {
tint: 0x404040
}, {
duration: 100
});
}
}
// Make trap boxes dark when not in perfect zone
for (var tb = 0; tb < trapBoxes.length; tb++) {
var trapBox = trapBoxes[tb];
if (!trapBox.isDying) {
tween(trapBox, {
tint: 0x404040
}, {
duration: 100
});
}
}
// Make player dark when not in perfect zone
tween(player, {
tint: 0x404040
}, {
duration: 100
});
// Make weapon dark when not in perfect zone
tween(weapon, {
tint: 0x404040
}, {
duration: 100
});
// Dim all LED borders when not in perfect zone
tween(ledTop, {
tint: 0x404040,
alpha: 0.3
}, {
duration: 100
});
tween(ledBottom, {
tint: 0x404040,
alpha: 0.3
}, {
duration: 100
});
tween(ledLeft, {
tint: 0x404040,
alpha: 0.3
}, {
duration: 100
});
tween(ledRight, {
tint: 0x404040,
alpha: 0.3
}, {
duration: 100
});
}
};
return self;
});
var StoneEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('stoneEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Give stone enemy a gray stone-like tint but make it initially dark
self.originalColor = 0x808080;
// Make enemy initially dark
enemyGraphics.tint = 0x404040;
// Make stone enemy semi-transparent
enemyGraphics.alpha = 0.5;
self.speed = 0.8;
self.health = 7;
self.maxHealth = 7;
self.isDying = false;
self.direction = {
x: 0,
y: 0
};
// Health bar setup
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.y = -80; // Position above stone enemy
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5
});
healthBarFill.x = healthBarBg.x - healthBarBg.width / 2;
healthBarFill.y = healthBarBg.y;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBarFill.width = 120 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
// Initialize health bar
self.updateHealthBar();
self.startDeathSequence = function () {
if (self.isDying) return;
self.isDying = true;
self.speed = 0; // Stop movement
// Change color to red
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
// Wait one second then destroy
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
};
self.update = function () {
if (self.isDying) return; // Don't move if dying
// Calculate direction toward player every frame
var dx = player.x - self.x;
var dy = player.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
self.direction.x = dx / length;
self.direction.y = dy / length;
}
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Update health bar
self.updateHealthBar();
};
return self;
});
var Table = Container.expand(function () {
var self = Container.call(this);
var tableGraphics = self.attachAsset('table', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.isDestroyed = false;
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
// Visual feedback for table destruction
LK.effects.flashObject(self, 0xFF0000, 300);
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
Container.prototype.destroy.call(self);
}
});
};
return self;
});
var TrapBox = Container.expand(function () {
var self = Container.call(this);
var trapBoxGraphics = self.attachAsset('trapBox', {
anchorX: 0.5,
anchorY: 0.5
});
// Make trap box initially dark
trapBoxGraphics.tint = 0x404040;
self.health = 10;
self.maxHealth = 10;
self.isDying = false;
self.spawnTimer = 0;
self.spawnedEnemy = null; // Track the currently spawned enemy
// Health bar setup
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
healthBarBg.y = -80; // Position above trap box
var healthBarFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5
});
healthBarFill.x = healthBarBg.x - healthBarBg.width / 2;
healthBarFill.y = healthBarBg.y;
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
healthBarFill.width = 120 * healthPercent;
// Change color based on health
if (healthPercent > 0.6) {
healthBarFill.tint = 0x00ff00; // Green
} else if (healthPercent > 0.3) {
healthBarFill.tint = 0xffff00; // Yellow
} else {
healthBarFill.tint = 0xff0000; // Red
}
};
// Initialize health bar
self.updateHealthBar();
self.startDeathSequence = function () {
if (self.isDying) return;
self.isDying = true;
self.speed = 0; // Stop movement
// Change color to red
tween(trapBoxGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
// Wait one second then destroy
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
};
self.spawnRandomEnemy = function () {
// Only spawn if no enemy is currently spawned or if the spawned enemy is destroyed
if (self.spawnedEnemy && self.spawnedEnemy.parent) {
return; // Don't spawn if there's already an active enemy
}
var enemyTypes = ['normal', 'golden', 'explosive'];
var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
var newEnemy;
if (randomType === 'normal') {
newEnemy = new Enemy();
enemies.push(newEnemy);
} else if (randomType === 'golden') {
newEnemy = new GoldenEnemy();
goldenEnemies.push(newEnemy);
} else if (randomType === 'explosive') {
newEnemy = new ExplosiveEnemy();
explosiveEnemies.push(newEnemy);
}
// Position enemy at trap box location
newEnemy.x = self.x;
newEnemy.y = self.y;
self.spawnedEnemy = newEnemy; // Track this enemy
game.addChild(newEnemy);
};
self.update = function () {
if (self.isDying) return; // Don't move or spawn if dying
// Zigzag movement pattern
if (!self.zigzagDirection) self.zigzagDirection = 1; // Initialize zigzag direction
if (!self.zigzagSpeed) self.zigzagSpeed = 2; // Zigzag horizontal speed
if (!self.zigzagTimer) self.zigzagTimer = 0; // Timer for zigzag changes
// Change zigzag direction every 60 ticks (1 second)
self.zigzagTimer++;
if (self.zigzagTimer >= 60) {
self.zigzagDirection *= -1;
self.zigzagTimer = 0;
}
// Apply zigzag movement
self.x += self.zigzagDirection * self.zigzagSpeed;
// Fall down slowly only if above middle of screen
if (self.y < 1000) {
// Stop falling when reaching above middle (2732/2)
self.y += 1; // Much slower speed (was 3, now 1)
}
// Clean up reference to destroyed enemy
if (self.spawnedEnemy && !self.spawnedEnemy.parent) {
self.spawnedEnemy = null;
}
// Spawn enemy every 3 seconds (180 ticks at 60 FPS)
self.spawnTimer++;
if (self.spawnTimer >= 180) {
self.spawnRandomEnemy();
self.spawnTimer = 0;
}
// Update health bar
self.updateHealthBar();
};
return self;
});
var Weapon = Container.expand(function () {
var self = Container.call(this);
var weaponGraphics = self.attachAsset('weapon', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial weapon properties - start dark
weaponGraphics.tint = 0x404040; // Start dark like other elements
self.lastShotDirection = {
x: 0,
y: -1
}; // Default pointing up
self.rotateToDirection = function (dirX, dirY) {
// Calculate rotation angle based on direction
var angle = Math.atan2(dirY, dirX);
// Smooth rotation using tween
tween(weaponGraphics, {
rotation: angle
}, {
duration: 150,
easing: tween.easeOut
});
// Store last shot direction
self.lastShotDirection.x = dirX;
self.lastShotDirection.y = dirY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1A1A2E
});
/****
* Game Code
****/
// Create LED border lights
var ledTop = LK.getAsset('borderLedTop', {
anchorX: 0.5,
anchorY: 0
});
ledTop.x = 1024; // Center horizontally
ledTop.y = 0; // Top of screen
ledTop.alpha = 0.3; // Start dim
game.addChild(ledTop);
var ledBottom = LK.getAsset('borderLedBottom', {
anchorX: 0.5,
anchorY: 1
});
ledBottom.x = 1024; // Center horizontally
ledBottom.y = 2732; // Bottom of screen
ledBottom.alpha = 0.3; // Start dim
game.addChild(ledBottom);
var ledLeft = LK.getAsset('borderLedLeft', {
anchorX: 0,
anchorY: 0.5
});
ledLeft.x = 0; // Left edge
ledLeft.y = 1366; // Center vertically
ledLeft.alpha = 0.3; // Start dim
game.addChild(ledLeft);
var ledRight = LK.getAsset('borderLedRight', {
anchorX: 1,
anchorY: 0.5
});
ledRight.x = 2048; // Right edge
ledRight.y = 1366; // Center vertically
ledRight.alpha = 0.3; // Start dim
game.addChild(ledRight);
// Create ground texture first to render below enemies
var ground = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 1
});
ground.x = 1024; // Center horizontally
ground.y = 2732; // Position at bottom of screen
// Scale ground to ensure it covers the full screen width
ground.scaleX = 2048 / ground.width; // Scale to cover full width
ground.scaleY = Math.max(1, 2732 / ground.height); // Scale to cover full height if needed
// Make ground semi-transparent initially
ground.alpha = 0.3;
game.addChild(ground);
var player = game.addChild(new Player());
player.x = 1024;
player.y = 2400;
// Create and attach weapon to game
var weapon = new Weapon();
weapon.x = player.x; // Position at player location
weapon.y = player.y - 100; // Position weapon above player
game.addChild(weapon);
var rhythmIndicator = new RhythmIndicator();
rhythmIndicator.x = 1024;
rhythmIndicator.y = 200;
rhythmIndicator.beatDuration = 600; // 100 BPM = 60000ms / 100 beats = 600ms per beat (synchronized with bgmusic)
rhythmIndicator.startTime = LK.ticks * (1000 / 60);
var enemies = [];
var goldenEnemies = [];
var explosiveEnemies = [];
var bullets = [];
var powerupCrates = [];
var spawnTimer = 0;
var goldenSpawnTimer = 0;
var explosiveSpawnTimer = 0;
var stoneEnemies = [];
var stoneSpawnTimer = 0;
var gameSpeed = 1;
var bulletCount = 10;
var maxBullets = 10;
var isReloading = false;
var reloadTimer = 0;
var shotgunMode = false;
var shotgunBulletCount = 1;
var bosses = [];
var trapBoxes = [];
var trapBoxSpawnTimer = 0;
var comboCount = 0;
var lastShotWasPerfect = false;
var tables = [];
// Create tables in 2x3 grid formation in center of map
var tableStartX = 1024 - 400; // Center horizontally accounting for table width and spacing
var tableStartY = 766; // Position so second row is centered vertically
var tableSpacingX = 800; // 400 pixels distance between tables (400 table width + 400 spacing)
var tableSpacingY = 600; // 400 pixels distance between tables (200 table height + 400 spacing)
for (var row = 0; row < 3; row++) {
for (var col = 0; col < 2; col++) {
var table = new Table();
table.x = tableStartX + col * tableSpacingX;
table.y = tableStartY + row * tableSpacingY;
// Flip right column tables (col === 1) to face left
if (col === 1) {
table.scaleX = -1;
}
// Make tables initially dark
table.tint = 0x404040;
tables.push(table);
game.addChild(table);
}
}
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var rhythmTxt = new Text2('Hit the beat!', {
size: 40,
fill: 0x27AE60,
stroke: 0x000000,
strokeThickness: 4,
font: "'Arial Black', Impact, Arial, sans-serif",
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowBlur: 6,
dropShadowAngle: Math.PI / 4,
dropShadowDistance: 4
});
rhythmTxt.anchor.set(0.5, 0);
rhythmTxt.y = 40;
LK.gui.top.addChild(rhythmTxt);
// Add texture background for ammo counter
var ammoTexture = LK.getAsset('feedbackTexture', {
anchorX: 0.5,
anchorY: 1,
scaleX: 1.2,
scaleY: 2,
alpha: 0.8
});
ammoTexture.x = 180;
ammoTexture.y = -10;
LK.gui.bottomLeft.addChild(ammoTexture);
var ammoTxt = new Text2('Ammo: 10', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6,
font: "'Arial Black', Impact, Arial, sans-serif",
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowBlur: 4,
dropShadowAngle: Math.PI / 4,
dropShadowDistance: 3
});
ammoTxt.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(ammoTxt);
var comboTxt = new Text2('Combo: 0', {
size: 60,
fill: 0xFFD700
});
comboTxt.anchor.set(1, 1);
LK.gui.bottomRight.addChild(comboTxt);
function playRandomEnemyKillSound() {
var killSounds = ['enemyKill', 'enemyKill2', 'enemyKill3', 'enemyKill4'];
var randomSound = killSounds[Math.floor(Math.random() * killSounds.length)];
LK.getSound(randomSound).play();
}
function spawnEnemy() {
// Check if we've reached the enemy limit
if (enemies.length >= 10) {
return; // Don't spawn if we have 10 or more enemies
}
var enemy = new Enemy();
// Only spawn from top
enemy.x = Math.random() * 2048;
enemy.y = -50;
// Direction will be calculated in enemy.update() to continuously track player
enemies.push(enemy);
game.addChild(enemy);
}
function spawnGoldenEnemy() {
var goldenEnemy = new GoldenEnemy();
// Only spawn from top
goldenEnemy.x = Math.random() * 2048;
goldenEnemy.y = -50;
// Direction will be calculated in goldenEnemy.update() to continuously track player
goldenEnemies.push(goldenEnemy);
game.addChild(goldenEnemy);
}
function spawnExplosiveEnemy() {
var explosiveEnemy = new ExplosiveEnemy();
// Only spawn from top
explosiveEnemy.x = Math.random() * 2048;
explosiveEnemy.y = -50;
explosiveEnemies.push(explosiveEnemy);
game.addChild(explosiveEnemy);
}
function spawnStoneEnemy() {
var stoneEnemy = new StoneEnemy();
// Only spawn from top
stoneEnemy.x = Math.random() * 2048;
stoneEnemy.y = -50;
stoneEnemies.push(stoneEnemy);
game.addChild(stoneEnemy);
}
function spawnBoss() {
// Starting from generation 5, spawn double bosses
var bossesToSpawn = bossGeneration >= 5 ? 2 : 1;
for (var b = 0; b < bossesToSpawn; b++) {
var boss = new Boss(bossGeneration);
// Spawn from top with some horizontal spacing for double bosses
if (bossesToSpawn === 2) {
boss.x = Math.random() * 1024 + b * 1024; // Split screen into two halves
} else {
boss.x = Math.random() * 2048;
}
boss.y = -500;
bosses.push(boss);
game.addChild(boss);
}
// Visual feedback for boss spawn
LK.effects.flashScreen(0x8B0000, 1000);
if (bossesToSpawn === 2) {
rhythmTxt.setText('DOUBLE BOSS LV' + bossGeneration + ' INCOMING!');
} else {
rhythmTxt.setText('BOSS LV' + bossGeneration + ' INCOMING!');
}
rhythmTxt.tint = 0x8B0000;
// Increase enemy spawn rate when boss appears
gameSpeed += 0.5;
// Increment boss generation for next spawn
bossGeneration++;
}
function shootBullet(targetX, targetY) {
if (bulletCount <= 0 || isReloading) {
rhythmTxt.setText('Reloading...');
rhythmTxt.tint = 0xFFFF00;
return;
}
if (!player.canShoot) {
rhythmTxt.setText('Wrong timing!');
rhythmTxt.tint = 0xE74C3C;
// Reset combo on wrong timing
comboCount = 0;
comboTxt.setText('Combo: ' + comboCount);
lastShotWasPerfect = false;
// Consume bullet even on wrong timing
bulletCount--;
ammoTxt.setText('Ammo: ' + bulletCount);
// Add failure effect
LK.effects.flashScreen(0xFF4444, 300);
// Play miss sound
LK.getSound('miss').play();
// Check if need to reload
if (bulletCount <= 0) {
isReloading = true;
reloadTimer = 120; // 2 seconds at 60 FPS
ammoTxt.setText('Reloading...');
ammoTxt.tint = 0xFFFF00;
}
return;
}
rhythmTxt.setText('Perfect!');
rhythmTxt.tint = 0x27AE60;
// Increment combo count on perfect shot
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
lastShotWasPerfect = true;
bulletCount--;
ammoTxt.setText('Ammo: ' + bulletCount);
if (bulletCount <= 0) {
isReloading = true;
reloadTimer = 120; // 2 seconds at 60 FPS
ammoTxt.setText('Reloading...');
ammoTxt.tint = 0xFFFF00;
}
// Calculate base direction
var dx = targetX - player.x;
var dy = targetY - player.y;
var length = Math.sqrt(dx * dx + dy * dy);
var baseAngle = Math.atan2(dy, dx);
// Rotate weapon to face shooting direction
if (length > 0) {
weapon.rotateToDirection(dx / length, dy / length);
}
// Check if we should create a guided bullet (every 5 combos) or redirect all bullets (every 10 combos)
var shouldCreateGuided = comboCount > 0 && comboCount % 5 === 0 && lastShotWasPerfect;
var shouldRedirectAll = comboCount > 0 && comboCount % 10 === 0 && lastShotWasPerfect;
if (shotgunMode) {
// Fire bullets based on accumulated shotgun count
var totalBullets = shotgunBulletCount;
var spreadRange = 0.8; // Total spread range in radians
var angleStep = totalBullets > 1 ? spreadRange / (totalBullets - 1) : 0;
var startAngle = baseAngle - spreadRange / 2;
for (var s = 0; s < totalBullets; s++) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
var angle = totalBullets > 1 ? startAngle + s * angleStep : baseAngle;
bullet.setDirection(Math.cos(angle), Math.sin(angle));
// Assign random color to bullet
var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF];
var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)];
bullet.tint = randomColor;
// Make first bullet guided if combo condition is met, or all bullets if combo 10
if (s === 0 && shouldCreateGuided || shouldRedirectAll) {
bullet.isGuided = true;
// Visual feedback for guided bullet
var bulletColor = shouldRedirectAll ? 0xFFD700 : 0x00FFFF; // Gold for combo 10, cyan for combo 5
tween(bullet, {
tint: bulletColor
}, {
duration: 200
});
}
bullets.push(bullet);
game.addChild(bullet);
}
} else {
// Fire single bullet
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
bullet.setDirection(dx / length, dy / length);
// Assign random color to bullet
var randomColors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE7C, 0xF38BA8, 0xA8E6CF, 0xC7CEEA, 0xFFB3BA, 0xBAE1FF];
var randomColor = randomColors[Math.floor(Math.random() * randomColors.length)];
bullet.tint = randomColor;
// Make bullet guided if combo condition is met
if (shouldCreateGuided || shouldRedirectAll) {
bullet.isGuided = true;
// Visual feedback for guided bullet
var bulletColor = shouldRedirectAll ? 0xFFD700 : 0x00FFFF; // Gold for combo 10, cyan for combo 5
tween(bullet, {
tint: bulletColor
}, {
duration: 200
});
}
bullets.push(bullet);
game.addChild(bullet);
}
// Show special feedback for combo milestones
if (shouldRedirectAll) {
rhythmTxt.setText('ALL SHOTS GUIDED! Combo x' + comboCount);
rhythmTxt.tint = 0xFFD700;
LK.effects.flashScreen(0xFFD700, 500);
} else if (shouldCreateGuided) {
rhythmTxt.setText('GUIDED SHOT! Combo x' + comboCount);
rhythmTxt.tint = 0x00FFFF;
LK.effects.flashScreen(0x00FFFF, 300);
}
LK.getSound('shoot').play();
// Add camera shake when shooting
var shakeIntensity = shotgunMode ? shotgunBulletCount * 8 : 20;
tween(game, {
x: game.x + shakeIntensity
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: game.x - shakeIntensity * 2
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 50
});
}
});
}
});
}
game.down = function (x, y, obj) {
shootBullet(x, y);
};
game.update = function () {
spawnTimer++;
goldenSpawnTimer++;
explosiveSpawnTimer++;
if (spawnTimer >= 120 / gameSpeed) {
spawnEnemy();
spawnTimer = 0;
}
// Spawn golden enemy every 10 seconds (600 ticks at 60 FPS)
if (goldenSpawnTimer >= 600) {
spawnGoldenEnemy();
goldenSpawnTimer = 0;
}
// Spawn explosive enemy every 8 seconds (480 ticks at 60 FPS)
if (explosiveSpawnTimer >= 480) {
spawnExplosiveEnemy();
explosiveSpawnTimer = 0;
}
// Spawn stone enemy every 12 seconds (720 ticks at 60 FPS)
stoneSpawnTimer++;
if (stoneSpawnTimer >= 720) {
spawnStoneEnemy();
stoneSpawnTimer = 0;
}
// Spawn trap box every 25 seconds (1500 ticks at 60 FPS)
trapBoxSpawnTimer++;
if (trapBoxSpawnTimer >= 1500) {
var trapBox = new TrapBox();
trapBox.x = Math.random() * 2048;
trapBox.y = -50;
trapBoxes.push(trapBox);
game.addChild(trapBox);
trapBoxSpawnTimer = 0;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy) && !enemy.isDying) {
enemy.health--;
if (enemy.health <= 0) {
LK.setScore(LK.getScore() + 10);
scoreTxt.setText('Score: ' + LK.getScore());
// Add combo point for hitting enemy
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
LK.getSound('hit').play();
playRandomEnemyKillSound();
LK.effects.flashObject(enemy, 0xFFFFFF, 200);
// Start death sequence
enemy.startDeathSequence();
// Remove from array after starting death sequence
enemies.splice(j, 1);
// Add camera shake when hitting enemy
tween(game, {
x: game.x + 12
}, {
duration: 30,
onFinish: function onFinish() {
tween(game, {
x: game.x - 24
}, {
duration: 30,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 30
});
}
});
}
});
}
bullet.destroy();
bullets.splice(i, 1);
break;
}
}
// Check bullet-golden enemy collisions
for (var j = goldenEnemies.length - 1; j >= 0; j--) {
var goldenEnemy = goldenEnemies[j];
if (bullet.intersects(goldenEnemy)) {
goldenEnemy.health--;
// Add combo point for hitting golden enemy
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
LK.getSound('hit').play();
LK.effects.flashObject(goldenEnemy, 0xFFFFFF, 200);
// Add camera shake when hitting golden enemy
tween(game, {
x: game.x + 16
}, {
duration: 35,
onFinish: function onFinish() {
tween(game, {
x: game.x - 32
}, {
duration: 35,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 35
});
}
});
}
});
bullet.destroy();
bullets.splice(i, 1);
if (goldenEnemy.health <= 0) {
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore());
playRandomEnemyKillSound();
// Drop powerup crate at golden enemy position
var crate = new PowerupCrate();
crate.x = goldenEnemy.x;
crate.y = goldenEnemy.y;
crate.baseY = goldenEnemy.y;
powerupCrates.push(crate);
game.addChild(crate);
// Start death sequence
goldenEnemy.startDeathSequence();
goldenEnemies.splice(j, 1);
} else {
LK.setScore(LK.getScore() + 10);
scoreTxt.setText('Score: ' + LK.getScore());
}
break;
}
}
// Check bullet-explosive enemy collisions
for (var j = explosiveEnemies.length - 1; j >= 0; j--) {
var explosiveEnemy = explosiveEnemies[j];
if (bullet.intersects(explosiveEnemy)) {
explosiveEnemy.health--;
// Add combo point for hitting explosive enemy
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
LK.getSound('hit').play();
LK.effects.flashObject(explosiveEnemy, 0xFFFFFF, 200);
// Add camera shake when hitting explosive enemy
tween(game, {
x: game.x + 18
}, {
duration: 35,
onFinish: function onFinish() {
tween(game, {
x: game.x - 36
}, {
duration: 35,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 35
});
}
});
}
});
bullet.destroy();
bullets.splice(i, 1);
if (explosiveEnemy.health <= 0) {
LK.setScore(LK.getScore() + 30);
scoreTxt.setText('Score: ' + LK.getScore());
playRandomEnemyKillSound();
// Start death sequence
explosiveEnemy.startDeathSequence();
explosiveEnemies.splice(j, 1);
} else {
LK.setScore(LK.getScore() + 15);
scoreTxt.setText('Score: ' + LK.getScore());
}
break;
}
}
// Check bullet-boss collisions
for (var j = bosses.length - 1; j >= 0; j--) {
var boss = bosses[j];
if (bullet.intersects(boss)) {
boss.health--;
boss.updateHealthBar(); // Update health bar immediately
// Add combo point for hitting boss
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
LK.getSound('hit').play();
LK.effects.flashObject(boss, 0xFFFFFF, 200);
// Add camera shake when hitting boss
tween(game, {
x: game.x + 24
}, {
duration: 40,
onFinish: function onFinish() {
tween(game, {
x: game.x - 48
}, {
duration: 40,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 40
});
}
});
}
});
bullet.destroy();
bullets.splice(i, 1);
if (boss.health <= 0) {
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore());
playRandomEnemyKillSound();
// Visual feedback for boss defeat
LK.effects.flashScreen(0x00FF00, 800);
rhythmTxt.setText('BOSS DEFEATED!');
rhythmTxt.tint = 0x00FF00;
// Start death sequence
boss.startDeathSequence();
bosses.splice(j, 1);
} else {
LK.setScore(LK.getScore() + 20);
scoreTxt.setText('Score: ' + LK.getScore());
}
break;
}
}
// Check bullet-powerup crate collisions
for (var j = powerupCrates.length - 1; j >= 0; j--) {
var crate = powerupCrates[j];
if (bullet.intersects(crate) && !crate.isDestroying) {
// Check if we haven't reached the maximum of 2 crates
var maxShotgunBullets = 9; // 3^2 = 9 (maximum 2 crates accumulated)
if (shotgunBulletCount < maxShotgunBullets) {
// Enable shotgun mode and triple bullet count
shotgunMode = true;
shotgunBulletCount *= 3;
// Visual feedback
LK.effects.flashScreen(0x8e44ad, 500);
rhythmTxt.setText('Shotgun x' + shotgunBulletCount + '!');
rhythmTxt.tint = 0x8e44ad;
} else {
// Maximum reached, show different feedback
LK.effects.flashScreen(0xFFD700, 300);
rhythmTxt.setText('Max Shotgun!');
rhythmTxt.tint = 0xFFD700;
}
// Remove crate and bullet
bullet.destroy();
bullets.splice(i, 1);
crate.destroy();
powerupCrates.splice(j, 1);
break;
}
}
// Check bullet-stone enemy collisions
for (var j = stoneEnemies.length - 1; j >= 0; j--) {
var stoneEnemy = stoneEnemies[j];
if (bullet.intersects(stoneEnemy) && !stoneEnemy.isDying) {
stoneEnemy.health--;
// Add combo point for hitting stone enemy
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
LK.getSound('hit').play();
LK.effects.flashObject(stoneEnemy, 0xFFFFFF, 200);
// Add camera shake when hitting stone enemy
tween(game, {
x: game.x + 14
}, {
duration: 35,
onFinish: function onFinish() {
tween(game, {
x: game.x - 28
}, {
duration: 35,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 35
});
}
});
}
});
bullet.destroy();
bullets.splice(i, 1);
if (stoneEnemy.health <= 0) {
LK.setScore(LK.getScore() + 35);
scoreTxt.setText('Score: ' + LK.getScore());
playRandomEnemyKillSound();
// Start death sequence
stoneEnemy.startDeathSequence();
stoneEnemies.splice(j, 1);
} else {
LK.setScore(LK.getScore() + 5);
scoreTxt.setText('Score: ' + LK.getScore());
}
break;
}
}
// Check bullet-trap box collisions
for (var j = trapBoxes.length - 1; j >= 0; j--) {
var trapBox = trapBoxes[j];
if (bullet.intersects(trapBox) && !trapBox.isDying) {
trapBox.health--;
// Add combo point for hitting trap box
comboCount++;
comboTxt.setText('Combo: ' + comboCount);
LK.getSound('hit').play();
LK.effects.flashObject(trapBox, 0xFFFFFF, 200);
bullet.destroy();
bullets.splice(i, 1);
if (trapBox.health <= 0) {
LK.setScore(LK.getScore() + 20);
scoreTxt.setText('Score: ' + LK.getScore());
playRandomEnemyKillSound();
// Start death sequence
trapBox.startDeathSequence();
trapBoxes.splice(j, 1);
} else {
LK.setScore(LK.getScore() + 5);
scoreTxt.setText('Score: ' + LK.getScore());
}
break;
}
}
}
// Update enemies
for (var k = enemies.length - 1; k >= 0; k--) {
var enemy = enemies[k];
if (enemy.intersects(player)) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check enemy-table collisions
for (var t = tables.length - 1; t >= 0; t--) {
var table = tables[t];
if (!table.isDestroyed && enemy.intersects(table)) {
table.destroy();
tables.splice(t, 1);
break;
}
}
if (enemy.x < -100 || enemy.x > 2148 || enemy.y < -100 || enemy.y > 2832) {
enemy.destroy();
enemies.splice(k, 1);
}
}
// Update golden enemies
for (var k = goldenEnemies.length - 1; k >= 0; k--) {
var goldenEnemy = goldenEnemies[k];
if (goldenEnemy.intersects(player)) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check golden enemy-table collisions
for (var t = tables.length - 1; t >= 0; t--) {
var table = tables[t];
if (!table.isDestroyed && goldenEnemy.intersects(table)) {
table.destroy();
tables.splice(t, 1);
break;
}
}
if (goldenEnemy.x < -100 || goldenEnemy.x > 2148 || goldenEnemy.y < -100 || goldenEnemy.y > 2832) {
goldenEnemy.destroy();
goldenEnemies.splice(k, 1);
}
}
// Update bosses
for (var k = bosses.length - 1; k >= 0; k--) {
var boss = bosses[k];
if (boss.intersects(player)) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check boss-table collisions
for (var t = tables.length - 1; t >= 0; t--) {
var table = tables[t];
if (!table.isDestroyed && boss.intersects(table)) {
table.destroy();
tables.splice(t, 1);
break;
}
}
if (boss.x < -200 || boss.x > 2248 || boss.y < -200 || boss.y > 2932) {
boss.destroy();
bosses.splice(k, 1);
}
}
// Update explosive enemies
for (var k = explosiveEnemies.length - 1; k >= 0; k--) {
var explosiveEnemy = explosiveEnemies[k];
if (explosiveEnemy.intersects(player)) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check explosive enemy-table collisions
for (var t = tables.length - 1; t >= 0; t--) {
var table = tables[t];
if (!table.isDestroyed && explosiveEnemy.intersects(table)) {
table.destroy();
tables.splice(t, 1);
break;
}
}
if (explosiveEnemy.x < -100 || explosiveEnemy.x > 2148 || explosiveEnemy.y < -100 || explosiveEnemy.y > 2832) {
explosiveEnemy.destroy();
explosiveEnemies.splice(k, 1);
}
}
// Update and cleanup powerup crates
for (var k = powerupCrates.length - 1; k >= 0; k--) {
var crate = powerupCrates[k];
// Check if crate was destroyed (by timer or going off screen)
if (!crate.parent) {
powerupCrates.splice(k, 1);
continue;
}
if (crate.x < -100 || crate.x > 2148 || crate.y < -100 || crate.y > 2832) {
crate.destroy();
powerupCrates.splice(k, 1);
}
}
// Update stone enemies
for (var k = stoneEnemies.length - 1; k >= 0; k--) {
var stoneEnemy = stoneEnemies[k];
if (stoneEnemy.intersects(player)) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check stone enemy-table collisions
for (var t = tables.length - 1; t >= 0; t--) {
var table = tables[t];
if (!table.isDestroyed && stoneEnemy.intersects(table)) {
table.destroy();
tables.splice(t, 1);
break;
}
}
if (stoneEnemy.x < -150 || stoneEnemy.x > 2198 || stoneEnemy.y < -150 || stoneEnemy.y > 2882) {
stoneEnemy.destroy();
stoneEnemies.splice(k, 1);
}
}
// Update trap boxes
for (var k = trapBoxes.length - 1; k >= 0; k--) {
var trapBox = trapBoxes[k];
if (trapBox.intersects(player)) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
// Check trap box-table collisions
for (var t = tables.length - 1; t >= 0; t--) {
var table = tables[t];
if (!table.isDestroyed && trapBox.intersects(table)) {
table.destroy();
tables.splice(t, 1);
break;
}
}
if (trapBox.x < -200 || trapBox.x > 2248 || trapBox.y < -200 || trapBox.y > 2932) {
trapBox.destroy();
trapBoxes.splice(k, 1);
}
}
// Handle reloading
if (isReloading) {
reloadTimer--;
if (reloadTimer <= 0) {
bulletCount = maxBullets;
isReloading = false;
ammoTxt.setText('Ammo: ' + bulletCount);
ammoTxt.tint = 0xFFFFFF;
}
}
// Update weapon position to follow player
weapon.x = player.x;
weapon.y = player.y - 100;
// Ensure player renders above weapon
game.setChildIndex(weapon, game.getChildIndex(player) - 1);
// Ensure rhythm indicator stays on top by moving it to the end of children array
if (rhythmIndicator.parent) {
game.setChildIndex(rhythmIndicator, game.children.length - 1);
}
// Increase difficulty over time
if (LK.getScore() > 0 && LK.getScore() % 100 === 0 && LK.ticks % 60 === 0) {
gameSpeed += 0.1;
rhythmIndicator.beatDuration = Math.max(600, rhythmIndicator.beatDuration - 50);
}
};
// Add rhythm indicator last to ensure it renders above all other elements
game.addChild(rhythmIndicator);
// Ensure rhythm indicator stays on top of all enemies and ground
game.setChildIndex(rhythmIndicator, game.children.length - 1);
// Tutorial overlay setup
var tutorialOverlay = new Container();
tutorialOverlay.x = 0;
tutorialOverlay.y = 0;
// Semi-transparent background
var tutorialBg = LK.getAsset('borderLedTop', {
anchorX: 0,
anchorY: 0,
scaleX: 1,
scaleY: 91.07 // Scale to cover full screen height (2732/30)
});
tutorialBg.tint = 0x000000;
tutorialBg.alpha = 0.8;
tutorialOverlay.addChild(tutorialBg);
// Tutorial title
var tutorialTitle = new Text2('RHYTHM SHOOTER', {
size: 120,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6,
font: "'Arial Black', Impact, Arial, sans-serif"
});
tutorialTitle.anchor.set(0.5, 0.5);
tutorialTitle.x = 1024;
tutorialTitle.y = 400;
tutorialOverlay.addChild(tutorialTitle);
// Tutorial instructions
var tutorialText1 = new Text2('🎵 TIME YOUR SHOTS TO THE BEAT! 🎵', {
size: 70,
fill: 0x27AE60,
stroke: 0x000000,
strokeThickness: 4,
font: "'Arial Black', Impact, Arial, sans-serif"
});
tutorialText1.anchor.set(0.5, 0.5);
tutorialText1.x = 1024;
tutorialText1.y = 600;
tutorialOverlay.addChild(tutorialText1);
var tutorialText2 = new Text2('• Watch the green rhythm bar', {
size: 60,
fill: 0xFFFFFF
});
tutorialText2.anchor.set(0.5, 0.5);
tutorialText2.x = 1024;
tutorialText2.y = 800;
tutorialOverlay.addChild(tutorialText2);
var tutorialText3 = new Text2('• Shoot ONLY when in orange zone', {
size: 60,
fill: 0xFFFFFF
});
tutorialText3.anchor.set(0.5, 0.5);
tutorialText3.x = 1024;
tutorialText3.y = 900;
tutorialOverlay.addChild(tutorialText3);
var tutorialText4 = new Text2('• Wrong timing = wasted ammo!', {
size: 60,
fill: 0xE74C3C
});
tutorialText4.anchor.set(0.5, 0.5);
tutorialText4.x = 1024;
tutorialText4.y = 1000;
tutorialOverlay.addChild(tutorialText4);
var tutorialText5 = new Text2('• Perfect shots build combos', {
size: 60,
fill: 0xFFD700
});
tutorialText5.anchor.set(0.5, 0.5);
tutorialText5.x = 1024;
tutorialText5.y = 1100;
tutorialOverlay.addChild(tutorialText5);
var tutorialText6 = new Text2('• Destroy enemies before they reach you', {
size: 60,
fill: 0xFFFFFF
});
tutorialText6.anchor.set(0.5, 0.5);
tutorialText6.x = 1024;
tutorialText6.y = 1200;
tutorialOverlay.addChild(tutorialText6);
// Start button
var startButton = new Text2('🎯 TAP TO START 🎯', {
size: 90,
fill: 0x00FF00,
stroke: 0x000000,
strokeThickness: 5,
font: "'Arial Black', Impact, Arial, sans-serif"
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 1024;
startButton.y = 1600;
tutorialOverlay.addChild(startButton);
// Add pulsing effect to start button
tween(startButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
yoyo: true,
repeat: Infinity
});
// Game state management
var gameStarted = false;
// Override game.down to handle tutorial
var originalGameDown = game.down;
game.down = function (x, y, obj) {
if (!gameStarted) {
// Start the game
gameStarted = true;
// Remove tutorial overlay
tutorialOverlay.destroy();
// Start music
LK.playMusic('bgmusic');
return;
}
// Call original shoot function
originalGameDown(x, y, obj);
};
// Override game.update to pause game mechanics until started
var originalGameUpdate = game.update;
game.update = function () {
if (!gameStarted) {
return; // Don't run game logic until started
}
// Call original update function
originalGameUpdate();
};
// Add tutorial overlay to game
game.addChild(tutorialOverlay);
;
;
Modern App Store icon, high definition, square with rounded corners, for a game titled "Rhythm Shooter" and with the description "Time your shots to the beat! A rhythm-based shooter where you can only fire when hitting the perfect musical timing.". No text on icon!
Zombie desde arriba estilo rpg, pixelart. In-Game asset. 2d. High contrast. No shadows
Zombie de oro , pixelart
Cámbialo a color neon blanco retro
Borra todo lo del sentro y crea un marco multicolor retro
Caja de munición de colores retro pixelart. In-Game asset. 2d. High contrast. No shadows
Vuelvelo neon brillante por bordes
Agrégale una bata negra y un bastón, pixelart
Agrega un círculo en el medio estilo retro con un arma, pixelart
Cambiarles los colores a azul y rojo escuro , pixelart
Una barra neon de forma cuadrada. In-Game asset. 2d. High contrast. No shadows