User prompt
rename the powerup tags as power boost
User prompt
put double shot option also ingame popup screens
User prompt
trigger with parabolically increasing numbers by maximum 500 kills
User prompt
make the achievement 10 kill
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'y')' in or related to this line: 'achievement50Txt.y = magnetTimerTxt.y + magnetTimerTxt.height + 20;' Line Number: 858
User prompt
move the achievement text under the magnet text
User prompt
When you kill 50 enemies, the achievement will be awarded and a 2-second notification will be displayed on the screen.
User prompt
move the kills text to the bottom right corner of screen
User prompt
Add a counter text that counts the number of enemies killed
User prompt
add "Enemey killed" text to the screen
User prompt
set hero movement speed to 5
User prompt
show the enemy kill count UI on the screen
User prompt
I cant see the text on the screen
User prompt
add count text for tracking the enemies that killed by hero
User prompt
add a count text at the bottom right corner
User prompt
remo the green glow effect
User prompt
add challenge count to kill 50 enemies
User prompt
set hero movement speed to 10
User prompt
lower enemy density by %25
User prompt
add bullet count to the hero, after every 10 shots he should reload for 1 second
User prompt
make it 300ms
User prompt
increase the interval by%100
User prompt
increase the time between two shots by%100
User prompt
make that It will not fire two bullets at the same time, it will fire twice in succession with a very short interval.
User prompt
add new option to the pop up screen which grants "double shot" it allows hero to shoot 2 times when shooting
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // unique asset for attack speed powerup // AttackSpeedPowerup class (doubles attack speed for 5 seconds) var AttackSpeedPowerup = Container.expand(function () { var self = Container.call(this); // Use unique attack_speed_powerup asset var sprite = self.attachAsset('attack_speed_powerup', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 30; self.update = function () {}; return self; }); // Bullet class var Bullet = Container.expand(function () { var self = Container.call(this); var bulletSprite = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 18; self.speed = 9.5; self.dirX = 1; self.dirY = 0; self.pierce = 1; // Track enemies already hit by this bullet self._hitEnemies = []; self.update = function () { if (typeof game !== "undefined" && game._levelUpFrozen) { return; } // Homing logic: if enabled, adjust direction toward nearest enemy if (Bullet.prototype.homing && typeof enemies !== "undefined" && enemies.length > 0) { // Spread phase: if more than 1 bullet, spread out for a short time before homing if (typeof bullets !== "undefined" && bullets.length > 1) { if (typeof self._spreadTimer === "undefined") { // Each bullet gets a unique spread angle based on its index in bullets array var idx = 0; for (var bidx = 0; bidx < bullets.length; bidx++) { if (bullets[bidx] === self) { idx = bidx; break; } } var spreadTotal = bullets.length; var spreadAngle = Math.PI / 6; // 30 degrees total spread var baseAngle = Math.atan2(self.dirY, self.dirX); var angleOffset = -spreadAngle / 2 + (spreadTotal === 1 ? 0 : spreadAngle * idx / (spreadTotal - 1)); self._spreadTargetAngle = baseAngle + angleOffset; self._spreadTimer = 12; // frames to spread before homing } if (self._spreadTimer > 0) { // Lerp direction toward spread angle var currentAngle = Math.atan2(self.dirY, self.dirX); var targetAngle = self._spreadTargetAngle; // Shortest angle difference var da = targetAngle - currentAngle; while (da > Math.PI) { da -= Math.PI * 2; } while (da < -Math.PI) { da += Math.PI * 2; } var lerp = 0.25; var newAngle = currentAngle + da * lerp; self.dirX = Math.cos(newAngle); self.dirY = Math.sin(newAngle); self.rotation = newAngle; self._spreadTimer--; } else { // After spread, assign a unique enemy to follow if not already assigned if (typeof self._homingTarget === "undefined" || !self._homingTarget || enemies.indexOf(self._homingTarget) === -1) { // Assign a unique enemy to this bullet var assigned = {}; // Mark all enemies already assigned to other bullets for (var bidx = 0; bidx < bullets.length; bidx++) { var b2 = bullets[bidx]; if (b2 !== self && b2._homingTarget && enemies.indexOf(b2._homingTarget) !== -1) { assigned[b2._homingTarget._uniqueEnemyId] = true; } } // Assign unique IDs to enemies if not present for (var i = 0; i < enemies.length; i++) { if (typeof enemies[i]._uniqueEnemyId === "undefined") { enemies[i]._uniqueEnemyId = i + 1 + Math.floor(Math.random() * 1000000); } } // Find the nearest unassigned enemy var minDist = 99999; var nearest = null; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; if (assigned[e._uniqueEnemyId]) continue; // Exclude enemies already hit by this bullet if (self._hitEnemies.indexOf(e) !== -1) continue; var dx = e.x - self.x; var dy = e.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; nearest = e; } } // If all are assigned, just pick the nearest if (!nearest) { minDist = 99999; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; if (self._hitEnemies.indexOf(e) !== -1) continue; var dx = e.x - self.x; var dy = e.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; nearest = e; } } } self._homingTarget = nearest; } var target = self._homingTarget; if (target) { var dx = target.x - self.x; var dy = target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var homingStrength = 0.18; var targetDirX = dx / dist; var targetDirY = dy / dist; self.dirX = self.dirX * (1 - homingStrength) + targetDirX * homingStrength; self.dirY = self.dirY * (1 - homingStrength) + targetDirY * homingStrength; var norm = Math.sqrt(self.dirX * self.dirX + self.dirY * self.dirY); if (norm > 0) { self.dirX /= norm; self.dirY /= norm; } self.rotation = Math.atan2(self.dirY, self.dirX); } } } } else { // Only one bullet, home immediately var minDist = 99999; var nearest = null; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; // Exclude enemies already hit by this bullet if (self._hitEnemies.indexOf(e) !== -1) { continue; } var dx = e.x - self.x; var dy = e.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; nearest = e; } } if (nearest) { var dx = nearest.x - self.x; var dy = nearest.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var homingStrength = 0.18; var targetDirX = dx / dist; var targetDirY = dy / dist; self.dirX = self.dirX * (1 - homingStrength) + targetDirX * homingStrength; self.dirY = self.dirY * (1 - homingStrength) + targetDirY * homingStrength; var norm = Math.sqrt(self.dirX * self.dirX + self.dirY * self.dirY); if (norm > 0) { self.dirX /= norm; self.dirY /= norm; } self.rotation = Math.atan2(self.dirY, self.dirX); } } } } self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Ricochet logic: if enabled, on hit, bounce to nearest enemy up to N times if (typeof self.ricochetLeft === "undefined" && Bullet.prototype.ricochet) { self.ricochetLeft = Bullet.prototype.ricochet; } if (typeof self.ricochetLeft !== "undefined" && self.ricochetLeft > 0 && typeof enemies !== "undefined") { // Ricochet handled in collision, see main game.update } }; return self; }); // Enemy class var Enemy = Container.expand(function (opts) { var self = Container.call(this); opts = opts || {}; self.isBoss = !!opts.isBoss; // Preload both right and left sprites, only one visible at a time if (self.isBoss) { var bossSpriteRight = self.attachAsset('boss_enemy', { anchorX: 0.5, anchorY: 0.5 }); bossSpriteRight.assetId = 'boss_enemy'; var bossSpriteLeft = self.attachAsset('boss_enemy_left', { anchorX: 0.5, anchorY: 0.5 }); bossSpriteLeft.assetId = 'boss_enemy_left'; bossSpriteLeft.visible = false; self.radius = 120; self.speed = 0.7; self.hp = 30; self._bossSpriteRight = bossSpriteRight; self._bossSpriteLeft = bossSpriteLeft; } else { var enemySpriteRight = self.attachAsset('enemy_right', { anchorX: 0.5, anchorY: 0.5 }); enemySpriteRight.assetId = 'enemy_right'; var enemySpriteLeft = self.attachAsset('enemy_left', { anchorX: 0.5, anchorY: 0.5 }); enemySpriteLeft.assetId = 'enemy_left'; enemySpriteLeft.visible = false; self.radius = 50; self.speed = 1.5 + Math.random(); self._enemySpriteRight = enemySpriteRight; self._enemySpriteLeft = enemySpriteLeft; } self._lastFacingRight = true; // Track last facing direction self.update = function () { if (typeof game !== "undefined" && game._levelUpFrozen) { return; } var dx = hero.x - self.x; var dy = hero.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } // Face enemy left/right depending on hero position, by toggling visibility var shouldFaceRight = hero.x > self.x; if (self.isBoss) { if (shouldFaceRight !== self._lastFacingRight) { self._bossSpriteRight.visible = shouldFaceRight; self._bossSpriteLeft.visible = !shouldFaceRight; self._lastFacingRight = shouldFaceRight; } } else { if (shouldFaceRight !== self._lastFacingRight) { self._enemySpriteRight.visible = shouldFaceRight; self._enemySpriteLeft.visible = !shouldFaceRight; self._lastFacingRight = shouldFaceRight; } } }; return self; }); // Experience Gem class (handles both normal and big XP gems) var Gem = Container.expand(function (opts) { var self = Container.call(this); // Default to normal gem self.type = opts && opts.type || 'xp'; if (self.type === 'big') { var gemSprite = self.attachAsset('big_xp_gem', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 30; } else { var gemSprite = self.attachAsset('xp_gem', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 20; } self.update = function () {}; return self; }); // HeartPowerup class (can artıran kalp) var HeartPowerup = Container.expand(function () { var self = Container.call(this); self.attachAsset('heart_powerup', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 30; self.update = function () {}; return self; }); // Hero class var Hero = Container.expand(function () { var self = Container.call(this); // Preload both right and left hero sprites, only one visible at a time var heroSpriteRight = self.attachAsset('hero_right', { anchorX: 0.5, anchorY: 0.5 }); heroSpriteRight.assetId = 'hero_right'; var heroSpriteLeft = self.attachAsset('hero_left', { anchorX: 0.5, anchorY: 0.5 }); heroSpriteLeft.assetId = 'hero_left'; heroSpriteLeft.visible = false; self.radius = 60; self.speed = 10; self.targetX = 1024; self.targetY = 1366; self.magnetActive = false; self.magnetDuration = 0; self.magnetRange = 300; self.magnetRangeBoosted = 800; self._lastFacingRight = true; // Track last facing direction self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { var move = Math.min(self.speed, dist); var prevX = self.x; self.x += dx / dist * move; self.y += dy / dist * move; // Face hero left/right depending on movement direction, by toggling visibility var shouldFaceRight = self.x > prevX; if (shouldFaceRight !== self._lastFacingRight) { heroSpriteRight.visible = shouldFaceRight; heroSpriteLeft.visible = !shouldFaceRight; self._lastFacingRight = shouldFaceRight; } } if (self.magnetActive) { // Make hero glow blue while magnet is active if (!self._magnetGlowActive) { LK.effects.flashObject(self, 0x00ffff, 60000); // long duration, will be reset when deactivated self._magnetGlowActive = true; } // Magnet duration is now synchronized with game time (ticksSurvived) if (typeof self.magnetEndTick === "number") { if (typeof ticksSurvived === "number" && ticksSurvived >= self.magnetEndTick) { self.magnetActive = false; self.magnetDuration = 0; self.magnetEndTick = undefined; } else { // Update magnetDuration for UI display if (typeof ticksSurvived === "number") { self.magnetDuration = self.magnetEndTick - ticksSurvived; } } } } else { self._magnetGlowActive = false; } }; return self; }); // InvulnerabilityCircle class for hero invulnerability visual effect var InvulnerabilityCircle = Container.expand(function () { var self = Container.call(this); // Attach the invulnerability circle asset, centered var circle = self.attachAsset('invuln_circle', { anchorX: 0.5, anchorY: 0.5 }); // Set alpha to 0.25 (50% of previous 0.5, and lower than previous 0.35) circle.alpha = 0.25; // Make sure it's always above the hero sprite self.zIndex = 1; // Optionally animate alpha for a pulsing effect self._pulseDir = 1; self.update = function () { // Pulse alpha between 0.25 and 0.5 (lowered by 50%) if (circle.alpha === undefined) circle.alpha = 0.25; circle.alpha += 0.005 * self._pulseDir; if (circle.alpha > 0.5) { circle.alpha = 0.5; self._pulseDir = -1; } else if (circle.alpha < 0.25) { circle.alpha = 0.25; self._pulseDir = 1; } // Always follow hero position if (typeof hero !== "undefined") { self.x = hero.x; self.y = hero.y; } }; return self; }); // MagnetCircle class for hero magnet visual effect var MagnetCircle = Container.expand(function () { var self = Container.call(this); // Attach the magnet circle asset, centered var circle = self.attachAsset('magnet_circle', { anchorX: 0.5, anchorY: 0.5 }); // Set the size of the magnet effect asset to match the magnet effect area // Use hero.magnetActive to determine which range to use var magnetRange = typeof hero !== "undefined" && hero.magnetActive ? hero.magnetRangeBoosted : hero.magnetRange; circle.width = magnetRange * 2; circle.height = magnetRange * 2; // Make sure it's always below the hero sprite self.zIndex = -2; // Optionally animate alpha for a pulsing effect self._pulseDir = 1; self.update = function () { // Pulse alpha between 0.25 and 0.35 (reduced by 50%) if (circle.alpha === undefined) circle.alpha = 0.25; circle.alpha += 0.002 * self._pulseDir; if (circle.alpha > 0.35) { circle.alpha = 0.35; self._pulseDir = -1; } else if (circle.alpha < 0.25) { circle.alpha = 0.25; self._pulseDir = 1; } // Always follow hero position and update size to match magnet effect area if (typeof hero !== "undefined") { self.x = hero.x; self.y = hero.y; // Dynamically update the size to match the current magnet area var magnetRange = hero.magnetActive ? hero.magnetRangeBoosted : hero.magnetRange; circle.width = magnetRange * 2; circle.height = magnetRange * 2; } }; return self; }); // Powerup class (now only for magnet) var Powerup = Container.expand(function (opts) { var self = Container.call(this); self.isMagnet = opts && opts.isMagnet || false; if (self.isMagnet) { self.attachAsset('magnet_powerup', { anchorX: 0.5, anchorY: 0.5 }); } self.radius = 30; self.update = function () {}; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1e1b1d }); /**** * Game Code ****/ // <-- Kırmızı kalp powerup shape var hero; var heroLives = 5; var enemies = []; var bullets = []; var gems = []; var powerups = []; var spawnTimer = 0; var spawnInterval = 90; var wave = 1; var xp = 0; var xpToLevel = 10; var level = 1; var dragging = false; var lastGameOver = false; var scoreTxt, xpTxt, levelTxt; var centerX = 2048 / 2; var centerY = 2732 / 2; // --- Challenge: Kill 50 enemies --- var challengeKillCount = 0; var challengeKillTarget = 50; var challengeTxt = new Text2('Enemies: 0/50', { size: 54, fill: 0xFFD700, font: "Montserrat" }); challengeTxt.anchor.set(0.5, 0); challengeTxt.x = 2048 / 2; challengeTxt.y = 10; LK.gui.top.addChild(challengeTxt); // --- Bullet count and reload state --- var heroBulletCount = 10; var heroBulletMax = 10; var heroReloading = false; var heroReloadTimeout = null; // --- Reload UI --- var reloadTxt = new Text2('', { size: 54, fill: 0xFFD700, font: "Montserrat" }); reloadTxt.anchor.set(0.5, 0); reloadTxt.x = 2048 / 2; reloadTxt.y = 10 + 60 + 10; LK.gui.top.addChild(reloadTxt); // --- Option popup at game start --- var startOptionPopup; function showStartOptionPopup() { // Freeze all game logic and input game._levelUpFrozen = true; // Remove any previous popup if present if (typeof startOptionPopup !== "undefined" && startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } startOptionPopup = new Container(); // Dim background var bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x5e5e5d // green }); bg.width = 1100; bg.height = 1500; bg.alpha = 0.92; bg.x = centerX; bg.y = centerY; // Removed outline (LK.effects.outline not supported) startOptionPopup.addChild(bg); // Title var titleTxt = new Text2('Choose Your Power!', { size: 90, fill: "#fff", font: "Montserrat" // Set font to Montserrat }); titleTxt.anchor.set(0.5, 0); titleTxt.x = centerX; titleTxt.y = centerY - 610; startOptionPopup.addChild(titleTxt); // Option vertical layout var optionStartY = centerY - 320; var optionSpacing = 220; // Option 1: Attack Speed Up var opt1 = new Container(); var opt1Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt1Bg.width = 800; opt1Bg.height = 160; opt1Bg.alpha = 0.98; opt1Bg.x = centerX; opt1Bg.y = optionStartY; // Removed outline (LK.effects.outline not supported) opt1.addChild(opt1Bg); var opt1Txt = new Text2('Attack Speed', { size: 54, fill: 0xF7E967, font: "Montserrat" }); opt1Txt.anchor.set(0.5, 0.5); opt1Txt.x = centerX; opt1Txt.y = optionStartY - 25; opt1.addChild(opt1Txt); var opt1Desc = new Text2('Fire faster from the start!', { size: 40, fill: "#fff", font: "Montserrat" }); opt1Desc.anchor.set(0.5, 0.5); opt1Desc.x = centerX; opt1Desc.y = optionStartY + 35; opt1.addChild(opt1Desc); opt1.interactive = true; opt1.down = function (x, y, obj) { // Apply attack speed boost autoAttackInterval = Math.max(6, autoAttackInterval - 18); // Make hero invulnerable for 3 seconds and glow yellow hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); // Resume game and remove popup if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; startOptionPopup.addChild(opt1); // Option 2: Ricochet Bullet var opt2 = new Container(); var opt2Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt2Bg.width = 800; opt2Bg.height = 160; opt2Bg.alpha = 0.98; opt2Bg.x = centerX; opt2Bg.y = optionStartY + optionSpacing; // Removed outline (LK.effects.outline not supported) opt2.addChild(opt2Bg); var opt2Txt = new Text2('Ricochet', { size: 54, fill: 0x7BE495, font: "Montserrat" }); opt2Txt.anchor.set(0.5, 0.5); opt2Txt.x = centerX; opt2Txt.y = optionStartY + optionSpacing - 25; opt2.addChild(opt2Txt); var opt2Desc = new Text2('Bullets bounce to another enemy', { size: 40, fill: "#fff", font: "Montserrat" }); opt2Desc.anchor.set(0.5, 0.5); opt2Desc.x = centerX; opt2Desc.y = optionStartY + optionSpacing + 35; opt2.addChild(opt2Desc); opt2.interactive = true; opt2.down = function (x, y, obj) { Bullet.prototype.ricochet = 1; // Make hero invulnerable for 3 seconds and glow yellow hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; startOptionPopup.addChild(opt2); // Option 3: +1 Bullet (spread shot) var opt3 = new Container(); var opt3Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt3Bg.width = 800; opt3Bg.height = 160; opt3Bg.alpha = 0.98; opt3Bg.x = centerX; opt3Bg.y = optionStartY + optionSpacing * 2; // Removed outline (LK.effects.outline not supported) opt3.addChild(opt3Bg); var opt3Txt = new Text2('Bullet +1', { size: 54, fill: 0xFFB347, font: "Montserrat" }); opt3Txt.anchor.set(0.5, 0.5); opt3Txt.x = centerX; opt3Txt.y = optionStartY + optionSpacing * 2 - 25; opt3.addChild(opt3Txt); var opt3Desc = new Text2('Shoot more bullets at once', { size: 40, fill: "#fff", font: "Montserrat" }); opt3Desc.anchor.set(0.5, 0.5); opt3Desc.x = centerX; opt3Desc.y = optionStartY + optionSpacing * 2 + 35; opt3.addChild(opt3Desc); opt3.interactive = true; opt3.down = function (x, y, obj) { Bullet.prototype.extraBullets = 1; // Make hero invulnerable for 3 seconds and glow yellow hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; startOptionPopup.addChild(opt3); // Option 4: Pierce +1 var opt4 = new Container(); var opt4Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt4Bg.width = 800; opt4Bg.height = 160; opt4Bg.alpha = 0.98; opt4Bg.x = centerX; opt4Bg.y = optionStartY + optionSpacing * 3; // Removed outline (LK.effects.outline not supported) opt4.addChild(opt4Bg); var opt4Txt = new Text2('Pierce +1', { size: 54, fill: 0x7BE4FF, font: "Montserrat" }); opt4Txt.anchor.set(0.5, 0.5); opt4Txt.x = centerX; opt4Txt.y = optionStartY + optionSpacing * 3 - 25; opt4.addChild(opt4Txt); var opt4Desc = new Text2('Bullets pass through more enemies', { size: 40, fill: "#fff", font: "Montserrat" }); opt4Desc.anchor.set(0.5, 0.5); opt4Desc.x = centerX; opt4Desc.y = optionStartY + optionSpacing * 3 + 35; opt4.addChild(opt4Desc); opt4.interactive = true; opt4.down = function (x, y, obj) { Bullet.prototype.pierce = 2; // Make hero invulnerable for 3 seconds and glow yellow hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; startOptionPopup.addChild(opt4); // Option 5: Bullets can follow up enemies var opt5 = new Container(); var opt5Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt5Bg.width = 800; opt5Bg.height = 160; opt5Bg.alpha = 0.98; opt5Bg.x = centerX; opt5Bg.y = optionStartY + optionSpacing * 4; // Removed outline (LK.effects.outline not supported) opt5.addChild(opt5Bg); var opt5Txt = new Text2('Missile', { size: 54, fill: 0xFF77FF, font: "Montserrat" }); opt5Txt.anchor.set(0.5, 0.5); opt5Txt.x = centerX; opt5Txt.y = optionStartY + optionSpacing * 4 - 25; opt5.addChild(opt5Txt); var opt5Desc = new Text2('Bullets home in on enemies', { size: 40, fill: "#fff", font: "Montserrat" }); opt5Desc.anchor.set(0.5, 0.5); opt5Desc.x = centerX; opt5Desc.y = optionStartY + optionSpacing * 4 + 35; opt5.addChild(opt5Desc); opt5.interactive = true; opt5.down = function (x, y, obj) { Bullet.prototype.homing = true; // Make hero invulnerable for 3 seconds and glow yellow hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; startOptionPopup.addChild(opt5); // Option 6: Double Shot var opt6 = new Container(); var opt6Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt6Bg.width = 800; opt6Bg.height = 160; opt6Bg.alpha = 0.98; opt6Bg.x = centerX; opt6Bg.y = optionStartY + optionSpacing * 5; opt6.addChild(opt6Bg); var opt6Txt = new Text2('Double Shot', { size: 54, fill: 0xFFD700, font: "Montserrat" }); opt6Txt.anchor.set(0.5, 0.5); opt6Txt.x = centerX; opt6Txt.y = optionStartY + optionSpacing * 5 - 25; opt6.addChild(opt6Txt); var opt6Desc = new Text2('Shoot two times at once', { size: 40, fill: "#fff", font: "Montserrat" }); opt6Desc.anchor.set(0.5, 0.5); opt6Desc.x = centerX; opt6Desc.y = optionStartY + optionSpacing * 5 + 35; opt6.addChild(opt6Desc); opt6.interactive = true; opt6.down = function (x, y, obj) { // Enable double shot for hero hero._doubleShot = true; // Make hero invulnerable for 3 seconds and glow yellow hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; startOptionPopup.addChild(opt6); // Add popup to game game.addChild(startOptionPopup); } // Show the popup at game start hero = new Hero(); hero.x = centerX; hero.y = centerY; game.addChild(hero); showStartOptionPopup(); // Removed scoreTxt, replaced by timer in top right var livesTxt = new Text2('Lives: ' + heroLives, { size: 54, fill: 0xFF5555, font: "Montserrat" }); livesTxt.anchor.set(1, 0); livesTxt.y = 10 + 60 - 10; livesTxt.x = -25; LK.gui.topRight.addChild(livesTxt); var timerTxt = new Text2('00:00', { size: 64, fill: "#fff", font: "Montserrat" }); timerTxt.anchor.set(0.5, 0); timerTxt.y = 55; LK.gui.top.addChild(timerTxt); var magnetTimerTxt = new Text2('', { size: 44, fill: 0x00FFFF, font: "Montserrat" }); magnetTimerTxt.anchor.set(0.5, 0); // Defer setting y until after levelTxt is created // LK.gui.top.addChild(magnetTimerTxt); will be called after levelTxt is created below xpTxt = new Text2('XP: 0/10', { size: 44, fill: 0x7BE495, font: "Montserrat" }); xpTxt.anchor.set(1, 0); // Move XP text below the lives text (livesTxt.y + livesTxt.height + 8 for spacing), then 30px further down, then 70px further down, then 20px further down, then 10px up, then 60px up xpTxt.y = livesTxt.y + livesTxt.height + 8 - 30 + 30 + 70 + 20 - 10 - 60; xpTxt.x = -25 - 25 + 5; LK.gui.topRight.addChild(xpTxt); levelTxt = new Text2('Level: 1', { size: 44, fill: 0xF7E967, font: "Montserrat" }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); levelTxt.y = 150; // Now that levelTxt is defined, set magnetTimerTxt.y and add to LK.gui.top magnetTimerTxt.y = levelTxt.y + levelTxt.height + 10; LK.gui.top.addChild(magnetTimerTxt); function spawnEnemy() { var edge = Math.floor(Math.random() * 4); var x, y; var useRight = false; if (edge === 0) { x = Math.random() * 2048; y = -100; if (x > 1024) { useRight = true; } } else if (edge === 1) { x = 2048 + 100; y = Math.random() * 2732; useRight = true; } else if (edge === 2) { x = Math.random() * 2048; y = 2732 + 100; if (x > 1024) { useRight = true; } } else { x = -100; y = Math.random() * 2732; } var enemy = new Enemy(); // Set initial facing direction based on spawn side if (useRight) { // Face right: right sprite visible, left sprite hidden if (enemy.children && enemy.children.length > 0) { for (var i = 0; i < enemy.children.length; i++) { var child = enemy.children[i]; if (child.assetId === 'enemy_right') { child.visible = true; } if (child.assetId === 'enemy_left') { child.visible = false; } } } enemy._lastFacingRight = true; } else { // Face left: left sprite visible, right sprite hidden if (enemy.children && enemy.children.length > 0) { for (var i = 0; i < enemy.children.length; i++) { var child = enemy.children[i]; if (child.assetId === 'enemy_right') { child.visible = false; } if (child.assetId === 'enemy_left') { child.visible = true; } } } enemy._lastFacingRight = false; } enemy.x = x; enemy.y = y; enemies.push(enemy); game.addChild(enemy); } function spawnGem(x, y, opts) { var gem = new Gem(opts); gem.x = x; gem.y = y; gems.push(gem); game.addChild(gem); } function spawnMagnetPowerup(x, y) { var powerup = new Powerup({ isMagnet: true }); // isMagnet parametresiyle oluştur powerup.x = x; powerup.y = y; powerups.push(powerup); game.addChild(powerup); } function spawnPowerup(x, y) { var powerup = new Powerup({ isMagnet: false }); // Diğer poweruplar powerup.x = x; powerup.y = y; powerups.push(powerup); // Remove and re-add to ensure it's above any background (if present) if (game.children && game.children.length > 0) { game.removeChild(powerup); game.addChild(powerup); } else { game.addChild(powerup); } } // Yeni: kalp powerup spawn fonksiyonu function spawnHeartPowerup(x, y) { var heart = new HeartPowerup(); heart.x = x; heart.y = y; powerups.push(heart); game.addChild(heart); } // Spawn attack speed powerup function spawnAttackSpeedPowerup(x, y) { var asp = new AttackSpeedPowerup(); asp.x = x; asp.y = y; powerups.push(asp); game.addChild(asp); } function fireBullet(dx, dy) { var bullet = new Bullet(); bullet.x = hero.x; bullet.y = hero.y; if (typeof Bullet.prototype.pierce === "undefined") { Bullet.prototype.pierce = 1; } bullet.pierce = Bullet.prototype.pierce; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) { bullet.dirX = 1; bullet.dirY = 0; } else { bullet.dirX = dx / dist; bullet.dirY = dy / dist; } bullet.rotation = Math.atan2(bullet.dirY, bullet.dirX); bullets.push(bullet); game.addChild(bullet); } function randomDir() { var angle = Math.random() * Math.PI * 2; return { x: Math.cos(angle), y: Math.sin(angle) }; } function dist2(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); } game.down = function (x, y, obj) { if (game._levelUpFrozen) { return; } if (x < 100 && y < 100) { return; } hero.targetX = x; hero.targetY = y; dragging = true; }; game.move = function (x, y, obj) { if (game._levelUpFrozen) { return; } hero.targetX = x; hero.targetY = y; }; game.up = function (x, y, obj) { if (game._levelUpFrozen) { return; } dragging = false; }; var ticksSurvived = 0; var autoAttackTimer = 0; var autoAttackInterval = 96; game.update = function () { if (game._levelUpFrozen) { // Freeze all game logic and input while level up popup is active return; } // --- Invulnerability circle effect logic --- if (typeof hero !== "undefined") { // Track invuln circle instance globally if (typeof invulnCircle === "undefined") { invulnCircle = null; } var invulnActive = typeof hero._invulnerableUntilTick === "number" && typeof ticksSurvived === "number" && ticksSurvived < hero._invulnerableUntilTick; if (invulnActive) { if (!invulnCircle || !invulnCircle.parent) { invulnCircle = new InvulnerabilityCircle(); invulnCircle.x = hero.x; invulnCircle.y = hero.y; // Add above hero in display list var heroIdx = game.children.indexOf(hero); if (heroIdx !== -1) { game.addChildAt(invulnCircle, heroIdx + 1); } else { game.addChild(invulnCircle); } } // Always ensure hero is behind invulnCircle in display list if (invulnCircle && invulnCircle.parent && hero && hero.parent === game) { var invulnIdx = game.children.indexOf(invulnCircle); var heroIdx = game.children.indexOf(hero); if (heroIdx > invulnIdx - 1) { // Remove and re-add hero just before invulnCircle game.removeChild(hero); game.addChildAt(hero, invulnIdx); } } } else { if (invulnCircle && invulnCircle.parent) { invulnCircle.parent.removeChild(invulnCircle); invulnCircle = null; } } if (invulnCircle) { invulnCircle.update(); } // --- Magnet circle effect logic --- if (typeof magnetCircle === "undefined") { magnetCircle = null; } var magnetActive = hero.magnetActive && hero.magnetDuration > 0; if (magnetActive) { if (!magnetCircle || !magnetCircle.parent) { magnetCircle = new MagnetCircle(); magnetCircle.x = hero.x; magnetCircle.y = hero.y; // Add below hero in display list var heroIdx = game.children.indexOf(hero); if (heroIdx !== -1) { game.addChildAt(magnetCircle, heroIdx); } else { game.addChild(magnetCircle); } } } else { if (magnetCircle && magnetCircle.parent) { magnetCircle.parent.removeChild(magnetCircle); magnetCircle = null; } } if (magnetCircle) { magnetCircle.update(); } } hero.update(); for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); // Track lastWasTouchingHero for exact frame detection if (typeof e.lastWasTouchingHero === "undefined") { e.lastWasTouchingHero = false; } var isTouchingHero = dist2(e, hero) < e.radius + hero.radius; if (!e.lastWasTouchingHero && isTouchingHero) { // Check for hero invulnerability if (typeof hero._invulnerableUntilTick === "number" && typeof ticksSurvived === "number" && ticksSurvived < hero._invulnerableUntilTick) { // Do nothing, hero is invulnerable } else { LK.effects.flashScreen(0xff0000, 1000); heroLives--; livesTxt.setText('Lives: ' + heroLives); if (heroLives <= 0) { LK.showGameOver(); lastGameOver = true; } } } e.lastWasTouchingHero = isTouchingHero; } for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; b.update(); if (b.x < -100 || b.x > 2148 || b.y < -100 || b.y > 2832) { b.destroy(); bullets.splice(i, 1); continue; } for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; if (dist2(b, e) < b.radius + e.radius) { // Mark this enemy as hit by this bullet if (b._hitEnemies && b._hitEnemies.indexOf(e) === -1) { b._hitEnemies.push(e); } // Boss enemy logic if (typeof e.hp === "number" && e.hp > 0) { e.hp -= 1; LK.effects.flashObject(e, 0xffffff, 120); if (e.hp <= 0) { // --- Powerup drop logic for boss --- var droppedPowerup = false; // 50% chance to drop a magnet powerup from boss if (Math.random() < 0.5) { spawnMagnetPowerup(e.x, e.y); droppedPowerup = true; } // 50% chance to drop a heart powerup from boss if (Math.random() < 0.5) { spawnHeartPowerup(e.x, e.y); droppedPowerup = true; } // 50% chance to drop attack speed powerup from boss if (Math.random() < 0.5) { spawnAttackSpeedPowerup(e.x, e.y); droppedPowerup = true; } // Boss defeated: drop multiple big gems and normal gems ONLY if no powerup dropped if (!droppedPowerup) { for (var drop = 0; drop < 3; drop++) { var angle = Math.random() * Math.PI * 2; var dist = 60 + Math.random() * 40; var gemX = e.x + Math.cos(angle) * dist; var gemY = e.y + Math.sin(angle) * dist; spawnGem(gemX, gemY, { type: 'big' }); } for (var drop = 0; drop < 5; drop++) { var angle = Math.random() * Math.PI * 2; var dist = 80 + Math.random() * 60; var gemX = e.x + Math.cos(angle) * dist; var gemY = e.y + Math.sin(angle) * dist; spawnGem(gemX, gemY); } } e.destroy(); enemies.splice(j, 1); } } else { // --- Powerup drop logic for normal enemies --- var droppedPowerup = false; // Set magnet drop rate to 5% (same as attack speed powerup) if (Math.random() < 0.05) { spawnMagnetPowerup(e.x, e.y); droppedPowerup = true; } // Set heart powerup drop rate to 5% (same as attack speed powerup) if (Math.random() < 0.05) { spawnHeartPowerup(e.x, e.y); droppedPowerup = true; } // 5% chance to drop attack speed powerup from normal enemies if (Math.random() < 0.05) { spawnAttackSpeedPowerup(e.x, e.y); droppedPowerup = true; } // Only drop XP gems if no powerup dropped if (!droppedPowerup) { var bigGemSpawned = false; if (Math.random() < 0.10) { // Drop big xp gem at 10% rate spawnGem(e.x, e.y, { type: 'big' }); bigGemSpawned = true; } // Offset the normal gem if big gem was spawned to avoid overlap if (bigGemSpawned) { var offsetAngle = Math.random() * Math.PI * 2; var offsetDist = 40; var gemX = e.x + Math.cos(offsetAngle) * offsetDist; var gemY = e.y + Math.sin(offsetAngle) * offsetDist; spawnGem(gemX, gemY); } else { spawnGem(e.x, e.y); } } e.destroy(); enemies.splice(j, 1); // --- Challenge: Increment kill count and update UI --- challengeKillCount++; if (challengeKillCount > challengeKillTarget) challengeKillCount = challengeKillTarget; challengeTxt.setText('Enemies: ' + challengeKillCount + '/' + challengeKillTarget); if (challengeKillCount === challengeKillTarget) { LK.effects.flashScreen(0x00ff00, 1000); // Optionally, you could show a message or trigger a reward here } } b.pierce -= 1; // Ricochet logic: if bullet has ricochetLeft, bounce to nearest enemy if (typeof b.ricochetLeft !== "undefined" && b.ricochetLeft > 0 && enemies.length > 1) { // Find nearest enemy that is not the one just hit var minDist = 99999; var nearest = null; for (var ricI = 0; ricI < enemies.length; ricI++) { var ricE = enemies[ricI]; // Exclude the just-hit enemy and any already-hit enemies if (ricE === e) { continue; } if (b._hitEnemies && b._hitEnemies.indexOf(ricE) !== -1) { continue; } var d = dist2(b, ricE); if (d < minDist) { minDist = d; nearest = ricE; } } if (nearest) { // Ricochet: set bullet position to current, aim at nearest enemy var dx = nearest.x - b.x; var dy = nearest.y - b.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { b.dirX = dx / dist; b.dirY = dy / dist; b.rotation = Math.atan2(b.dirY, b.dirX); b.ricochetLeft--; // Don't destroy or remove bullet, let it continue // Restore pierce for next hit b.pierce = Math.max(1, Bullet.prototype.pierce || 1); // Move bullet slightly toward new direction to avoid instant re-collision b.x += b.dirX * 10; b.y += b.dirY * 10; continue; } } } // If no ricochet, destroy as normal if (b.pierce <= 0) { b.destroy(); bullets.splice(i, 1); } break; } } } for (var i = gems.length - 1; i >= 0; i--) { var g = gems[i]; // Set XP gem attraction area to 800 pixels when magnet is active, otherwise 150 var xpAttractRange = hero.magnetActive ? 800 : 150; var d = dist2(g, hero); if (d < xpAttractRange) { var dx = hero.x - g.x; var dy = hero.y - g.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { g.x += dx / dist * 18; g.y += dy / dist * 18; } } if (dist2(g, hero) < g.radius + hero.radius) { // Big XP gem gives 5 XP, normal gives 1 if (g.type === 'big') { xp += 5; } else { xp += 1; } g.destroy(); gems.splice(i, 1); if (xp >= xpToLevel) { level += 1; xp = 0; // Small automatic attack speed boost on every level up (less than upgrade option) autoAttackInterval = Math.max(6, autoAttackInterval - 3); // Easier XP curve: gentler growth for faster level up xpToLevel = 8 + level * 5 + Math.floor(level * level * 0.7); LK.effects.flashObject(hero, 0xf7e967, 600); // On level up, spawn a big XP gem near hero spawnGem(hero.x + (Math.random() - 0.5) * 200, hero.y + (Math.random() - 0.5) * 200, { type: 'big' }); // Pause game and show level up popup with two options // Remove any previous popup if present if (typeof levelUpPopup !== "undefined" && levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } // Freeze all game logic and input game._levelUpFrozen = true; // Create popup container var levelUpPopup = new Container(); // Dim background (match start popup) var bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x008000 // green }); bg.width = 1100; bg.height = 1500; bg.alpha = 0.92; bg.x = centerX; bg.y = centerY; // Removed outline (LK.effects.outline not supported) levelUpPopup.addChild(bg); // Title (match start popup style) var titleTxt = new Text2('Level Up!', { size: 90, fill: "#fff", font: "Montserrat" }); titleTxt.anchor.set(0.5, 0); titleTxt.x = centerX; titleTxt.y = centerY - 550; levelUpPopup.addChild(titleTxt); // Option vertical layout (match start popup) var optionStartY = centerY - 320; var optionSpacing = 220; // Option 1: Permanent Attack Speed Boost var opt1 = new Container(); var opt1Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt1Bg.width = 800; opt1Bg.height = 160; opt1Bg.alpha = 0.98; opt1Bg.x = centerX; opt1Bg.y = optionStartY; // Removed outline (LK.effects.outline not supported) opt1.addChild(opt1Bg); var opt1Txt = new Text2('Attack Speed', { size: 54, fill: 0xF7E967, font: "Montserrat" }); opt1Txt.anchor.set(0.5, 0.5); opt1Txt.x = centerX; opt1Txt.y = optionStartY - 25; opt1.addChild(opt1Txt); var opt1Desc = new Text2('Fire faster every level!', { size: 40, fill: "#fff", font: "Montserrat" }); opt1Desc.anchor.set(0.5, 0.5); opt1Desc.x = centerX; opt1Desc.y = optionStartY + 35; opt1.addChild(opt1Desc); opt1.interactive = true; opt1.down = function (x, y, obj) { autoAttackInterval = Math.max(6, autoAttackInterval - 12); // Make hero invulnerable for 3 seconds and glow yellow after every level up option choose hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } game._levelUpFrozen = false; }; levelUpPopup.addChild(opt1); // Option 2: Ricochet bullet var opt2 = new Container(); var opt2Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt2Bg.width = 800; opt2Bg.height = 160; opt2Bg.alpha = 0.98; opt2Bg.x = centerX; opt2Bg.y = optionStartY + optionSpacing; // Removed outline (LK.effects.outline not supported) opt2.addChild(opt2Bg); var ricochetText = 'Ricochet'; if (typeof Bullet.prototype.ricochet !== "undefined" && Bullet.prototype.ricochet >= 1) { ricochetText = 'Ricochet +1 bounce'; } var opt2Txt = new Text2(ricochetText, { size: 54, fill: 0x7BE495, font: "Montserrat" }); opt2Txt.anchor.set(0.5, 0.5); opt2Txt.x = centerX; opt2Txt.y = optionStartY + optionSpacing - 25; opt2.addChild(opt2Txt); var opt2Desc = new Text2('Bullets bounce to another enemy', { size: 40, fill: "#fff", font: "Montserrat" }); opt2Desc.anchor.set(0.5, 0.5); opt2Desc.x = centerX; opt2Desc.y = optionStartY + optionSpacing + 35; opt2.addChild(opt2Desc); opt2.interactive = true; opt2.down = function (x, y, obj) { if (typeof Bullet.prototype.ricochet === "undefined" || Bullet.prototype.ricochet < 1) { Bullet.prototype.ricochet = 1; } else { Bullet.prototype.ricochet += 1; } // Make hero invulnerable for 3 seconds and glow yellow after every level up option choose hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } game._levelUpFrozen = false; }; levelUpPopup.addChild(opt2); // Option 3: +1 Bullet (spread shot) var opt3 = new Container(); var opt3Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt3Bg.width = 800; opt3Bg.height = 160; opt3Bg.alpha = 0.98; opt3Bg.x = centerX; opt3Bg.y = optionStartY + optionSpacing * 2; // Removed outline (LK.effects.outline not supported) opt3.addChild(opt3Bg); var opt3Txt = new Text2('Bullet +1', { size: 54, fill: 0xFFB347, font: "Montserrat" }); opt3Txt.anchor.set(0.5, 0.5); opt3Txt.x = centerX; opt3Txt.y = optionStartY + optionSpacing * 2 - 25; opt3.addChild(opt3Txt); var opt3Desc = new Text2('Shoot more bullets at once', { size: 40, fill: "#fff", font: "Montserrat" }); opt3Desc.anchor.set(0.5, 0.5); opt3Desc.x = centerX; opt3Desc.y = optionStartY + optionSpacing * 2 + 35; opt3.addChild(opt3Desc); opt3.interactive = true; opt3.down = function (x, y, obj) { if (typeof Bullet.prototype.extraBullets === "undefined") { Bullet.prototype.extraBullets = 1; } else { Bullet.prototype.extraBullets += 1; } // Make hero invulnerable for 3 seconds and glow yellow after every level up option choose hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } game._levelUpFrozen = false; }; levelUpPopup.addChild(opt3); // Option 4: Pierce +1 var opt4 = new Container(); var opt4Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt4Bg.width = 800; opt4Bg.height = 160; opt4Bg.alpha = 0.98; opt4Bg.x = centerX; opt4Bg.y = optionStartY + optionSpacing * 3; // Removed outline (LK.effects.outline not supported) opt4.addChild(opt4Bg); var opt4Txt = new Text2('Pierce +1', { size: 54, fill: 0x7BE4FF, font: "Montserrat" }); opt4Txt.anchor.set(0.5, 0.5); opt4Txt.x = centerX; opt4Txt.y = optionStartY + optionSpacing * 3 - 25; opt4.addChild(opt4Txt); var opt4Desc = new Text2('Bullets pass through more enemies', { size: 40, fill: "#fff", font: "Montserrat" }); opt4Desc.anchor.set(0.5, 0.5); opt4Desc.x = centerX; opt4Desc.y = optionStartY + optionSpacing * 3 + 35; opt4.addChild(opt4Desc); opt4.interactive = true; opt4.down = function (x, y, obj) { if (typeof Bullet.prototype.pierce === "undefined") { Bullet.prototype.pierce = 2; } else { Bullet.prototype.pierce += 1; } // Make hero invulnerable for 3 seconds and glow yellow after every level up option choose hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } game._levelUpFrozen = false; }; levelUpPopup.addChild(opt4); // Option 5: Bullets can follow up enemies var opt5 = new Container(); var opt5Bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, color: 0x4eff00 // green }); opt5Bg.width = 800; opt5Bg.height = 160; opt5Bg.alpha = 0.98; opt5Bg.x = centerX; opt5Bg.y = optionStartY + optionSpacing * 4; // Removed outline (LK.effects.outline not supported) opt5.addChild(opt5Bg); var opt5Txt = new Text2('Missile', { size: 54, fill: 0xFF77FF, font: "Montserrat" }); opt5Txt.anchor.set(0.5, 0.5); opt5Txt.x = centerX; opt5Txt.y = optionStartY + optionSpacing * 4 - 25; opt5.addChild(opt5Txt); var opt5Desc = new Text2('Bullets home in on enemies', { size: 40, fill: "#fff", font: "Montserrat" }); opt5Desc.anchor.set(0.5, 0.5); opt5Desc.x = centerX; opt5Desc.y = optionStartY + optionSpacing * 4 + 35; opt5.addChild(opt5Desc); opt5.interactive = true; opt5.down = function (x, y, obj) { Bullet.prototype.homing = true; // Make hero invulnerable for 3 seconds and glow yellow after every level up option choose hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); if (levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } game._levelUpFrozen = false; }; levelUpPopup.addChild(opt5); // Add popup to game game.addChild(levelUpPopup); } } } for (var i = powerups.length - 1; i >= 0; i--) { var p = powerups[i]; // Powerup collection area: always 150px (not affected by magnet) var powerupAttractRange = 150; var d = dist2(p, hero); // Attract powerups (magnet, heart, and others) if within hero's collection area (150px), but NOT by magnet effect if (d < powerupAttractRange && d > p.radius + hero.radius) { // Move powerup toward hero (gentle attraction) var dx = hero.x - p.x; var dy = hero.y - p.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { p.x += dx / dist * 10; p.y += dy / dist * 10; } } // Magnet powerup pickup radius (same as attract range) if (p.isMagnet && d < p.radius + hero.radius) { hero.magnetActive = true; // Set magnetEndTick to 10 seconds (600 frames) from now, synchronized with game time if (typeof ticksSurvived === "number") { hero.magnetEndTick = ticksSurvived + 600; hero.magnetDuration = 600; } else { hero.magnetEndTick = undefined; hero.magnetDuration = 600; } hero._magnetGlowActive = false; // force re-apply blue glow in Hero.update LK.effects.flashObject(hero, 0x00ffff, 800); p.destroy(); powerups.splice(i, 1); } // Yeni: Kalp powerup toplama else if (p.constructor === HeartPowerup && d < p.radius + hero.radius) { heroLives++; if (heroLives > 5) { heroLives = 5; } livesTxt.setText('Lives: ' + heroLives); p.destroy(); powerups.splice(i, 1); } // Diğer poweruplar için (şu an sadece magnet ve heart var) else if (!p.isMagnet && !(p.constructor === HeartPowerup) && d < p.radius + hero.radius) { // Attack speed powerup pickup if (p.constructor === AttackSpeedPowerup) { // Set attack speed boost for 5 seconds (300 ticks) if (typeof hero._attackSpeedBoostEndTick === "undefined" || typeof ticksSurvived === "undefined" || ticksSurvived >= hero._attackSpeedBoostEndTick) { hero._attackSpeedBoostEndTick = ticksSurvived + 300; } else { // If already active, extend duration hero._attackSpeedBoostEndTick += 300; } LK.effects.flashObject(hero, 0xffff00, 800); } // Pick up other powerups if needed (future-proof) p.destroy(); powerups.splice(i, 1); } } // Handle attack speed boost effect if (typeof hero._attackSpeedBoostEndTick !== "undefined" && typeof ticksSurvived !== "undefined" && ticksSurvived < hero._attackSpeedBoostEndTick) { if (typeof hero._attackSpeedBoostActive === "undefined" || !hero._attackSpeedBoostActive) { hero._attackSpeedBoostActive = true; hero._originalAutoAttackInterval = autoAttackInterval; autoAttackInterval = Math.max(3, Math.floor(autoAttackInterval / 2)); } } else if (typeof hero._attackSpeedBoostActive !== "undefined" && hero._attackSpeedBoostActive) { hero._attackSpeedBoostActive = false; if (typeof hero._originalAutoAttackInterval !== "undefined") { autoAttackInterval = hero._originalAutoAttackInterval; } } autoAttackTimer++; if (autoAttackTimer >= autoAttackInterval) { autoAttackTimer = 0; // Block firing if reloading if (heroReloading) { // Do nothing, wait for reload to finish } else { var nearest = null, minDist = 99999; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; var d = dist2(hero, e); if (d < minDist) { minDist = d; nearest = e; } } if (nearest) { var dx = nearest.x - hero.x; var dy = nearest.y - hero.y; // Remove automatic extra bullets from level; now controlled by upgrade if (typeof Bullet.prototype.extraBullets === "undefined") { Bullet.prototype.extraBullets = 0; } var extraBullets = Bullet.prototype.extraBullets; var totalBullets = 1 + extraBullets; var spread; var baseAngle = Math.atan2(dy, dx); // Double Shot logic: if enabled, fire two volleys in succession (not at the same time) var doubleShotActive = hero._doubleShot === true; // Count how many shots will be fired (1 volley or 2 for double shot) var shotsToFire = doubleShotActive ? 2 : 1; // If not enough bullets left, only fire as many as available if (heroBulletCount < shotsToFire) { shotsToFire = heroBulletCount; } // If no bullets left, start reload and block firing if (heroBulletCount <= 0) { if (!heroReloading) { heroReloading = true; reloadTxt.setText('Reloading...'); heroReloadTimeout = LK.setTimeout(function () { heroBulletCount = heroBulletMax; heroReloading = false; reloadTxt.setText(''); }, 1000); } } else { // Actually fire if (doubleShotActive) { // Fire first volley now (function fireDoubleShotVolley(volleyIdx) { if (heroBulletCount <= 0) return; var shotAngle = baseAngle; var doubleShotAngleOffset = Math.PI / 32; // ~5.6 degrees if (volleyIdx === 1) { shotAngle = baseAngle + doubleShotAngleOffset / 2; } else if (volleyIdx === 0) { shotAngle = baseAngle - doubleShotAngleOffset / 2; } if (totalBullets % 2 === 0 && totalBullets > 1) { // Even number of bullets: 2 center bullets go straight and parallel, others scatter var centerIdx1 = totalBullets / 2 - 1; var centerIdx2 = totalBullets / 2; var offsetDist = 30; var perpAngle = shotAngle + Math.PI / 2; for (var b = 0; b < totalBullets; b++) { if (b === centerIdx1 || b === centerIdx2) { var offset = (b === centerIdx1 ? -1 : 1) * offsetDist / 2; var bulletX = hero.x + Math.cos(perpAngle) * offset; var bulletY = hero.y + Math.sin(perpAngle) * offset; var dirX = Math.cos(shotAngle); var dirY = Math.sin(shotAngle); var bullet = new Bullet(); bullet.x = bulletX; bullet.y = bulletY; bullet.pierce = Bullet.prototype.pierce || 1; bullet.dirX = dirX; bullet.dirY = dirY; bullet.rotation = shotAngle; bullets.push(bullet); game.addChild(bullet); } else { // Scattered bullets var scatterCount = totalBullets - 2; var scatterIdx = b < centerIdx1 ? b : b - 2; var scatterSpread = Math.PI / 32 + (level - 2) * Math.PI / 48; if (scatterSpread > Math.PI / 4) { scatterSpread = Math.PI / 4; } var angle = shotAngle; if (scatterCount > 1) { angle = shotAngle - scatterSpread / 2 + scatterSpread * scatterIdx / (scatterCount - 1); } var dirX = Math.cos(angle); var dirY = Math.sin(angle); fireBullet(dirX, dirY); } } } else if (level > 2) { var spread = Math.PI / 32 + (level - 2) * Math.PI / 48; if (spread > Math.PI / 4) { spread = Math.PI / 4; } for (var b = 0; b < totalBullets; b++) { var angle = shotAngle; if (totalBullets > 1) { angle = shotAngle - spread / 2 + spread * b / (totalBullets - 1); } var dirX = Math.cos(angle); var dirY = Math.sin(angle); fireBullet(dirX, dirY); } } else { var spread = Math.PI / 16; for (var b = 0; b < totalBullets; b++) { var angle = shotAngle; if (totalBullets > 1) { angle = shotAngle - spread / 2 + spread * b / (totalBullets - 1); } var dirX = Math.cos(angle); var dirY = Math.sin(angle); fireBullet(dirX, dirY); } } // If this was the first volley, schedule the second volley after a short delay if (volleyIdx === 0 && heroBulletCount > 1) { heroBulletCount--; reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); LK.setTimeout(function () { fireDoubleShotVolley(1); heroBulletCount--; reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); // If out of ammo after second shot, trigger reload if (heroBulletCount <= 0 && !heroReloading) { heroReloading = true; reloadTxt.setText('Reloading...'); heroReloadTimeout = LK.setTimeout(function () { heroBulletCount = heroBulletMax; heroReloading = false; reloadTxt.setText(''); }, 1000); } }, 300); // 300ms delay between shots } else if (volleyIdx === 1) { // Already decremented in timeout above } else { heroBulletCount--; reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); if (heroBulletCount <= 0 && !heroReloading) { heroReloading = true; reloadTxt.setText('Reloading...'); heroReloadTimeout = LK.setTimeout(function () { heroBulletCount = heroBulletMax; heroReloading = false; reloadTxt.setText(''); }, 1000); } } })(0); } else { // Single volley (normal fire) var shotAngle = baseAngle; if (totalBullets % 2 === 0 && totalBullets > 1) { var centerIdx1 = totalBullets / 2 - 1; var centerIdx2 = totalBullets / 2; var offsetDist = 30; var perpAngle = shotAngle + Math.PI / 2; for (var b = 0; b < totalBullets; b++) { if (b === centerIdx1 || b === centerIdx2) { var offset = (b === centerIdx1 ? -1 : 1) * offsetDist / 2; var bulletX = hero.x + Math.cos(perpAngle) * offset; var bulletY = hero.y + Math.sin(perpAngle) * offset; var dirX = Math.cos(shotAngle); var dirY = Math.sin(shotAngle); var bullet = new Bullet(); bullet.x = bulletX; bullet.y = bulletY; bullet.pierce = Bullet.prototype.pierce || 1; bullet.dirX = dirX; bullet.dirY = dirY; bullet.rotation = shotAngle; bullets.push(bullet); game.addChild(bullet); } else { var scatterCount = totalBullets - 2; var scatterIdx = b < centerIdx1 ? b : b - 2; var scatterSpread = Math.PI / 32 + (level - 2) * Math.PI / 48; if (scatterSpread > Math.PI / 4) { scatterSpread = Math.PI / 4; } var angle = shotAngle; if (scatterCount > 1) { angle = shotAngle - scatterSpread / 2 + scatterSpread * scatterIdx / (scatterCount - 1); } var dirX = Math.cos(angle); var dirY = Math.sin(angle); fireBullet(dirX, dirY); } } } else if (level > 2) { var spread = Math.PI / 32 + (level - 2) * Math.PI / 48; if (spread > Math.PI / 4) { spread = Math.PI / 4; } for (var b = 0; b < totalBullets; b++) { var angle = shotAngle; if (totalBullets > 1) { angle = shotAngle - spread / 2 + spread * b / (totalBullets - 1); } var dirX = Math.cos(angle); var dirY = Math.sin(angle); fireBullet(dirX, dirY); } } else { var spread = Math.PI / 16; for (var b = 0; b < totalBullets; b++) { var angle = shotAngle; if (totalBullets > 1) { angle = shotAngle - spread / 2 + spread * b / (totalBullets - 1); } var dirX = Math.cos(angle); var dirY = Math.sin(angle); fireBullet(dirX, dirY); } } heroBulletCount--; reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); if (heroBulletCount <= 0 && !heroReloading) { heroReloading = true; reloadTxt.setText('Reloading...'); heroReloadTimeout = LK.setTimeout(function () { heroBulletCount = heroBulletMax; heroReloading = false; reloadTxt.setText(''); }, 1000); } } } } } } spawnTimer++; if (spawnTimer >= spawnInterval) { spawnTimer = 0; var toSpawn = Math.max(1, Math.floor((1 + Math.floor(wave / 3)) / 4 * 0.75)); for (var i = 0; i < toSpawn; i++) { spawnEnemy(); } // Boss spawn logic: spawn boss every 30 waves if (typeof bossSpawnedWaves === "undefined") { bossSpawnedWaves = {}; } if (wave % 30 === 0 && !bossSpawnedWaves[wave]) { var boss = new Enemy({ isBoss: true }); // Spawn boss at a random edge var edge = Math.floor(Math.random() * 4); if (edge === 0) { boss.x = Math.random() * 2048; boss.y = -200; } else if (edge === 1) { boss.x = 2048 + 200; boss.y = Math.random() * 2732; } else if (edge === 2) { boss.x = Math.random() * 2048; boss.y = 2732 + 200; } else { boss.x = -200; boss.y = Math.random() * 2732; } enemies.push(boss); game.addChild(boss); bossSpawnedWaves[wave] = true; } wave++; spawnInterval = Math.max(24, 90 - Math.floor(wave / 2)); } ticksSurvived++; // Removed scoreTxt.setText, timer is shown in top right only xpTxt.setText('XP: ' + xp + '/' + xpToLevel); levelTxt.setText('Level: ' + level); var totalSeconds = Math.floor(ticksSurvived / 60); var minutes = Math.floor(totalSeconds / 60); var seconds = totalSeconds % 60; var minStr = minutes < 10 ? '0' + minutes : '' + minutes; var secStr = seconds < 10 ? '0' + seconds : '' + seconds; timerTxt.setText(minStr + ':' + secStr); // Only reset heroLives and livesTxt on game restart, not every frame if (lastGameOver && hero && hero.parent) { lastGameOver = false; heroLives = 5; if (heroLives > 5) { heroLives = 5; } if (livesTxt) { livesTxt.setText('Lives: ' + heroLives); } // Reset bullet count and reload state heroBulletCount = heroBulletMax; heroReloading = false; reloadTxt.setText(''); if (heroReloadTimeout) { LK.clearTimeout(heroReloadTimeout); heroReloadTimeout = null; } // --- Reset challenge kill count and UI --- challengeKillCount = 0; challengeTxt.setText('Enemies: 0/' + challengeKillTarget); } // Magnet/Attack Speed timer display if (hero.magnetActive && hero.magnetDuration > 0) { var magnetSeconds = Math.ceil(hero.magnetDuration / 60); magnetTimerTxt.setText('Magnet: ' + magnetSeconds + 's'); } else if (typeof hero._attackSpeedBoostEndTick !== "undefined" && typeof ticksSurvived !== "undefined" && ticksSurvived < hero._attackSpeedBoostEndTick) { var aspSeconds = Math.ceil((hero._attackSpeedBoostEndTick - ticksSurvived) / 60); magnetTimerTxt.setText('Attack Speed: ' + aspSeconds + 's'); } else { magnetTimerTxt.setText(''); } // Show ammo count if not reloading if (!heroReloading) { reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); } // --- Update challenge UI every frame (defensive, in case of external changes) --- challengeTxt.setText('Enemies: ' + challengeKillCount + '/' + challengeKillTarget); };
===================================================================
--- original.js
+++ change.js
@@ -479,8 +479,20 @@
var lastGameOver = false;
var scoreTxt, xpTxt, levelTxt;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
+// --- Challenge: Kill 50 enemies ---
+var challengeKillCount = 0;
+var challengeKillTarget = 50;
+var challengeTxt = new Text2('Enemies: 0/50', {
+ size: 54,
+ fill: 0xFFD700,
+ font: "Montserrat"
+});
+challengeTxt.anchor.set(0.5, 0);
+challengeTxt.x = 2048 / 2;
+challengeTxt.y = 10;
+LK.gui.top.addChild(challengeTxt);
// --- Bullet count and reload state ---
var heroBulletCount = 10;
var heroBulletMax = 10;
var heroReloading = false;
@@ -1222,8 +1234,16 @@
}
}
e.destroy();
enemies.splice(j, 1);
+ // --- Challenge: Increment kill count and update UI ---
+ challengeKillCount++;
+ if (challengeKillCount > challengeKillTarget) challengeKillCount = challengeKillTarget;
+ challengeTxt.setText('Enemies: ' + challengeKillCount + '/' + challengeKillTarget);
+ if (challengeKillCount === challengeKillTarget) {
+ LK.effects.flashScreen(0x00ff00, 1000);
+ // Optionally, you could show a message or trigger a reward here
+ }
}
b.pierce -= 1;
// Ricochet logic: if bullet has ricochetLeft, bounce to nearest enemy
if (typeof b.ricochetLeft !== "undefined" && b.ricochetLeft > 0 && enemies.length > 1) {
@@ -1970,8 +1990,11 @@
if (heroReloadTimeout) {
LK.clearTimeout(heroReloadTimeout);
heroReloadTimeout = null;
}
+ // --- Reset challenge kill count and UI ---
+ challengeKillCount = 0;
+ challengeTxt.setText('Enemies: 0/' + challengeKillTarget);
}
// Magnet/Attack Speed timer display
if (hero.magnetActive && hero.magnetDuration > 0) {
var magnetSeconds = Math.ceil(hero.magnetDuration / 60);
@@ -1985,5 +2008,7 @@
// Show ammo count if not reloading
if (!heroReloading) {
reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax);
}
+ // --- Update challenge UI every frame (defensive, in case of external changes) ---
+ challengeTxt.setText('Enemies: ' + challengeKillCount + '/' + challengeKillTarget);
};
\ No newline at end of file
16x16 pixel wounded guy holding pistol. In-Game asset. 2d. High contrast. No shadows. pixel art. retro arcade game
3x3 pixel green coin. In-Game asset. 2d. High contrast. No shadows. retro arcade. Pixel art
3x3 pixel blue coin. In-Game asset. 2d. High contrast. No shadows. Pixel art. retro arcade
4x4 pixel art heart. In-Game asset. 2d. High contrast. No shadows. retro arcade. Pixel art. 8 bit
fill the circle with yellow colour
remove cars and buildings
Create an 8-bit style effect representing a magnetic power-up area. The effect should be a circular, glowing field with a soft, pulsing light. The colors should be green and blue, with a slight gradient effect to indicate the area where objects (such as coins or experience points) are attracted towards the character. The circle should have a subtle flicker to show the magnetic pull, and it should be designed to fit within the retro, pixel-art aesthetic of an 8-bit game. In-Game asset. 2d. High contrast. No shadows
Vertical windowed filled rectangle HUD for the 2d zombie theme game. Use green colours. Do not make it too much pixelated. In-Game asset. 2d. High contrast. No shadows. No text. No icon. No background Transparent.Retro arcade theme.
windowed filled rectangle HUD button for the 2d pixel art zombie theme game. Use dark green colours. In-Game asset. 2d. High contrast. No shadows. No text. No icon. No background Transparent.Retro arcade theme.
pixelart magnet In-Game asset. 2d. High contrast. No shadows. Pixel art
pixelart red circular enemy projectile to dodge In-Game asset. 2d. High contrast. No shadows. Pixel art
pixelart yellow circular bullet to shoot enemies In-Game asset. 2d. High contrast. No shadows. Pixel art
pixelart blue circular enemy projectile to dodge In-Game asset. 2d. High contrast. No shadows. Pixel art
4x4pixel bow and arrow. In-Game asset. 2d. High contrast. No shadows. Black outline
8x8 pixel movement speed powerup icon. boot with wings. In-Game asset. 2d. High contrast. No shadows. Black outline