User prompt
Que el maximo de cajas sea de 2
User prompt
Que el maximo de cajas acumuladas sea de 3
User prompt
Que el efecto de la caja de acumule , y triplique la cantidad de balas disparadas ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Reduce la vida de los enemigos dorados a solo 2 disparos
User prompt
Al obtener la caja las balas disparadas ahora serán 3 estilo escopeta ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Cambia el tiempo de recarga a 2
User prompt
Al eliminadar al enemigo dorado este dejara una caja , y si el jugador le dispara a la caja las balas se duplicarán ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega un nuevo enemigo de color dorado que aparezca cada 10 segundos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Si el jugador toca en un momento incorrecto, perderá una bala y agrega un efecto de fallo
User prompt
Ahora agrega un sistema de balas , y un contador grande en la parte inferior izquierda, que sean 10 balas, y al acabarlas esperar 3 segundos para que recargue ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Que los enemigos se dirijan al jugador ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Que los enemigos vengan de arriba
User prompt
Aa la barra de acierto más grande
User prompt
As la barra de cronometro un poco más grande
User prompt
As la barra de acierto un poco más grande
User prompt
Sincroniza los bpm con los de la música
User prompt
Cambia el metrónomo a 75 bpm
User prompt
Sincroniza el metrónomo a 100 bpm ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Baja al jugador un poco
User prompt
Cambia la velocidad de la bala x10
User prompt
Cambia el estilo de apuntado, que allá una mira o puntero que el jugador use para disparar dónde toque
Code edit (1 edits merged)
Please save this source code
User prompt
Rhythm Shooter
Initial prompt
Crea un juego rítmico con disparos, dónde allá que seguir el ritmo para poder disparar
/**** * 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