User prompt
Убери механику при которой при выборе карты ускорения враги устраивались
User prompt
Добавь на фон маленькие белые квадратики
User prompt
Сделай игрока бессмертным
User prompt
Добавь возможность игроку наносить и уничтожить кругового босса
User prompt
Сделай игрока бессмертным
User prompt
Воспроиводи музыку из ресурсов под названием Boss во время битв с боссами
User prompt
Воспроизводи музыку под названием Boss во время битвы с боссом
User prompt
Добавь возможность наносить урон круговому боссу
User prompt
Сделай игрока бессмертным
User prompt
Добавь музыку под названием Boss во время битвы с боссом
User prompt
Исправь баг при котором после убийства босса пропадала музыка
User prompt
Сделай игрока бессмертным
User prompt
Сделай так чтоб ракеты босса следавали не постоянно а лещер останавливается во время выстрела и после него
User prompt
Сделай игрока бессмертным
User prompt
Доблвь нового боса гигантский корабль в форме круга с с двумя ракетницами по бокам и лазерной по середине ракетници стреляют самагаважязимеся ракетами ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Сделай игрока бессмертным
User prompt
Исправь баг при котором после убийства босса взрыв не заканчивается
User prompt
Сделай так чтоб при появление босса другие враги не справились
User prompt
Сделай игрока бессмертным
User prompt
Добавь модульного босса состоящего из трех больших частей двух пушек по бокам и корпуса по центру
User prompt
Добавь карту раздвоины луч используя рисурс Асплитминд при его выборе из корабля выле ает два снаряда в разные направления из одной точки
User prompt
Исправь баг при котором взрыв начинается на другом корабле
User prompt
Сделай фон черным
User prompt
Добавь фон чтоб я мог его изменить
User prompt
И тоже самое с cards 1
/**** * Classes ****/ // New armored enemy ship: slower, more health, different sprite var ArmoredEnemyShip = Container.expand(function () { var self = Container.call(this); self.fireCooldown = 120; // Fires more often than normal enemy self.fireTimer = Math.random() * self.fireCooldown; self.enemyProjectileSpawnPoint = { x: 0, y: 0 }; self.hit = function () { if (self.isDestroyed) return; self.health -= 1; if (self.health <= 0) { self.isDestroyed = true; } else { // Flash magenta on hit if (self.shipSprite) LK.effects.flashObject(self.shipSprite, 0xe011a9, 80); } }; self.update = function () { if (self.isOffScreen || self.isDestroyed) return; if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Target the player ship if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dx = playerShip.x - self.x; var dy = playerShip.y - self.y; var targetAngle = Math.atan2(dy, dx); self.angle = targetAngle; if (self.shipSprite) self.shipSprite.rotation = self.angle + Math.PI / 2; } self.lastX = self.x; self.lastY = self.y; // Armored enemy moves slower var moveX = Math.cos(self.angle) * self.speed; var moveY = Math.sin(self.angle) * self.speed; if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dxToPlayer = self.x + moveX - playerShip.x; var dyToPlayer = self.y + moveY - playerShip.y; var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer); if (distToPlayer < 120) { moveX = 0; moveY = 0; } } self.x += moveX; self.y += moveY; var shipAsset = self.shipSprite; if (shipAsset) { var noseDistance = shipAsset.height / 2; self.enemyProjectileSpawnPoint.x = self.x + Math.cos(self.angle) * noseDistance; self.enemyProjectileSpawnPoint.y = self.y + Math.sin(self.angle) * noseDistance; } self.fireTimer--; if (self.fireTimer <= 0) { if (typeof EnemyProjectile !== 'undefined' && typeof enemyProjectiles !== 'undefined' && game && typeof game.addChild === 'function') { var newProjectile = new EnemyProjectile(self.angle); newProjectile.x = self.enemyProjectileSpawnPoint.x; newProjectile.y = self.enemyProjectileSpawnPoint.y; enemyProjectiles.push(newProjectile); game.addChild(newProjectile); self.fireTimer = self.fireCooldown; } } var gameWidth = 2048; var gameHeight = 2732; var marginWidth = self.shipSprite && self.shipSprite.width ? self.shipSprite.width / 2 + 50 : 100; var marginHeight = self.shipSprite && self.shipSprite.height ? self.shipSprite.height / 2 + 50 : 100; if (self.x < -marginWidth || self.x > gameWidth + marginWidth || self.y < -marginHeight || self.y > gameHeight + marginHeight) { self.isOffScreen = true; } }; // Use a magenta box for armored enemy self.shipSprite = self.attachAsset('Armoredenyme', { anchorX: 0.5, anchorY: 0.5 }); self.angle = Math.PI / 2; if (self.shipSprite) self.shipSprite.rotation = self.angle + Math.PI / 2; self.speed = 2.5; // Slower than normal enemy self.isOffScreen = false; self.lastX = self.x; self.lastY = self.y; self.health = 3; // Armored enemy starts with 3 HP self.isDestroyed = false; return self; }); var EnemyProjectile = Container.expand(function (fireAngle) { var self = Container.call(this); self.bulletSprite = self.attachAsset('enemyBulletSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = fireAngle; // Angle of movement self.speed = 10; // Enemy projectiles are a bit slower if (self.bulletSprite) { // Assuming the bullet shape is wider than tall, rotating by angle aligns its length with movement. self.bulletSprite.rotation = self.angle; } self.isOffScreen = false; self.update = function () { if (self.isOffScreen) return; if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Freeze enemy projectiles during card choice self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; var gameWidth = 2048; var gameHeight = 2732; // Margin based on projectile size to ensure it's fully off-screen var margin = 50; // Default margin if (self.bulletSprite && (self.bulletSprite.width || self.bulletSprite.height)) { margin = Math.max(self.bulletSprite.width || 0, self.bulletSprite.height || 0) / 2 + 50; } if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) { self.isOffScreen = true; } }; return self; }); // Orange rectangular bullet var EnemyShip = Container.expand(function () { var self = Container.call(this); self.fireCooldown = 180; // Fire every 3 seconds (180 ticks at 60FPS) self.fireTimer = Math.random() * self.fireCooldown; // Stagger initial firing self.enemyProjectileSpawnPoint = { x: 0, y: 0 }; self.hit = function () { if (self.isDestroyed) return; // Already destroyed self.health--; if (self.health <= 0) { self.isDestroyed = true; // Optionally, trigger a small visual effect here like a flash // LK.effects.flashObject(self, 0xffffff, 100); } else { // Optionally, visual effect for taking damage but not destroyed // LK.effects.flashObject(self, 0xffaaaa, 50); } }; self.update = function () { if (self.isOffScreen || self.isDestroyed) return; // Don't update if off-screen or destroyed if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Freeze all enemy movement and firing during card choice // Target the player ship if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dx = playerShip.x - self.x; var dy = playerShip.y - self.y; // Calculate angle towards player var targetAngle = Math.atan2(dy, dx); // Update the enemy's angle to face the player self.angle = targetAngle; // Update sprite rotation to match the new angle // Assuming the ship sprite is designed "pointing up" (nose along its local -Y axis or top) // visual rotation = world angle + PI/2. if (self.shipSprite) { self.shipSprite.rotation = self.angle + Math.PI / 2; } } // If playerShip is not available or is destroyed, the enemy will continue in its current self.angle. self.lastX = self.x; self.lastY = self.y; // Prevent enemy from moving too close to the player var safeDistance = 120; // Minimum allowed distance between enemy and player var moveX = Math.cos(self.angle) * self.speed; var moveY = Math.sin(self.angle) * self.speed; if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) { var dxToPlayer = self.x + moveX - playerShip.x; var dyToPlayer = self.y + moveY - playerShip.y; var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer); if (distToPlayer < safeDistance) { // If moving would bring us too close, do not move this frame moveX = 0; moveY = 0; } } self.x += moveX; self.y += moveY; // Update enemy projectile spawn point var shipAsset = self.shipSprite; if (shipAsset) { var noseDistance = shipAsset.height / 2; // Assuming front is along height axis from center self.enemyProjectileSpawnPoint.x = self.x + Math.cos(self.angle) * noseDistance; self.enemyProjectileSpawnPoint.y = self.y + Math.sin(self.angle) * noseDistance; } // Firing logic self.fireTimer--; if (self.fireTimer <= 0) { if (typeof EnemyProjectile !== 'undefined' && typeof enemyProjectiles !== 'undefined' && game && typeof game.addChild === 'function') { var newProjectile = new EnemyProjectile(self.angle); // Fire in the direction the ship is moving newProjectile.x = self.enemyProjectileSpawnPoint.x; newProjectile.y = self.enemyProjectileSpawnPoint.y; enemyProjectiles.push(newProjectile); game.addChild(newProjectile); self.fireTimer = self.fireCooldown; // Reset cooldown } } // Generalized off-screen check var gameWidth = 2048; var gameHeight = 2732; var marginWidth = self.shipSprite && self.shipSprite.width ? self.shipSprite.width / 2 + 50 : 100; var marginHeight = self.shipSprite && self.shipSprite.height ? self.shipSprite.height / 2 + 50 : 100; if (self.x < -marginWidth || self.x > gameWidth + marginWidth || self.y < -marginHeight || self.y > gameHeight + marginHeight) { self.isOffScreen = true; } }; self.shipSprite = self.attachAsset('enemyShipSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = Math.PI / 2; // Default angle: moving downwards (positive Y direction) if (self.shipSprite) { self.shipSprite.rotation = self.angle + Math.PI / 2; } self.speed = 4; self.isOffScreen = false; self.lastX = self.x; self.lastY = self.y; self.health = 1; self.isDestroyed = false; return self; }); var FireButton = Container.expand(function () { var self = Container.call(this); self.buttonSprite = self.attachAsset('fireButtonSprite', { anchorX: 0.5, anchorY: 0.5 }); self.lastFireTick = -1000; // Track last tick when fired self.down = function (x, y, obj) { // Prevent holding down for auto-fire: only allow firing if enough time has passed since last fire if (typeof LK !== 'undefined' && typeof LK.ticks !== 'undefined') { if (typeof self.lastFireTick === 'undefined') self.lastFireTick = -1000; // Only allow firing if at least fireButtonCooldown ticks have passed since last fire if (typeof fireButtonCooldown === 'undefined') fireButtonCooldown = 30; if (LK.ticks - self.lastFireTick < fireButtonCooldown) return; } // Only fire if player has ammo and not holding down for auto-fire if (typeof playerAmmo !== 'undefined' && playerAmmo > 0) { if (playerShip && typeof playerShip.currentAngle !== 'undefined' && typeof projectileSpawnPoint !== 'undefined' && playerProjectiles && PlayerProjectile) { var newProjectile = new PlayerProjectile(playerShip.currentAngle); // Pass the ship's current firing angle newProjectile.x = projectileSpawnPoint.x; // Set initial position from calculated spawn point newProjectile.y = projectileSpawnPoint.y; playerProjectiles.push(newProjectile); game.addChild(newProjectile); // Add projectiles to the main game stage playerAmmo = Math.max(0, playerAmmo - 1); // Decrease ammo by 1, never below 0 // Removed ammo counter update as per requirements if (typeof LK !== 'undefined' && typeof LK.ticks !== 'undefined') { self.lastFireTick = LK.ticks; } // Play shot sound when firing if (typeof LK !== 'undefined' && typeof LK.getSound === 'function') { var shotSound = LK.getSound('Shot'); if (shotSound && typeof shotSound.play === 'function') { shotSound.play(); } } } } }; return self; }); var HealthRestore = Container.expand(function () { var self = Container.call(this); self.restoreSprite = self.attachAsset('healthRestoreSprite', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { // Check for collision with player ship if (playerShip && !playerShip.isDestroyed && self.intersects(playerShip)) { if (playerShip.health < 3) { playerShip.health = Math.min(playerShip.health + 1, 3); // Restore health, max 3 if (playerShip.shipSprite) { LK.effects.flashObject(playerShip.shipSprite, 0x00ff00, 150); // Flash green on health restore } // Update hearts display for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } self.destroy(); } }; return self; }); var Heart = Container.expand(function () { var self = Container.call(this); self.heartSprite = self.attachAsset('heartSprite', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); return self; }); // Red circular fire button var Joystick = Container.expand(function () { var self = Container.call(this); self.baseSprite = self.attachAsset('joystickBaseSprite', { anchorX: 0.5, anchorY: 0.5 }); self.knobSprite = self.attachAsset('joystickKnobSprite', { anchorX: 0.5, anchorY: 0.5 }); // Maximum distance the knob's center can move from the base's center self.radius = self.baseSprite.width / 2 - self.knobSprite.width / 2; if (self.radius <= 0) { // Ensure a sensible minimum radius self.radius = self.baseSprite.width > 0 ? self.baseSprite.width / 4 : 50; // Use 1/4 of base width or 50 if base has no width } self.isDragging = false; self.inputVector = { x: 0, y: 0 }; // Normalized output (-1 to 1) self.handleDown = function (localX, localY) { // localX, localY are relative to the joystick's center (its origin) // Check if the touch is within the larger base area to start dragging var distSqFromCenter = localX * localX + localY * localY; if (distSqFromCenter <= self.baseSprite.width / 2 * (self.baseSprite.width / 2)) { self.isDragging = true; self.handleMove(localX, localY); // Snap knob to initial touch position return true; // Indicates joystick took control } return false; // Joystick not activated }; self.handleMove = function (localX, localY) { if (!self.isDragging) return; var dist = Math.sqrt(localX * localX + localY * localY); if (dist > self.radius) { // Normalize and scale to radius if touch is outside draggable area self.knobSprite.x = localX / dist * self.radius; self.knobSprite.y = localY / dist * self.radius; } else { self.knobSprite.x = localX; self.knobSprite.y = localY; } // Calculate normalized input vector if (self.radius > 0) { self.inputVector.x = self.knobSprite.x / self.radius; self.inputVector.y = self.knobSprite.y / self.radius; } else { // Avoid division by zero if radius is zero self.inputVector.x = 0; self.inputVector.y = 0; } }; self.handleUp = function () { if (self.isDragging) { self.isDragging = false; // Reset knob to center and clear input vector self.knobSprite.x = 0; self.knobSprite.y = 0; self.inputVector.x = 0; self.inputVector.y = 0; } }; self.getInput = function () { return self.inputVector; }; self.isActive = function () { return self.isDragging; }; return self; }); // Projectile class for player's bullets var PlayerProjectile = Container.expand(function (fireAngle) { var self = Container.call(this); // Attach bullet sprite self.bulletSprite = self.attachAsset('playerBulletSprite', { anchorX: 0.5, anchorY: 0.5 }); self.angle = fireAngle; // Store the firing angle self.speed = 20; // Projectile speed magnitude // The playerBulletSprite has orientation:1, meaning it's rotated 90deg clockwise. // If original was thin vertical, it's now thin horizontal, "pointing" along its X-axis. // So, self.angle directly applies. self.bulletSprite.rotation = self.angle; self.isOffScreen = false; // Flag to indicate if projectile is off-screen // Update method to move projectile self.update = function () { if (self.isOffScreen) return; // Don't update if already marked off-screen self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; // Check if off-screen var gameWidth = 2048; var gameHeight = 2732; // Margin based on projectile size to ensure it's fully off-screen var margin = Math.max(self.bulletSprite.width, self.bulletSprite.height) / 2 + 50; if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) { self.isOffScreen = true; } }; //{M} // Note: Original L was self.destroy related, removed. return self; }); // PlayerShip class to handle player ship logic and projectile spawn point calculation var PlayerShip = Container.expand(function () { var self = Container.call(this); self.health = 3; // Player can take a few hits self._fractionalHealth = self.health; // For fractional damage (e.g., 0.5 from armored enemy) self.isDestroyed = false; self.hit = function () { if (self.isDestroyed) return; if (typeof self._fractionalHealth === 'undefined') self._fractionalHealth = self.health; self._fractionalHealth -= 1; self.health = Math.floor(self._fractionalHealth); LK.effects.flashObject(self.shipSprite || self, 0xff0000, 150); // Flash red on hit (flash sprite if available) // Update hearts display for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < self.health; } if (self._fractionalHealth <= 0) { self.isDestroyed = true; // Game over will be triggered in game.update } }; // Attach player ship sprite and set reference for spawn point calculation self.shipSprite = self.attachAsset('playerShipSprite', { anchorX: 0.5, anchorY: 0.5 }); self.moveSpeed = 10; // Pixels per tick for movement speed // currentAngle is the direction the ship moves and fires projectiles. // 0 radians = right, -PI/2 = up (screen coordinates). self.currentAngle = -Math.PI / 2; // Initial angle: pointing up. // playerShipSprite is assumed to be designed pointing "up" (its nose along its local -Y axis). // To make the sprite's nose align with currentAngle, its visual rotation needs adjustment. // Visual rotation = currentAngle + Math.PI / 2. // E.g., currentAngle = -PI/2 (up) => visual rotation = 0. // E.g., currentAngle = 0 (right) => visual rotation = PI/2. self.shipSprite.rotation = self.currentAngle + Math.PI / 2; // Initialize projectile spawn point (remains important) // This will be updated relative to the ship's current position in self.update() // Initial dummy values, will be set correctly on first update. projectileSpawnPoint.x = 0; projectileSpawnPoint.y = 0; self.applyJoystickInput = function (inputX, inputY) { // Update angle only if joystick provides directional input if (inputX !== 0 || inputY !== 0) { self.currentAngle = Math.atan2(inputY, inputX); self.shipSprite.rotation = self.currentAngle + Math.PI / 2; } self.x += inputX * self.moveSpeed; self.y += inputY * self.moveSpeed; // Boundary checks to keep ship on screen var halfWidth = self.shipSprite.width / 2; var halfHeight = self.shipSprite.height / 2; var gameWidth = 2048; var gameHeight = 2732; var topSafeMargin = 100; // Top 100px area is reserved if (self.x - halfWidth < 0) self.x = halfWidth; if (self.x + halfWidth > gameWidth) self.x = gameWidth - halfWidth; if (self.y - halfHeight < topSafeMargin) self.y = topSafeMargin + halfHeight; if (self.y + halfHeight > gameHeight) self.y = gameHeight - halfHeight; }; // Update projectile spawn point every frame based on player ship position and rotation self.update = function () { var shipAsset = self.shipSprite; if (!shipAsset) return; // Calculate spawn point at the tip of the ship, considering its currentAngle. // The "nose" is shipAsset.height / 2 distance from the center. var noseDistance = shipAsset.height / 2; projectileSpawnPoint.x = self.x + Math.cos(self.currentAngle) * noseDistance; projectileSpawnPoint.y = self.y + Math.sin(self.currentAngle) * noseDistance; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Projectile spawn point (relative to player ship center) // Will be updated in PlayerShip class update var projectileSpawnPoint = { x: 0, y: 0 }; var joystick; // Declare joystick instance // Array to keep track of all player projectiles var playerProjectiles = []; // Player projectile damage (can be doubled by Cardsttsck) var playerProjectileDamage = 1; // Player ammo count (start with 10). Player cannot fire forever: must wait for ammo to restore and cannot hold fire for auto-fire. var playerAmmo = 10; var maxPlayerAmmo = 10; // Maximum player ammo capacity // Fire button cooldown in ticks (default 30, can be halved by Card1) var fireButtonCooldown = 30; // Array to keep track of all enemy ships var enemyShips = []; // Array to keep track of all enemy projectiles var enemyProjectiles = []; // Global flag: freeze all enemy movement and firing during card choice var freezeEnemies = false; // Create player ship and add to game var playerShip = new PlayerShip(); game.addChild(playerShip); // Display hearts for player health var hearts = []; for (var i = 0; i < playerShip.health; i++) { var heart = new Heart(); heart.x = i * 120 + 120; // Position hearts with increased spacing heart.y = 60; // Top-left corner LK.gui.topLeft.addChild(heart); hearts.push(heart); } // Money counter (pixel style) in top-right corner var money = 0; var moneyTxt = new Text2(money + " $", { size: 120, fill: 0xFFE066, font: "monospace, 'Press Start 2P', 'VT323', 'Courier New', Courier, monospace", // pixel/retro style align: "right" }); moneyTxt.anchor.set(1, 0); // Right-top anchor moneyTxt.x = -60; // Padding from right edge moneyTxt.y = 60; // Padding from top edge LK.gui.topRight.addChild(moneyTxt); // Ammo counter removed as per requirements // Center player ship horizontally, place near bottom playerShip.x = 2048 / 2; playerShip.y = 2732 - 350; // Create Joystick joystick = new Joystick(); game.addChild(joystick); // Set initial position (e.g., 0,0), though it will jump to touch. joystick.x = 0; joystick.y = 0; // Enemy spawn multiplier (default 1, can be set to 3 for 5 seconds after Card2) game._enemySpawnMultiplier = 1; game._enemySpawnMultiplierTimeout = null; // Create and add Enemy Ships var enemyShip1 = new EnemyShip(); var enemyShipAssetHeight1 = enemyShip1.shipSprite && enemyShip1.shipSprite.height ? enemyShip1.shipSprite.height : 146; // Default to asset height enemyShip1.x = 2048 / 3; enemyShip1.y = -(enemyShipAssetHeight1 / 2) - 50; // Start off-screen at the top enemyShip1.lastX = enemyShip1.x; // Initialize lastX to the actual starting x enemyShip1.lastY = enemyShip1.y; // Initialize lastY to the actual starting y game.addChild(enemyShip1); enemyShips.push(enemyShip1); var enemyShip2 = new EnemyShip(); var enemyShipAssetHeight2 = enemyShip2.shipSprite && enemyShip2.shipSprite.height ? enemyShip2.shipSprite.height : 146; // Default to asset height enemyShip2.x = 2048 * 2 / 3; enemyShip2.y = -(enemyShipAssetHeight2 / 2) - 150; // Start off-screen, staggered further up enemyShip2.lastX = enemyShip2.x; // Initialize lastX to the actual starting x enemyShip2.lastY = enemyShip2.y; // Initialize lastY to the actual starting y game.addChild(enemyShip2); enemyShips.push(enemyShip2); // Game event handlers // Create and position Fire Button var fireButton = new FireButton(); LK.gui.bottomLeft.addChild(fireButton); // Position the fire button visually in the bottom-left corner with some margin // The buttonSprite is 200x200 and anchored at its center (0.5, 0.5) // fireButton (Container) is added to LK.gui.bottomLeft, positioning its top-left (0,0) at screen bottom-left. // Adjust x and y to make the button appear correctly. var buttonMargin = 50; // 50px margin from screen edges fireButton.x = fireButton.buttonSprite.width / 2 + buttonMargin; fireButton.y = -fireButton.buttonSprite.height / 2 - buttonMargin; // Negative Y moves up from the bottom edge game.down = function (x, y, obj) { if (game._cardChoiceActive) { // Block joystick and ship movement while card choice is active return; } // x, y are global game coordinates // Move the joystick to the touch position joystick.x = x; joystick.y = y; joystick.visible = true; // Activate the joystick. Since the joystick's origin is now at the touch point (x,y), // the local coordinates for the touch within the joystick are (0,0). // This centers the knob under the finger and initiates dragging. joystick.handleDown(0, 0); // Pass (0,0) as local coordinates // Note: With this "floating joystick" implementation, the joystick activates on any screen press. // The previous mechanic, where tapping away from a fixed joystick fired projectiles, // is superseded. Further design would be needed for a separate firing mechanism. }; game.move = function (x, y, obj) { if (game._cardChoiceActive) { // Block joystick movement while card choice is active return; } if (joystick.isActive()) { var joystickLocalPos = joystick.toLocal({ x: x, y: y }); joystick.handleMove(joystickLocalPos.x, joystickLocalPos.y); } }; game.up = function (x, y, obj) { if (game._cardChoiceActive) { // Block joystick release while card choice is active return; } if (joystick.isActive()) { joystick.handleUp(); // Resets knob, inputVector, and isDragging flag joystick.visible = false; // Hide the joystick when the touch is released } }; // Update game state var ammoRestoreCooldown = 0; // Ticks until next ammo restore game.update = function () { // Prevent all game logic and object updates while card choice overlay is active if (game._cardChoiceActive) { // Only allow card overlay input, skip all updates and movement return; } // Restore ammo if not full, with a delay (e.g., 30 ticks = 0.5s) if (typeof playerAmmo !== 'undefined') { if (typeof maxPlayerAmmo === 'undefined') maxPlayerAmmo = 10; // Defensive: ensure maxPlayerAmmo is defined if (playerAmmo < maxPlayerAmmo) { // Use maxPlayerAmmo instead of hardcoded 10 if (typeof ammoRestoreCooldown === 'undefined') ammoRestoreCooldown = 0; if (ammoRestoreCooldown > 0) { ammoRestoreCooldown--; } else { playerAmmo += 1; // Removed ammo counter update as per requirements ammoRestoreCooldown = 30; // 0.5s at 60FPS } } else { // Player is at or above max ammo. // Ensure current ammo doesn't exceed new max if it somehow did, though unlikely with current regen. playerAmmo = Math.min(playerAmmo, maxPlayerAmmo); ammoRestoreCooldown = 0; } } // Apply joystick input to player ship movement if (joystick && playerShip && playerShip.applyJoystickInput) { var input = joystick.getInput(); playerShip.applyJoystickInput(input.x, input.y); } // Update player ship (e.g., to recalculate projectileSpawnPoint after moving) if (playerShip && playerShip.update) { playerShip.update(); } // Update and clean up projectiles, and check for collisions with enemies for (var i = playerProjectiles.length - 1; i >= 0; i--) { var proj = playerProjectiles[i]; if (proj.update) { proj.update(); // Call projectile's own update logic (movement) } // Check if the projectile flagged itself as off-screen AFTER moving if (proj.isOffScreen) { proj.destroy(); // Destroy the projectile playerProjectiles.splice(i, 1); // Remove from the tracking array continue; // Move to the next projectile } // Collision detection with enemy ships for (var k = enemyShips.length - 1; k >= 0; k--) { var enemy = enemyShips[k]; // Skip already destroyed or off-screen enemies for collision checks if (enemy.isDestroyed || enemy.isOffScreen) { continue; } if (proj.intersects(enemy)) { // Use playerProjectileDamage if defined, else default to 1 var dmg = typeof playerProjectileDamage !== 'undefined' ? playerProjectileDamage : 1; if (typeof enemy.health === 'number') { enemy.health -= dmg; if (enemy.health <= 0) { enemy.isDestroyed = true; } else { if (typeof enemy.hit === 'function') enemy.hit(); } } else { if (typeof enemy.hit === 'function') enemy.hit(); } proj.destroy(); // Projectile is consumed on hit playerProjectiles.splice(i, 1); // Remove projectile // If enemy.isDestroyed became true, it will be handled in the enemyShips loop below. // Score will be awarded there too. break; // Projectile is gone, no need to check against other enemies } } // If proj was spliced, loop continues correctly to the new element at index i or finishes. } // Update and clean up enemy projectiles, and check for collisions with player for (var l = enemyProjectiles.length - 1; l >= 0; l--) { var eProj = enemyProjectiles[l]; if (eProj.update) { eProj.update(); // Call projectile's own update logic (movement) } // Check if the projectile flagged itself as off-screen AFTER moving if (eProj.isOffScreen) { if (typeof eProj.destroy === 'function') eProj.destroy(); enemyProjectiles.splice(l, 1); // Remove from the tracking array continue; // Move to the next projectile } // Collision detection with player ship // Ensure playerShip exists and is not already destroyed before checking intersection if (playerShip && !playerShip.isDestroyed && typeof eProj.intersects === 'function' && eProj.intersects(playerShip)) { // Check if the projectile's parent is an ArmoredEnemyShip (for 0.5 damage) var isArmored = false; if (typeof enemyShips !== 'undefined') { for (var ai = 0; ai < enemyShips.length; ai++) { var es = enemyShips[ai]; if (es instanceof ArmoredEnemyShip && es.enemyProjectileSpawnPoint && Math.abs(eProj.x - es.enemyProjectileSpawnPoint.x) < 2 && Math.abs(eProj.y - es.enemyProjectileSpawnPoint.y) < 2) { isArmored = true; break; } } } if (isArmored) { // Deal 0.5 damage: use a fractional health system if (typeof playerShip.health === 'number') { if (typeof playerShip._fractionalHealth === 'undefined') playerShip._fractionalHealth = playerShip.health; playerShip._fractionalHealth -= 0.5; if (playerShip._fractionalHealth <= 0) { playerShip.health = 0; playerShip.isDestroyed = true; if (playerShip.shipSprite) playerShip.shipSprite.visible = false;else if (typeof playerShip.visible !== 'undefined') playerShip.visible = false; LK.showGameOver(); } else { playerShip.health = Math.floor(playerShip._fractionalHealth); LK.effects.flashObject(playerShip.shipSprite || playerShip, 0xff0000, 150); // Update hearts display for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } } } else { if (typeof playerShip.hit === 'function') playerShip.hit(); // Player takes damage if (playerShip.isDestroyed) { if (playerShip.shipSprite) playerShip.shipSprite.visible = false;else if (typeof playerShip.visible !== 'undefined') playerShip.visible = false; LK.showGameOver(); } } if (typeof eProj.destroy === 'function') eProj.destroy(); // Projectile is consumed on hit enemyProjectiles.splice(l, 1); // Remove projectile // If projectile hit player, it's gone. No need to check further for this projectile. } } // Update and clean up enemy ships // This loop should still run even if player is destroyed, to clean up existing enemies, // though new game state might prevent further updates if LK.showGameOver() is effective immediately. var _loop = function _loop() { enemy = enemyShips[j]; // First, check if the enemy ship is marked as destroyed (e.g., by a projectile hit) if (enemy.isDestroyed) { // Show Boom image at enemy's position boomImg = LK.getAsset('Boom', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y }); game.addChild(boomImg); // Remove Boom image after a short delay (e.g., 125ms), then show Boom2 at the same position LK.setTimeout(function () { if (boomImg && typeof boomImg.destroy === 'function') boomImg.destroy(); // Show Boom2 image at the same position after Boom disappears var boom2Img = LK.getAsset('Boom2', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y }); game.addChild(boom2Img); // Remove Boom2 image after a short delay (e.g., 125ms), then show Boom3 at the same position LK.setTimeout(function () { if (boom2Img && typeof boom2Img.destroy === 'function') boom2Img.destroy(); // Show Boom3 image at the same position after Boom2 disappears var boom3Img = LK.getAsset('Boom3', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y }); game.addChild(boom3Img); // Remove Boom3 image after a short delay (e.g., 125ms), then show Boom4 at the same position LK.setTimeout(function () { if (boom3Img && typeof boom3Img.destroy === 'function') boom3Img.destroy(); // Show Boom4 image at the same position after Boom3 disappears var boom4Img = LK.getAsset('Boom4', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y }); game.addChild(boom4Img); // Remove Boom4 image after a short delay (e.g., 125ms), then show Boom5 at the same position LK.setTimeout(function () { if (boom4Img && typeof boom4Img.destroy === 'function') boom4Img.destroy(); // Show Boom5 image at the same position after Boom4 disappears var boom5Img = LK.getAsset('Boom5', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y }); game.addChild(boom5Img); // Remove Boom5 image after a short delay (e.g., 125ms), then show Boom6 at the same position LK.setTimeout(function () { if (boom5Img && typeof boom5Img.destroy === 'function') boom5Img.destroy(); // Show Boom6 image at the same position after Boom5 disappears var boom6Img = LK.getAsset('Boom6', { anchorX: 0.5, anchorY: 0.5, x: enemy.x, y: enemy.y }); game.addChild(boom6Img); // Remove Boom6 image after a short delay (e.g., 125ms) LK.setTimeout(function () { if (boom6Img && typeof boom6Img.destroy === 'function') boom6Img.destroy(); }, 125); }, 125); }, 125); }, 125); }, 125); }, 125); enemy.destroy(); // Destroy the enemy ship enemyShips.splice(j, 1); // Remove from the tracking array LK.setScore(LK.getScore() + 10); // Award score for destroying an enemy money += 5; // Add 5 money per enemy destroyed if (typeof moneyTxt !== 'undefined' && moneyTxt.setText) { moneyTxt.setText(money + " $"); } LK.getSound('Boom').play(); // Play 'Boom' sound effect // 25% chance to drop a health restoration if (Math.random() < 0.25) { healthRestore = new HealthRestore(); healthRestore.x = enemy.x; healthRestore.y = enemy.y; game.addChild(healthRestore); } // --- Card choice after 3 kills --- if (typeof enemiesKilled === 'undefined') { enemiesKilled = 0; } enemiesKilled++; if (enemiesKilled > 0 && enemiesKilled % 3 === 0 && !game._cardChoiceActive) { // Helper to finish card choice and resume game var finishCardChoice = function finishCardChoice() { if (!game._cardChoiceActive) return; game._cardChoiceActive = false; freezeEnemies = false; if (cardOverlay && typeof cardOverlay.destroy === 'function') cardOverlay.destroy(); // Restore input game.down = oldGameDown; game.move = oldGameMove; game.up = oldGameUp; // Resume game logic // No need to call LK.resumeGame(); LK engine will resume game after overlay is destroyed }; game._cardChoiceActive = true; // Freeze all enemy movement and firing freezeEnemies = true; // --- Block card selection for 2 seconds --- game._cardChoiceBlock = true; LK.setTimeout(function () { game._cardChoiceBlock = false; }, 2000); // Pause game logic is handled by LK.showGameOver() and LK.resumeGame(), no need to call LK.pause() here // Create overlay for card choice cardOverlay = new Container(); cardOverlay.name = "cardChoiceOverlay"; // Semi-transparent background bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2, tint: 0x000000, alpha: 0.7 }); cardOverlay.addChild(bg); // --- Define all available cards and their effects --- var allAvailableCards = [{ key: "card1_ammo_firerate", // Card1: More Ammo, Faster Fire Rate asset: "Card1", applyEffect: function applyEffect() { if (typeof maxPlayerAmmo === 'undefined') maxPlayerAmmo = 10; // Defensive initialization maxPlayerAmmo += 2; // Increase maximum ammo capacity playerAmmo = Math.min(playerAmmo + 2, maxPlayerAmmo); // Add 2 to current ammo, capped by new maxPlayerAmmo fireButtonCooldown = Math.max(1, Math.floor(fireButtonCooldown / 2)); // Halve cooldown, min 1 } }, { key: "card2_speed_spawn_heal", // Card2: Speed Boost, Enemy Spawn Boost, Heal 1 HP asset: "Csrd2", applyEffect: function applyEffect() { if (playerShip) { if (typeof playerShip._originalMoveSpeed === 'undefined') { playerShip._originalMoveSpeed = playerShip.moveSpeed; } playerShip.moveSpeed = playerShip._originalMoveSpeed * 2; // Speed boost is now permanent, timeout removed. // Heal 1 HP if not at max health var currentMaxHealth = playerShip.maxHealth || 3; if (playerShip.health < currentMaxHealth) { playerShip.health = Math.min(playerShip.health + 1, currentMaxHealth); if (typeof playerShip._fractionalHealth !== 'undefined') { // Ensure fractional health is updated playerShip._fractionalHealth = playerShip.health; } for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } } // Enemy spawn multiplier if (typeof game._enemySpawnMultiplierTimeout !== 'undefined' && game._enemySpawnMultiplierTimeout) { LK.clearTimeout(game._enemySpawnMultiplierTimeout); } game._enemySpawnMultiplier = 3; game._enemySpawnMultiplierTimeout = LK.setTimeout(function () { game._enemySpawnMultiplier = 1; game._enemySpawnMultiplierTimeout = null; }, 5000); // Enemy spawn boost for 5 seconds } }, { key: "cardhp_maxhp_heal", // Cardhp: Increase Max HP, Heal 1 HP asset: "Cardhp", applyEffect: function applyEffect() { if (playerShip) { if (typeof playerShip.maxHealth === 'undefined') { playerShip.maxHealth = 3; } playerShip.maxHealth += 1; playerShip.health = Math.min(playerShip.health + 1, playerShip.maxHealth); // Heal 1 HP up to new max if (typeof playerShip._fractionalHealth !== 'undefined') { // Ensure fractional health is updated playerShip._fractionalHealth = playerShip.health; } // Add a new heart icon if needed (maxHealth might exceed initial hearts array length) // Ensure heart icons match current maxHealth and health while (hearts.length < playerShip.maxHealth) { var newHeart = new Heart(); newHeart.x = hearts.length * 120 + 120; newHeart.y = 60; LK.gui.topLeft.addChild(newHeart); hearts.push(newHeart); } for (var i = 0; i < hearts.length; i++) { hearts[i].visible = i < playerShip.health; } } } }, { key: "cardsttsck_doubledamage", // Cardsttsck: Double Projectile Damage asset: "Cardsttsck", applyEffect: function applyEffect() { if (typeof playerProjectileDamage === 'undefined') { playerProjectileDamage = 1; } playerProjectileDamage *= 2; } }]; // --- Shuffle and select 3 cards to display --- // Fisher-Yates shuffle for (var k = allAvailableCards.length - 1; k > 0; k--) { var j_shuffle = Math.floor(Math.random() * (k + 1)); var tempCard = allAvailableCards[k]; allAvailableCards[k] = allAvailableCards[j_shuffle]; allAvailableCards[j_shuffle] = tempCard; } var cardsToDisplay = allAvailableCards.slice(0, 3); // Get the first 3 cards // --- Position and display selected cards --- var cardPositions = [{ x: 2048 / 2 - 450, y: 2732 / 2 }, // Left card { x: 2048 / 2, y: 2732 / 2 }, // Middle card { x: 2048 / 2 + 450, y: 2732 / 2 } // Right card ]; for (var cardIdx = 0; cardIdx < cardsToDisplay.length; cardIdx++) { var cardData = cardsToDisplay[cardIdx]; var cardContainer = new Container(); // Create a new container for each card // The image itself is positioned globally based on cardPositions var cardImg = LK.getAsset(cardData.asset, { anchorX: 0.5, anchorY: 0.5, x: cardPositions[cardIdx].x, y: cardPositions[cardIdx].y }); cardContainer.addChild(cardImg); // The container itself can be at 0,0 as its child (the image) is globally positioned. // However, for consistency with how interactives are often handled, // we can also set the container's position if we wanted the image local to it. // For this setup, global positioning of the image is fine. cardContainer.x = 0; cardContainer.y = 0; cardContainer.interactive = true; // Use a closure to correctly capture cardData for each event handler (function (currentCardData) { cardContainer.down = function (x_down, y_down, obj_down) { if (!game._cardChoiceActive || game._cardChoiceBlock) return; currentCardData.applyEffect(); finishCardChoice(); }; })(cardData); cardOverlay.addChild(cardContainer); } // Add overlay to game game.addChild(cardOverlay); // Block all input except card choice oldGameDown = game.down; oldGameMove = game.move; oldGameUp = game.up; game.down = function (x, y, obj) { if (game._cardChoiceBlock) return; // Defensive: Only forward to cards that exist in overlay if (cardOverlay && cardOverlay.children) { for (var i = 0; i < cardOverlay.children.length; i++) { var child = cardOverlay.children[i]; if (child && child.interactive && typeof child.down === "function" && child.children && child.children.length > 0) { var img = child.children[0]; if (img && typeof child.toLocal === "function") { var local = child.toLocal({ x: x, y: y }); if (img.width && img.height && Math.abs(local.x) < img.width / 2 && Math.abs(local.y) < img.height / 2) { child.down(x, y, obj); return; } } } } } }; game.move = function () {}; game.up = function () {}; } // } // if (typeof scoreTxt !== 'undefined' && scoreTxt && scoreTxt.setText) { // scoreTxt.setText(LK.getScore()); // } return 1; // continue // Move to the next enemy ship in the list } if (enemy.update) { enemy.update(); // Call enemy ship's own update logic (movement, etc.) } // Then, check if the enemy ship flagged itself as off-screen (if not already destroyed) if (enemy.isOffScreen) { enemy.destroy(); // Destroy the enemy ship enemyShips.splice(j, 1); // Remove from the tracking array // No score for merely going off-screen, unless desired. } }, enemy, boomImg, healthRestore, cardOverlay, bg, card1, card1Bg, card1Text, card2, card2Bg, card2Text, oldGameDown, oldGameMove, oldGameUp; for (var j = enemyShips.length - 1; j >= 0; j--) { if (_loop()) continue; } // Increase the number of enemies over time if (LK.ticks % Math.max(30, 150 - Math.floor(LK.ticks / 300)) === 0) { // Determine multiplier (default 1, or 3x if card2 effect active) var multiplier = typeof game._enemySpawnMultiplier !== 'undefined' && game._enemySpawnMultiplier > 1 ? game._enemySpawnMultiplier : 1; for (var spawnIdx = 0; spawnIdx < multiplier; spawnIdx++) { // 25% chance to spawn armored enemy, else normal var newEnemyShip; if (Math.random() < 0.25 && typeof ArmoredEnemyShip !== 'undefined') { newEnemyShip = new ArmoredEnemyShip(); } else { newEnemyShip = new EnemyShip(); } var enemyShipAssetHeight = newEnemyShip.shipSprite && newEnemyShip.shipSprite.height ? newEnemyShip.shipSprite.height : 146; newEnemyShip.x = Math.random() * 2048; newEnemyShip.y = -(enemyShipAssetHeight / 2) - 50; newEnemyShip.lastX = newEnemyShip.x; newEnemyShip.lastY = newEnemyShip.y; game.addChild(newEnemyShip); enemyShips.push(newEnemyShip); } } }; LK.playMusic('Space');
/****
* Classes
****/
// New armored enemy ship: slower, more health, different sprite
var ArmoredEnemyShip = Container.expand(function () {
var self = Container.call(this);
self.fireCooldown = 120; // Fires more often than normal enemy
self.fireTimer = Math.random() * self.fireCooldown;
self.enemyProjectileSpawnPoint = {
x: 0,
y: 0
};
self.hit = function () {
if (self.isDestroyed) return;
self.health -= 1;
if (self.health <= 0) {
self.isDestroyed = true;
} else {
// Flash magenta on hit
if (self.shipSprite) LK.effects.flashObject(self.shipSprite, 0xe011a9, 80);
}
};
self.update = function () {
if (self.isOffScreen || self.isDestroyed) return;
if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return;
// Target the player ship
if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) {
var dx = playerShip.x - self.x;
var dy = playerShip.y - self.y;
var targetAngle = Math.atan2(dy, dx);
self.angle = targetAngle;
if (self.shipSprite) self.shipSprite.rotation = self.angle + Math.PI / 2;
}
self.lastX = self.x;
self.lastY = self.y;
// Armored enemy moves slower
var moveX = Math.cos(self.angle) * self.speed;
var moveY = Math.sin(self.angle) * self.speed;
if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) {
var dxToPlayer = self.x + moveX - playerShip.x;
var dyToPlayer = self.y + moveY - playerShip.y;
var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer);
if (distToPlayer < 120) {
moveX = 0;
moveY = 0;
}
}
self.x += moveX;
self.y += moveY;
var shipAsset = self.shipSprite;
if (shipAsset) {
var noseDistance = shipAsset.height / 2;
self.enemyProjectileSpawnPoint.x = self.x + Math.cos(self.angle) * noseDistance;
self.enemyProjectileSpawnPoint.y = self.y + Math.sin(self.angle) * noseDistance;
}
self.fireTimer--;
if (self.fireTimer <= 0) {
if (typeof EnemyProjectile !== 'undefined' && typeof enemyProjectiles !== 'undefined' && game && typeof game.addChild === 'function') {
var newProjectile = new EnemyProjectile(self.angle);
newProjectile.x = self.enemyProjectileSpawnPoint.x;
newProjectile.y = self.enemyProjectileSpawnPoint.y;
enemyProjectiles.push(newProjectile);
game.addChild(newProjectile);
self.fireTimer = self.fireCooldown;
}
}
var gameWidth = 2048;
var gameHeight = 2732;
var marginWidth = self.shipSprite && self.shipSprite.width ? self.shipSprite.width / 2 + 50 : 100;
var marginHeight = self.shipSprite && self.shipSprite.height ? self.shipSprite.height / 2 + 50 : 100;
if (self.x < -marginWidth || self.x > gameWidth + marginWidth || self.y < -marginHeight || self.y > gameHeight + marginHeight) {
self.isOffScreen = true;
}
};
// Use a magenta box for armored enemy
self.shipSprite = self.attachAsset('Armoredenyme', {
anchorX: 0.5,
anchorY: 0.5
});
self.angle = Math.PI / 2;
if (self.shipSprite) self.shipSprite.rotation = self.angle + Math.PI / 2;
self.speed = 2.5; // Slower than normal enemy
self.isOffScreen = false;
self.lastX = self.x;
self.lastY = self.y;
self.health = 3; // Armored enemy starts with 3 HP
self.isDestroyed = false;
return self;
});
var EnemyProjectile = Container.expand(function (fireAngle) {
var self = Container.call(this);
self.bulletSprite = self.attachAsset('enemyBulletSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.angle = fireAngle; // Angle of movement
self.speed = 10; // Enemy projectiles are a bit slower
if (self.bulletSprite) {
// Assuming the bullet shape is wider than tall, rotating by angle aligns its length with movement.
self.bulletSprite.rotation = self.angle;
}
self.isOffScreen = false;
self.update = function () {
if (self.isOffScreen) return;
if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Freeze enemy projectiles during card choice
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
var gameWidth = 2048;
var gameHeight = 2732;
// Margin based on projectile size to ensure it's fully off-screen
var margin = 50; // Default margin
if (self.bulletSprite && (self.bulletSprite.width || self.bulletSprite.height)) {
margin = Math.max(self.bulletSprite.width || 0, self.bulletSprite.height || 0) / 2 + 50;
}
if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) {
self.isOffScreen = true;
}
};
return self;
});
// Orange rectangular bullet
var EnemyShip = Container.expand(function () {
var self = Container.call(this);
self.fireCooldown = 180; // Fire every 3 seconds (180 ticks at 60FPS)
self.fireTimer = Math.random() * self.fireCooldown; // Stagger initial firing
self.enemyProjectileSpawnPoint = {
x: 0,
y: 0
};
self.hit = function () {
if (self.isDestroyed) return; // Already destroyed
self.health--;
if (self.health <= 0) {
self.isDestroyed = true;
// Optionally, trigger a small visual effect here like a flash
// LK.effects.flashObject(self, 0xffffff, 100);
} else {
// Optionally, visual effect for taking damage but not destroyed
// LK.effects.flashObject(self, 0xffaaaa, 50);
}
};
self.update = function () {
if (self.isOffScreen || self.isDestroyed) return; // Don't update if off-screen or destroyed
if (typeof freezeEnemies !== 'undefined' && freezeEnemies) return; // Freeze all enemy movement and firing during card choice
// Target the player ship
if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) {
var dx = playerShip.x - self.x;
var dy = playerShip.y - self.y;
// Calculate angle towards player
var targetAngle = Math.atan2(dy, dx);
// Update the enemy's angle to face the player
self.angle = targetAngle;
// Update sprite rotation to match the new angle
// Assuming the ship sprite is designed "pointing up" (nose along its local -Y axis or top)
// visual rotation = world angle + PI/2.
if (self.shipSprite) {
self.shipSprite.rotation = self.angle + Math.PI / 2;
}
}
// If playerShip is not available or is destroyed, the enemy will continue in its current self.angle.
self.lastX = self.x;
self.lastY = self.y;
// Prevent enemy from moving too close to the player
var safeDistance = 120; // Minimum allowed distance between enemy and player
var moveX = Math.cos(self.angle) * self.speed;
var moveY = Math.sin(self.angle) * self.speed;
if (typeof playerShip !== 'undefined' && playerShip && !playerShip.isDestroyed) {
var dxToPlayer = self.x + moveX - playerShip.x;
var dyToPlayer = self.y + moveY - playerShip.y;
var distToPlayer = Math.sqrt(dxToPlayer * dxToPlayer + dyToPlayer * dyToPlayer);
if (distToPlayer < safeDistance) {
// If moving would bring us too close, do not move this frame
moveX = 0;
moveY = 0;
}
}
self.x += moveX;
self.y += moveY;
// Update enemy projectile spawn point
var shipAsset = self.shipSprite;
if (shipAsset) {
var noseDistance = shipAsset.height / 2; // Assuming front is along height axis from center
self.enemyProjectileSpawnPoint.x = self.x + Math.cos(self.angle) * noseDistance;
self.enemyProjectileSpawnPoint.y = self.y + Math.sin(self.angle) * noseDistance;
}
// Firing logic
self.fireTimer--;
if (self.fireTimer <= 0) {
if (typeof EnemyProjectile !== 'undefined' && typeof enemyProjectiles !== 'undefined' && game && typeof game.addChild === 'function') {
var newProjectile = new EnemyProjectile(self.angle); // Fire in the direction the ship is moving
newProjectile.x = self.enemyProjectileSpawnPoint.x;
newProjectile.y = self.enemyProjectileSpawnPoint.y;
enemyProjectiles.push(newProjectile);
game.addChild(newProjectile);
self.fireTimer = self.fireCooldown; // Reset cooldown
}
}
// Generalized off-screen check
var gameWidth = 2048;
var gameHeight = 2732;
var marginWidth = self.shipSprite && self.shipSprite.width ? self.shipSprite.width / 2 + 50 : 100;
var marginHeight = self.shipSprite && self.shipSprite.height ? self.shipSprite.height / 2 + 50 : 100;
if (self.x < -marginWidth || self.x > gameWidth + marginWidth || self.y < -marginHeight || self.y > gameHeight + marginHeight) {
self.isOffScreen = true;
}
};
self.shipSprite = self.attachAsset('enemyShipSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.angle = Math.PI / 2; // Default angle: moving downwards (positive Y direction)
if (self.shipSprite) {
self.shipSprite.rotation = self.angle + Math.PI / 2;
}
self.speed = 4;
self.isOffScreen = false;
self.lastX = self.x;
self.lastY = self.y;
self.health = 1;
self.isDestroyed = false;
return self;
});
var FireButton = Container.expand(function () {
var self = Container.call(this);
self.buttonSprite = self.attachAsset('fireButtonSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.lastFireTick = -1000; // Track last tick when fired
self.down = function (x, y, obj) {
// Prevent holding down for auto-fire: only allow firing if enough time has passed since last fire
if (typeof LK !== 'undefined' && typeof LK.ticks !== 'undefined') {
if (typeof self.lastFireTick === 'undefined') self.lastFireTick = -1000;
// Only allow firing if at least fireButtonCooldown ticks have passed since last fire
if (typeof fireButtonCooldown === 'undefined') fireButtonCooldown = 30;
if (LK.ticks - self.lastFireTick < fireButtonCooldown) return;
}
// Only fire if player has ammo and not holding down for auto-fire
if (typeof playerAmmo !== 'undefined' && playerAmmo > 0) {
if (playerShip && typeof playerShip.currentAngle !== 'undefined' && typeof projectileSpawnPoint !== 'undefined' && playerProjectiles && PlayerProjectile) {
var newProjectile = new PlayerProjectile(playerShip.currentAngle); // Pass the ship's current firing angle
newProjectile.x = projectileSpawnPoint.x; // Set initial position from calculated spawn point
newProjectile.y = projectileSpawnPoint.y;
playerProjectiles.push(newProjectile);
game.addChild(newProjectile); // Add projectiles to the main game stage
playerAmmo = Math.max(0, playerAmmo - 1); // Decrease ammo by 1, never below 0
// Removed ammo counter update as per requirements
if (typeof LK !== 'undefined' && typeof LK.ticks !== 'undefined') {
self.lastFireTick = LK.ticks;
}
// Play shot sound when firing
if (typeof LK !== 'undefined' && typeof LK.getSound === 'function') {
var shotSound = LK.getSound('Shot');
if (shotSound && typeof shotSound.play === 'function') {
shotSound.play();
}
}
}
}
};
return self;
});
var HealthRestore = Container.expand(function () {
var self = Container.call(this);
self.restoreSprite = self.attachAsset('healthRestoreSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Check for collision with player ship
if (playerShip && !playerShip.isDestroyed && self.intersects(playerShip)) {
if (playerShip.health < 3) {
playerShip.health = Math.min(playerShip.health + 1, 3); // Restore health, max 3
if (playerShip.shipSprite) {
LK.effects.flashObject(playerShip.shipSprite, 0x00ff00, 150); // Flash green on health restore
}
// Update hearts display
for (var i = 0; i < hearts.length; i++) {
hearts[i].visible = i < playerShip.health;
}
}
self.destroy();
}
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
self.heartSprite = self.attachAsset('heartSprite', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
return self;
});
// Red circular fire button
var Joystick = Container.expand(function () {
var self = Container.call(this);
self.baseSprite = self.attachAsset('joystickBaseSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.knobSprite = self.attachAsset('joystickKnobSprite', {
anchorX: 0.5,
anchorY: 0.5
});
// Maximum distance the knob's center can move from the base's center
self.radius = self.baseSprite.width / 2 - self.knobSprite.width / 2;
if (self.radius <= 0) {
// Ensure a sensible minimum radius
self.radius = self.baseSprite.width > 0 ? self.baseSprite.width / 4 : 50; // Use 1/4 of base width or 50 if base has no width
}
self.isDragging = false;
self.inputVector = {
x: 0,
y: 0
}; // Normalized output (-1 to 1)
self.handleDown = function (localX, localY) {
// localX, localY are relative to the joystick's center (its origin)
// Check if the touch is within the larger base area to start dragging
var distSqFromCenter = localX * localX + localY * localY;
if (distSqFromCenter <= self.baseSprite.width / 2 * (self.baseSprite.width / 2)) {
self.isDragging = true;
self.handleMove(localX, localY); // Snap knob to initial touch position
return true; // Indicates joystick took control
}
return false; // Joystick not activated
};
self.handleMove = function (localX, localY) {
if (!self.isDragging) return;
var dist = Math.sqrt(localX * localX + localY * localY);
if (dist > self.radius) {
// Normalize and scale to radius if touch is outside draggable area
self.knobSprite.x = localX / dist * self.radius;
self.knobSprite.y = localY / dist * self.radius;
} else {
self.knobSprite.x = localX;
self.knobSprite.y = localY;
}
// Calculate normalized input vector
if (self.radius > 0) {
self.inputVector.x = self.knobSprite.x / self.radius;
self.inputVector.y = self.knobSprite.y / self.radius;
} else {
// Avoid division by zero if radius is zero
self.inputVector.x = 0;
self.inputVector.y = 0;
}
};
self.handleUp = function () {
if (self.isDragging) {
self.isDragging = false;
// Reset knob to center and clear input vector
self.knobSprite.x = 0;
self.knobSprite.y = 0;
self.inputVector.x = 0;
self.inputVector.y = 0;
}
};
self.getInput = function () {
return self.inputVector;
};
self.isActive = function () {
return self.isDragging;
};
return self;
});
// Projectile class for player's bullets
var PlayerProjectile = Container.expand(function (fireAngle) {
var self = Container.call(this);
// Attach bullet sprite
self.bulletSprite = self.attachAsset('playerBulletSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.angle = fireAngle; // Store the firing angle
self.speed = 20; // Projectile speed magnitude
// The playerBulletSprite has orientation:1, meaning it's rotated 90deg clockwise.
// If original was thin vertical, it's now thin horizontal, "pointing" along its X-axis.
// So, self.angle directly applies.
self.bulletSprite.rotation = self.angle;
self.isOffScreen = false; // Flag to indicate if projectile is off-screen
// Update method to move projectile
self.update = function () {
if (self.isOffScreen) return; // Don't update if already marked off-screen
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
// Check if off-screen
var gameWidth = 2048;
var gameHeight = 2732;
// Margin based on projectile size to ensure it's fully off-screen
var margin = Math.max(self.bulletSprite.width, self.bulletSprite.height) / 2 + 50;
if (self.x < -margin || self.x > gameWidth + margin || self.y < -margin || self.y > gameHeight + margin) {
self.isOffScreen = true;
}
}; //{M} // Note: Original L was self.destroy related, removed.
return self;
});
// PlayerShip class to handle player ship logic and projectile spawn point calculation
var PlayerShip = Container.expand(function () {
var self = Container.call(this);
self.health = 3; // Player can take a few hits
self._fractionalHealth = self.health; // For fractional damage (e.g., 0.5 from armored enemy)
self.isDestroyed = false;
self.hit = function () {
if (self.isDestroyed) return;
if (typeof self._fractionalHealth === 'undefined') self._fractionalHealth = self.health;
self._fractionalHealth -= 1;
self.health = Math.floor(self._fractionalHealth);
LK.effects.flashObject(self.shipSprite || self, 0xff0000, 150); // Flash red on hit (flash sprite if available)
// Update hearts display
for (var i = 0; i < hearts.length; i++) {
hearts[i].visible = i < self.health;
}
if (self._fractionalHealth <= 0) {
self.isDestroyed = true;
// Game over will be triggered in game.update
}
};
// Attach player ship sprite and set reference for spawn point calculation
self.shipSprite = self.attachAsset('playerShipSprite', {
anchorX: 0.5,
anchorY: 0.5
});
self.moveSpeed = 10; // Pixels per tick for movement speed
// currentAngle is the direction the ship moves and fires projectiles.
// 0 radians = right, -PI/2 = up (screen coordinates).
self.currentAngle = -Math.PI / 2; // Initial angle: pointing up.
// playerShipSprite is assumed to be designed pointing "up" (its nose along its local -Y axis).
// To make the sprite's nose align with currentAngle, its visual rotation needs adjustment.
// Visual rotation = currentAngle + Math.PI / 2.
// E.g., currentAngle = -PI/2 (up) => visual rotation = 0.
// E.g., currentAngle = 0 (right) => visual rotation = PI/2.
self.shipSprite.rotation = self.currentAngle + Math.PI / 2;
// Initialize projectile spawn point (remains important)
// This will be updated relative to the ship's current position in self.update()
// Initial dummy values, will be set correctly on first update.
projectileSpawnPoint.x = 0;
projectileSpawnPoint.y = 0;
self.applyJoystickInput = function (inputX, inputY) {
// Update angle only if joystick provides directional input
if (inputX !== 0 || inputY !== 0) {
self.currentAngle = Math.atan2(inputY, inputX);
self.shipSprite.rotation = self.currentAngle + Math.PI / 2;
}
self.x += inputX * self.moveSpeed;
self.y += inputY * self.moveSpeed;
// Boundary checks to keep ship on screen
var halfWidth = self.shipSprite.width / 2;
var halfHeight = self.shipSprite.height / 2;
var gameWidth = 2048;
var gameHeight = 2732;
var topSafeMargin = 100; // Top 100px area is reserved
if (self.x - halfWidth < 0) self.x = halfWidth;
if (self.x + halfWidth > gameWidth) self.x = gameWidth - halfWidth;
if (self.y - halfHeight < topSafeMargin) self.y = topSafeMargin + halfHeight;
if (self.y + halfHeight > gameHeight) self.y = gameHeight - halfHeight;
};
// Update projectile spawn point every frame based on player ship position and rotation
self.update = function () {
var shipAsset = self.shipSprite;
if (!shipAsset) return;
// Calculate spawn point at the tip of the ship, considering its currentAngle.
// The "nose" is shipAsset.height / 2 distance from the center.
var noseDistance = shipAsset.height / 2;
projectileSpawnPoint.x = self.x + Math.cos(self.currentAngle) * noseDistance;
projectileSpawnPoint.y = self.y + Math.sin(self.currentAngle) * noseDistance;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Projectile spawn point (relative to player ship center)
// Will be updated in PlayerShip class update
var projectileSpawnPoint = {
x: 0,
y: 0
};
var joystick; // Declare joystick instance
// Array to keep track of all player projectiles
var playerProjectiles = [];
// Player projectile damage (can be doubled by Cardsttsck)
var playerProjectileDamage = 1;
// Player ammo count (start with 10). Player cannot fire forever: must wait for ammo to restore and cannot hold fire for auto-fire.
var playerAmmo = 10;
var maxPlayerAmmo = 10; // Maximum player ammo capacity
// Fire button cooldown in ticks (default 30, can be halved by Card1)
var fireButtonCooldown = 30;
// Array to keep track of all enemy ships
var enemyShips = [];
// Array to keep track of all enemy projectiles
var enemyProjectiles = [];
// Global flag: freeze all enemy movement and firing during card choice
var freezeEnemies = false;
// Create player ship and add to game
var playerShip = new PlayerShip();
game.addChild(playerShip);
// Display hearts for player health
var hearts = [];
for (var i = 0; i < playerShip.health; i++) {
var heart = new Heart();
heart.x = i * 120 + 120; // Position hearts with increased spacing
heart.y = 60; // Top-left corner
LK.gui.topLeft.addChild(heart);
hearts.push(heart);
}
// Money counter (pixel style) in top-right corner
var money = 0;
var moneyTxt = new Text2(money + " $", {
size: 120,
fill: 0xFFE066,
font: "monospace, 'Press Start 2P', 'VT323', 'Courier New', Courier, monospace",
// pixel/retro style
align: "right"
});
moneyTxt.anchor.set(1, 0); // Right-top anchor
moneyTxt.x = -60; // Padding from right edge
moneyTxt.y = 60; // Padding from top edge
LK.gui.topRight.addChild(moneyTxt);
// Ammo counter removed as per requirements
// Center player ship horizontally, place near bottom
playerShip.x = 2048 / 2;
playerShip.y = 2732 - 350;
// Create Joystick
joystick = new Joystick();
game.addChild(joystick);
// Set initial position (e.g., 0,0), though it will jump to touch.
joystick.x = 0;
joystick.y = 0;
// Enemy spawn multiplier (default 1, can be set to 3 for 5 seconds after Card2)
game._enemySpawnMultiplier = 1;
game._enemySpawnMultiplierTimeout = null;
// Create and add Enemy Ships
var enemyShip1 = new EnemyShip();
var enemyShipAssetHeight1 = enemyShip1.shipSprite && enemyShip1.shipSprite.height ? enemyShip1.shipSprite.height : 146; // Default to asset height
enemyShip1.x = 2048 / 3;
enemyShip1.y = -(enemyShipAssetHeight1 / 2) - 50; // Start off-screen at the top
enemyShip1.lastX = enemyShip1.x; // Initialize lastX to the actual starting x
enemyShip1.lastY = enemyShip1.y; // Initialize lastY to the actual starting y
game.addChild(enemyShip1);
enemyShips.push(enemyShip1);
var enemyShip2 = new EnemyShip();
var enemyShipAssetHeight2 = enemyShip2.shipSprite && enemyShip2.shipSprite.height ? enemyShip2.shipSprite.height : 146; // Default to asset height
enemyShip2.x = 2048 * 2 / 3;
enemyShip2.y = -(enemyShipAssetHeight2 / 2) - 150; // Start off-screen, staggered further up
enemyShip2.lastX = enemyShip2.x; // Initialize lastX to the actual starting x
enemyShip2.lastY = enemyShip2.y; // Initialize lastY to the actual starting y
game.addChild(enemyShip2);
enemyShips.push(enemyShip2);
// Game event handlers
// Create and position Fire Button
var fireButton = new FireButton();
LK.gui.bottomLeft.addChild(fireButton);
// Position the fire button visually in the bottom-left corner with some margin
// The buttonSprite is 200x200 and anchored at its center (0.5, 0.5)
// fireButton (Container) is added to LK.gui.bottomLeft, positioning its top-left (0,0) at screen bottom-left.
// Adjust x and y to make the button appear correctly.
var buttonMargin = 50; // 50px margin from screen edges
fireButton.x = fireButton.buttonSprite.width / 2 + buttonMargin;
fireButton.y = -fireButton.buttonSprite.height / 2 - buttonMargin; // Negative Y moves up from the bottom edge
game.down = function (x, y, obj) {
if (game._cardChoiceActive) {
// Block joystick and ship movement while card choice is active
return;
}
// x, y are global game coordinates
// Move the joystick to the touch position
joystick.x = x;
joystick.y = y;
joystick.visible = true;
// Activate the joystick. Since the joystick's origin is now at the touch point (x,y),
// the local coordinates for the touch within the joystick are (0,0).
// This centers the knob under the finger and initiates dragging.
joystick.handleDown(0, 0); // Pass (0,0) as local coordinates
// Note: With this "floating joystick" implementation, the joystick activates on any screen press.
// The previous mechanic, where tapping away from a fixed joystick fired projectiles,
// is superseded. Further design would be needed for a separate firing mechanism.
};
game.move = function (x, y, obj) {
if (game._cardChoiceActive) {
// Block joystick movement while card choice is active
return;
}
if (joystick.isActive()) {
var joystickLocalPos = joystick.toLocal({
x: x,
y: y
});
joystick.handleMove(joystickLocalPos.x, joystickLocalPos.y);
}
};
game.up = function (x, y, obj) {
if (game._cardChoiceActive) {
// Block joystick release while card choice is active
return;
}
if (joystick.isActive()) {
joystick.handleUp(); // Resets knob, inputVector, and isDragging flag
joystick.visible = false; // Hide the joystick when the touch is released
}
};
// Update game state
var ammoRestoreCooldown = 0; // Ticks until next ammo restore
game.update = function () {
// Prevent all game logic and object updates while card choice overlay is active
if (game._cardChoiceActive) {
// Only allow card overlay input, skip all updates and movement
return;
}
// Restore ammo if not full, with a delay (e.g., 30 ticks = 0.5s)
if (typeof playerAmmo !== 'undefined') {
if (typeof maxPlayerAmmo === 'undefined') maxPlayerAmmo = 10; // Defensive: ensure maxPlayerAmmo is defined
if (playerAmmo < maxPlayerAmmo) {
// Use maxPlayerAmmo instead of hardcoded 10
if (typeof ammoRestoreCooldown === 'undefined') ammoRestoreCooldown = 0;
if (ammoRestoreCooldown > 0) {
ammoRestoreCooldown--;
} else {
playerAmmo += 1;
// Removed ammo counter update as per requirements
ammoRestoreCooldown = 30; // 0.5s at 60FPS
}
} else {
// Player is at or above max ammo.
// Ensure current ammo doesn't exceed new max if it somehow did, though unlikely with current regen.
playerAmmo = Math.min(playerAmmo, maxPlayerAmmo);
ammoRestoreCooldown = 0;
}
}
// Apply joystick input to player ship movement
if (joystick && playerShip && playerShip.applyJoystickInput) {
var input = joystick.getInput();
playerShip.applyJoystickInput(input.x, input.y);
}
// Update player ship (e.g., to recalculate projectileSpawnPoint after moving)
if (playerShip && playerShip.update) {
playerShip.update();
}
// Update and clean up projectiles, and check for collisions with enemies
for (var i = playerProjectiles.length - 1; i >= 0; i--) {
var proj = playerProjectiles[i];
if (proj.update) {
proj.update(); // Call projectile's own update logic (movement)
}
// Check if the projectile flagged itself as off-screen AFTER moving
if (proj.isOffScreen) {
proj.destroy(); // Destroy the projectile
playerProjectiles.splice(i, 1); // Remove from the tracking array
continue; // Move to the next projectile
}
// Collision detection with enemy ships
for (var k = enemyShips.length - 1; k >= 0; k--) {
var enemy = enemyShips[k];
// Skip already destroyed or off-screen enemies for collision checks
if (enemy.isDestroyed || enemy.isOffScreen) {
continue;
}
if (proj.intersects(enemy)) {
// Use playerProjectileDamage if defined, else default to 1
var dmg = typeof playerProjectileDamage !== 'undefined' ? playerProjectileDamage : 1;
if (typeof enemy.health === 'number') {
enemy.health -= dmg;
if (enemy.health <= 0) {
enemy.isDestroyed = true;
} else {
if (typeof enemy.hit === 'function') enemy.hit();
}
} else {
if (typeof enemy.hit === 'function') enemy.hit();
}
proj.destroy(); // Projectile is consumed on hit
playerProjectiles.splice(i, 1); // Remove projectile
// If enemy.isDestroyed became true, it will be handled in the enemyShips loop below.
// Score will be awarded there too.
break; // Projectile is gone, no need to check against other enemies
}
}
// If proj was spliced, loop continues correctly to the new element at index i or finishes.
}
// Update and clean up enemy projectiles, and check for collisions with player
for (var l = enemyProjectiles.length - 1; l >= 0; l--) {
var eProj = enemyProjectiles[l];
if (eProj.update) {
eProj.update(); // Call projectile's own update logic (movement)
}
// Check if the projectile flagged itself as off-screen AFTER moving
if (eProj.isOffScreen) {
if (typeof eProj.destroy === 'function') eProj.destroy();
enemyProjectiles.splice(l, 1); // Remove from the tracking array
continue; // Move to the next projectile
}
// Collision detection with player ship
// Ensure playerShip exists and is not already destroyed before checking intersection
if (playerShip && !playerShip.isDestroyed && typeof eProj.intersects === 'function' && eProj.intersects(playerShip)) {
// Check if the projectile's parent is an ArmoredEnemyShip (for 0.5 damage)
var isArmored = false;
if (typeof enemyShips !== 'undefined') {
for (var ai = 0; ai < enemyShips.length; ai++) {
var es = enemyShips[ai];
if (es instanceof ArmoredEnemyShip && es.enemyProjectileSpawnPoint && Math.abs(eProj.x - es.enemyProjectileSpawnPoint.x) < 2 && Math.abs(eProj.y - es.enemyProjectileSpawnPoint.y) < 2) {
isArmored = true;
break;
}
}
}
if (isArmored) {
// Deal 0.5 damage: use a fractional health system
if (typeof playerShip.health === 'number') {
if (typeof playerShip._fractionalHealth === 'undefined') playerShip._fractionalHealth = playerShip.health;
playerShip._fractionalHealth -= 0.5;
if (playerShip._fractionalHealth <= 0) {
playerShip.health = 0;
playerShip.isDestroyed = true;
if (playerShip.shipSprite) playerShip.shipSprite.visible = false;else if (typeof playerShip.visible !== 'undefined') playerShip.visible = false;
LK.showGameOver();
} else {
playerShip.health = Math.floor(playerShip._fractionalHealth);
LK.effects.flashObject(playerShip.shipSprite || playerShip, 0xff0000, 150);
// Update hearts display
for (var i = 0; i < hearts.length; i++) {
hearts[i].visible = i < playerShip.health;
}
}
}
} else {
if (typeof playerShip.hit === 'function') playerShip.hit(); // Player takes damage
if (playerShip.isDestroyed) {
if (playerShip.shipSprite) playerShip.shipSprite.visible = false;else if (typeof playerShip.visible !== 'undefined') playerShip.visible = false;
LK.showGameOver();
}
}
if (typeof eProj.destroy === 'function') eProj.destroy(); // Projectile is consumed on hit
enemyProjectiles.splice(l, 1); // Remove projectile
// If projectile hit player, it's gone. No need to check further for this projectile.
}
}
// Update and clean up enemy ships
// This loop should still run even if player is destroyed, to clean up existing enemies,
// though new game state might prevent further updates if LK.showGameOver() is effective immediately.
var _loop = function _loop() {
enemy = enemyShips[j]; // First, check if the enemy ship is marked as destroyed (e.g., by a projectile hit)
if (enemy.isDestroyed) {
// Show Boom image at enemy's position
boomImg = LK.getAsset('Boom', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y
});
game.addChild(boomImg);
// Remove Boom image after a short delay (e.g., 125ms), then show Boom2 at the same position
LK.setTimeout(function () {
if (boomImg && typeof boomImg.destroy === 'function') boomImg.destroy();
// Show Boom2 image at the same position after Boom disappears
var boom2Img = LK.getAsset('Boom2', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y
});
game.addChild(boom2Img);
// Remove Boom2 image after a short delay (e.g., 125ms), then show Boom3 at the same position
LK.setTimeout(function () {
if (boom2Img && typeof boom2Img.destroy === 'function') boom2Img.destroy();
// Show Boom3 image at the same position after Boom2 disappears
var boom3Img = LK.getAsset('Boom3', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y
});
game.addChild(boom3Img);
// Remove Boom3 image after a short delay (e.g., 125ms), then show Boom4 at the same position
LK.setTimeout(function () {
if (boom3Img && typeof boom3Img.destroy === 'function') boom3Img.destroy();
// Show Boom4 image at the same position after Boom3 disappears
var boom4Img = LK.getAsset('Boom4', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y
});
game.addChild(boom4Img);
// Remove Boom4 image after a short delay (e.g., 125ms), then show Boom5 at the same position
LK.setTimeout(function () {
if (boom4Img && typeof boom4Img.destroy === 'function') boom4Img.destroy();
// Show Boom5 image at the same position after Boom4 disappears
var boom5Img = LK.getAsset('Boom5', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y
});
game.addChild(boom5Img);
// Remove Boom5 image after a short delay (e.g., 125ms), then show Boom6 at the same position
LK.setTimeout(function () {
if (boom5Img && typeof boom5Img.destroy === 'function') boom5Img.destroy();
// Show Boom6 image at the same position after Boom5 disappears
var boom6Img = LK.getAsset('Boom6', {
anchorX: 0.5,
anchorY: 0.5,
x: enemy.x,
y: enemy.y
});
game.addChild(boom6Img);
// Remove Boom6 image after a short delay (e.g., 125ms)
LK.setTimeout(function () {
if (boom6Img && typeof boom6Img.destroy === 'function') boom6Img.destroy();
}, 125);
}, 125);
}, 125);
}, 125);
}, 125);
}, 125);
enemy.destroy(); // Destroy the enemy ship
enemyShips.splice(j, 1); // Remove from the tracking array
LK.setScore(LK.getScore() + 10); // Award score for destroying an enemy
money += 5; // Add 5 money per enemy destroyed
if (typeof moneyTxt !== 'undefined' && moneyTxt.setText) {
moneyTxt.setText(money + " $");
}
LK.getSound('Boom').play(); // Play 'Boom' sound effect
// 25% chance to drop a health restoration
if (Math.random() < 0.25) {
healthRestore = new HealthRestore();
healthRestore.x = enemy.x;
healthRestore.y = enemy.y;
game.addChild(healthRestore);
}
// --- Card choice after 3 kills ---
if (typeof enemiesKilled === 'undefined') {
enemiesKilled = 0;
}
enemiesKilled++;
if (enemiesKilled > 0 && enemiesKilled % 3 === 0 && !game._cardChoiceActive) {
// Helper to finish card choice and resume game
var finishCardChoice = function finishCardChoice() {
if (!game._cardChoiceActive) return;
game._cardChoiceActive = false;
freezeEnemies = false;
if (cardOverlay && typeof cardOverlay.destroy === 'function') cardOverlay.destroy();
// Restore input
game.down = oldGameDown;
game.move = oldGameMove;
game.up = oldGameUp;
// Resume game logic
// No need to call LK.resumeGame(); LK engine will resume game after overlay is destroyed
};
game._cardChoiceActive = true;
// Freeze all enemy movement and firing
freezeEnemies = true;
// --- Block card selection for 2 seconds ---
game._cardChoiceBlock = true;
LK.setTimeout(function () {
game._cardChoiceBlock = false;
}, 2000);
// Pause game logic is handled by LK.showGameOver() and LK.resumeGame(), no need to call LK.pause() here
// Create overlay for card choice
cardOverlay = new Container();
cardOverlay.name = "cardChoiceOverlay";
// Semi-transparent background
bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 20,
scaleY: 20,
x: 2048 / 2,
y: 2732 / 2,
tint: 0x000000,
alpha: 0.7
});
cardOverlay.addChild(bg);
// --- Define all available cards and their effects ---
var allAvailableCards = [{
key: "card1_ammo_firerate",
// Card1: More Ammo, Faster Fire Rate
asset: "Card1",
applyEffect: function applyEffect() {
if (typeof maxPlayerAmmo === 'undefined') maxPlayerAmmo = 10; // Defensive initialization
maxPlayerAmmo += 2; // Increase maximum ammo capacity
playerAmmo = Math.min(playerAmmo + 2, maxPlayerAmmo); // Add 2 to current ammo, capped by new maxPlayerAmmo
fireButtonCooldown = Math.max(1, Math.floor(fireButtonCooldown / 2)); // Halve cooldown, min 1
}
}, {
key: "card2_speed_spawn_heal",
// Card2: Speed Boost, Enemy Spawn Boost, Heal 1 HP
asset: "Csrd2",
applyEffect: function applyEffect() {
if (playerShip) {
if (typeof playerShip._originalMoveSpeed === 'undefined') {
playerShip._originalMoveSpeed = playerShip.moveSpeed;
}
playerShip.moveSpeed = playerShip._originalMoveSpeed * 2;
// Speed boost is now permanent, timeout removed.
// Heal 1 HP if not at max health
var currentMaxHealth = playerShip.maxHealth || 3;
if (playerShip.health < currentMaxHealth) {
playerShip.health = Math.min(playerShip.health + 1, currentMaxHealth);
if (typeof playerShip._fractionalHealth !== 'undefined') {
// Ensure fractional health is updated
playerShip._fractionalHealth = playerShip.health;
}
for (var i = 0; i < hearts.length; i++) {
hearts[i].visible = i < playerShip.health;
}
}
}
// Enemy spawn multiplier
if (typeof game._enemySpawnMultiplierTimeout !== 'undefined' && game._enemySpawnMultiplierTimeout) {
LK.clearTimeout(game._enemySpawnMultiplierTimeout);
}
game._enemySpawnMultiplier = 3;
game._enemySpawnMultiplierTimeout = LK.setTimeout(function () {
game._enemySpawnMultiplier = 1;
game._enemySpawnMultiplierTimeout = null;
}, 5000); // Enemy spawn boost for 5 seconds
}
}, {
key: "cardhp_maxhp_heal",
// Cardhp: Increase Max HP, Heal 1 HP
asset: "Cardhp",
applyEffect: function applyEffect() {
if (playerShip) {
if (typeof playerShip.maxHealth === 'undefined') {
playerShip.maxHealth = 3;
}
playerShip.maxHealth += 1;
playerShip.health = Math.min(playerShip.health + 1, playerShip.maxHealth); // Heal 1 HP up to new max
if (typeof playerShip._fractionalHealth !== 'undefined') {
// Ensure fractional health is updated
playerShip._fractionalHealth = playerShip.health;
}
// Add a new heart icon if needed (maxHealth might exceed initial hearts array length)
// Ensure heart icons match current maxHealth and health
while (hearts.length < playerShip.maxHealth) {
var newHeart = new Heart();
newHeart.x = hearts.length * 120 + 120;
newHeart.y = 60;
LK.gui.topLeft.addChild(newHeart);
hearts.push(newHeart);
}
for (var i = 0; i < hearts.length; i++) {
hearts[i].visible = i < playerShip.health;
}
}
}
}, {
key: "cardsttsck_doubledamage",
// Cardsttsck: Double Projectile Damage
asset: "Cardsttsck",
applyEffect: function applyEffect() {
if (typeof playerProjectileDamage === 'undefined') {
playerProjectileDamage = 1;
}
playerProjectileDamage *= 2;
}
}];
// --- Shuffle and select 3 cards to display ---
// Fisher-Yates shuffle
for (var k = allAvailableCards.length - 1; k > 0; k--) {
var j_shuffle = Math.floor(Math.random() * (k + 1));
var tempCard = allAvailableCards[k];
allAvailableCards[k] = allAvailableCards[j_shuffle];
allAvailableCards[j_shuffle] = tempCard;
}
var cardsToDisplay = allAvailableCards.slice(0, 3); // Get the first 3 cards
// --- Position and display selected cards ---
var cardPositions = [{
x: 2048 / 2 - 450,
y: 2732 / 2
},
// Left card
{
x: 2048 / 2,
y: 2732 / 2
},
// Middle card
{
x: 2048 / 2 + 450,
y: 2732 / 2
} // Right card
];
for (var cardIdx = 0; cardIdx < cardsToDisplay.length; cardIdx++) {
var cardData = cardsToDisplay[cardIdx];
var cardContainer = new Container(); // Create a new container for each card
// The image itself is positioned globally based on cardPositions
var cardImg = LK.getAsset(cardData.asset, {
anchorX: 0.5,
anchorY: 0.5,
x: cardPositions[cardIdx].x,
y: cardPositions[cardIdx].y
});
cardContainer.addChild(cardImg);
// The container itself can be at 0,0 as its child (the image) is globally positioned.
// However, for consistency with how interactives are often handled,
// we can also set the container's position if we wanted the image local to it.
// For this setup, global positioning of the image is fine.
cardContainer.x = 0;
cardContainer.y = 0;
cardContainer.interactive = true;
// Use a closure to correctly capture cardData for each event handler
(function (currentCardData) {
cardContainer.down = function (x_down, y_down, obj_down) {
if (!game._cardChoiceActive || game._cardChoiceBlock) return;
currentCardData.applyEffect();
finishCardChoice();
};
})(cardData);
cardOverlay.addChild(cardContainer);
}
// Add overlay to game
game.addChild(cardOverlay);
// Block all input except card choice
oldGameDown = game.down;
oldGameMove = game.move;
oldGameUp = game.up;
game.down = function (x, y, obj) {
if (game._cardChoiceBlock) return;
// Defensive: Only forward to cards that exist in overlay
if (cardOverlay && cardOverlay.children) {
for (var i = 0; i < cardOverlay.children.length; i++) {
var child = cardOverlay.children[i];
if (child && child.interactive && typeof child.down === "function" && child.children && child.children.length > 0) {
var img = child.children[0];
if (img && typeof child.toLocal === "function") {
var local = child.toLocal({
x: x,
y: y
});
if (img.width && img.height && Math.abs(local.x) < img.width / 2 && Math.abs(local.y) < img.height / 2) {
child.down(x, y, obj);
return;
}
}
}
}
}
};
game.move = function () {};
game.up = function () {};
}
// }
// if (typeof scoreTxt !== 'undefined' && scoreTxt && scoreTxt.setText) {
// scoreTxt.setText(LK.getScore());
// }
return 1; // continue
// Move to the next enemy ship in the list
}
if (enemy.update) {
enemy.update(); // Call enemy ship's own update logic (movement, etc.)
}
// Then, check if the enemy ship flagged itself as off-screen (if not already destroyed)
if (enemy.isOffScreen) {
enemy.destroy(); // Destroy the enemy ship
enemyShips.splice(j, 1); // Remove from the tracking array
// No score for merely going off-screen, unless desired.
}
},
enemy,
boomImg,
healthRestore,
cardOverlay,
bg,
card1,
card1Bg,
card1Text,
card2,
card2Bg,
card2Text,
oldGameDown,
oldGameMove,
oldGameUp;
for (var j = enemyShips.length - 1; j >= 0; j--) {
if (_loop()) continue;
}
// Increase the number of enemies over time
if (LK.ticks % Math.max(30, 150 - Math.floor(LK.ticks / 300)) === 0) {
// Determine multiplier (default 1, or 3x if card2 effect active)
var multiplier = typeof game._enemySpawnMultiplier !== 'undefined' && game._enemySpawnMultiplier > 1 ? game._enemySpawnMultiplier : 1;
for (var spawnIdx = 0; spawnIdx < multiplier; spawnIdx++) {
// 25% chance to spawn armored enemy, else normal
var newEnemyShip;
if (Math.random() < 0.25 && typeof ArmoredEnemyShip !== 'undefined') {
newEnemyShip = new ArmoredEnemyShip();
} else {
newEnemyShip = new EnemyShip();
}
var enemyShipAssetHeight = newEnemyShip.shipSprite && newEnemyShip.shipSprite.height ? newEnemyShip.shipSprite.height : 146;
newEnemyShip.x = Math.random() * 2048;
newEnemyShip.y = -(enemyShipAssetHeight / 2) - 50;
newEnemyShip.lastX = newEnemyShip.x;
newEnemyShip.lastY = newEnemyShip.y;
game.addChild(newEnemyShip);
enemyShips.push(newEnemyShip);
}
}
};
LK.playMusic('Space');
Звездолет вид сверху два д для 2d игры пиксельный. In-Game asset
Красный лазерный луч пиксельный вид сверху. In-Game asset. 2d. High contrast. No shadows
Пиксельная шестерёнка с гаечным ключом. In-Game asset. 2d. High contrast. No shadows
Карточка с изоброжение скорости атака пиксельная карточка улучшения пиксельная космическая. In-Game asset. 2d. High contrast. No shadows
Карта усиления пиксельная усиливает скорость игрока космическая 2д пиксели. In-Game asset. 2d. High contrast. No shadows
Бронированный летающий корабль звездолет пиксельный вид сверху 2д. In-Game asset. 2d. High contrast. No shadows
Start в космической пмксельном стиле. In-Game asset. 2d. High contrast. No shadows
pixel inscription battle of starships in the style of space pixel art. In-Game asset. 2d. High contrast. No shadows
Карта усиления дающие + хп пиксельная космическая. In-Game asset. 2d. High contrast. No shadows
Пиксельная карта усиления атаки космос битва. In-Game asset. 2d. High contrast. No shadows
Карточка улучшения раздватвает атаку пиксельная в стиле космоса. In-Game asset. 2d. High contrast. No shadows
Пиксельная круглая кнопка атаки. In-Game asset. 2d. High contrast. No shadows
Пиксельный корабль сверху с нарисованным огнем спереди вид сверху. In-Game asset. 2d. High contrast. No shadows
Звездолет оформление в стиле призрака пиксельный вид сверху. In-Game asset. 2d. High contrast. No shadows
Пиксельная черная дыра желто черного цвета. In-Game asset. 2d. High contrast. No shadows