/****
* 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