User prompt
Zırha düşman çarpma sesi ekle
User prompt
Kurşun sesi ve düsman ölme sesi asset ekle
User prompt
Düşman yuvarlak asset ekle ve onu kullan.
User prompt
Canbarı hep yukarda olsun oyuncu dönse bile.
User prompt
Düşman daha yavaş gelsin.
User prompt
Zırhlar daire olsun
User prompt
3 katman cember zirh ekle
User prompt
Oyuncunun etrafinda 3 katman daire zırh var. Düşmanlar geçemez ama çarptıkça zarar verirler.
Code edit (1 edits merged)
Please save this source code
User prompt
360 Savunma: Merkezde Adam
Initial prompt
Tam merkez de bir adamımız var. Etraftan yaklaşan düşmanlara her dokunmada ateş edecek. Oyuncumuzun ve düşmanin can barı üzerinde olacak. Adamımiz sadece 360 dere e donebilir ve ateş edenilir. Düşman 360 derece her yerden gelebilir.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // --- Bullet Class --- var Bullet = Container.expand(function () { var self = Container.call(this); // Attach bullet asset (white ellipse) var bulletAsset = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Bullet speed (pixels per frame) self.speed = 160; // Direction in radians (set on creation) self.direction = 0; // Damage dealt by this bullet self.damage = 1; // Defensive: track last position for possible future use self.lastX = self.x; self.lastY = self.y; // Update method called every tick self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += Math.cos(self.direction) * self.speed; self.y += Math.sin(self.direction) * self.speed; }; return self; }); // --- Enemy Class --- var Enemy = Container.expand(function () { var self = Container.call(this); // Attach enemy asset (pink ellipse), scaled up 2x var enemyAsset = self.attachAsset('enemyCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); // Enemy properties self.radius = 60 + Math.random() * 40; // for spawn distance self.speed = 1.15 + Math.random() * 0.55; // even faster approach self.maxHp = 3; // always 3 HP self.hp = self.maxHp; // Direction towards center (set on spawn) self.direction = 0; // Health bar (white background, green foreground), both rectangles, above the enemy self.hpBarBg = self.addChild(LK.getAsset('hpbar_bg', { anchorX: 0.5, anchorY: 1.0, y: -140 // higher above the enemy })); self.hpBar = self.addChild(LK.getAsset('hpbar_fg', { anchorX: 0.5, anchorY: 1.0, y: -140 // match background bar position })); // Defensive: track last position for possible future use self.lastX = self.x; self.lastY = self.y; // Update health bar width self.updateHpBar = function () { // Use asset width for scaling, so health bar always matches background self.hpBar.width = self.hpBarBg.width * (self.hp / self.maxHp); self.hpBar.x = self.hpBarBg.x - (self.hpBarBg.width - self.hpBar.width) / 2; }; // Update method called every tick self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += Math.cos(self.direction) * self.speed; self.y += Math.sin(self.direction) * self.speed; }; // Take damage self.takeDamage = function (amount) { self.hp -= amount; if (self.hp < 0) self.hp = 0; self.updateHpBar(); }; // On death self.die = function () { // Flash effect LK.effects.flashObject(self, 0xffffff, 200); // Particle explosion (intense red, gravity-free) // More and bigger particles, even more red! for (var i = 0; i < 38; i++) { var p = new Container(); var asset = p.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, // Make particles much bigger overall scaleX: 1.1 + Math.random() * 0.8, scaleY: 1.1 + Math.random() * 0.8, // Use a deeper, more saturated red color: 0xff0000, alpha: 0.82 + Math.random() * 0.18 }); p.x = self.x; p.y = self.y; var angle = Math.random() * Math.PI * 2; var speed = 22 + Math.random() * 20; var life = 26 + Math.floor(Math.random() * 18); p.update = function (a, s, l) { var vx = Math.cos(a) * s; var vy = Math.sin(a) * s; var ticks = 0; return function () { this.x += vx; this.y += vy; vx *= 0.88; vy *= 0.88; this.alpha *= 0.93; ticks++; if (ticks > l) { this.destroy(); } }; }(angle, speed, life); game.addChild(p); // Add to update loop if (!game._enemyParticles) game._enemyParticles = []; game._enemyParticles.push(p); } }; // Initialize health bar self.updateHpBar(); return self; }); // --- HealthBox Class --- var HealthBox = Container.expand(function () { var self = Container.call(this); // Attach health asset (red box), scaled up 1.5x var asset = self.attachAsset('healthBox', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.radius = 60 + Math.random() * 40; self.speed = 0.32 + Math.random() * 0.16; // even faster movement self.maxHp = 1; self.hp = self.maxHp; self.direction = 0; // Defensive: track last position self.lastX = self.x; self.lastY = self.y; // Update method self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += Math.cos(self.direction) * self.speed; self.y += Math.sin(self.direction) * self.speed; }; // Take damage self.takeDamage = function (amount) { self.hp -= amount; if (self.hp < 0) self.hp = 0; }; // On death self.die = function () { LK.effects.flashObject(self, 0xffffff, 200); // Particle effect (white explosion) for (var i = 0; i < 18; i++) { var p = new Container(); var asset = p.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9 + Math.random() * 0.7, scaleY: 0.9 + Math.random() * 0.7, color: 0xffffff, alpha: 0.8 + Math.random() * 0.15 }); p.x = self.x; p.y = self.y; var angle = Math.random() * Math.PI * 2; var speed = 12 + Math.random() * 10; var life = 16 + Math.floor(Math.random() * 10); p.update = function (a, s, l) { var vx = Math.cos(a) * s; var vy = Math.sin(a) * s; var ticks = 0; return function () { this.x += vx; this.y += vy; vx *= 0.88; vy *= 0.88; this.alpha *= 0.91; ticks++; if (ticks > l) { this.destroy(); } }; }(angle, speed, life); game.addChild(p); if (!game._enemyParticles) game._enemyParticles = []; game._enemyParticles.push(p); } }; return self; }); // --- Player Class --- var Player = Container.expand(function () { var self = Container.call(this); // Attach player asset (blue ellipse), scaled up 2x var playerAsset = self.attachAsset('character', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); // Player properties self.radius = 0; // always at center self.rotation = 0; // radians self.maxHp = 5; self.hp = self.maxHp; // Health bar (red background, green foreground) - always on top, not rotating self.hpBarBg = LK.getAsset('hpbar_bg', { anchorX: 0.5, anchorY: 0.5, y: -140 }); self.hpBar = LK.getAsset('hpbar_fg', { anchorX: 0.5, anchorY: 0.5, y: -140 }); // Defensive: track last position for possible future use self.lastX = self.x; self.lastY = self.y; // Update health bar width self.updateHpBar = function () { // Clamp hp to [0, maxHp] if (self.hp < 0) self.hp = 0; if (self.hp > self.maxHp) self.hp = self.maxHp; // Use asset width for scaling, so health bar always matches background // Health bar shrinks from left to right (left edge fixed) var targetWidth = self.hpBarBg.width * (self.hp / self.maxHp); if (targetWidth < 0) targetWidth = 0; // Smoothly animate width decrease (if needed) if (typeof self.hpBar.width === "undefined") self.hpBar.width = self.hpBarBg.width; if (self.hpBar.width > targetWidth) { self.hpBar.width -= Math.max(6, (self.hpBar.width - targetWidth) * 0.18); if (self.hpBar.width < targetWidth) self.hpBar.width = targetWidth; } else { self.hpBar.width = targetWidth; } // Left edge fixed: set x so left edge stays at bg left self.hpBar.x = self.hpBarBg.x - self.hpBarBg.width / 2 + self.hpBar.width / 2; // Add a white border to the health bar background for better visibility if (!self.hpBarBg._borderAdded) { var border = LK.getAsset('hpbar_bg', { anchorX: 0.5, anchorY: 0.5, y: self.hpBarBg.y, width: self.hpBarBg.width + 8, height: self.hpBarBg.height + 8, color: 0xffffff, alpha: 0.25 }); border.x = self.hpBarBg.x; border.y = self.hpBarBg.y; if (self.hpBarBg.parent) self.hpBarBg.parent.addChild(border); self.hpBarBg._borderAdded = true; } }; // Take damage self.takeDamage = function (amount) { self.hp -= amount; if (self.hp < 0) self.hp = 0; if (self.hp > self.maxHp) self.hp = self.maxHp; self.updateHpBar(); }; // On death self.die = function () { LK.effects.flashObject(self, 0xff0000, 500); }; // Initialize health bar self.updateHpBar(); return self; }); // --- Shield Layer Class --- var ShieldLayer = Container.expand(function () { var self = Container.call(this); // Properties self.radius = 0; // distance from player center self.maxHp = 3; self.hp = self.maxHp; self.index = 0; // 0=innermost, 1=outer self.active = true; // Visual: support shield1, shield2, and shield3 assets var shieldAssetNames = ['shield1', 'shield2', 'shield3']; var assetName = shieldAssetNames[self.index] || 'shield1'; self.shieldAsset = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5, scaleX: 4.2, scaleY: 4.2, alpha: 0.25 }); // Health bar above shield self.hpBarBg = self.addChild(LK.getAsset('hpbar_bg', { anchorX: 0.5, anchorY: 0.5, y: -self.radius - 120 })); self.hpBar = self.addChild(LK.getAsset('hpbar_fg', { anchorX: 0.5, anchorY: 0.5, y: -self.radius - 120 })); // Defensive: track last position for possible future use self.lastX = self.x; self.lastY = self.y; // Update health bar width self.updateHpBar = function () { // Use asset width for scaling, so health bar always matches background self.hpBar.width = self.hpBarBg.width * (self.hp / self.maxHp); self.hpBar.x = self.hpBarBg.x - (self.hpBarBg.width - self.hpBar.width) / 2; self.hpBar.visible = self.active; self.hpBarBg.visible = self.active; }; // Take damage self.takeDamage = function (amount) { if (!self.active) return; self.hp -= amount; if (self.hp < 0) self.hp = 0; self.updateHpBar(); if (self.hp <= 0) { // Play shield explosion sound LK.getSound('shieldExplode').play(); self.active = false; self.visible = false; self.hpBar.visible = false; self.hpBarBg.visible = false; } }; // Reset shield self.reset = function () { self.hp = self.maxHp; self.active = true; self.visible = true; self.updateHpBar(); }; // Initialize self.updateHpBar(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Health bar assets for bg/fg // --- Background Image --- var backgroundImg = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 2048 / 4096, scaleY: 2732 / 4096 }); game.addChild(backgroundImg); // --- Global Variables --- var player; var enemies = []; var bullets = []; var spawnTimer = 0; var fireCooldown = 0; var dragActive = false; var lastTouchAngle = 0; // --- Score Display --- var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Helper Line for Fire Direction (dotted, in fire direction, aligned with bullet) --- var helperLineDots = []; var helperLineAlpha = 0.7; var helperLineWidth = 18; var helperLineDotSpacing = 48; // px between dots // Calculate max possible length from player to farthest screen edge function getHelperLineLength(angle) { // Player is always at center var cx = 2048 / 2; var cy = 2732 / 2; // Find intersection with screen edge in direction of angle var dx = Math.cos(angle); var dy = Math.sin(angle); // Calculate t for each edge, pick the smallest positive t var tVals = []; if (dx !== 0) { var t1 = (0 - cx) / dx; var t2 = (2048 - cx) / dx; tVals.push(t1, t2); } if (dy !== 0) { var t3 = (0 - cy) / dy; var t4 = (2732 - cy) / dy; tVals.push(t3, t4); } // Only consider positive t (forward direction) var maxLen = 0; for (var i = 0; i < tVals.length; i++) { if (tVals[i] > 0) { var px = cx + dx * tVals[i]; var py = cy + dy * tVals[i]; // Check if point is on screen if (px >= 0 && px <= 2048 && py >= 0 && py <= 2732) { var dist = Math.sqrt((px - cx) * (px - cx) + (py - cy) * (py - cy)); if (dist > maxLen) maxLen = dist; } } } // Clamp to a minimum length if (maxLen < 200) maxLen = 200; return maxLen; } // Pre-create enough dots for the longest possible line (diagonal) var maxPossibleLength = Math.sqrt(2048 * 2048 + 2732 * 2732); var helperLineDotCount = Math.ceil(maxPossibleLength / helperLineDotSpacing); for (var i = 1; i <= helperLineDotCount; i++) { var dot = new Container(); var asset = dot.attachAsset('helperDot', { anchorX: 0.5, anchorY: 0.5, scaleX: helperLineWidth / 32, scaleY: helperLineWidth / 32, alpha: helperLineAlpha }); dot.visible = false; game.addChild(dot); helperLineDots.push(dot); } // --- Player Initialization --- player = new Player(); player.x = 2048 / 2; player.y = 2732 / 2; // Always keep player sprite visually fixed (no rotation) player.rotation = 0; game.addChild(player); // Add player health bar to game (not as child of player, so it doesn't rotate) game.addChild(player.hpBarBg); game.addChild(player.hpBar); // Position health bar above player player.hpBarBg.x = player.x; player.hpBarBg.y = player.y - 170; player.hpBar.x = player.x; player.hpBar.y = player.y - 170; // --- Shield Layers Initialization --- // Add three shield layers: shield1 (innermost), shield2 (2x), shield3 (3x) var shieldLayers = []; var shieldRadii = [110, 220, 330]; // shield1, shield2, shield3 (further out) var shieldScales = [4.2, 8.4, 12.6]; // shield2 is 2x, shield3 is 3x larger than shield1 var shieldAssetNames = ['shield1', 'shield2', 'shield3']; for (var i = 0; i < 3; i++) { var shield = new ShieldLayer(); shield.index = i; shield.radius = shieldRadii[i]; // Place shield at player center shield.x = 2048 / 2; shield.y = 2732 / 2; // Set shield visual size shield.shieldAsset.destroy(); // Remove default asset shield.shieldAsset = shield.attachAsset(shieldAssetNames[i], { anchorX: 0.5, anchorY: 0.5, scaleX: shieldScales[i], scaleY: shieldScales[i], alpha: 0.25 + 0.15 * i }); // Position health bar above shield if (i === 1) { // shield2: place its health bar above shield2 visual, and above shield1/player health bar // Calculate shield2 visual height (scaled) var shield2VisualHeight = LK.getAsset('shield2', { anchorX: 0.5, anchorY: 0.5 }).height * shieldScales[1]; // Place health bar just above shield2 visual shield.hpBarBg.y = -shield2VisualHeight / 2 - 60; shield.hpBar.y = -shield2VisualHeight / 2 - 60; } else if (i === 2) { // shield3: place its health bar above shield3 visual, and above shield2 var shield3VisualHeight = LK.getAsset('shield3', { anchorX: 0.5, anchorY: 0.5 }).height * shieldScales[2]; shield.hpBarBg.y = -shield3VisualHeight / 2 - 60; shield.hpBar.y = -shield3VisualHeight / 2 - 60; } else { // shield1: default position shield.hpBarBg.y = -shield.radius - 120; shield.hpBar.y = -shield.radius - 120; } // Add to game and to shieldLayers array game.addChild(shield); shieldLayers.push(shield); } // --- Helper: Get angle from center to (x, y) --- function getAngleToCenter(x, y) { var cx = 2048 / 2; var cy = 2732 / 2; return Math.atan2(y - cy, x - cx); } // --- Helper: Fire Bullet --- function fireBullet(angle) { if (fireCooldown > 0) return; var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; bullet.direction = angle; // Defensive: initialize lastX/lastY bullet.lastX = bullet.x; bullet.lastY = bullet.y; bullets.push(bullet); game.addChild(bullet); fireCooldown = 4; // frames (was 10, now fires much more frequently) // Play shoot sound LK.getSound('shoot').play(); } // --- Handle Touch/Drag to Set Fire Angle (Player sprite does NOT rotate) --- game.down = function (x, y, obj) { dragActive = true; // Store angle from player center to touch point var angle = Math.atan2(y - player.y, x - player.x); lastTouchAngle = angle; }; game.move = function (x, y, obj) { if (dragActive) { // Update angle from player center to current touch point var angle = Math.atan2(y - player.y, x - player.x); lastTouchAngle = angle; } }; game.up = function (x, y, obj) { dragActive = false; // Fire bullet in the direction of last drag/touch (from center to release point) fireBullet(lastTouchAngle); }; // --- Enemy Spawning --- function spawnEnemy() { var angle = Math.random() * Math.PI * 2; var distance = 1100 + Math.random() * 400; var ex = 2048 / 2 + Math.cos(angle) * distance; var ey = 2732 / 2 + Math.sin(angle) * distance; // 1 in 5 chance to spawn a health box instead of enemy (increased frequency) if (Math.random() < 0.2) { var healthBox = new HealthBox(); healthBox.x = ex; healthBox.y = ey; healthBox.direction = Math.atan2(2732 / 2 - ey, 2048 / 2 - ex); healthBox.lastX = healthBox.x; healthBox.lastY = healthBox.y; enemies.push(healthBox); game.addChild(healthBox); } else { var enemy = new Enemy(); enemy.x = ex; enemy.y = ey; // Set direction towards player center enemy.direction = Math.atan2(2732 / 2 - ey, 2048 / 2 - ex); // Scale enemy speed with score: base speed + (score * 0.08), capped at 7x base var score = LK.getScore(); var speedScale = 1 + Math.min(score * 0.08, 6); // up to 7x base speed enemy.speed *= speedScale; // Defensive: initialize lastX/lastY enemy.lastX = enemy.x; enemy.lastY = enemy.y; enemies.push(enemy); game.addChild(enemy); } } // --- Game Update Loop --- game.update = function () { // --- Fire cooldown --- if (fireCooldown > 0) fireCooldown--; // --- Update Helper Line (dotted, in fire direction, aligned with bullet) --- var cx = player.x; var cy = player.y; var lineLen = getHelperLineLength(lastTouchAngle); var dotCount = Math.floor(lineLen / helperLineDotSpacing); for (var i = 0; i < helperLineDots.length; i++) { var dot = helperLineDots[i]; if (i < dotCount) { var dist = (i + 1) * helperLineDotSpacing; dot.x = cx + Math.cos(lastTouchAngle) * dist; dot.y = cy + Math.sin(lastTouchAngle) * dist; dot.rotation = lastTouchAngle; dot.visible = true; } else { dot.visible = false; } } // Keep player health bar above player (not rotating) player.hpBarBg.x = player.x; player.hpBarBg.y = player.y - 170; player.hpBar.x = player.x; player.hpBar.y = player.y - 170; player.updateHpBar(); // --- Enemy spawn timer --- spawnTimer--; if (spawnTimer <= 0) { spawnEnemy(); // Make enemies and health boxes spawn less frequently (every 2.5-3.5s) spawnTimer = 150 + Math.floor(Math.random() * 60); // spawn every 2.5-3.5s } // --- Update Bullets --- for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Defensive: initialize lastX/lastY if not set if (typeof bullet.lastX === "undefined") bullet.lastX = bullet.x; if (typeof bullet.lastY === "undefined") bullet.lastY = bullet.y; bullet.update(); // Remove if out of bounds or destroyed if (bullet.x < -200 || bullet.x > 2048 + 200 || bullet.y < -200 || bullet.y > 2732 + 200 || bullet.destroyed) { if (!bullet.destroyed) bullet.destroy(); bullets.splice(i, 1); continue; } // Check collision with enemies var hit = false; for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; // Defensive: initialize lastX/lastY if not set if (typeof enemy.lastX === "undefined") enemy.lastX = enemy.x; if (typeof enemy.lastY === "undefined") enemy.lastY = enemy.y; if (enemy.destroyed) continue; if (bullet.intersects(enemy)) { // HealthBox dies in 1 hit, Enemy dies at 0 hp var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1; if (isHealthBox) { // HealthBox: always die in one hit, regardless of hp enemy.hp = 0; } else { enemy.takeDamage(bullet.damage); } if (enemy.hp <= 0) { var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1; if (isHealthBox) { // HealthBox: fill player health to max player.hp = player.maxHp; player.updateHpBar(); // Find the outermost active shield (highest index, active) var outermostActive = -1; for (var si = shieldLayers.length - 1; si >= 0; si--) { if (shieldLayers[si].active) { outermostActive = si; break; } } // If all shields are inactive, add the next shield (lowest inactive) if (outermostActive === -1) { for (var si = 0; si < shieldLayers.length; si++) { if (!shieldLayers[si].active) { shieldLayers[si].reset(); LK.effects.flashObject(shieldLayers[si], 0x44ff44, 400); LK.getSound('shieldHit').play(); break; } } } else { // If outermost shield is not full, restore its HP var shield = shieldLayers[outermostActive]; if (shield.hp < shield.maxHp) { shield.hp = shield.maxHp; shield.updateHpBar(); LK.effects.flashObject(shield, 0x44ff44, 400); LK.getSound('shieldHit').play(); } else { // If outermost shield is full, try to add the next shield (if exists and inactive) if (outermostActive + 1 < shieldLayers.length && !shieldLayers[outermostActive + 1].active) { shieldLayers[outermostActive + 1].reset(); LK.effects.flashObject(shieldLayers[outermostActive + 1], 0x44ff44, 400); LK.getSound('shieldHit').play(); } else { // All shields are active and full, just play effect on outermost LK.effects.flashObject(shield, 0x44ff44, 400); LK.getSound('shieldHit').play(); } } } } else { LK.getSound('enemyDie').play(); } enemy.die(); if (!enemy.destroyed) enemy.destroy(); enemies.splice(j, 1); if (!(typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1)) { LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore()); } } if (!bullet.destroyed) bullet.destroy(); bullets.splice(i, 1); hit = true; break; } } if (hit) continue; } // Remove destroyed bullets (defensive, in case any remain) for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].destroyed) bullets.splice(i, 1); } // --- Update Enemies --- for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; // Defensive: initialize lastX/lastY if not set if (typeof enemy.lastX === "undefined") enemy.lastX = enemy.x; if (typeof enemy.lastY === "undefined") enemy.lastY = enemy.y; if (enemy.destroyed) { enemies.splice(i, 1); continue; } enemy.update(); // Check collision with shields (outermost to innermost) var blocked = false; for (var s = shieldLayers.length - 1; s >= 0; s--) { var shield = shieldLayers[s]; if (shield.active) { // Defensive: initialize lastX/lastY if not set if (typeof shield.lastX === "undefined") shield.lastX = shield.x; if (typeof shield.lastY === "undefined") shield.lastY = shield.y; // Check distance from player center to enemy center var dx = enemy.x - player.x; var dy = enemy.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); // Use actual shield visual size for collision (shieldAsset is ellipse, scaleX/scaleY applied) // Defensive: recalculate radius using current scale for each shield asset var shieldAssetNames = ['shield1', 'shield2', 'shield3']; var assetName = shieldAssetNames[shield.index] || 'shield1'; var baseRadius = LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }).width / 2; var shieldVisualRadius = baseRadius * shield.shieldAsset.scaleX; var enemyVisualRadius = (enemy.width > enemy.height ? enemy.width : enemy.height) / 2; if (dist < shieldVisualRadius + enemyVisualRadius) { // Collided with shield var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1; if (isHealthBox) { // HealthBox: just disappears, does not affect shield, no revive or restore enemy.die(); if (!enemy.destroyed) enemy.destroy(); enemies.splice(i, 1); blocked = true; break; } else { // Regular enemy: damage shield shield.takeDamage(1); LK.getSound('shieldHit').play(); enemy.die(); if (!enemy.destroyed) enemy.destroy(); enemies.splice(i, 1); // Increase score when enemy dies by hitting shield LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore()); // Flash shield LK.effects.flashObject(shield, 0x44aaff, 200); blocked = true; break; } } } } if (blocked) continue; // If not blocked, check collision with player if (enemy.intersects(player)) { var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1; if (isHealthBox) { // HealthBox: just disappears, does not affect player health enemy.die(); if (!enemy.destroyed) enemy.destroy(); enemies.splice(i, 1); // Do not change player health or flash screen } else { player.takeDamage(1); if (player.hp < 0) player.hp = 0; if (player.hp > player.maxHp) player.hp = player.maxHp; LK.getSound('enemyDie').play(); enemy.die(); if (!enemy.destroyed) enemy.destroy(); enemies.splice(i, 1); // Flash screen LK.effects.flashScreen(0xff0000, 400); // Game over if player dead if (player.hp <= 0) { player.die(); LK.showGameOver(); return; } } } } // Remove destroyed enemies (defensive, in case any remain) for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i].destroyed) enemies.splice(i, 1); } ; // --- Update and cleanup enemy death particles --- if (game._enemyParticles) { for (var i = game._enemyParticles.length - 1; i >= 0; i--) { var p = game._enemyParticles[i]; // Defensive: initialize lastX/lastY if not set if (typeof p.lastX === "undefined") p.lastX = p.x; if (typeof p.lastY === "undefined") p.lastY = p.y; if (p.destroyed) { game._enemyParticles.splice(i, 1); continue; } if (typeof p.update === "function") p.update(); } // Defensive: remove any remaining destroyed particles for (var i = game._enemyParticles.length - 1; i >= 0; i--) { if (game._enemyParticles[i].destroyed) game._enemyParticles.splice(i, 1); } } }; // --- Score Initialization --- LK.setScore(0); scoreTxt.setText(LK.getScore());
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- Bullet Class ---
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (white ellipse)
var bulletAsset = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Bullet speed (pixels per frame)
self.speed = 160;
// Direction in radians (set on creation)
self.direction = 0;
// Damage dealt by this bullet
self.damage = 1;
// Defensive: track last position for possible future use
self.lastX = self.x;
self.lastY = self.y;
// Update method called every tick
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
};
return self;
});
// --- Enemy Class ---
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (pink ellipse), scaled up 2x
var enemyAsset = self.attachAsset('enemyCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// Enemy properties
self.radius = 60 + Math.random() * 40; // for spawn distance
self.speed = 1.15 + Math.random() * 0.55; // even faster approach
self.maxHp = 3; // always 3 HP
self.hp = self.maxHp;
// Direction towards center (set on spawn)
self.direction = 0;
// Health bar (white background, green foreground), both rectangles, above the enemy
self.hpBarBg = self.addChild(LK.getAsset('hpbar_bg', {
anchorX: 0.5,
anchorY: 1.0,
y: -140 // higher above the enemy
}));
self.hpBar = self.addChild(LK.getAsset('hpbar_fg', {
anchorX: 0.5,
anchorY: 1.0,
y: -140 // match background bar position
}));
// Defensive: track last position for possible future use
self.lastX = self.x;
self.lastY = self.y;
// Update health bar width
self.updateHpBar = function () {
// Use asset width for scaling, so health bar always matches background
self.hpBar.width = self.hpBarBg.width * (self.hp / self.maxHp);
self.hpBar.x = self.hpBarBg.x - (self.hpBarBg.width - self.hpBar.width) / 2;
};
// Update method called every tick
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
};
// Take damage
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp < 0) self.hp = 0;
self.updateHpBar();
};
// On death
self.die = function () {
// Flash effect
LK.effects.flashObject(self, 0xffffff, 200);
// Particle explosion (intense red, gravity-free)
// More and bigger particles, even more red!
for (var i = 0; i < 38; i++) {
var p = new Container();
var asset = p.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
// Make particles much bigger overall
scaleX: 1.1 + Math.random() * 0.8,
scaleY: 1.1 + Math.random() * 0.8,
// Use a deeper, more saturated red
color: 0xff0000,
alpha: 0.82 + Math.random() * 0.18
});
p.x = self.x;
p.y = self.y;
var angle = Math.random() * Math.PI * 2;
var speed = 22 + Math.random() * 20;
var life = 26 + Math.floor(Math.random() * 18);
p.update = function (a, s, l) {
var vx = Math.cos(a) * s;
var vy = Math.sin(a) * s;
var ticks = 0;
return function () {
this.x += vx;
this.y += vy;
vx *= 0.88;
vy *= 0.88;
this.alpha *= 0.93;
ticks++;
if (ticks > l) {
this.destroy();
}
};
}(angle, speed, life);
game.addChild(p);
// Add to update loop
if (!game._enemyParticles) game._enemyParticles = [];
game._enemyParticles.push(p);
}
};
// Initialize health bar
self.updateHpBar();
return self;
});
// --- HealthBox Class ---
var HealthBox = Container.expand(function () {
var self = Container.call(this);
// Attach health asset (red box), scaled up 1.5x
var asset = self.attachAsset('healthBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.radius = 60 + Math.random() * 40;
self.speed = 0.32 + Math.random() * 0.16; // even faster movement
self.maxHp = 1;
self.hp = self.maxHp;
self.direction = 0;
// Defensive: track last position
self.lastX = self.x;
self.lastY = self.y;
// Update method
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
};
// Take damage
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp < 0) self.hp = 0;
};
// On death
self.die = function () {
LK.effects.flashObject(self, 0xffffff, 200);
// Particle effect (white explosion)
for (var i = 0; i < 18; i++) {
var p = new Container();
var asset = p.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9 + Math.random() * 0.7,
scaleY: 0.9 + Math.random() * 0.7,
color: 0xffffff,
alpha: 0.8 + Math.random() * 0.15
});
p.x = self.x;
p.y = self.y;
var angle = Math.random() * Math.PI * 2;
var speed = 12 + Math.random() * 10;
var life = 16 + Math.floor(Math.random() * 10);
p.update = function (a, s, l) {
var vx = Math.cos(a) * s;
var vy = Math.sin(a) * s;
var ticks = 0;
return function () {
this.x += vx;
this.y += vy;
vx *= 0.88;
vy *= 0.88;
this.alpha *= 0.91;
ticks++;
if (ticks > l) {
this.destroy();
}
};
}(angle, speed, life);
game.addChild(p);
if (!game._enemyParticles) game._enemyParticles = [];
game._enemyParticles.push(p);
}
};
return self;
});
// --- Player Class ---
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player asset (blue ellipse), scaled up 2x
var playerAsset = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// Player properties
self.radius = 0; // always at center
self.rotation = 0; // radians
self.maxHp = 5;
self.hp = self.maxHp;
// Health bar (red background, green foreground) - always on top, not rotating
self.hpBarBg = LK.getAsset('hpbar_bg', {
anchorX: 0.5,
anchorY: 0.5,
y: -140
});
self.hpBar = LK.getAsset('hpbar_fg', {
anchorX: 0.5,
anchorY: 0.5,
y: -140
});
// Defensive: track last position for possible future use
self.lastX = self.x;
self.lastY = self.y;
// Update health bar width
self.updateHpBar = function () {
// Clamp hp to [0, maxHp]
if (self.hp < 0) self.hp = 0;
if (self.hp > self.maxHp) self.hp = self.maxHp;
// Use asset width for scaling, so health bar always matches background
// Health bar shrinks from left to right (left edge fixed)
var targetWidth = self.hpBarBg.width * (self.hp / self.maxHp);
if (targetWidth < 0) targetWidth = 0;
// Smoothly animate width decrease (if needed)
if (typeof self.hpBar.width === "undefined") self.hpBar.width = self.hpBarBg.width;
if (self.hpBar.width > targetWidth) {
self.hpBar.width -= Math.max(6, (self.hpBar.width - targetWidth) * 0.18);
if (self.hpBar.width < targetWidth) self.hpBar.width = targetWidth;
} else {
self.hpBar.width = targetWidth;
}
// Left edge fixed: set x so left edge stays at bg left
self.hpBar.x = self.hpBarBg.x - self.hpBarBg.width / 2 + self.hpBar.width / 2;
// Add a white border to the health bar background for better visibility
if (!self.hpBarBg._borderAdded) {
var border = LK.getAsset('hpbar_bg', {
anchorX: 0.5,
anchorY: 0.5,
y: self.hpBarBg.y,
width: self.hpBarBg.width + 8,
height: self.hpBarBg.height + 8,
color: 0xffffff,
alpha: 0.25
});
border.x = self.hpBarBg.x;
border.y = self.hpBarBg.y;
if (self.hpBarBg.parent) self.hpBarBg.parent.addChild(border);
self.hpBarBg._borderAdded = true;
}
};
// Take damage
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp < 0) self.hp = 0;
if (self.hp > self.maxHp) self.hp = self.maxHp;
self.updateHpBar();
};
// On death
self.die = function () {
LK.effects.flashObject(self, 0xff0000, 500);
};
// Initialize health bar
self.updateHpBar();
return self;
});
// --- Shield Layer Class ---
var ShieldLayer = Container.expand(function () {
var self = Container.call(this);
// Properties
self.radius = 0; // distance from player center
self.maxHp = 3;
self.hp = self.maxHp;
self.index = 0; // 0=innermost, 1=outer
self.active = true;
// Visual: support shield1, shield2, and shield3 assets
var shieldAssetNames = ['shield1', 'shield2', 'shield3'];
var assetName = shieldAssetNames[self.index] || 'shield1';
self.shieldAsset = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.2,
scaleY: 4.2,
alpha: 0.25
});
// Health bar above shield
self.hpBarBg = self.addChild(LK.getAsset('hpbar_bg', {
anchorX: 0.5,
anchorY: 0.5,
y: -self.radius - 120
}));
self.hpBar = self.addChild(LK.getAsset('hpbar_fg', {
anchorX: 0.5,
anchorY: 0.5,
y: -self.radius - 120
}));
// Defensive: track last position for possible future use
self.lastX = self.x;
self.lastY = self.y;
// Update health bar width
self.updateHpBar = function () {
// Use asset width for scaling, so health bar always matches background
self.hpBar.width = self.hpBarBg.width * (self.hp / self.maxHp);
self.hpBar.x = self.hpBarBg.x - (self.hpBarBg.width - self.hpBar.width) / 2;
self.hpBar.visible = self.active;
self.hpBarBg.visible = self.active;
};
// Take damage
self.takeDamage = function (amount) {
if (!self.active) return;
self.hp -= amount;
if (self.hp < 0) self.hp = 0;
self.updateHpBar();
if (self.hp <= 0) {
// Play shield explosion sound
LK.getSound('shieldExplode').play();
self.active = false;
self.visible = false;
self.hpBar.visible = false;
self.hpBarBg.visible = false;
}
};
// Reset shield
self.reset = function () {
self.hp = self.maxHp;
self.active = true;
self.visible = true;
self.updateHpBar();
};
// Initialize
self.updateHpBar();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Health bar assets for bg/fg
// --- Background Image ---
var backgroundImg = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2048 / 4096,
scaleY: 2732 / 4096
});
game.addChild(backgroundImg);
// --- Global Variables ---
var player;
var enemies = [];
var bullets = [];
var spawnTimer = 0;
var fireCooldown = 0;
var dragActive = false;
var lastTouchAngle = 0;
// --- Score Display ---
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper Line for Fire Direction (dotted, in fire direction, aligned with bullet) ---
var helperLineDots = [];
var helperLineAlpha = 0.7;
var helperLineWidth = 18;
var helperLineDotSpacing = 48; // px between dots
// Calculate max possible length from player to farthest screen edge
function getHelperLineLength(angle) {
// Player is always at center
var cx = 2048 / 2;
var cy = 2732 / 2;
// Find intersection with screen edge in direction of angle
var dx = Math.cos(angle);
var dy = Math.sin(angle);
// Calculate t for each edge, pick the smallest positive t
var tVals = [];
if (dx !== 0) {
var t1 = (0 - cx) / dx;
var t2 = (2048 - cx) / dx;
tVals.push(t1, t2);
}
if (dy !== 0) {
var t3 = (0 - cy) / dy;
var t4 = (2732 - cy) / dy;
tVals.push(t3, t4);
}
// Only consider positive t (forward direction)
var maxLen = 0;
for (var i = 0; i < tVals.length; i++) {
if (tVals[i] > 0) {
var px = cx + dx * tVals[i];
var py = cy + dy * tVals[i];
// Check if point is on screen
if (px >= 0 && px <= 2048 && py >= 0 && py <= 2732) {
var dist = Math.sqrt((px - cx) * (px - cx) + (py - cy) * (py - cy));
if (dist > maxLen) maxLen = dist;
}
}
}
// Clamp to a minimum length
if (maxLen < 200) maxLen = 200;
return maxLen;
}
// Pre-create enough dots for the longest possible line (diagonal)
var maxPossibleLength = Math.sqrt(2048 * 2048 + 2732 * 2732);
var helperLineDotCount = Math.ceil(maxPossibleLength / helperLineDotSpacing);
for (var i = 1; i <= helperLineDotCount; i++) {
var dot = new Container();
var asset = dot.attachAsset('helperDot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: helperLineWidth / 32,
scaleY: helperLineWidth / 32,
alpha: helperLineAlpha
});
dot.visible = false;
game.addChild(dot);
helperLineDots.push(dot);
}
// --- Player Initialization ---
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
// Always keep player sprite visually fixed (no rotation)
player.rotation = 0;
game.addChild(player);
// Add player health bar to game (not as child of player, so it doesn't rotate)
game.addChild(player.hpBarBg);
game.addChild(player.hpBar);
// Position health bar above player
player.hpBarBg.x = player.x;
player.hpBarBg.y = player.y - 170;
player.hpBar.x = player.x;
player.hpBar.y = player.y - 170;
// --- Shield Layers Initialization ---
// Add three shield layers: shield1 (innermost), shield2 (2x), shield3 (3x)
var shieldLayers = [];
var shieldRadii = [110, 220, 330]; // shield1, shield2, shield3 (further out)
var shieldScales = [4.2, 8.4, 12.6]; // shield2 is 2x, shield3 is 3x larger than shield1
var shieldAssetNames = ['shield1', 'shield2', 'shield3'];
for (var i = 0; i < 3; i++) {
var shield = new ShieldLayer();
shield.index = i;
shield.radius = shieldRadii[i];
// Place shield at player center
shield.x = 2048 / 2;
shield.y = 2732 / 2;
// Set shield visual size
shield.shieldAsset.destroy(); // Remove default asset
shield.shieldAsset = shield.attachAsset(shieldAssetNames[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: shieldScales[i],
scaleY: shieldScales[i],
alpha: 0.25 + 0.15 * i
});
// Position health bar above shield
if (i === 1) {
// shield2: place its health bar above shield2 visual, and above shield1/player health bar
// Calculate shield2 visual height (scaled)
var shield2VisualHeight = LK.getAsset('shield2', {
anchorX: 0.5,
anchorY: 0.5
}).height * shieldScales[1];
// Place health bar just above shield2 visual
shield.hpBarBg.y = -shield2VisualHeight / 2 - 60;
shield.hpBar.y = -shield2VisualHeight / 2 - 60;
} else if (i === 2) {
// shield3: place its health bar above shield3 visual, and above shield2
var shield3VisualHeight = LK.getAsset('shield3', {
anchorX: 0.5,
anchorY: 0.5
}).height * shieldScales[2];
shield.hpBarBg.y = -shield3VisualHeight / 2 - 60;
shield.hpBar.y = -shield3VisualHeight / 2 - 60;
} else {
// shield1: default position
shield.hpBarBg.y = -shield.radius - 120;
shield.hpBar.y = -shield.radius - 120;
}
// Add to game and to shieldLayers array
game.addChild(shield);
shieldLayers.push(shield);
}
// --- Helper: Get angle from center to (x, y) ---
function getAngleToCenter(x, y) {
var cx = 2048 / 2;
var cy = 2732 / 2;
return Math.atan2(y - cy, x - cx);
}
// --- Helper: Fire Bullet ---
function fireBullet(angle) {
if (fireCooldown > 0) return;
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
bullet.direction = angle;
// Defensive: initialize lastX/lastY
bullet.lastX = bullet.x;
bullet.lastY = bullet.y;
bullets.push(bullet);
game.addChild(bullet);
fireCooldown = 4; // frames (was 10, now fires much more frequently)
// Play shoot sound
LK.getSound('shoot').play();
}
// --- Handle Touch/Drag to Set Fire Angle (Player sprite does NOT rotate) ---
game.down = function (x, y, obj) {
dragActive = true;
// Store angle from player center to touch point
var angle = Math.atan2(y - player.y, x - player.x);
lastTouchAngle = angle;
};
game.move = function (x, y, obj) {
if (dragActive) {
// Update angle from player center to current touch point
var angle = Math.atan2(y - player.y, x - player.x);
lastTouchAngle = angle;
}
};
game.up = function (x, y, obj) {
dragActive = false;
// Fire bullet in the direction of last drag/touch (from center to release point)
fireBullet(lastTouchAngle);
};
// --- Enemy Spawning ---
function spawnEnemy() {
var angle = Math.random() * Math.PI * 2;
var distance = 1100 + Math.random() * 400;
var ex = 2048 / 2 + Math.cos(angle) * distance;
var ey = 2732 / 2 + Math.sin(angle) * distance;
// 1 in 5 chance to spawn a health box instead of enemy (increased frequency)
if (Math.random() < 0.2) {
var healthBox = new HealthBox();
healthBox.x = ex;
healthBox.y = ey;
healthBox.direction = Math.atan2(2732 / 2 - ey, 2048 / 2 - ex);
healthBox.lastX = healthBox.x;
healthBox.lastY = healthBox.y;
enemies.push(healthBox);
game.addChild(healthBox);
} else {
var enemy = new Enemy();
enemy.x = ex;
enemy.y = ey;
// Set direction towards player center
enemy.direction = Math.atan2(2732 / 2 - ey, 2048 / 2 - ex);
// Scale enemy speed with score: base speed + (score * 0.08), capped at 7x base
var score = LK.getScore();
var speedScale = 1 + Math.min(score * 0.08, 6); // up to 7x base speed
enemy.speed *= speedScale;
// Defensive: initialize lastX/lastY
enemy.lastX = enemy.x;
enemy.lastY = enemy.y;
enemies.push(enemy);
game.addChild(enemy);
}
}
// --- Game Update Loop ---
game.update = function () {
// --- Fire cooldown ---
if (fireCooldown > 0) fireCooldown--;
// --- Update Helper Line (dotted, in fire direction, aligned with bullet) ---
var cx = player.x;
var cy = player.y;
var lineLen = getHelperLineLength(lastTouchAngle);
var dotCount = Math.floor(lineLen / helperLineDotSpacing);
for (var i = 0; i < helperLineDots.length; i++) {
var dot = helperLineDots[i];
if (i < dotCount) {
var dist = (i + 1) * helperLineDotSpacing;
dot.x = cx + Math.cos(lastTouchAngle) * dist;
dot.y = cy + Math.sin(lastTouchAngle) * dist;
dot.rotation = lastTouchAngle;
dot.visible = true;
} else {
dot.visible = false;
}
}
// Keep player health bar above player (not rotating)
player.hpBarBg.x = player.x;
player.hpBarBg.y = player.y - 170;
player.hpBar.x = player.x;
player.hpBar.y = player.y - 170;
player.updateHpBar();
// --- Enemy spawn timer ---
spawnTimer--;
if (spawnTimer <= 0) {
spawnEnemy();
// Make enemies and health boxes spawn less frequently (every 2.5-3.5s)
spawnTimer = 150 + Math.floor(Math.random() * 60); // spawn every 2.5-3.5s
}
// --- Update Bullets ---
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Defensive: initialize lastX/lastY if not set
if (typeof bullet.lastX === "undefined") bullet.lastX = bullet.x;
if (typeof bullet.lastY === "undefined") bullet.lastY = bullet.y;
bullet.update();
// Remove if out of bounds or destroyed
if (bullet.x < -200 || bullet.x > 2048 + 200 || bullet.y < -200 || bullet.y > 2732 + 200 || bullet.destroyed) {
if (!bullet.destroyed) bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check collision with enemies
var hit = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
// Defensive: initialize lastX/lastY if not set
if (typeof enemy.lastX === "undefined") enemy.lastX = enemy.x;
if (typeof enemy.lastY === "undefined") enemy.lastY = enemy.y;
if (enemy.destroyed) continue;
if (bullet.intersects(enemy)) {
// HealthBox dies in 1 hit, Enemy dies at 0 hp
var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1;
if (isHealthBox) {
// HealthBox: always die in one hit, regardless of hp
enemy.hp = 0;
} else {
enemy.takeDamage(bullet.damage);
}
if (enemy.hp <= 0) {
var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1;
if (isHealthBox) {
// HealthBox: fill player health to max
player.hp = player.maxHp;
player.updateHpBar();
// Find the outermost active shield (highest index, active)
var outermostActive = -1;
for (var si = shieldLayers.length - 1; si >= 0; si--) {
if (shieldLayers[si].active) {
outermostActive = si;
break;
}
}
// If all shields are inactive, add the next shield (lowest inactive)
if (outermostActive === -1) {
for (var si = 0; si < shieldLayers.length; si++) {
if (!shieldLayers[si].active) {
shieldLayers[si].reset();
LK.effects.flashObject(shieldLayers[si], 0x44ff44, 400);
LK.getSound('shieldHit').play();
break;
}
}
} else {
// If outermost shield is not full, restore its HP
var shield = shieldLayers[outermostActive];
if (shield.hp < shield.maxHp) {
shield.hp = shield.maxHp;
shield.updateHpBar();
LK.effects.flashObject(shield, 0x44ff44, 400);
LK.getSound('shieldHit').play();
} else {
// If outermost shield is full, try to add the next shield (if exists and inactive)
if (outermostActive + 1 < shieldLayers.length && !shieldLayers[outermostActive + 1].active) {
shieldLayers[outermostActive + 1].reset();
LK.effects.flashObject(shieldLayers[outermostActive + 1], 0x44ff44, 400);
LK.getSound('shieldHit').play();
} else {
// All shields are active and full, just play effect on outermost
LK.effects.flashObject(shield, 0x44ff44, 400);
LK.getSound('shieldHit').play();
}
}
}
} else {
LK.getSound('enemyDie').play();
}
enemy.die();
if (!enemy.destroyed) enemy.destroy();
enemies.splice(j, 1);
if (!(typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
}
}
if (!bullet.destroyed) bullet.destroy();
bullets.splice(i, 1);
hit = true;
break;
}
}
if (hit) continue;
}
// Remove destroyed bullets (defensive, in case any remain)
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].destroyed) bullets.splice(i, 1);
}
// --- Update Enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Defensive: initialize lastX/lastY if not set
if (typeof enemy.lastX === "undefined") enemy.lastX = enemy.x;
if (typeof enemy.lastY === "undefined") enemy.lastY = enemy.y;
if (enemy.destroyed) {
enemies.splice(i, 1);
continue;
}
enemy.update();
// Check collision with shields (outermost to innermost)
var blocked = false;
for (var s = shieldLayers.length - 1; s >= 0; s--) {
var shield = shieldLayers[s];
if (shield.active) {
// Defensive: initialize lastX/lastY if not set
if (typeof shield.lastX === "undefined") shield.lastX = shield.x;
if (typeof shield.lastY === "undefined") shield.lastY = shield.y;
// Check distance from player center to enemy center
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Use actual shield visual size for collision (shieldAsset is ellipse, scaleX/scaleY applied)
// Defensive: recalculate radius using current scale for each shield asset
var shieldAssetNames = ['shield1', 'shield2', 'shield3'];
var assetName = shieldAssetNames[shield.index] || 'shield1';
var baseRadius = LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
}).width / 2;
var shieldVisualRadius = baseRadius * shield.shieldAsset.scaleX;
var enemyVisualRadius = (enemy.width > enemy.height ? enemy.width : enemy.height) / 2;
if (dist < shieldVisualRadius + enemyVisualRadius) {
// Collided with shield
var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1;
if (isHealthBox) {
// HealthBox: just disappears, does not affect shield, no revive or restore
enemy.die();
if (!enemy.destroyed) enemy.destroy();
enemies.splice(i, 1);
blocked = true;
break;
} else {
// Regular enemy: damage shield
shield.takeDamage(1);
LK.getSound('shieldHit').play();
enemy.die();
if (!enemy.destroyed) enemy.destroy();
enemies.splice(i, 1);
// Increase score when enemy dies by hitting shield
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// Flash shield
LK.effects.flashObject(shield, 0x44aaff, 200);
blocked = true;
break;
}
}
}
}
if (blocked) continue;
// If not blocked, check collision with player
if (enemy.intersects(player)) {
var isHealthBox = typeof enemy.maxHp !== "undefined" && enemy.maxHp === 1;
if (isHealthBox) {
// HealthBox: just disappears, does not affect player health
enemy.die();
if (!enemy.destroyed) enemy.destroy();
enemies.splice(i, 1);
// Do not change player health or flash screen
} else {
player.takeDamage(1);
if (player.hp < 0) player.hp = 0;
if (player.hp > player.maxHp) player.hp = player.maxHp;
LK.getSound('enemyDie').play();
enemy.die();
if (!enemy.destroyed) enemy.destroy();
enemies.splice(i, 1);
// Flash screen
LK.effects.flashScreen(0xff0000, 400);
// Game over if player dead
if (player.hp <= 0) {
player.die();
LK.showGameOver();
return;
}
}
}
}
// Remove destroyed enemies (defensive, in case any remain)
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].destroyed) enemies.splice(i, 1);
}
;
// --- Update and cleanup enemy death particles ---
if (game._enemyParticles) {
for (var i = game._enemyParticles.length - 1; i >= 0; i--) {
var p = game._enemyParticles[i];
// Defensive: initialize lastX/lastY if not set
if (typeof p.lastX === "undefined") p.lastX = p.x;
if (typeof p.lastY === "undefined") p.lastY = p.y;
if (p.destroyed) {
game._enemyParticles.splice(i, 1);
continue;
}
if (typeof p.update === "function") p.update();
}
// Defensive: remove any remaining destroyed particles
for (var i = game._enemyParticles.length - 1; i >= 0; i--) {
if (game._enemyParticles[i].destroyed) game._enemyParticles.splice(i, 1);
}
}
};
// --- Score Initialization ---
LK.setScore(0);
scoreTxt.setText(LK.getScore());