User prompt
do the same with the boss enemy as big_zombie
User prompt
do the same with the fast enemy as zombie_dog
User prompt
rename ranged enemy as woman_zombie with the same way
User prompt
rename normal enemy as normal_zombie in codes,comments and assets
User prompt
rename normal enemy as normal_zombie
User prompt
rename boss enemy as big_zombie
User prompt
rename normal enemy as normal zombie, fast enemy as zombie_dog and ranged enemy as woman_zombie
User prompt
“Instead of calculating separate drop chances for each power-up, please change the logic so that when a drop occurs, the game randomly selects exactly one power-up to drop (according to their relative weights) and drops only that one.”
User prompt
Please change the Extra Shot logic so that all bullets in a volley are spawned first, and then heroBulletCount is decremented exactly once—after the entire volley—instead of once per bullet.
User prompt
Move the heroBulletCount-- call outside the extra‐bullet loop so ammo is only decremented once per shot, not once per extra bullet.
User prompt
Modify the ‘Extra Shot’ upgrade so that the additional projectiles it fires do not consume any ammo — only the base shot should decrement the magazine count.
User prompt
Prevent extra shot upgrades from decreasing the magazine
User prompt
do the same for the future selected extra shot options
User prompt
extra shots should not decrease the magazine
User prompt
add shoot count down text for showing how many shoots left before reload below hero which moving with him
User prompt
make hero reload every 20 shoot
User prompt
while reloading add "Reloading" text above hero which moving with him
User prompt
reduce enemy frequency by %50
User prompt
change ranged and fast enemies spawn rate to %3
User prompt
change ranged enemies spawn rate to %10 instead of every 5th wave
User prompt
“Update the collision/gem-drop logic so that only enemies with isBoss === true (or isFinalBoss === true) drop multiple XP gems; all other enemies should always drop exactly one XP gem, regardless of their hp value.”
User prompt
set normal enemies hitpoint to 2
User prompt
make normal enemies hitpoint 2
User prompt
Ensure that all enemies always drop experience gems at a fixed, constant rat independent of their hitpoints.
User prompt
break this connection and restore the gem drop rate as before
/**** * 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 }); // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(sprite.width, sprite.height) / 2; 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 }); // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(bulletSprite.width, bulletSprite.height) / 2; self.speed = 9.5; self.dirX = 1; self.dirY = 0; // Set bullet pierce to default 1, or use Bullet.prototype.pierce if set by upgrades if (typeof Bullet.prototype.pierce === "undefined") { Bullet.prototype.pierce = 1; } self.pierce = Bullet.prototype.pierce; // 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, always home in on the nearest enemy (not unique assignment) 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 all are hit, just pick the nearest anyway if (!nearest) { minDist = 99999; for (var i = 0; i < enemies.length; i++) { var e = enemies[i]; 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) { // Homing strength controls how fast the bullet can turn (0.0 = no turn, 1.0 = instant turn) var homingStrength = typeof Bullet.prototype.homingStrength === "number" ? Bullet.prototype.homingStrength : 0.08; // default 0.08 for smoothness // Calculate current and target angles var currentAngle = Math.atan2(self.dirY, self.dirX); var targetAngle = Math.atan2(dy, dx); // Interpolate angle using angular lerp var da = targetAngle - currentAngle; while (da > Math.PI) { da -= Math.PI * 2; } while (da < -Math.PI) { da += Math.PI * 2; } var newAngle = currentAngle + da * homingStrength; // Update direction self.dirX = Math.cos(newAngle); self.dirY = Math.sin(newAngle); self.rotation = newAngle; } } } } 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) { // Homing strength controls how fast the bullet can turn (0.0 = no turn, 1.0 = instant turn) var homingStrength = typeof Bullet.prototype.homingStrength === "number" ? Bullet.prototype.homingStrength : 0.08; // default 0.08 for smoothness // Calculate current and target angles var currentAngle = Math.atan2(self.dirY, self.dirX); var targetAngle = Math.atan2(dy, dx); // Interpolate angle using angular lerp var da = targetAngle - currentAngle; while (da > Math.PI) { da -= Math.PI * 2; } while (da < -Math.PI) { da += Math.PI * 2; } var newAngle = currentAngle + da * homingStrength; // Update direction self.dirX = Math.cos(newAngle); self.dirY = Math.sin(newAngle); self.rotation = newAngle; } } } } 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; self.isFinalBoss = !!opts.isFinalBoss; // allow explicit final boss flag // Preload both right and left sprites, only one visible at a time if (self.isBoss) { // Use special assets for Final Boss if (self.isFinalBoss) { var bossSpriteRight = self.attachAsset('Finalboss_right', { anchorX: 0.5, anchorY: 0.5 }); bossSpriteRight.assetId = 'Finalboss_right'; var bossSpriteLeft = self.attachAsset('Finalboss_left', { anchorX: 0.5, anchorY: 0.5 }); bossSpriteLeft.assetId = 'Finalboss_left'; bossSpriteLeft.visible = false; // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(bossSpriteRight.width, bossSpriteRight.height) / 2; self.speed = 0.7; self.hp = 30 * 10; // Final Boss HP (should be set by spawner too) self._bossSpriteRight = bossSpriteRight; self._bossSpriteLeft = bossSpriteLeft; } else { 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; // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(bossSpriteRight.width, bossSpriteRight.height) / 2; self.speed = 0.7; self.hp = 30; self._bossSpriteRight = bossSpriteRight; self._bossSpriteLeft = bossSpriteLeft; } } else { // If opts.fastEnemy is set, use fast enemy assets if (opts && opts.fastEnemy) { var enemySpriteRight = self.attachAsset('zombiedog_right', { anchorX: 0.5, anchorY: 0.5 }); enemySpriteRight.assetId = 'zombiedog_right'; var enemySpriteLeft = self.attachAsset('zombiedog_left', { anchorX: 0.5, anchorY: 0.5 }); enemySpriteLeft.assetId = 'zombiedog_left'; enemySpriteLeft.visible = false; // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(enemySpriteRight.width, enemySpriteRight.height) / 2; self.speed = 5; self._enemySpriteRight = enemySpriteRight; self._enemySpriteLeft = enemySpriteLeft; self.isFastEnemy = true; } 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; // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(enemySpriteRight.width, enemySpriteRight.height) / 2; self.speed = 1; self.hp = 2; self._enemySpriteRight = enemySpriteRight; self._enemySpriteLeft = enemySpriteLeft; self.isFastEnemy = false; } } 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 }); // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(gemSprite.width, gemSprite.height) / 2; } else { var gemSprite = self.attachAsset('xp_gem', { anchorX: 0.5, anchorY: 0.5 }); // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(gemSprite.width, gemSprite.height) / 2; } 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 }); // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(self.children[0].width, self.children[0].height) / 2; 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; // Set radius to match asset size (max of width/height / 2) self.radius = Math.max(heroSpriteRight.width, heroSpriteRight.height) / 2; self.speed = 5; 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; } } // --- Magnet glow effect --- 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; } // --- Red glow effect when life is 1 --- if (typeof heroLives !== "undefined" && heroLives === 1) { if (!self._redGlowActive) { LK.effects.flashObject(self, 0xff0000, 60000); // long duration, will be reset when life changes self._redGlowActive = true; } } else { if (self._redGlowActive) { // Remove red glow by flashing with no color (or a neutral color, e.g. white, for a short time) LK.effects.flashObject(self, 0xffffff, 100); self._redGlowActive = 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 }); } // Set radius to match asset size (max of width/height / 2) if (self.children.length > 0) { self.radius = Math.max(self.children[0].width, self.children[0].height) / 2; } else { self.radius = 30; } self.update = function () {}; return self; }); // RangedEnemy class: maintains distance from hero and fires projectiles var RangedEnemy = Container.expand(function () { var self = Container.call(this); // Attach unique ranged enemy asset var rangedSpriteRight = self.attachAsset('ranged_enemy_right', { anchorX: 0.5, anchorY: 0.5 }); rangedSpriteRight.assetId = 'ranged_enemy_right'; var rangedSpriteLeft = self.attachAsset('ranged_enemy_left', { anchorX: 0.5, anchorY: 0.5 }); rangedSpriteLeft.assetId = 'ranged_enemy_left'; rangedSpriteLeft.visible = false; self.radius = Math.max(rangedSpriteRight.width, rangedSpriteRight.height) / 2; self.speed = 1.2; self._lastFacingRight = true; self._fireCooldown = 0; self._fireInterval = 450; // fires every 7.5 seconds self._desiredDistance = 800; self.update = function () { if (typeof game !== "undefined" && game._levelUpFrozen) { return; } // --- Stop after advancing 50px from entry --- // Track initial position and total distance advanced after entering screen if (typeof self._entryState === "undefined") { // Determine entry direction and record initial position if (self.x <= 0) { self._entryState = { edge: "left", start: { x: self.x, y: self.y }, advanced: 0, stopped: false }; } else if (self.x >= 2048) { self._entryState = { edge: "right", start: { x: self.x, y: self.y }, advanced: 0, stopped: false }; } else if (self.y <= 0) { self._entryState = { edge: "top", start: { x: self.x, y: self.y }, advanced: 0, stopped: false }; } else if (self.y >= 2732) { self._entryState = { edge: "bottom", start: { x: self.x, y: self.y }, advanced: 0, stopped: false }; } else { // Fallback: treat as already inside, allow movement self._entryState = { edge: "unknown", start: { x: self.x, y: self.y }, advanced: 0, stopped: false }; } } var dx = hero.x - self.x; var dy = hero.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); var canMove = true; if (!self._entryState.stopped) { // Calculate intended movement var moveDist = dist > 0 ? self.speed : 0; var moveX = dx / dist * moveDist; var moveY = dy / dist * moveDist; // Predict new position var nextX = self.x + moveX; var nextY = self.y + moveY; // Calculate how far we've advanced from entry point (projected along entry direction) var advanced = 0; if (self._entryState.edge === "left" || self._entryState.edge === "right") { advanced = Math.abs(self.x - self._entryState.start.x); } else if (self._entryState.edge === "top" || self._entryState.edge === "bottom") { advanced = Math.abs(self.y - self._entryState.start.y); } else { // Fallback: use total distance from start var dx0 = self.x - self._entryState.start.x; var dy0 = self.y - self._entryState.start.y; advanced = Math.sqrt(dx0 * dx0 + dy0 * dy0); } // If next move would exceed 250px, clamp to exactly 250px and stop if (advanced < 250 && dist > 0) { var remaining = 250 - advanced; if (moveDist > remaining) { moveX = dx / dist * remaining; moveY = dy / dist * remaining; self._entryState.stopped = true; } self.x += moveX; self.y += moveY; } else { self._entryState.stopped = true; // Do not move further } canMove = !self._entryState.stopped; } else { // Already stopped, do not move canMove = false; } // Face left/right var shouldFaceRight = hero.x > self.x; if (shouldFaceRight !== self._lastFacingRight) { rangedSpriteRight.visible = shouldFaceRight; rangedSpriteLeft.visible = !shouldFaceRight; self._lastFacingRight = shouldFaceRight; } // Fire projectile at hero with no range restriction, but only after stopping movement if (self._entryState.stopped) { self._fireCooldown--; if (self._fireCooldown <= 0) { self._fireCooldown = self._fireInterval; // Fire 3 projectiles toward hero in a spread pattern if (typeof game !== "undefined" && typeof RangedEnemyProjectile === "function") { var angleToHero = Math.atan2(hero.y - self.y, hero.x - self.x); var spread = Math.PI / 6; // 30 degrees for (var s = -1; s <= 1; s++) { var angle = angleToHero + s * spread; var targetX = self.x + Math.cos(angle) * 100; var targetY = self.y + Math.sin(angle) * 100; var proj = new RangedEnemyProjectile(self.x, self.y, targetX, targetY); game.addChild(proj); if (typeof enemyProjectiles !== "undefined") { enemyProjectiles.push(proj); } } } } } // Update lastX, lastY for next frame (legacy, not used for movement anymore) self.lastX = self.x; self.lastY = self.y; }; return self; }); // RangedEnemyProjectile class (simple straight projectile) var RangedEnemyProjectile = Container.expand(function (startX, startY, targetX, targetY) { var self = Container.call(this); // Use bullet asset, but tint red for enemy var sprite = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); sprite.tint = 0xff3333; self.radius = Math.max(sprite.width, sprite.height) / 2; self.x = startX; self.y = startY; var dx = targetX - startX; var dy = targetY - startY; var dist = Math.sqrt(dx * dx + dy * dy); self.speed = 2; self.dirX = dist > 0 ? dx / dist : 1; self.dirY = dist > 0 ? dy / dist : 0; self.rotation = Math.atan2(self.dirY, self.dirX); self.update = function () { if (typeof game !== "undefined" && game._levelUpFrozen) { return; } self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1e1b1d }); /**** * Game Code ****/ // Unique ranged enemy assets // fast enemy left // fast enemy right // Set Bullet.prototype.homingStrength to control how fast homing bullets turn (0.0 = no turn, 1.0 = instant turn) // Example: Bullet.prototype.homingStrength = 0.08; // default is 0.08 for smooth, natural turning Bullet.prototype.homingStrength = 0.008; // Lower value for even smoother, slower homing // Add background image to the game scene var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, width: 2048, height: 2732 }); game.addChild(background); var hero; var heroLives = 5; var enemies = []; var bullets = []; var enemyProjectiles = []; // For RangedEnemy projectiles var gems = []; var powerups = []; var spawnTimer = 0; var spawnInterval = 180; // doubled from 90 to reduce frequency by 50% 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; // --- 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.57, color: 0x5e5e5d // green }); bg.width = 1100; bg.height = 1500; bg.alpha = 1; bg.x = centerX; bg.y = centerY; // Removed outline (LK.effects.outline not supported) startOptionPopup.addChild(bg); // Title replaced with LeveledUp asset var leveledUpImg = LK.getAsset('LeveledUp', { anchorX: 0.5, anchorY: 0.25, x: centerX, y: centerY - 610 }); startOptionPopup.addChild(leveledUpImg); // Option vertical layout var optionStartY = centerY - 320; var optionSpacing = 220; // --- Upgrade Option Definitions --- var allUpgradeOptions = [{ label: 'Attack Speed', desc: 'Fire faster from the start!', color: 0xF7E967, onSelect: function onSelect() { autoAttackInterval = Math.max(6, autoAttackInterval - 18); hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Ricochet', desc: 'Bullets bounces', color: 0x7BE495, onSelect: function onSelect() { Bullet.prototype.ricochet = 1; hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Bullet +1', desc: 'Shoot an extra bullet', color: 0xFFB347, onSelect: function onSelect() { Bullet.prototype.extraBullets = 1; hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Pierce +1', desc: 'Bullets pierce enemies', color: 0x7BE4FF, onSelect: function onSelect() { if (typeof Bullet.prototype.pierce === "undefined") { Bullet.prototype.pierce = 2; } else { Bullet.prototype.pierce += 1; } hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Missile', desc: 'Bullets home in on enemies', color: 0xFF77FF, onSelect: function onSelect() { Bullet.prototype.homing = true; window._missileChosen = true; hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: typeof hero !== "undefined" && typeof hero._doubleShotCount === "number" ? 'Extra Shot +1' : 'Extra Shot', desc: 'Adds additional shot', color: 0xFFD700, onSelect: function onSelect() { if (typeof hero._doubleShotCount === "undefined") { hero._doubleShotCount = 2; hero._doubleShot = true; } else { hero._doubleShotCount += 1; hero._doubleShot = true; } hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }]; // --- Shuffle and pick 4 random options --- function shuffleArray(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // Track if missile has been chosen if (typeof window._missileChosen === "undefined") { window._missileChosen = false; } var upgradeOptions = allUpgradeOptions.slice(); // Remove 'Missile' option if already chosen if (window._missileChosen) { upgradeOptions = upgradeOptions.filter(function (opt) { return opt.label !== 'Missile'; }); } shuffleArray(upgradeOptions); var selectedOptions = upgradeOptions.slice(0, 4); // --- Render 4 random options --- for (var i = 0; i < selectedOptions.length; i++) { var opt = selectedOptions[i]; var optContainer = new Container(); var optBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); optBg.width = 700; optBg.height = 200; optBg.alpha = 0.98; optBg.x = centerX; optBg.y = optionStartY + optionSpacing * i; optContainer.addChild(optBg); var optTxt = new Text2(opt.label, { size: 54, fill: opt.color, font: "Montserrat" }); optTxt.anchor.set(0.5, 0.5); optTxt.x = centerX; optTxt.y = optionStartY + optionSpacing * i - 25; optContainer.addChild(optTxt); var optDesc = new Text2(opt.desc, { size: 40, fill: "#fff", font: "Montserrat" }); optDesc.anchor.set(0.5, 0.5); optDesc.x = centerX; optDesc.y = optionStartY + optionSpacing * i + 35; optContainer.addChild(optDesc); optContainer.interactive = true; // Defensive: wrap onSelect to also close popup and resume game optContainer.down = function (opt) { return function (x, y, obj) { opt.onSelect(); if (startOptionPopup && startOptionPopup.parent) { startOptionPopup.parent.removeChild(startOptionPopup); startOptionPopup = null; } game._levelUpFrozen = false; }; }(opt); startOptionPopup.addChild(optContainer); } // 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 enemyKillCount = 0; var achievement50Shown = false; var achievement50Timeout = null; var achievement50Txt = new Text2('Achievement: 50 Kills!', { size: 80, fill: 0xFFD700, font: "Montserrat" }); achievement50Txt.anchor.set(0.5, 0); achievement50Txt.x = 2048 / 2; // achievement50Txt.y will be set after magnetTimerTxt is added and y is set achievement50Txt.visible = false; var enemyKillTxt = new Text2('Kills: 0', { size: 54, fill: 0xFFD700, font: "Montserrat" }); // Anchor bottom right enemyKillTxt.anchor.set(1, 1); // Place at bottom right corner, with 40px margin from right and bottom enemyKillTxt.x = -40; enemyKillTxt.y = -40; LK.gui.bottomRight.addChild(enemyKillTxt); game.addChild(achievement50Txt); 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); var attackSpeedTimerTxt = new Text2('', { size: 44, fill: 0xFFD700, font: "Montserrat" }); attackSpeedTimerTxt.anchor.set(0.5, 0); 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); // Set attackSpeedTimerTxt.y below magnetTimerTxt and add to LK.gui.top attackSpeedTimerTxt.y = magnetTimerTxt.y + magnetTimerTxt.height + 10; LK.gui.top.addChild(attackSpeedTimerTxt); // Now that magnetTimerTxt and attackSpeedTimerTxt are added and y is set, set achievement50Txt.y achievement50Txt.y = attackSpeedTimerTxt.y + attackSpeedTimerTxt.height + 20; 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; } // 3% chance to make this enemy a rare fast enemy (speed 5, not a boss/final boss) var isFast = false; if (Math.random() < 0.03) { isFast = true; } // 3% chance to spawn a RangedEnemy, otherwise normal/fast enemy var enemy; if (Math.random() < 0.03) { enemy = new RangedEnemy(); } else { enemy = new Enemy({ fastEnemy: isFast }); } // No need to set speed here, handled in Enemy class // 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.assetId === 'zombiedog_right') { child.visible = true; } if (child.assetId === 'enemy_left' || child.assetId === 'zombiedog_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.assetId === 'zombiedog_right') { child.visible = false; } if (child.assetId === 'enemy_left' || child.assetId === 'zombiedog_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) { // 1) Mutlaka a ve b tanımlı olmalı if (!a || !b) { return 99999; } // 2) x,y özellikleri sayı değilse if (typeof a.x !== "number" || typeof a.y !== "number" || typeof b.x !== "number" || typeof b.y !== "number") { return 99999; } // 3) radius yoksa width/height'den ya da 0'dan tahmin et var aRadius = 0; if (a && typeof a.radius === "number") { aRadius = a.radius; } else if (a && typeof a.width === "number" && typeof a.height === "number") { aRadius = Math.max(a.width, a.height) / 2; } else { aRadius = 0; } var bRadius = 0; if (b && typeof b.radius === "number") { bRadius = b.radius; } else if (b && typeof b.width === "number" && typeof b.height === "number") { bRadius = Math.max(b.width, b.height) / 2; } else { bRadius = 0; } // 4) İki nokta arası mesafe 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) { dragging = false; return; } // Do not pause the game on left click/tap; just move the hero if not in the top-left menu area if (x < 100 && y < 100) { return; } // Prevent updating targetX/targetY if any popup is open (startOptionPopup or levelUpPopup) if (typeof startOptionPopup !== "undefined" && startOptionPopup && startOptionPopup.parent || typeof levelUpPopup !== "undefined" && levelUpPopup && levelUpPopup.parent) { return; } hero.targetX = x; hero.targetY = y; dragging = true; }; game.move = function (x, y, obj) { if (game._levelUpFrozen) { return; } // Prevent updating targetX/targetY if any popup is open (startOptionPopup or levelUpPopup) if (typeof startOptionPopup !== "undefined" && startOptionPopup && startOptionPopup.parent || typeof levelUpPopup !== "undefined" && levelUpPopup && levelUpPopup.parent) { 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 () { // --- Final Boss logic at 5 minutes --- if (typeof finalBossActive === "undefined") { finalBossActive = false; } if (typeof finalBossDefeated === "undefined") { finalBossDefeated = false; } if (typeof finalBoss === "undefined") { finalBoss = null; } if (typeof finalBossTriggered === "undefined") { finalBossTriggered = false; } if (typeof ticksSurvived !== "undefined" && !finalBossTriggered && Math.floor(ticksSurvived / 60) >= 120) { // 2 minutes reached, trigger Final Boss finalBossTriggered = true; finalBossActive = true; finalBossDefeated = false; // Destroy all enemies on screen for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] && enemies[i].parent) { enemies[i].destroy(); } enemies.splice(i, 1); } // Spawn Final Boss at top center (x = centerX, y = -200) finalBoss = new Enemy({ isBoss: true, isFinalBoss: true // use special asset }); finalBoss.x = centerX; finalBoss.y = -200; finalBoss.hp = 30 * 10; // 10x normal boss HP enemies.push(finalBoss); game.addChild(finalBoss); } // If Final Boss is active, halt wave spawns if (finalBossActive && finalBoss && typeof finalBoss.hp === "number" && finalBoss.hp > 0) { // Only update Final Boss and other logic, skip wave/enemy spawns // Allow all other update logic to run, but skip spawnTimer/wave logic below // Check if Final Boss is defeated if (finalBoss.hp <= 0) { finalBossActive = false; finalBossDefeated = true; finalBoss = null; // Resume normal wave spawning next frame } } else if (finalBossActive && (!finalBoss || finalBoss.hp <= 0)) { // Final Boss defeated, resume normal wave spawning finalBossActive = false; finalBossDefeated = true; finalBoss = null; } 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; } // --- RangedEnemy projectile update and collision --- for (var i = enemyProjectiles.length - 1; i >= 0; i--) { var proj = enemyProjectiles[i]; proj.update(); // Remove if offscreen if (proj.x < -100 || proj.x > 2148 || proj.y < -100 || proj.y > 2832) { proj.destroy(); enemyProjectiles.splice(i, 1); continue; } // Collision with hero if (dist2(proj, hero) < proj.radius + hero.radius) { // Check for hero invulnerability if (!(typeof hero._invulnerableUntilTick === "number" && typeof ticksSurvived === "number" && ticksSurvived < hero._invulnerableUntilTick)) { LK.effects.flashScreen(0xff0000, 1000); heroLives--; livesTxt.setText('Lives: ' + heroLives); if (heroLives <= 0) { LK.showGameOver(); lastGameOver = true; } } proj.destroy(); enemyProjectiles.splice(i, 1); continue; } } for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; b.update(); // Remove bullet if it goes off-screen (outside visible area) 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 (b && typeof b.radius === "number" && e && typeof e.radius === "number" && 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 and normal enemy logic (bullets can pierce all enemy types) 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; // Set boss/final boss powerup drop rates var bossPowerupRate = 0.25; if (e.isFinalBoss) { bossPowerupRate = 0.5; } // Only drop one powerup at most, chosen randomly if any if (Math.random() < bossPowerupRate) { // Pick one of the three powerups at random var powerupType = Math.floor(Math.random() * 3); if (powerupType === 0) { spawnMagnetPowerup(e.x, e.y); } else if (powerupType === 1) { spawnHeartPowerup(e.x, e.y); } else { spawnAttackSpeedPowerup(e.x, e.y); } droppedPowerup = true; } // Boss defeated: drop multiple big gems and normal gems ONLY if no powerup dropped if (!droppedPowerup) { if (e.isBoss === true || e.isFinalBoss === true) { // Boss or Final Boss: drop 1 big xp gem and 5 normal gems 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); } } else { // All other enemies: always drop exactly one normal XP gem spawnGem(e.x, e.y); } } enemyKillCount++; e.destroy(); // Remove Final Boss reference if this was the Final Boss if (e.isFinalBoss) { finalBossActive = false; finalBossDefeated = true; finalBoss = null; } enemies.splice(j, 1); } } else { // --- Powerup drop logic for normal enemies --- var droppedPowerup = false; // Set magnet drop rate to 2% for normal enemies if (Math.random() < 0.02) { spawnMagnetPowerup(e.x, e.y); droppedPowerup = true; } // Set heart powerup drop rate to 2% for normal enemies if (Math.random() < 0.02) { spawnHeartPowerup(e.x, e.y); droppedPowerup = true; } // 2% chance to drop attack speed powerup from normal enemies if (Math.random() < 0.02) { spawnAttackSpeedPowerup(e.x, e.y); droppedPowerup = true; } // Only drop XP gems if no powerup dropped if (!droppedPowerup) { // All non-boss enemies always drop exactly one normal XP gem spawnGem(e.x, e.y); } enemyKillCount++; e.destroy(); enemies.splice(j, 1); } // Always decrement pierce on every enemy hit, regardless of enemy type (including bosses/final bosses) 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, but only if pierce <= 0 if (b.pierce <= 0) { b.destroy(); bullets.splice(i, 1); } break; } } } var _loop = function _loop() { g = gems[i]; // Set XP gem attraction area to 800 pixels when magnet is active, otherwise 150 xpAttractRange = hero.magnetActive ? 800 : 150; d = dist2(g, hero); if (d < xpAttractRange) { dx = hero.x - g.x; dy = hero.y - g.y; dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { g.x += dx / dist * 18; g.y += dy / dist * 18; } } if (g && typeof g.radius === "number" && hero && typeof hero.radius === "number" && 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) { // --- Shuffle and pick 4 random options --- var shuffleArray = function shuffleArray(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }; 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 levelUpPopup = new Container(); // Dim background (match start popup) bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.57, color: 0x008000 // green }); bg.width = 1100; bg.height = 1500; bg.alpha = 0.92; bg.x = centerX; bg.y = centerY; levelUpPopup.addChild(bg); // Title replaced with LeveledUp asset var leveledUpImg = LK.getAsset('LeveledUp', { anchorX: 0.5, anchorY: 0.25, x: centerX, y: centerY - 610 }); levelUpPopup.addChild(leveledUpImg); // Option vertical layout (match start popup) optionStartY = centerY - 320; optionSpacing = 220; // --- Upgrade Option Definitions (same as start popup, but update text for in-game context) --- allUpgradeOptions = [{ label: 'Attack Speed', desc: 'Fire faster every level!', color: 0xF7E967, onSelect: function onSelect() { autoAttackInterval = Math.max(6, autoAttackInterval - 12); hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: typeof Bullet.prototype.ricochet !== "undefined" && Bullet.prototype.ricochet >= 1 ? 'Ricochet +1 bounce' : 'Ricochet', desc: 'Bullets bounces', color: 0x7BE495, onSelect: function onSelect() { if (typeof Bullet.prototype.ricochet === "undefined" || Bullet.prototype.ricochet < 1) { Bullet.prototype.ricochet = 1; } else { Bullet.prototype.ricochet += 1; } hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Bullet +1', desc: 'Shoot an extra bullet', color: 0xFFB347, onSelect: function onSelect() { if (typeof Bullet.prototype.extraBullets === "undefined") { Bullet.prototype.extraBullets = 1; } else { Bullet.prototype.extraBullets += 1; } hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Pierce +1', desc: 'Bullets pierce enemies', color: 0x7BE4FF, onSelect: function onSelect() { if (typeof Bullet.prototype.pierce === "undefined") { Bullet.prototype.pierce = 2; } else { Bullet.prototype.pierce += 1; } hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: 'Missile', desc: 'Bullets home in on enemies', color: 0xFF77FF, onSelect: function onSelect() { Bullet.prototype.homing = true; window._missileChosen = true; hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }, { label: typeof hero !== "undefined" && typeof hero._doubleShotCount === "number" ? 'Extra Shot +1' : 'Extra Shot', desc: 'Adds additional shot', color: 0xFFD700, onSelect: function onSelect() { if (typeof hero._doubleShotCount === "undefined") { hero._doubleShotCount = 2; hero._doubleShot = true; } else { hero._doubleShotCount += 1; hero._doubleShot = true; } hero._invulnerableUntilTick = (typeof ticksSurvived === "number" ? ticksSurvived : 0) + 180; LK.effects.flashObject(hero, 0xffff00, 3000); } }]; // Track if missile has been chosen if (typeof window._missileChosen === "undefined") { window._missileChosen = false; } upgradeOptions = allUpgradeOptions.slice(); // Remove 'Missile' option if already chosen if (window._missileChosen) { upgradeOptions = upgradeOptions.filter(function (opt) { return opt.label !== 'Missile'; }); } shuffleArray(upgradeOptions); selectedOptions = upgradeOptions.slice(0, 4); // --- Render 4 random options with Button asset background --- for (i = 0; i < selectedOptions.length; i++) { opt = selectedOptions[i]; optContainer = new Container(); optBg = LK.getAsset('Button', { anchorX: 0.5, anchorY: 0.5 }); optBg.width = 700; optBg.height = 200; optBg.alpha = 0.98; optBg.x = centerX; optBg.y = optionStartY + optionSpacing * i; optContainer.addChild(optBg); optTxt = new Text2(opt.label, { size: 54, fill: opt.color, font: "Montserrat" }); optTxt.anchor.set(0.5, 0.5); optTxt.x = centerX; optTxt.y = optionStartY + optionSpacing * i - 25; optContainer.addChild(optTxt); optDesc = new Text2(opt.desc, { size: 40, fill: "#fff", font: "Montserrat" }); optDesc.anchor.set(0.5, 0.5); optDesc.x = centerX; optDesc.y = optionStartY + optionSpacing * i + 35; optContainer.addChild(optDesc); optContainer.interactive = true; optContainer.down = function (opt) { return function (x, y, obj) { opt.onSelect(); if (levelUpPopup && levelUpPopup.parent) { levelUpPopup.parent.removeChild(levelUpPopup); levelUpPopup = null; } game._levelUpFrozen = false; }; }(opt); levelUpPopup.addChild(optContainer); } // Add popup to game game.addChild(levelUpPopup); } } }, g, xpAttractRange, d, dx, dy, dist, levelUpPopup, bg, titleTxt, optionStartY, optionSpacing, allUpgradeOptions, upgradeOptions, selectedOptions, i, opt, optContainer, optBg, optTxt, optDesc; for (var i = gems.length - 1; i >= 0; i--) { _loop(); } 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), but do not stack duration, just reset to 5s hero._attackSpeedBoostEndTick = ticksSurvived + 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 multiple volleys in succession (not at the same time) var doubleShotActive = hero._doubleShot === true; var doubleShotCount = typeof hero._doubleShotCount === "number" && hero._doubleShotCount > 1 ? hero._doubleShotCount : doubleShotActive ? 2 : 1; // Count how many shots will be fired (1 volley or more for double shot) var shotsToFire = doubleShotActive ? doubleShotCount : 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 multiple volleys in succession, number of volleys = shotsToFire (function fireDoubleShotVolley(volleyIdx) { if (heroBulletCount <= 0 || volleyIdx >= shotsToFire) { return; } var shotAngle = baseAngle; // Double shot bullets go in the same direction (no angle offset between volleys) var doubleShotAngleOffset = 0; shotAngle = baseAngle; 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); } } heroBulletCount--; reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); // If out of ammo after this shot, trigger reload if (heroBulletCount <= 0 && !heroReloading) { heroReloading = true; reloadTxt.setText('Reloading...'); heroReloadTimeout = LK.setTimeout(function () { heroBulletCount = heroBulletMax; heroReloading = false; reloadTxt.setText(''); }, 1000); } // Schedule next volley if more remain if (volleyIdx + 1 < shotsToFire && heroBulletCount > 0) { LK.setTimeout(function () { fireDoubleShotVolley(volleyIdx + 1); }, 300); // 300ms delay between shots } })(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); } } } } } } // Prevent enemy/wave spawn logic while Final Boss is active if (!finalBossActive) { spawnTimer++; if (spawnTimer >= spawnInterval) { spawnTimer = 0; var toSpawn = Math.max(1, Math.floor((1 + Math.floor(wave / 3)) / 4 * 0.75 / 2)); // halve the number of spawns 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 enemyKillTxt.setText('Kills: ' + enemyKillCount); if (!achievement50Shown && enemyKillCount >= 50) { achievement50Shown = true; achievement50Txt.visible = true; if (achievement50Timeout) { LK.clearTimeout(achievement50Timeout); } achievement50Timeout = LK.setTimeout(function () { achievement50Txt.visible = false; }, 2000); } 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; } } // Magnet/Attack Speed timer display if (hero.magnetActive && hero.magnetDuration > 0) { var magnetSeconds = Math.ceil(hero.magnetDuration / 60); magnetTimerTxt.setText('Magnet: ' + magnetSeconds + 's'); } else { magnetTimerTxt.setText(''); } if (typeof hero._attackSpeedBoostEndTick !== "undefined" && typeof ticksSurvived !== "undefined" && ticksSurvived < hero._attackSpeedBoostEndTick) { var aspSeconds = Math.ceil((hero._attackSpeedBoostEndTick - ticksSurvived) / 60); attackSpeedTimerTxt.setText('Attack Speed: ' + aspSeconds + 's'); } else { attackSpeedTimerTxt.setText(''); } // Show ammo count if not reloading if (!heroReloading) { reloadTxt.setText('Ammo: ' + heroBulletCount + '/' + heroBulletMax); } };
===================================================================
--- original.js
+++ change.js
@@ -749,9 +749,9 @@
var enemyProjectiles = []; // For RangedEnemy projectiles
var gems = [];
var powerups = [];
var spawnTimer = 0;
-var spawnInterval = 90;
+var spawnInterval = 180; // doubled from 90 to reduce frequency by 50%
var wave = 1;
var xp = 0;
var xpToLevel = 10;
var level = 1;
@@ -2111,9 +2111,9 @@
if (!finalBossActive) {
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnTimer = 0;
- var toSpawn = Math.max(1, Math.floor((1 + Math.floor(wave / 3)) / 4 * 0.75));
+ var toSpawn = Math.max(1, Math.floor((1 + Math.floor(wave / 3)) / 4 * 0.75 / 2)); // halve the number of spawns
for (var i = 0; i < toSpawn; i++) {
spawnEnemy();
}
// Boss spawn logic: spawn boss every 30 waves
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