/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BossEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('jefe', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
self.speed = -1; // Very slow
self.lastX = self.x; // Initialize in constructor
self.lastY = self.y; // Initialize in constructor
self.shootCooldown = 0;
self.health = 10; // Takes 10 hits to destroy
self.verticalSpeed = 2;
self.verticalDirection = 1;
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Move vertically up and down
self.y += self.verticalSpeed * self.verticalDirection;
// Calculate bounds based on scaled boss height to keep it fully visible
var margin = 50; // Safety margin
var scaledHeight = 126 * 2; // Original height * scale factor
var topBound = scaledHeight / 2 + margin;
var bottomBound = 2732 - scaledHeight / 2 - margin;
if (self.y <= topBound || self.y >= bottomBound) {
self.verticalDirection *= -1;
}
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update last values for next frame
self.lastX = self.x;
self.lastY = self.y;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 60; // Slower shooting
return true;
}
return false;
};
self.takeDamage = function () {
self.health--;
// Visual feedback for taking damage
enemyGraphics.alpha = Math.max(0.3, Math.min(1.0, 0.3 + self.health / 10 * 0.7));
return self.health <= 0;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.lastY = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.health = 0;
self.verticalSpeed = 0;
self.verticalDirection = 1;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
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 = 8;
self.lastX = self.x; // Initialize in constructor
self.lastY = self.y; // Initialize in constructor
self.directionX = 1; // Default horizontal direction
self.directionY = 0; // Default no vertical movement
self.update = function () {
// Null checks for safety
if (self.lastX === null || self.lastX === undefined) self.lastX = self.x;
if (self.lastY === null || self.lastY === undefined) self.lastY = self.y;
// Set bullet rotation based on direction
var angle = Math.atan2(self.directionY, self.directionX);
bulletGraphics.rotation = angle;
self.x += self.speed * self.directionX;
self.y += self.speed * self.directionY;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.directionX = 0;
self.directionY = 0;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var CharacterSelectionScreen = Container.expand(function () {
var self = Container.call(this);
// Create title image
var titleImage = self.attachAsset('titulopersonajes', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 400,
scaleX: 4,
scaleY: 4
});
// Create character images
var player1Image = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 600,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
var player2Image = self.attachAsset('jugador2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
var player3Image = self.attachAsset('jugador3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 200,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
var player4Image = self.attachAsset('jugador4', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 600,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
// Create play button image
var playImage = self.attachAsset('jugar', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1400,
scaleX: 2,
scaleY: 2
});
// Store references for event handling
self.player1Image = player1Image;
self.player2Image = player2Image;
self.player3Image = player3Image;
self.player4Image = player4Image;
self.playButton = playImage;
self.selectedCharacter = 'player'; // Default selection
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -3;
self.lastX = self.x; // Initialize in constructor
self.shootCooldown = 0;
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update lastX for next frame
self.lastX = self.x;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 180; // 3 second cooldown
return true;
}
return false;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.lastX = self.x; // Initialize in constructor
self.lastY = self.y; // Initialize in constructor
self.directionX = -1; // Default moving left
self.directionY = 0; // Default no vertical movement
self.update = function () {
// Null checks for safety
if (self.lastX === null || self.lastX === undefined) self.lastX = self.x;
if (self.lastY === null || self.lastY === undefined) self.lastY = self.y;
// Set bullet rotation based on direction
var angle = Math.atan2(self.directionY, self.directionX);
bulletGraphics.rotation = angle;
self.x += self.speed * self.directionX;
self.y += self.speed * self.directionY;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.directionX = 0;
self.directionY = 0;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemigorapido', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -6; // Faster than regular enemy
self.lastX = self.x; // Initialize in constructor
self.shootCooldown = 0;
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update lastX for next frame
self.lastX = self.x;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 120; // 2 second cooldown
return true;
}
return false;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var HealthPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('healthPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2;
self.lastX = self.x;
self.lastY = self.y;
self.bobOffset = 0;
self.isFirstFrame = true;
self.update = function () {
if (self.isFirstFrame) {
self.isFirstFrame = false;
return;
}
// Move left
self.x += self.speed;
// Bob up and down for visual appeal
self.bobOffset += 0.1;
powerupGraphics.y = Math.sin(self.bobOffset) * 10;
// Gentle rotation
powerupGraphics.rotation += 0.02;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.isFirstFrame = true;
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset(selectedPlayerAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.shootCooldown = 0;
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update shield visual effect
if (playerShieldTime > 0) {
// Pulsing blue tint when shielded
var pulse = Math.sin(LK.ticks * 0.3) * 0.3 + 0.7;
playerGraphics.tint = 0x4488ff;
playerGraphics.alpha = pulse;
} else {
// Normal appearance
playerGraphics.tint = 0xffffff;
playerGraphics.alpha = 1.0;
}
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
// Faster shooting with weapon upgrades
var cooldown = Math.max(5, 10 - (playerWeaponLevel - 1) * 2);
self.shootCooldown = cooldown;
return playerWeaponLevel; // Return weapon level for multi-shot
}
return 0;
};
return self;
});
var ShieldEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemigoescudo', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2; // Slower than regular enemy
self.lastX = self.x; // Initialize in constructor
self.shootCooldown = 0;
self.health = 3; // Takes 3 hits to destroy
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update lastX for next frame
self.lastX = self.x;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 150; // Much slower shooting
return true;
}
return false;
};
self.takeDamage = function () {
self.health--;
// Visual feedback for taking damage
enemyGraphics.alpha = Math.max(0.5, Math.min(1.0, 0.5 + self.health / 3 * 0.5));
return self.health <= 0;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.health = 0;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var ShieldPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('shieldPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2;
self.lastX = self.x;
self.lastY = self.y;
self.bobOffset = 0;
self.isFirstFrame = true;
self.update = function () {
if (self.isFirstFrame) {
self.isFirstFrame = false;
return;
}
// Move left
self.x += self.speed;
// Bob up and down for visual appeal
self.bobOffset += 0.15;
powerupGraphics.y = Math.sin(self.bobOffset) * 15;
// Pulsing effect
var pulse = Math.sin(self.bobOffset * 2) * 0.2 + 1.0;
powerupGraphics.scaleX = pulse;
powerupGraphics.scaleY = pulse;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.isFirstFrame = true;
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var StartScreen = Container.expand(function () {
var self = Container.call(this);
// Create title image
var titleImage = self.attachAsset('titulo', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 450,
scaleX: 8,
scaleY: 8
});
// Create play button image
var playImage = self.attachAsset('jugar', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 50,
scaleX: 2,
scaleY: 2
});
// Create character selection button image
var characterImage = self.attachAsset('seleccionpersonaje', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 450,
scaleX: 2,
scaleY: 2
});
// Store button references for event handling
self.playButton = playImage;
self.characterButton = characterImage;
return self;
});
var WeaponPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('weaponPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2;
self.lastX = self.x;
self.lastY = self.y;
self.bobOffset = 0;
self.isFirstFrame = true;
self.update = function () {
if (self.isFirstFrame) {
self.isFirstFrame = false;
return;
}
// Move left
self.x += self.speed;
// Bob up and down for visual appeal
self.bobOffset += 0.12;
powerupGraphics.y = Math.sin(self.bobOffset) * 12;
// Spinning effect
powerupGraphics.rotation += 0.05;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.isFirstFrame = true;
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game state variables
var gameStarted = false;
var startScreen = null;
var characterSelectionScreen = null;
var selectedPlayerAsset = 'player'; // Track which character asset to use
var player = null;
var enemies = [];
var fastEnemies = [];
var shieldEnemies = [];
var bossEnemies = [];
var bullets = [];
var enemyBullets = [];
var enemySpawnTimer = 0;
var enemySpawnRate = 120; // Start spawning every 2 seconds (120 frames)
var difficultyTimer = 0;
var playerLives = 3;
var playerShieldTime = 0; // Shield duration in frames
var playerWeaponLevel = 1; // Weapon upgrade level (1-3)
var playerWeaponTime = 0; // Weapon upgrade duration in frames
// Power-up arrays
var healthPowerups = [];
var shieldPowerups = [];
var weaponPowerups = [];
var powerupSpawnTimer = 0;
var powerupSpawnRate = 180; // Spawn every 3 seconds
var enemiesEscaped = 0; // Track how many enemies have escaped
var bossTimer = 0; // Track time for boss battle cycles (2 minutes = 7200 frames)
var bossPhase = false; // Track if we're in boss phase
var bossesSpawned = 0; // Track how many bosses have been spawned in current phase
// Arrays to track objects marked for removal (using object references for safer iteration)
var bulletsToRemove = [];
var healthPowerupsToRemove = [];
var shieldPowerupsToRemove = [];
var weaponPowerupsToRemove = [];
var enemyBulletsToRemove = [];
var enemiesToRemove = [];
var fastEnemiesToRemove = [];
var shieldEnemiesToRemove = [];
var bossEnemiesToRemove = [];
var isDragging = false;
var scoreTxt = null;
var livesTxt = null;
var livesNumberTxt = null;
// Add background image
var backgroundImage = game.attachAsset('fondo', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Initialize start screen
startScreen = new StartScreen();
game.addChild(startScreen);
function showCharacterSelection() {
// Remove start screen
if (startScreen && startScreen.parent) {
startScreen.destroy();
startScreen = null;
}
// Show character selection screen
characterSelectionScreen = new CharacterSelectionScreen();
game.addChild(characterSelectionScreen);
}
function startGame() {
gameStarted = true;
// Remove start screen
if (startScreen && startScreen.parent) {
startScreen.destroy();
startScreen = null;
}
// Remove character selection screen
if (characterSelectionScreen && characterSelectionScreen.parent) {
characterSelectionScreen.destroy();
characterSelectionScreen = null;
}
// Initialize player with selected character asset
player = new Player();
player.x = 300;
player.y = 2732 - 150; // Near bottom with some margin
game.addChild(player);
// Create score display
scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.setText(LK.getScore());
// Create lives display with image
livesTxt = LK.getAsset('vidas', {
anchorX: 0,
anchorY: 0,
x: 120,
y: 20
});
LK.gui.topLeft.addChild(livesTxt);
// Create lives number display
livesNumberTxt = new Text2(playerLives.toString(), {
size: 80,
fill: 0xFFFFFF
});
livesNumberTxt.anchor.set(0, 0.5);
livesNumberTxt.x = 330; // Position to the right of vidas image
livesNumberTxt.y = 77; // Center vertically with vidas image
LK.gui.topLeft.addChild(livesNumberTxt);
}
// Touch/mouse down - handle start screen or game play
game.down = function (x, y, obj) {
if (!gameStarted) {
// Handle start screen
if (startScreen) {
// Check if play button was clicked
if (startScreen.playButton) {
var deltaX = x - startScreen.playButton.x;
var deltaY = y - startScreen.playButton.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
// Button click radius
startGame();
return;
}
}
// Check if character selection button was clicked
if (startScreen.characterButton) {
var deltaX = x - startScreen.characterButton.x;
var deltaY = y - startScreen.characterButton.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 200) {
// Button click radius
showCharacterSelection();
return;
}
}
return;
}
// Handle character selection screen
if (characterSelectionScreen) {
// Check character image clicks
if (characterSelectionScreen.player1Image) {
var deltaX = x - characterSelectionScreen.player1Image.x;
var deltaY = y - characterSelectionScreen.player1Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'player';
characterSelectionScreen.selectedCharacter = 'player';
return;
}
}
if (characterSelectionScreen.player2Image) {
var deltaX = x - characterSelectionScreen.player2Image.x;
var deltaY = y - characterSelectionScreen.player2Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'jugador2';
characterSelectionScreen.selectedCharacter = 'jugador2';
return;
}
}
if (characterSelectionScreen.player3Image) {
var deltaX = x - characterSelectionScreen.player3Image.x;
var deltaY = y - characterSelectionScreen.player3Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'jugador3';
characterSelectionScreen.selectedCharacter = 'jugador3';
return;
}
}
if (characterSelectionScreen.player4Image) {
var deltaX = x - characterSelectionScreen.player4Image.x;
var deltaY = y - characterSelectionScreen.player4Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'jugador4';
characterSelectionScreen.selectedCharacter = 'jugador4';
return;
}
}
// Check if play button was clicked
if (characterSelectionScreen.playButton) {
var deltaX = x - characterSelectionScreen.playButton.x;
var deltaY = y - characterSelectionScreen.playButton.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
// Button click radius
startGame();
return;
}
}
return;
}
}
isDragging = true;
// Move player to touch position (constrain to vertical movement)
player.y = Math.max(100, Math.min(2732 - 100, y));
var weaponLevel = player.shoot();
if (weaponLevel > 0) {
// Calculate direction vector from player to tap position
var deltaX = x - player.x;
var deltaY = y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var directionX = 1;
var directionY = 0;
if (distance > 1.0) {
directionX = deltaX / distance;
directionY = deltaY / distance;
}
// Create bullets based on weapon level
for (var w = 0; w < weaponLevel; w++) {
var bullet = new Bullet();
bullet.x = player.x + 40; // Spawn slightly ahead of player
bullet.y = player.y - 40 + (w - Math.floor(weaponLevel / 2)) * 20; // Spread bullets vertically
bullet.lastX = bullet.x;
bullet.lastY = bullet.y;
// Slight angle variation for multiple bullets
var angleOffset = (w - Math.floor(weaponLevel / 2)) * 0.2;
var cos = Math.cos(angleOffset);
var sin = Math.sin(angleOffset);
bullet.directionX = directionX * cos - directionY * sin;
bullet.directionY = directionX * sin + directionY * cos;
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
}
};
// Touch/mouse move - move player vertically while dragging
game.move = function (x, y, obj) {
if (!gameStarted || characterSelectionScreen) return;
if (isDragging) {
// Move player to touch position (constrain to vertical movement)
player.y = Math.max(100, Math.min(2732 - 100, y));
}
};
// Touch/mouse up - stop dragging
game.up = function (x, y, obj) {
if (!gameStarted || characterSelectionScreen) return;
isDragging = false;
};
game.update = function () {
// Only update game logic if game has started and not in character selection
if (!gameStarted || characterSelectionScreen) return;
// Update enemy spawn timer and difficulty
enemySpawnTimer++;
difficultyTimer++;
powerupSpawnTimer++;
// Update power-up timers
if (playerShieldTime > 0) {
playerShieldTime--;
}
if (playerWeaponTime > 0) {
playerWeaponTime--;
if (playerWeaponTime <= 0) {
playerWeaponLevel = 1; // Reset to normal weapon
}
}
// Increase difficulty every 10 seconds (600 frames)
if (difficultyTimer % 600 === 0 && enemySpawnRate > 30) {
enemySpawnRate -= 10; // Spawn enemies more frequently
}
// Update boss timer (always increment)
bossTimer++;
// Check for boss battle cycle (every 1 minute = 3600 frames)
if (bossTimer >= 3600) {
bossPhase = true;
bossesSpawned = 0;
}
// Spawn enemies - normal enemies spawn during non-boss phase OR after bosses are defeated
if (!bossPhase || bossPhase && bossesSpawned >= 2 && bossEnemies.length === 0) {
// Normal enemy spawning during non-boss phase or after boss defeat
if (enemySpawnTimer >= enemySpawnRate) {
var spawnType = Math.random();
// Define three spawn positions: top-right, center-right, bottom-right
var spawnPositions = [400,
// Top-right corner (with margin, lowered)
1566,
// Center-right (middle of screen, lowered)
2432 // Bottom-right corner (with margin)
];
var spawnY = spawnPositions[Math.floor(Math.random() * 3)];
if (spawnType < 0.6) {
// 60% chance for regular enemy
var enemy = new Enemy();
enemy.x = 2048 + 30;
enemy.y = spawnY;
enemy.lastX = enemy.x;
enemies.push(enemy);
game.addChild(enemy);
} else if (spawnType < 0.8) {
// 20% chance for fast enemy
var fastEnemy = new FastEnemy();
fastEnemy.x = 2048 + 30;
fastEnemy.y = spawnY;
fastEnemy.lastX = fastEnemy.x;
fastEnemies.push(fastEnemy);
game.addChild(fastEnemy);
} else {
// 20% chance for shield enemy
var shieldEnemy = new ShieldEnemy();
shieldEnemy.x = 2048 + 30;
shieldEnemy.y = spawnY;
shieldEnemy.lastX = shieldEnemy.x;
shieldEnemies.push(shieldEnemy);
game.addChild(shieldEnemy);
}
enemySpawnTimer = 0;
}
} else if (bossPhase && bossesSpawned < 2) {
// Boss phase - spawn 2 bosses then wait for them to be defeated
if (enemySpawnTimer >= 60) {
// Small delay between boss spawns
// Define three spawn positions for bosses: top-right, center-right, bottom-right
var bossSpawnPositions = [400,
// Top-right corner (with margin, lowered)
1566,
// Center-right (middle of screen, lowered)
2432 // Bottom-right corner (with margin)
];
var spawnY1 = bossSpawnPositions[Math.floor(Math.random() * 3)]; // First boss position
var spawnY2 = bossSpawnPositions[Math.floor(Math.random() * 3)]; // Second boss position
// Ensure bosses don't spawn at the same position
while (spawnY2 === spawnY1) {
spawnY2 = bossSpawnPositions[Math.floor(Math.random() * 3)];
}
// Spawn first boss
var bossEnemy1 = new BossEnemy();
bossEnemy1.x = 2048 + 30;
bossEnemy1.y = spawnY1;
bossEnemy1.lastX = bossEnemy1.x;
bossEnemies.push(bossEnemy1);
game.addChild(bossEnemy1);
// Spawn second boss
var bossEnemy2 = new BossEnemy();
bossEnemy2.x = 2048 + 30;
bossEnemy2.y = spawnY2;
bossEnemy2.lastX = bossEnemy2.x;
bossEnemies.push(bossEnemy2);
game.addChild(bossEnemy2);
bossesSpawned = 2;
enemySpawnTimer = 0;
}
}
// Check if boss phase should end (when timer reaches next cycle)
if (bossPhase && bossTimer >= 3600) {
bossPhase = false;
bossTimer = 0; // Reset timer for next boss cycle
}
// Spawn power-ups
if (powerupSpawnTimer >= powerupSpawnRate) {
var powerupType = Math.random();
var spawnY = Math.random() * (2732 - 200) + 100;
if (powerupType < 0.4) {
// 40% chance for health power-up
var healthPowerup = new HealthPowerup();
healthPowerup.x = 2048 + 30;
healthPowerup.y = spawnY;
healthPowerup.lastX = healthPowerup.x;
healthPowerup.lastY = healthPowerup.y;
healthPowerups.push(healthPowerup);
game.addChild(healthPowerup);
} else if (powerupType < 0.7) {
// 30% chance for shield power-up
var shieldPowerup = new ShieldPowerup();
shieldPowerup.x = 2048 + 30;
shieldPowerup.y = spawnY;
shieldPowerup.lastX = shieldPowerup.x;
shieldPowerup.lastY = shieldPowerup.y;
shieldPowerups.push(shieldPowerup);
game.addChild(shieldPowerup);
} else {
// 30% chance for weapon power-up
var weaponPowerup = new WeaponPowerup();
weaponPowerup.x = 2048 + 30;
weaponPowerup.y = spawnY;
weaponPowerup.lastX = weaponPowerup.x;
weaponPowerup.lastY = weaponPowerup.y;
weaponPowerups.push(weaponPowerup);
game.addChild(weaponPowerup);
}
powerupSpawnTimer = 0;
}
// Update and check bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Mark bullets that go off screen for removal
if (bullet.x > 2048 + 50 || bullet.y < -50) {
bulletsToRemove.push(bullet);
continue;
}
// Skip collision detection for bullets outside meaningful collision zones
if (bullet.x < -50 || bullet.x > 2100 || bullet.y < -50 || bullet.y > 2800) {
continue;
}
// Check bullet vs enemy collisions
var bulletHit = false;
// Check regular enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
// Skip enemies outside collision zone (bounding box check)
if (enemy.x < -250 || enemy.x > 2100 || enemy.y < -50 || enemy.y > 2800) {
continue;
}
// Check bullet collision with regular enemy using distance-based detection
var deltaX = bullet.x - enemy.x;
var deltaY = bullet.y - enemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 80) {
// 80px collision radius for regular enemies
// Bullet hit enemy
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Check for victory condition
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
// Visual feedback
tween(enemy, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (enemy.parent) {
enemy.destroy();
}
}
});
enemiesToRemove.push(enemy);
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
// Check fast enemies
for (var j = fastEnemies.length - 1; j >= 0; j--) {
var fastEnemy = fastEnemies[j];
// Skip fast enemies outside collision zone (bounding box check)
if (fastEnemy.x < -250 || fastEnemy.x > 2100 || fastEnemy.y < -50 || fastEnemy.y > 2800) {
continue;
}
// Check bullet collision with fast enemy using distance-based detection
var deltaX = bullet.x - fastEnemy.x;
var deltaY = bullet.y - fastEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 85) {
// 85px collision radius for fast enemies
LK.setScore(LK.getScore() + 15);
scoreTxt.setText(LK.getScore());
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
tween(fastEnemy, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (fastEnemy.parent) {
fastEnemy.destroy();
}
}
});
fastEnemiesToRemove.push(fastEnemy);
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
// Check shield enemies
for (var j = shieldEnemies.length - 1; j >= 0; j--) {
var shieldEnemy = shieldEnemies[j];
// Skip shield enemies outside collision zone (bounding box check)
if (shieldEnemy.x < -250 || shieldEnemy.x > 2100 || shieldEnemy.y < -50 || shieldEnemy.y > 2800) {
continue;
}
// Check bullet collision with shield enemy using distance-based detection
var deltaX = bullet.x - shieldEnemy.x;
var deltaY = bullet.y - shieldEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 90) {
// 90px collision radius for shield enemies (larger due to shield)
if (shieldEnemy.takeDamage()) {
LK.setScore(LK.getScore() + 30);
scoreTxt.setText(LK.getScore());
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
tween(shieldEnemy, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (shieldEnemy.parent) {
shieldEnemy.destroy();
}
}
});
shieldEnemiesToRemove.push(shieldEnemy);
} else {
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
}
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
// Check boss enemies
for (var j = bossEnemies.length - 1; j >= 0; j--) {
var bossEnemy = bossEnemies[j];
// Skip boss enemies outside collision zone (bounding box check)
if (bossEnemy.x < -250 || bossEnemy.x > 2100 || bossEnemy.y < -50 || bossEnemy.y > 2800) {
continue;
}
// Check bullet collision with boss enemy using distance-based detection
var deltaX = bullet.x - bossEnemy.x;
var deltaY = bullet.y - bossEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 80) {
// 80px collision radius for boss enemies (much larger due to 2x scale and boss status)
if (bossEnemy.takeDamage()) {
LK.setScore(LK.getScore() + 100);
scoreTxt.setText(LK.getScore());
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
tween(bossEnemy, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (bossEnemy.parent) {
bossEnemy.destroy();
}
}
});
bossEnemiesToRemove.push(bossEnemy);
} else {
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
}
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
if (bulletHit) continue;
}
// Update and check enemy bullets
for (var m = enemyBullets.length - 1; m >= 0; m--) {
var enemyBullet = enemyBullets[m];
// Mark enemy bullets that go off screen for removal
if (enemyBullet.x < -50 || enemyBullet.y < -50 || enemyBullet.y > 2732 + 50) {
enemyBulletsToRemove.push(enemyBullet);
continue;
}
// Skip collision detection for enemy bullets outside meaningful collision zones
if (enemyBullet.x < -50 || enemyBullet.x > 2100 || enemyBullet.y < -50 || enemyBullet.y > 2800) {
continue;
}
// Check if enemy bullet hits player center (more precise collision)
var deltaX = enemyBullet.x - player.x;
var deltaY = enemyBullet.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 45) {
// Only hits if within 45 pixels of player center (consistent bullet collision radius)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
enemyBulletsToRemove.push(enemyBullet);
}
}
// Update and check regular enemies
for (var k = enemies.length - 1; k >= 0; k--) {
var enemy = enemies[k];
// Mark enemies that go off screen for removal
if (enemy.x < -200) {
// Regular enemy width is 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
enemiesToRemove.push(enemy);
continue;
}
// Skip processing for enemies outside meaningful interaction zones
if (enemy.x < -250 || enemy.x > 2100 || enemy.y < -50 || enemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - enemy.x;
var deltaY = player.y - enemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Enemy shooting logic
if (enemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = enemy.x - (enemy.width / 2 + bulletOffset); // Spawn relative to enemy width
enemyBullet.y = enemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if enemy collides with player center using same distance calculation
if (distance < 60) {
// Only hits if within 60 pixels of player center (larger radius for direct contact)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
enemiesToRemove.push(enemy);
}
}
// Update and check fast enemies
for (var k = fastEnemies.length - 1; k >= 0; k--) {
var fastEnemy = fastEnemies[k];
// Mark fast enemies that go off screen for removal
if (fastEnemy.x < -200) {
// Fast enemy width is 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
fastEnemiesToRemove.push(fastEnemy);
continue;
}
// Skip processing for fast enemies outside meaningful interaction zones
if (fastEnemy.x < -250 || fastEnemy.x > 2100 || fastEnemy.y < -50 || fastEnemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - fastEnemy.x;
var deltaY = player.y - fastEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (fastEnemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = fastEnemy.x - (fastEnemy.width / 2 + bulletOffset); // Spawn relative to enemy width
enemyBullet.y = fastEnemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if fast enemy collides with player center using same distance calculation
if (distance < 65) {
// Only hits if within 65 pixels of player center (slightly larger due to fast movement)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
fastEnemiesToRemove.push(fastEnemy);
}
}
// Update and check shield enemies
for (var k = shieldEnemies.length - 1; k >= 0; k--) {
var shieldEnemy = shieldEnemies[k];
// Mark shield enemies that go off screen for removal
if (shieldEnemy.x < -200) {
// Shield enemy width is 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
shieldEnemiesToRemove.push(shieldEnemy);
continue;
}
// Skip processing for shield enemies outside meaningful interaction zones
if (shieldEnemy.x < -250 || shieldEnemy.x > 2100 || shieldEnemy.y < -50 || shieldEnemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - shieldEnemy.x;
var deltaY = player.y - shieldEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (shieldEnemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = shieldEnemy.x - (shieldEnemy.width / 2 + bulletOffset); // Spawn relative to enemy width
enemyBullet.y = shieldEnemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if shield enemy collides with player center using same distance calculation
if (distance < 70) {
// Only hits if within 70 pixels of player center (larger due to shield size)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
shieldEnemiesToRemove.push(shieldEnemy);
}
}
// Update and check health power-ups
for (var p = healthPowerups.length - 1; p >= 0; p--) {
var healthPowerup = healthPowerups[p];
// Remove if off screen
if (healthPowerup.x < -100) {
healthPowerupsToRemove.push(healthPowerup);
continue;
}
// Check collision with player
var deltaX = healthPowerup.x - player.x;
var deltaY = healthPowerup.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 60) {
// Player collected health power-up
if (playerLives < 5) {
// Max 5 lives
playerLives++;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.getSound('powerup').play();
LK.effects.flashScreen(0x00ff00, 300);
}
healthPowerupsToRemove.push(healthPowerup);
}
}
// Update and check shield power-ups
for (var p = shieldPowerups.length - 1; p >= 0; p--) {
var shieldPowerup = shieldPowerups[p];
// Remove if off screen
if (shieldPowerup.x < -100) {
shieldPowerupsToRemove.push(shieldPowerup);
continue;
}
// Check collision with player
var deltaX = shieldPowerup.x - player.x;
var deltaY = shieldPowerup.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 60) {
// Player collected shield power-up
playerShieldTime = 600; // 10 seconds of invincibility
LK.getSound('powerup').play();
LK.effects.flashScreen(0x0088ff, 300);
shieldPowerupsToRemove.push(shieldPowerup);
}
}
// Update and check weapon power-ups
for (var p = weaponPowerups.length - 1; p >= 0; p--) {
var weaponPowerup = weaponPowerups[p];
// Remove if off screen
if (weaponPowerup.x < -100) {
weaponPowerupsToRemove.push(weaponPowerup);
continue;
}
// Check collision with player
var deltaX = weaponPowerup.x - player.x;
var deltaY = weaponPowerup.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 60) {
// Player collected weapon power-up
playerWeaponLevel = Math.min(3, playerWeaponLevel + 1); // Max level 3
playerWeaponTime = 1800; // 30 seconds of upgrade
LK.getSound('powerup').play();
LK.effects.flashScreen(0xffaa00, 300);
weaponPowerupsToRemove.push(weaponPowerup);
}
}
// Update and check boss enemies
for (var k = bossEnemies.length - 1; k >= 0; k--) {
var bossEnemy = bossEnemies[k];
// Mark boss enemies that go off screen for removal
if (bossEnemy.x < -200) {
// Boss enemy width is 100px * 2 scale = 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
bossEnemiesToRemove.push(bossEnemy);
continue;
}
// Skip processing for boss enemies outside meaningful interaction zones
if (bossEnemy.x < -250 || bossEnemy.x > 2100 || bossEnemy.y < -50 || bossEnemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - bossEnemy.x;
var deltaY = player.y - bossEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (bossEnemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = bossEnemy.x - (bossEnemy.width / 2 + bulletOffset); // Spawn relative to enemy width (scaled)
enemyBullet.y = bossEnemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if boss enemy collides with player center using same distance calculation
if (distance < 90) {
// Only hits if within 90 pixels of player center (much larger due to 2x scale and boss size)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
bossEnemiesToRemove.push(bossEnemy);
}
}
// Deferred cleanup - process all marked objects for removal using safer object-based iteration
// Process bullets marked for removal
for (var i = bulletsToRemove.length - 1; i >= 0; i--) {
var bulletToRemove = bulletsToRemove[i];
if (bulletToRemove && bulletToRemove.parent) {
var index = bullets.indexOf(bulletToRemove);
if (index !== -1 && index < bullets.length) {
bullets[index].cleanup();
bullets[index].destroy();
bullets.splice(index, 1);
}
}
}
bulletsToRemove = [];
// Process enemy bullets marked for removal
for (var i = enemyBulletsToRemove.length - 1; i >= 0; i--) {
var enemyBulletToRemove = enemyBulletsToRemove[i];
if (enemyBulletToRemove && enemyBulletToRemove.parent) {
var index = enemyBullets.indexOf(enemyBulletToRemove);
if (index !== -1 && index < enemyBullets.length) {
enemyBullets[index].cleanup();
enemyBullets[index].destroy();
enemyBullets.splice(index, 1);
}
}
}
enemyBulletsToRemove = [];
// Process regular enemies marked for removal
for (var i = enemiesToRemove.length - 1; i >= 0; i--) {
var enemyToRemove = enemiesToRemove[i];
if (enemyToRemove && enemyToRemove.parent) {
var index = enemies.indexOf(enemyToRemove);
if (index !== -1 && index < enemies.length) {
enemies[index].cleanup();
enemies[index].destroy();
enemies.splice(index, 1);
}
}
}
enemiesToRemove = [];
// Process fast enemies marked for removal
for (var i = fastEnemiesToRemove.length - 1; i >= 0; i--) {
var fastEnemyToRemove = fastEnemiesToRemove[i];
if (fastEnemyToRemove && fastEnemyToRemove.parent) {
var index = fastEnemies.indexOf(fastEnemyToRemove);
if (index !== -1 && index < fastEnemies.length) {
fastEnemies[index].cleanup();
fastEnemies[index].destroy();
fastEnemies.splice(index, 1);
}
}
}
fastEnemiesToRemove = [];
// Process shield enemies marked for removal
for (var i = shieldEnemiesToRemove.length - 1; i >= 0; i--) {
var shieldEnemyToRemove = shieldEnemiesToRemove[i];
if (shieldEnemyToRemove && shieldEnemyToRemove.parent) {
var index = shieldEnemies.indexOf(shieldEnemyToRemove);
if (index !== -1 && index < shieldEnemies.length) {
shieldEnemies[index].cleanup();
shieldEnemies[index].destroy();
shieldEnemies.splice(index, 1);
}
}
}
shieldEnemiesToRemove = [];
// Process boss enemies marked for removal
for (var i = bossEnemiesToRemove.length - 1; i >= 0; i--) {
var bossEnemyToRemove = bossEnemiesToRemove[i];
if (bossEnemyToRemove && bossEnemyToRemove.parent) {
var index = bossEnemies.indexOf(bossEnemyToRemove);
if (index !== -1 && index < bossEnemies.length) {
bossEnemies[index].cleanup();
bossEnemies[index].destroy();
bossEnemies.splice(index, 1);
}
}
}
bossEnemiesToRemove = [];
// Process health power-ups marked for removal
for (var i = healthPowerupsToRemove.length - 1; i >= 0; i--) {
var healthPowerupToRemove = healthPowerupsToRemove[i];
if (healthPowerupToRemove && healthPowerupToRemove.parent) {
var index = healthPowerups.indexOf(healthPowerupToRemove);
if (index !== -1 && index < healthPowerups.length) {
healthPowerups[index].cleanup();
healthPowerups[index].destroy();
healthPowerups.splice(index, 1);
}
}
}
healthPowerupsToRemove = [];
// Process shield power-ups marked for removal
for (var i = shieldPowerupsToRemove.length - 1; i >= 0; i--) {
var shieldPowerupToRemove = shieldPowerupsToRemove[i];
if (shieldPowerupToRemove && shieldPowerupToRemove.parent) {
var index = shieldPowerups.indexOf(shieldPowerupToRemove);
if (index !== -1 && index < shieldPowerups.length) {
shieldPowerups[index].cleanup();
shieldPowerups[index].destroy();
shieldPowerups.splice(index, 1);
}
}
}
shieldPowerupsToRemove = [];
// Process weapon power-ups marked for removal
for (var i = weaponPowerupsToRemove.length - 1; i >= 0; i--) {
var weaponPowerupToRemove = weaponPowerupsToRemove[i];
if (weaponPowerupToRemove && weaponPowerupToRemove.parent) {
var index = weaponPowerups.indexOf(weaponPowerupToRemove);
if (index !== -1 && index < weaponPowerups.length) {
weaponPowerups[index].cleanup();
weaponPowerups[index].destroy();
weaponPowerups.splice(index, 1);
}
}
}
weaponPowerupsToRemove = [];
// Monitor array sizes and implement limits to prevent memory leaks
var maxBullets = 100;
var maxEnemyBullets = 200;
var maxEnemies = 50;
// Limit bullets array size with safety checks
if (bullets.length > maxBullets) {
for (var i = bullets.length - 1; i >= maxBullets; i--) {
if (i < bullets.length && bullets[i]) {
bullets[i].cleanup();
bullets[i].destroy();
bullets.splice(i, 1);
}
}
}
// Limit enemy bullets array size with safety checks
if (enemyBullets.length > maxEnemyBullets) {
for (var i = enemyBullets.length - 1; i >= maxEnemyBullets; i--) {
if (i < enemyBullets.length && enemyBullets[i]) {
enemyBullets[i].cleanup();
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
}
// Limit enemies array size with safety checks
if (enemies.length > maxEnemies) {
for (var i = enemies.length - 1; i >= maxEnemies; i--) {
if (i < enemies.length && enemies[i]) {
enemies[i].cleanup();
enemies[i].destroy();
enemies.splice(i, 1);
}
}
}
// Limit fast enemies array size with safety checks
if (fastEnemies.length > maxEnemies) {
for (var i = fastEnemies.length - 1; i >= maxEnemies; i--) {
if (i < fastEnemies.length && fastEnemies[i]) {
fastEnemies[i].cleanup();
fastEnemies[i].destroy();
fastEnemies.splice(i, 1);
}
}
}
// Limit shield enemies array size with safety checks
if (shieldEnemies.length > maxEnemies) {
for (var i = shieldEnemies.length - 1; i >= maxEnemies; i--) {
if (i < shieldEnemies.length && shieldEnemies[i]) {
shieldEnemies[i].cleanup();
shieldEnemies[i].destroy();
shieldEnemies.splice(i, 1);
}
}
}
// Limit boss enemies array size with safety checks
if (bossEnemies.length > maxEnemies) {
for (var i = bossEnemies.length - 1; i >= maxEnemies; i--) {
if (i < bossEnemies.length && bossEnemies[i]) {
bossEnemies[i].cleanup();
bossEnemies[i].destroy();
bossEnemies.splice(i, 1);
}
}
}
};
// Play background music
LK.playMusic('bgmusic'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BossEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('jefe', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
self.speed = -1; // Very slow
self.lastX = self.x; // Initialize in constructor
self.lastY = self.y; // Initialize in constructor
self.shootCooldown = 0;
self.health = 10; // Takes 10 hits to destroy
self.verticalSpeed = 2;
self.verticalDirection = 1;
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Move vertically up and down
self.y += self.verticalSpeed * self.verticalDirection;
// Calculate bounds based on scaled boss height to keep it fully visible
var margin = 50; // Safety margin
var scaledHeight = 126 * 2; // Original height * scale factor
var topBound = scaledHeight / 2 + margin;
var bottomBound = 2732 - scaledHeight / 2 - margin;
if (self.y <= topBound || self.y >= bottomBound) {
self.verticalDirection *= -1;
}
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update last values for next frame
self.lastX = self.x;
self.lastY = self.y;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 60; // Slower shooting
return true;
}
return false;
};
self.takeDamage = function () {
self.health--;
// Visual feedback for taking damage
enemyGraphics.alpha = Math.max(0.3, Math.min(1.0, 0.3 + self.health / 10 * 0.7));
return self.health <= 0;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.lastY = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.health = 0;
self.verticalSpeed = 0;
self.verticalDirection = 1;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
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 = 8;
self.lastX = self.x; // Initialize in constructor
self.lastY = self.y; // Initialize in constructor
self.directionX = 1; // Default horizontal direction
self.directionY = 0; // Default no vertical movement
self.update = function () {
// Null checks for safety
if (self.lastX === null || self.lastX === undefined) self.lastX = self.x;
if (self.lastY === null || self.lastY === undefined) self.lastY = self.y;
// Set bullet rotation based on direction
var angle = Math.atan2(self.directionY, self.directionX);
bulletGraphics.rotation = angle;
self.x += self.speed * self.directionX;
self.y += self.speed * self.directionY;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.directionX = 0;
self.directionY = 0;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var CharacterSelectionScreen = Container.expand(function () {
var self = Container.call(this);
// Create title image
var titleImage = self.attachAsset('titulopersonajes', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 400,
scaleX: 4,
scaleY: 4
});
// Create character images
var player1Image = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 600,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
var player2Image = self.attachAsset('jugador2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
var player3Image = self.attachAsset('jugador3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 200,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
var player4Image = self.attachAsset('jugador4', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 600,
y: 1000,
scaleX: 1.5,
scaleY: 1.5
});
// Create play button image
var playImage = self.attachAsset('jugar', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1400,
scaleX: 2,
scaleY: 2
});
// Store references for event handling
self.player1Image = player1Image;
self.player2Image = player2Image;
self.player3Image = player3Image;
self.player4Image = player4Image;
self.playButton = playImage;
self.selectedCharacter = 'player'; // Default selection
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -3;
self.lastX = self.x; // Initialize in constructor
self.shootCooldown = 0;
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update lastX for next frame
self.lastX = self.x;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 180; // 3 second cooldown
return true;
}
return false;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.lastX = self.x; // Initialize in constructor
self.lastY = self.y; // Initialize in constructor
self.directionX = -1; // Default moving left
self.directionY = 0; // Default no vertical movement
self.update = function () {
// Null checks for safety
if (self.lastX === null || self.lastX === undefined) self.lastX = self.x;
if (self.lastY === null || self.lastY === undefined) self.lastY = self.y;
// Set bullet rotation based on direction
var angle = Math.atan2(self.directionY, self.directionX);
bulletGraphics.rotation = angle;
self.x += self.speed * self.directionX;
self.y += self.speed * self.directionY;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.directionX = 0;
self.directionY = 0;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemigorapido', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -6; // Faster than regular enemy
self.lastX = self.x; // Initialize in constructor
self.shootCooldown = 0;
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update lastX for next frame
self.lastX = self.x;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 120; // 2 second cooldown
return true;
}
return false;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var HealthPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('healthPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2;
self.lastX = self.x;
self.lastY = self.y;
self.bobOffset = 0;
self.isFirstFrame = true;
self.update = function () {
if (self.isFirstFrame) {
self.isFirstFrame = false;
return;
}
// Move left
self.x += self.speed;
// Bob up and down for visual appeal
self.bobOffset += 0.1;
powerupGraphics.y = Math.sin(self.bobOffset) * 10;
// Gentle rotation
powerupGraphics.rotation += 0.02;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.isFirstFrame = true;
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset(selectedPlayerAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.shootCooldown = 0;
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update shield visual effect
if (playerShieldTime > 0) {
// Pulsing blue tint when shielded
var pulse = Math.sin(LK.ticks * 0.3) * 0.3 + 0.7;
playerGraphics.tint = 0x4488ff;
playerGraphics.alpha = pulse;
} else {
// Normal appearance
playerGraphics.tint = 0xffffff;
playerGraphics.alpha = 1.0;
}
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
// Faster shooting with weapon upgrades
var cooldown = Math.max(5, 10 - (playerWeaponLevel - 1) * 2);
self.shootCooldown = cooldown;
return playerWeaponLevel; // Return weapon level for multi-shot
}
return 0;
};
return self;
});
var ShieldEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemigoescudo', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2; // Slower than regular enemy
self.lastX = self.x; // Initialize in constructor
self.shootCooldown = 0;
self.health = 3; // Takes 3 hits to destroy
self.isFirstFrame = true; // Initialization flag
self.update = function () {
// Skip first frame logic to ensure proper initialization
if (self.isFirstFrame) {
self.isFirstFrame = false;
return; // Skip first frame to avoid premature trigger
}
// Move enemy left
self.x += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Update lastX for next frame
self.lastX = self.x;
};
self.canShoot = function () {
return self.shootCooldown <= 0;
};
self.shoot = function () {
if (self.canShoot()) {
self.shootCooldown = 150; // Much slower shooting
return true;
}
return false;
};
self.takeDamage = function () {
self.health--;
// Visual feedback for taking damage
enemyGraphics.alpha = Math.max(0.5, Math.min(1.0, 0.5 + self.health / 3 * 0.5));
return self.health <= 0;
};
self.cleanup = function () {
// Clear any references and reset properties
self.lastX = undefined;
self.shootCooldown = 0;
self.speed = 0;
self.health = 0;
self.isFirstFrame = true;
// Remove from parent if still attached
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var ShieldPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('shieldPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2;
self.lastX = self.x;
self.lastY = self.y;
self.bobOffset = 0;
self.isFirstFrame = true;
self.update = function () {
if (self.isFirstFrame) {
self.isFirstFrame = false;
return;
}
// Move left
self.x += self.speed;
// Bob up and down for visual appeal
self.bobOffset += 0.15;
powerupGraphics.y = Math.sin(self.bobOffset) * 15;
// Pulsing effect
var pulse = Math.sin(self.bobOffset * 2) * 0.2 + 1.0;
powerupGraphics.scaleX = pulse;
powerupGraphics.scaleY = pulse;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.isFirstFrame = true;
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
var StartScreen = Container.expand(function () {
var self = Container.call(this);
// Create title image
var titleImage = self.attachAsset('titulo', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 450,
scaleX: 8,
scaleY: 8
});
// Create play button image
var playImage = self.attachAsset('jugar', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 50,
scaleX: 2,
scaleY: 2
});
// Create character selection button image
var characterImage = self.attachAsset('seleccionpersonaje', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 450,
scaleX: 2,
scaleY: 2
});
// Store button references for event handling
self.playButton = playImage;
self.characterButton = characterImage;
return self;
});
var WeaponPowerup = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('weaponPowerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -2;
self.lastX = self.x;
self.lastY = self.y;
self.bobOffset = 0;
self.isFirstFrame = true;
self.update = function () {
if (self.isFirstFrame) {
self.isFirstFrame = false;
return;
}
// Move left
self.x += self.speed;
// Bob up and down for visual appeal
self.bobOffset += 0.12;
powerupGraphics.y = Math.sin(self.bobOffset) * 12;
// Spinning effect
powerupGraphics.rotation += 0.05;
self.lastX = self.x;
self.lastY = self.y;
};
self.cleanup = function () {
self.lastX = undefined;
self.lastY = undefined;
self.speed = 0;
self.isFirstFrame = true;
if (self.parent) {
self.parent.removeChild(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game state variables
var gameStarted = false;
var startScreen = null;
var characterSelectionScreen = null;
var selectedPlayerAsset = 'player'; // Track which character asset to use
var player = null;
var enemies = [];
var fastEnemies = [];
var shieldEnemies = [];
var bossEnemies = [];
var bullets = [];
var enemyBullets = [];
var enemySpawnTimer = 0;
var enemySpawnRate = 120; // Start spawning every 2 seconds (120 frames)
var difficultyTimer = 0;
var playerLives = 3;
var playerShieldTime = 0; // Shield duration in frames
var playerWeaponLevel = 1; // Weapon upgrade level (1-3)
var playerWeaponTime = 0; // Weapon upgrade duration in frames
// Power-up arrays
var healthPowerups = [];
var shieldPowerups = [];
var weaponPowerups = [];
var powerupSpawnTimer = 0;
var powerupSpawnRate = 180; // Spawn every 3 seconds
var enemiesEscaped = 0; // Track how many enemies have escaped
var bossTimer = 0; // Track time for boss battle cycles (2 minutes = 7200 frames)
var bossPhase = false; // Track if we're in boss phase
var bossesSpawned = 0; // Track how many bosses have been spawned in current phase
// Arrays to track objects marked for removal (using object references for safer iteration)
var bulletsToRemove = [];
var healthPowerupsToRemove = [];
var shieldPowerupsToRemove = [];
var weaponPowerupsToRemove = [];
var enemyBulletsToRemove = [];
var enemiesToRemove = [];
var fastEnemiesToRemove = [];
var shieldEnemiesToRemove = [];
var bossEnemiesToRemove = [];
var isDragging = false;
var scoreTxt = null;
var livesTxt = null;
var livesNumberTxt = null;
// Add background image
var backgroundImage = game.attachAsset('fondo', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Initialize start screen
startScreen = new StartScreen();
game.addChild(startScreen);
function showCharacterSelection() {
// Remove start screen
if (startScreen && startScreen.parent) {
startScreen.destroy();
startScreen = null;
}
// Show character selection screen
characterSelectionScreen = new CharacterSelectionScreen();
game.addChild(characterSelectionScreen);
}
function startGame() {
gameStarted = true;
// Remove start screen
if (startScreen && startScreen.parent) {
startScreen.destroy();
startScreen = null;
}
// Remove character selection screen
if (characterSelectionScreen && characterSelectionScreen.parent) {
characterSelectionScreen.destroy();
characterSelectionScreen = null;
}
// Initialize player with selected character asset
player = new Player();
player.x = 300;
player.y = 2732 - 150; // Near bottom with some margin
game.addChild(player);
// Create score display
scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.setText(LK.getScore());
// Create lives display with image
livesTxt = LK.getAsset('vidas', {
anchorX: 0,
anchorY: 0,
x: 120,
y: 20
});
LK.gui.topLeft.addChild(livesTxt);
// Create lives number display
livesNumberTxt = new Text2(playerLives.toString(), {
size: 80,
fill: 0xFFFFFF
});
livesNumberTxt.anchor.set(0, 0.5);
livesNumberTxt.x = 330; // Position to the right of vidas image
livesNumberTxt.y = 77; // Center vertically with vidas image
LK.gui.topLeft.addChild(livesNumberTxt);
}
// Touch/mouse down - handle start screen or game play
game.down = function (x, y, obj) {
if (!gameStarted) {
// Handle start screen
if (startScreen) {
// Check if play button was clicked
if (startScreen.playButton) {
var deltaX = x - startScreen.playButton.x;
var deltaY = y - startScreen.playButton.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
// Button click radius
startGame();
return;
}
}
// Check if character selection button was clicked
if (startScreen.characterButton) {
var deltaX = x - startScreen.characterButton.x;
var deltaY = y - startScreen.characterButton.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 200) {
// Button click radius
showCharacterSelection();
return;
}
}
return;
}
// Handle character selection screen
if (characterSelectionScreen) {
// Check character image clicks
if (characterSelectionScreen.player1Image) {
var deltaX = x - characterSelectionScreen.player1Image.x;
var deltaY = y - characterSelectionScreen.player1Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'player';
characterSelectionScreen.selectedCharacter = 'player';
return;
}
}
if (characterSelectionScreen.player2Image) {
var deltaX = x - characterSelectionScreen.player2Image.x;
var deltaY = y - characterSelectionScreen.player2Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'jugador2';
characterSelectionScreen.selectedCharacter = 'jugador2';
return;
}
}
if (characterSelectionScreen.player3Image) {
var deltaX = x - characterSelectionScreen.player3Image.x;
var deltaY = y - characterSelectionScreen.player3Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'jugador3';
characterSelectionScreen.selectedCharacter = 'jugador3';
return;
}
}
if (characterSelectionScreen.player4Image) {
var deltaX = x - characterSelectionScreen.player4Image.x;
var deltaY = y - characterSelectionScreen.player4Image.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
selectedPlayerAsset = 'jugador4';
characterSelectionScreen.selectedCharacter = 'jugador4';
return;
}
}
// Check if play button was clicked
if (characterSelectionScreen.playButton) {
var deltaX = x - characterSelectionScreen.playButton.x;
var deltaY = y - characterSelectionScreen.playButton.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 150) {
// Button click radius
startGame();
return;
}
}
return;
}
}
isDragging = true;
// Move player to touch position (constrain to vertical movement)
player.y = Math.max(100, Math.min(2732 - 100, y));
var weaponLevel = player.shoot();
if (weaponLevel > 0) {
// Calculate direction vector from player to tap position
var deltaX = x - player.x;
var deltaY = y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var directionX = 1;
var directionY = 0;
if (distance > 1.0) {
directionX = deltaX / distance;
directionY = deltaY / distance;
}
// Create bullets based on weapon level
for (var w = 0; w < weaponLevel; w++) {
var bullet = new Bullet();
bullet.x = player.x + 40; // Spawn slightly ahead of player
bullet.y = player.y - 40 + (w - Math.floor(weaponLevel / 2)) * 20; // Spread bullets vertically
bullet.lastX = bullet.x;
bullet.lastY = bullet.y;
// Slight angle variation for multiple bullets
var angleOffset = (w - Math.floor(weaponLevel / 2)) * 0.2;
var cos = Math.cos(angleOffset);
var sin = Math.sin(angleOffset);
bullet.directionX = directionX * cos - directionY * sin;
bullet.directionY = directionX * sin + directionY * cos;
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
}
};
// Touch/mouse move - move player vertically while dragging
game.move = function (x, y, obj) {
if (!gameStarted || characterSelectionScreen) return;
if (isDragging) {
// Move player to touch position (constrain to vertical movement)
player.y = Math.max(100, Math.min(2732 - 100, y));
}
};
// Touch/mouse up - stop dragging
game.up = function (x, y, obj) {
if (!gameStarted || characterSelectionScreen) return;
isDragging = false;
};
game.update = function () {
// Only update game logic if game has started and not in character selection
if (!gameStarted || characterSelectionScreen) return;
// Update enemy spawn timer and difficulty
enemySpawnTimer++;
difficultyTimer++;
powerupSpawnTimer++;
// Update power-up timers
if (playerShieldTime > 0) {
playerShieldTime--;
}
if (playerWeaponTime > 0) {
playerWeaponTime--;
if (playerWeaponTime <= 0) {
playerWeaponLevel = 1; // Reset to normal weapon
}
}
// Increase difficulty every 10 seconds (600 frames)
if (difficultyTimer % 600 === 0 && enemySpawnRate > 30) {
enemySpawnRate -= 10; // Spawn enemies more frequently
}
// Update boss timer (always increment)
bossTimer++;
// Check for boss battle cycle (every 1 minute = 3600 frames)
if (bossTimer >= 3600) {
bossPhase = true;
bossesSpawned = 0;
}
// Spawn enemies - normal enemies spawn during non-boss phase OR after bosses are defeated
if (!bossPhase || bossPhase && bossesSpawned >= 2 && bossEnemies.length === 0) {
// Normal enemy spawning during non-boss phase or after boss defeat
if (enemySpawnTimer >= enemySpawnRate) {
var spawnType = Math.random();
// Define three spawn positions: top-right, center-right, bottom-right
var spawnPositions = [400,
// Top-right corner (with margin, lowered)
1566,
// Center-right (middle of screen, lowered)
2432 // Bottom-right corner (with margin)
];
var spawnY = spawnPositions[Math.floor(Math.random() * 3)];
if (spawnType < 0.6) {
// 60% chance for regular enemy
var enemy = new Enemy();
enemy.x = 2048 + 30;
enemy.y = spawnY;
enemy.lastX = enemy.x;
enemies.push(enemy);
game.addChild(enemy);
} else if (spawnType < 0.8) {
// 20% chance for fast enemy
var fastEnemy = new FastEnemy();
fastEnemy.x = 2048 + 30;
fastEnemy.y = spawnY;
fastEnemy.lastX = fastEnemy.x;
fastEnemies.push(fastEnemy);
game.addChild(fastEnemy);
} else {
// 20% chance for shield enemy
var shieldEnemy = new ShieldEnemy();
shieldEnemy.x = 2048 + 30;
shieldEnemy.y = spawnY;
shieldEnemy.lastX = shieldEnemy.x;
shieldEnemies.push(shieldEnemy);
game.addChild(shieldEnemy);
}
enemySpawnTimer = 0;
}
} else if (bossPhase && bossesSpawned < 2) {
// Boss phase - spawn 2 bosses then wait for them to be defeated
if (enemySpawnTimer >= 60) {
// Small delay between boss spawns
// Define three spawn positions for bosses: top-right, center-right, bottom-right
var bossSpawnPositions = [400,
// Top-right corner (with margin, lowered)
1566,
// Center-right (middle of screen, lowered)
2432 // Bottom-right corner (with margin)
];
var spawnY1 = bossSpawnPositions[Math.floor(Math.random() * 3)]; // First boss position
var spawnY2 = bossSpawnPositions[Math.floor(Math.random() * 3)]; // Second boss position
// Ensure bosses don't spawn at the same position
while (spawnY2 === spawnY1) {
spawnY2 = bossSpawnPositions[Math.floor(Math.random() * 3)];
}
// Spawn first boss
var bossEnemy1 = new BossEnemy();
bossEnemy1.x = 2048 + 30;
bossEnemy1.y = spawnY1;
bossEnemy1.lastX = bossEnemy1.x;
bossEnemies.push(bossEnemy1);
game.addChild(bossEnemy1);
// Spawn second boss
var bossEnemy2 = new BossEnemy();
bossEnemy2.x = 2048 + 30;
bossEnemy2.y = spawnY2;
bossEnemy2.lastX = bossEnemy2.x;
bossEnemies.push(bossEnemy2);
game.addChild(bossEnemy2);
bossesSpawned = 2;
enemySpawnTimer = 0;
}
}
// Check if boss phase should end (when timer reaches next cycle)
if (bossPhase && bossTimer >= 3600) {
bossPhase = false;
bossTimer = 0; // Reset timer for next boss cycle
}
// Spawn power-ups
if (powerupSpawnTimer >= powerupSpawnRate) {
var powerupType = Math.random();
var spawnY = Math.random() * (2732 - 200) + 100;
if (powerupType < 0.4) {
// 40% chance for health power-up
var healthPowerup = new HealthPowerup();
healthPowerup.x = 2048 + 30;
healthPowerup.y = spawnY;
healthPowerup.lastX = healthPowerup.x;
healthPowerup.lastY = healthPowerup.y;
healthPowerups.push(healthPowerup);
game.addChild(healthPowerup);
} else if (powerupType < 0.7) {
// 30% chance for shield power-up
var shieldPowerup = new ShieldPowerup();
shieldPowerup.x = 2048 + 30;
shieldPowerup.y = spawnY;
shieldPowerup.lastX = shieldPowerup.x;
shieldPowerup.lastY = shieldPowerup.y;
shieldPowerups.push(shieldPowerup);
game.addChild(shieldPowerup);
} else {
// 30% chance for weapon power-up
var weaponPowerup = new WeaponPowerup();
weaponPowerup.x = 2048 + 30;
weaponPowerup.y = spawnY;
weaponPowerup.lastX = weaponPowerup.x;
weaponPowerup.lastY = weaponPowerup.y;
weaponPowerups.push(weaponPowerup);
game.addChild(weaponPowerup);
}
powerupSpawnTimer = 0;
}
// Update and check bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Mark bullets that go off screen for removal
if (bullet.x > 2048 + 50 || bullet.y < -50) {
bulletsToRemove.push(bullet);
continue;
}
// Skip collision detection for bullets outside meaningful collision zones
if (bullet.x < -50 || bullet.x > 2100 || bullet.y < -50 || bullet.y > 2800) {
continue;
}
// Check bullet vs enemy collisions
var bulletHit = false;
// Check regular enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
// Skip enemies outside collision zone (bounding box check)
if (enemy.x < -250 || enemy.x > 2100 || enemy.y < -50 || enemy.y > 2800) {
continue;
}
// Check bullet collision with regular enemy using distance-based detection
var deltaX = bullet.x - enemy.x;
var deltaY = bullet.y - enemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 80) {
// 80px collision radius for regular enemies
// Bullet hit enemy
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Check for victory condition
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
// Visual feedback
tween(enemy, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (enemy.parent) {
enemy.destroy();
}
}
});
enemiesToRemove.push(enemy);
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
// Check fast enemies
for (var j = fastEnemies.length - 1; j >= 0; j--) {
var fastEnemy = fastEnemies[j];
// Skip fast enemies outside collision zone (bounding box check)
if (fastEnemy.x < -250 || fastEnemy.x > 2100 || fastEnemy.y < -50 || fastEnemy.y > 2800) {
continue;
}
// Check bullet collision with fast enemy using distance-based detection
var deltaX = bullet.x - fastEnemy.x;
var deltaY = bullet.y - fastEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 85) {
// 85px collision radius for fast enemies
LK.setScore(LK.getScore() + 15);
scoreTxt.setText(LK.getScore());
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
tween(fastEnemy, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (fastEnemy.parent) {
fastEnemy.destroy();
}
}
});
fastEnemiesToRemove.push(fastEnemy);
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
// Check shield enemies
for (var j = shieldEnemies.length - 1; j >= 0; j--) {
var shieldEnemy = shieldEnemies[j];
// Skip shield enemies outside collision zone (bounding box check)
if (shieldEnemy.x < -250 || shieldEnemy.x > 2100 || shieldEnemy.y < -50 || shieldEnemy.y > 2800) {
continue;
}
// Check bullet collision with shield enemy using distance-based detection
var deltaX = bullet.x - shieldEnemy.x;
var deltaY = bullet.y - shieldEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 90) {
// 90px collision radius for shield enemies (larger due to shield)
if (shieldEnemy.takeDamage()) {
LK.setScore(LK.getScore() + 30);
scoreTxt.setText(LK.getScore());
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
tween(shieldEnemy, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (shieldEnemy.parent) {
shieldEnemy.destroy();
}
}
});
shieldEnemiesToRemove.push(shieldEnemy);
} else {
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
}
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
// Check boss enemies
for (var j = bossEnemies.length - 1; j >= 0; j--) {
var bossEnemy = bossEnemies[j];
// Skip boss enemies outside collision zone (bounding box check)
if (bossEnemy.x < -250 || bossEnemy.x > 2100 || bossEnemy.y < -50 || bossEnemy.y > 2800) {
continue;
}
// Check bullet collision with boss enemy using distance-based detection
var deltaX = bullet.x - bossEnemy.x;
var deltaY = bullet.y - bossEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 80) {
// 80px collision radius for boss enemies (much larger due to 2x scale and boss status)
if (bossEnemy.takeDamage()) {
LK.setScore(LK.getScore() + 100);
scoreTxt.setText(LK.getScore());
if (LK.getScore() >= 10000) {
LK.showYouWin();
return;
}
tween(bossEnemy, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (bossEnemy.parent) {
bossEnemy.destroy();
}
}
});
bossEnemiesToRemove.push(bossEnemy);
} else {
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
}
bulletsToRemove.push(bullet);
bulletHit = true;
LK.getSound('hit').play();
break;
}
}
if (bulletHit) continue;
}
// Update and check enemy bullets
for (var m = enemyBullets.length - 1; m >= 0; m--) {
var enemyBullet = enemyBullets[m];
// Mark enemy bullets that go off screen for removal
if (enemyBullet.x < -50 || enemyBullet.y < -50 || enemyBullet.y > 2732 + 50) {
enemyBulletsToRemove.push(enemyBullet);
continue;
}
// Skip collision detection for enemy bullets outside meaningful collision zones
if (enemyBullet.x < -50 || enemyBullet.x > 2100 || enemyBullet.y < -50 || enemyBullet.y > 2800) {
continue;
}
// Check if enemy bullet hits player center (more precise collision)
var deltaX = enemyBullet.x - player.x;
var deltaY = enemyBullet.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 45) {
// Only hits if within 45 pixels of player center (consistent bullet collision radius)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
enemyBulletsToRemove.push(enemyBullet);
}
}
// Update and check regular enemies
for (var k = enemies.length - 1; k >= 0; k--) {
var enemy = enemies[k];
// Mark enemies that go off screen for removal
if (enemy.x < -200) {
// Regular enemy width is 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
enemiesToRemove.push(enemy);
continue;
}
// Skip processing for enemies outside meaningful interaction zones
if (enemy.x < -250 || enemy.x > 2100 || enemy.y < -50 || enemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - enemy.x;
var deltaY = player.y - enemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Enemy shooting logic
if (enemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = enemy.x - (enemy.width / 2 + bulletOffset); // Spawn relative to enemy width
enemyBullet.y = enemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if enemy collides with player center using same distance calculation
if (distance < 60) {
// Only hits if within 60 pixels of player center (larger radius for direct contact)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
enemiesToRemove.push(enemy);
}
}
// Update and check fast enemies
for (var k = fastEnemies.length - 1; k >= 0; k--) {
var fastEnemy = fastEnemies[k];
// Mark fast enemies that go off screen for removal
if (fastEnemy.x < -200) {
// Fast enemy width is 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
fastEnemiesToRemove.push(fastEnemy);
continue;
}
// Skip processing for fast enemies outside meaningful interaction zones
if (fastEnemy.x < -250 || fastEnemy.x > 2100 || fastEnemy.y < -50 || fastEnemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - fastEnemy.x;
var deltaY = player.y - fastEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (fastEnemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = fastEnemy.x - (fastEnemy.width / 2 + bulletOffset); // Spawn relative to enemy width
enemyBullet.y = fastEnemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if fast enemy collides with player center using same distance calculation
if (distance < 65) {
// Only hits if within 65 pixels of player center (slightly larger due to fast movement)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
fastEnemiesToRemove.push(fastEnemy);
}
}
// Update and check shield enemies
for (var k = shieldEnemies.length - 1; k >= 0; k--) {
var shieldEnemy = shieldEnemies[k];
// Mark shield enemies that go off screen for removal
if (shieldEnemy.x < -200) {
// Shield enemy width is 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
shieldEnemiesToRemove.push(shieldEnemy);
continue;
}
// Skip processing for shield enemies outside meaningful interaction zones
if (shieldEnemy.x < -250 || shieldEnemy.x > 2100 || shieldEnemy.y < -50 || shieldEnemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - shieldEnemy.x;
var deltaY = player.y - shieldEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (shieldEnemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = shieldEnemy.x - (shieldEnemy.width / 2 + bulletOffset); // Spawn relative to enemy width
enemyBullet.y = shieldEnemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if shield enemy collides with player center using same distance calculation
if (distance < 70) {
// Only hits if within 70 pixels of player center (larger due to shield size)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
shieldEnemiesToRemove.push(shieldEnemy);
}
}
// Update and check health power-ups
for (var p = healthPowerups.length - 1; p >= 0; p--) {
var healthPowerup = healthPowerups[p];
// Remove if off screen
if (healthPowerup.x < -100) {
healthPowerupsToRemove.push(healthPowerup);
continue;
}
// Check collision with player
var deltaX = healthPowerup.x - player.x;
var deltaY = healthPowerup.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 60) {
// Player collected health power-up
if (playerLives < 5) {
// Max 5 lives
playerLives++;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.getSound('powerup').play();
LK.effects.flashScreen(0x00ff00, 300);
}
healthPowerupsToRemove.push(healthPowerup);
}
}
// Update and check shield power-ups
for (var p = shieldPowerups.length - 1; p >= 0; p--) {
var shieldPowerup = shieldPowerups[p];
// Remove if off screen
if (shieldPowerup.x < -100) {
shieldPowerupsToRemove.push(shieldPowerup);
continue;
}
// Check collision with player
var deltaX = shieldPowerup.x - player.x;
var deltaY = shieldPowerup.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 60) {
// Player collected shield power-up
playerShieldTime = 600; // 10 seconds of invincibility
LK.getSound('powerup').play();
LK.effects.flashScreen(0x0088ff, 300);
shieldPowerupsToRemove.push(shieldPowerup);
}
}
// Update and check weapon power-ups
for (var p = weaponPowerups.length - 1; p >= 0; p--) {
var weaponPowerup = weaponPowerups[p];
// Remove if off screen
if (weaponPowerup.x < -100) {
weaponPowerupsToRemove.push(weaponPowerup);
continue;
}
// Check collision with player
var deltaX = weaponPowerup.x - player.x;
var deltaY = weaponPowerup.y - player.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 60) {
// Player collected weapon power-up
playerWeaponLevel = Math.min(3, playerWeaponLevel + 1); // Max level 3
playerWeaponTime = 1800; // 30 seconds of upgrade
LK.getSound('powerup').play();
LK.effects.flashScreen(0xffaa00, 300);
weaponPowerupsToRemove.push(weaponPowerup);
}
}
// Update and check boss enemies
for (var k = bossEnemies.length - 1; k >= 0; k--) {
var bossEnemy = bossEnemies[k];
// Mark boss enemies that go off screen for removal
if (bossEnemy.x < -200) {
// Boss enemy width is 100px * 2 scale = 200px
enemiesEscaped++;
if (enemiesEscaped >= 5) {
LK.showGameOver();
return;
}
bossEnemiesToRemove.push(bossEnemy);
continue;
}
// Skip processing for boss enemies outside meaningful interaction zones
if (bossEnemy.x < -250 || bossEnemy.x > 2100 || bossEnemy.y < -50 || bossEnemy.y > 2800) {
continue;
}
// Calculate distance once for collision detection
var deltaX = player.x - bossEnemy.x;
var deltaY = player.y - bossEnemy.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (bossEnemy.shoot()) {
var enemyBullet = new EnemyBullet();
var bulletOffset = 10; // Additional offset beyond enemy edge
enemyBullet.x = bossEnemy.x - (bossEnemy.width / 2 + bulletOffset); // Spawn relative to enemy width (scaled)
enemyBullet.y = bossEnemy.y;
enemyBullet.lastX = enemyBullet.x;
enemyBullet.lastY = enemyBullet.y;
// Shoot straight left - no player tracking
enemyBullet.directionX = -1;
enemyBullet.directionY = 0;
enemyBullets.push(enemyBullet);
game.addChild(enemyBullet);
}
// Check if boss enemy collides with player center using same distance calculation
if (distance < 90) {
// Only hits if within 90 pixels of player center (much larger due to 2x scale and boss size)
if (playerShieldTime <= 0) {
// Only take damage if not shielded
playerLives--;
// Update lives number display
if (livesNumberTxt) {
livesNumberTxt.setText(playerLives.toString());
}
LK.effects.flashScreen(0xFF0000, 500);
if (playerLives <= 0) {
LK.showGameOver();
return;
}
}
bossEnemiesToRemove.push(bossEnemy);
}
}
// Deferred cleanup - process all marked objects for removal using safer object-based iteration
// Process bullets marked for removal
for (var i = bulletsToRemove.length - 1; i >= 0; i--) {
var bulletToRemove = bulletsToRemove[i];
if (bulletToRemove && bulletToRemove.parent) {
var index = bullets.indexOf(bulletToRemove);
if (index !== -1 && index < bullets.length) {
bullets[index].cleanup();
bullets[index].destroy();
bullets.splice(index, 1);
}
}
}
bulletsToRemove = [];
// Process enemy bullets marked for removal
for (var i = enemyBulletsToRemove.length - 1; i >= 0; i--) {
var enemyBulletToRemove = enemyBulletsToRemove[i];
if (enemyBulletToRemove && enemyBulletToRemove.parent) {
var index = enemyBullets.indexOf(enemyBulletToRemove);
if (index !== -1 && index < enemyBullets.length) {
enemyBullets[index].cleanup();
enemyBullets[index].destroy();
enemyBullets.splice(index, 1);
}
}
}
enemyBulletsToRemove = [];
// Process regular enemies marked for removal
for (var i = enemiesToRemove.length - 1; i >= 0; i--) {
var enemyToRemove = enemiesToRemove[i];
if (enemyToRemove && enemyToRemove.parent) {
var index = enemies.indexOf(enemyToRemove);
if (index !== -1 && index < enemies.length) {
enemies[index].cleanup();
enemies[index].destroy();
enemies.splice(index, 1);
}
}
}
enemiesToRemove = [];
// Process fast enemies marked for removal
for (var i = fastEnemiesToRemove.length - 1; i >= 0; i--) {
var fastEnemyToRemove = fastEnemiesToRemove[i];
if (fastEnemyToRemove && fastEnemyToRemove.parent) {
var index = fastEnemies.indexOf(fastEnemyToRemove);
if (index !== -1 && index < fastEnemies.length) {
fastEnemies[index].cleanup();
fastEnemies[index].destroy();
fastEnemies.splice(index, 1);
}
}
}
fastEnemiesToRemove = [];
// Process shield enemies marked for removal
for (var i = shieldEnemiesToRemove.length - 1; i >= 0; i--) {
var shieldEnemyToRemove = shieldEnemiesToRemove[i];
if (shieldEnemyToRemove && shieldEnemyToRemove.parent) {
var index = shieldEnemies.indexOf(shieldEnemyToRemove);
if (index !== -1 && index < shieldEnemies.length) {
shieldEnemies[index].cleanup();
shieldEnemies[index].destroy();
shieldEnemies.splice(index, 1);
}
}
}
shieldEnemiesToRemove = [];
// Process boss enemies marked for removal
for (var i = bossEnemiesToRemove.length - 1; i >= 0; i--) {
var bossEnemyToRemove = bossEnemiesToRemove[i];
if (bossEnemyToRemove && bossEnemyToRemove.parent) {
var index = bossEnemies.indexOf(bossEnemyToRemove);
if (index !== -1 && index < bossEnemies.length) {
bossEnemies[index].cleanup();
bossEnemies[index].destroy();
bossEnemies.splice(index, 1);
}
}
}
bossEnemiesToRemove = [];
// Process health power-ups marked for removal
for (var i = healthPowerupsToRemove.length - 1; i >= 0; i--) {
var healthPowerupToRemove = healthPowerupsToRemove[i];
if (healthPowerupToRemove && healthPowerupToRemove.parent) {
var index = healthPowerups.indexOf(healthPowerupToRemove);
if (index !== -1 && index < healthPowerups.length) {
healthPowerups[index].cleanup();
healthPowerups[index].destroy();
healthPowerups.splice(index, 1);
}
}
}
healthPowerupsToRemove = [];
// Process shield power-ups marked for removal
for (var i = shieldPowerupsToRemove.length - 1; i >= 0; i--) {
var shieldPowerupToRemove = shieldPowerupsToRemove[i];
if (shieldPowerupToRemove && shieldPowerupToRemove.parent) {
var index = shieldPowerups.indexOf(shieldPowerupToRemove);
if (index !== -1 && index < shieldPowerups.length) {
shieldPowerups[index].cleanup();
shieldPowerups[index].destroy();
shieldPowerups.splice(index, 1);
}
}
}
shieldPowerupsToRemove = [];
// Process weapon power-ups marked for removal
for (var i = weaponPowerupsToRemove.length - 1; i >= 0; i--) {
var weaponPowerupToRemove = weaponPowerupsToRemove[i];
if (weaponPowerupToRemove && weaponPowerupToRemove.parent) {
var index = weaponPowerups.indexOf(weaponPowerupToRemove);
if (index !== -1 && index < weaponPowerups.length) {
weaponPowerups[index].cleanup();
weaponPowerups[index].destroy();
weaponPowerups.splice(index, 1);
}
}
}
weaponPowerupsToRemove = [];
// Monitor array sizes and implement limits to prevent memory leaks
var maxBullets = 100;
var maxEnemyBullets = 200;
var maxEnemies = 50;
// Limit bullets array size with safety checks
if (bullets.length > maxBullets) {
for (var i = bullets.length - 1; i >= maxBullets; i--) {
if (i < bullets.length && bullets[i]) {
bullets[i].cleanup();
bullets[i].destroy();
bullets.splice(i, 1);
}
}
}
// Limit enemy bullets array size with safety checks
if (enemyBullets.length > maxEnemyBullets) {
for (var i = enemyBullets.length - 1; i >= maxEnemyBullets; i--) {
if (i < enemyBullets.length && enemyBullets[i]) {
enemyBullets[i].cleanup();
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
}
// Limit enemies array size with safety checks
if (enemies.length > maxEnemies) {
for (var i = enemies.length - 1; i >= maxEnemies; i--) {
if (i < enemies.length && enemies[i]) {
enemies[i].cleanup();
enemies[i].destroy();
enemies.splice(i, 1);
}
}
}
// Limit fast enemies array size with safety checks
if (fastEnemies.length > maxEnemies) {
for (var i = fastEnemies.length - 1; i >= maxEnemies; i--) {
if (i < fastEnemies.length && fastEnemies[i]) {
fastEnemies[i].cleanup();
fastEnemies[i].destroy();
fastEnemies.splice(i, 1);
}
}
}
// Limit shield enemies array size with safety checks
if (shieldEnemies.length > maxEnemies) {
for (var i = shieldEnemies.length - 1; i >= maxEnemies; i--) {
if (i < shieldEnemies.length && shieldEnemies[i]) {
shieldEnemies[i].cleanup();
shieldEnemies[i].destroy();
shieldEnemies.splice(i, 1);
}
}
}
// Limit boss enemies array size with safety checks
if (bossEnemies.length > maxEnemies) {
for (var i = bossEnemies.length - 1; i >= maxEnemies; i--) {
if (i < bossEnemies.length && bossEnemies[i]) {
bossEnemies[i].cleanup();
bossEnemies[i].destroy();
bossEnemies.splice(i, 1);
}
}
}
};
// Play background music
LK.playMusic('bgmusic');
tommy vercetti de gta vice city hecho con pixeles apuntando con un rifle de asalto m4 con la perspcetiva lateral. In-Game asset. 2d. High contrast. No shadows
un mafioso italiano usando un m4 con la perspectiva lateral de cuerpo completo In-Game asset. 2d. High contrast. No shadows
un pandillero mexicano apuntando con un SMG con la perspectiva lateral. In-Game asset. 2d. High contrast. No shadows de cuerpo completo
un pandillero negro con camisa azul oscuro y pantalon blanco usando una escopeta con la perspectiva lateral de cuerpo completo. In-Game asset. 2d. High contrast. No shadows
chaleco antibalas. In-Game asset. 2d. High contrast. No shadows
corazon verde dibujado. In-Game asset. 2d. High contrast. No shadows
escopeta dibujada. In-Game asset. 2d. High contrast. No shadows
claude speed de gta 3 apuntando con una ak47 desde una perspectiva lateral de cuerpo completo In-Game asset. 2d. High contrast. No shadows
las letras play con un color celeste y rosa combinados con una cursiva In-Game asset. 2d. High contrast. No shadows con contorno negro
letras que dicen seleccion de personaje con un color rosa y un color celeste y con contorno azul y rosa cursiva. In-Game asset. 2d. High contrast. No shadows
las letras vidas con un color rosa y azul con contorno rosa y azul combinados hechos con pixeles. In-Game asset. 2d. High contrast. No shadows
una mansion con piso rojo vista completa y totalmente desde arriba y desde dentro con una escalera grande igual que la de scarface la pelicula y pasillos a los lados. In-Game asset. 2d. High contrast. No shadows
no va a tener ese brazo especificamente
un motociclista vestido de negro apuntando con una smg de cuerpo completo con perspectiva lateral In-Game asset. 2d. High contrast. No shadows sin casco y sin moto
letras de color rosa y azul y con cursiva que digan vice city wars. In-Game asset. 2d. High contrast. No shadows
un hombre usando una chaqueta negra pantalones azules disparando un ak47. In-Game asset. 2d. High contrast. No shadows con la perspectiva lateral de cuerpo completo
letras de color rosa que dicen seleccion de personaje en cursiva y con contorno blanco