User prompt
reduce lag
User prompt
sceen bug agian need fixedblack c
User prompt
bullet speed caps at 120 kills
User prompt
you stop gaining extra bullets at 120 kills and zombines stop speeding up at 120 kills and bullets have no cap in the number
User prompt
max bulets can be fires at 120 kills and that is also max zombites spawn that kill too
User prompt
fix bugs teh scenn is black
User prompt
addd lag reducing
User prompt
bullets desapwn when they go out of teh sceen
User prompt
every 10 kills you gain 1 etraa bullet per fire
User prompt
and when you right click you can have it so your gun spins and itgoes back your next righ tlcik
User prompt
and after 20 kills your bullets have auto aim going to the closest zomibe to you
User prompt
add an upgrate to your gun every 5 kills
User prompt
player should be in teh middle
User prompt
add a online score borad
Code edit (1 edits merged)
Please save this source code
User prompt
Zombie Aim: Shooter Survival
Initial prompt
a shooter game wher you aim a gun at zombines and kill them you aim with your mouse and fires by clicking and it shows where the bullet will go
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Aiming line class var AimLine = Container.expand(function () { var self = Container.call(this); // Attach aim line asset, anchor at left (0,0.5) var lineGfx = self.attachAsset('aimLine', { anchorX: 0, anchorY: 0.5 }); // Set default scale lineGfx.scaleY = 0.2; // Thin line // Set color, etc. if needed return self; }); // Bullet class var Bullet = Container.expand(function () { var self = Container.call(this); // Determine bullet tier based on score at spawn var tier = Math.floor(score / 100); if (tier >= bulletColors.length) tier = bulletColors.length - 1; self.tier = tier; // Attach bullet asset, center anchor, color by tier var bulletGfx = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5, color: bulletColors[tier] }); // Direction vector (set on spawn) self.dx = 0; self.dy = 0; self.speed = 40 + tier * 2; // px per frame, slight speed bonus per tier // Damage increases with tier self.damage = 1 + tier; self.update = function () { // Bullet bounce logic: number of bounces = 1 + floor(score/10) if (typeof self.bouncesLeft === "undefined") { self.bouncesLeft = 1 + Math.floor(score / 10); self.lastHitZombies = []; } // If bullet just bounced, skip homing for this frame if (self.justBounced) { self.justBounced = false; } else if (zombies && zombies.length > 0) { // Home in on closest zombie not already hit var minDist = Infinity; var closest = null; for (var i = 0; i < zombies.length; i++) { var z = zombies[i]; // Don't home to zombies already hit by this bullet if (self.lastHitZombies.indexOf(z) !== -1) continue; var zx = z.x - self.x; var zy = z.y - self.y; var d = Math.sqrt(zx * zx + zy * zy); if (d < minDist) { minDist = d; closest = z; } } if (closest && minDist > 1) { // Adjust direction toward closest zombie var dx = closest.x - self.x; var dy = closest.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { // Smoothly adjust direction (homing) var homingStrength = 0.18; self.dx = (1 - homingStrength) * self.dx + homingStrength * (dx / dist); self.dy = (1 - homingStrength) * self.dy + homingStrength * (dy / dist); // Normalize direction var norm = Math.sqrt(self.dx * self.dx + self.dy * self.dy); if (norm > 0) { self.dx /= norm; self.dy /= norm; } } } } // At score >= 210, slow down bullet and instantly bounce to next zombie if possible if (score >= 210) { // Slow down bullet self.speed = Math.max(10, 18 + self.tier * 2); // Instantly bounce to next zombie if bounces left and zombies available if (typeof self.bouncesLeft === "undefined") { self.bouncesLeft = 1 + Math.floor(score / 10); self.lastHitZombies = []; } if (self.bouncesLeft > 0 && zombies && zombies.length > 1) { // Find next closest zombie not already hit var minDist = Infinity; var nextTarget = null; for (var zz = 0; zz < zombies.length; zz++) { var z2 = zombies[zz]; if (self.lastHitZombies.indexOf(z2) !== -1) continue; var zx = z2.x - self.x; var zy = z2.y - self.y; var d = Math.sqrt(zx * zx + zy * zy); if (d < minDist) { minDist = d; nextTarget = z2; } } if (nextTarget) { // Set direction to next zombie var dx = nextTarget.x - self.x; var dy = nextTarget.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.dx = dx / dist; self.dy = dy / dist; self.lastHitZombies.push(nextTarget); self.bouncesLeft--; } } } } self.x += self.dx * self.speed; self.y += self.dy * self.speed; }; return self; }); // Gun/player class var Gun = Container.expand(function () { var self = Container.call(this); // Attach gun asset, center anchor var gunGfx = self.attachAsset('gun', { anchorX: 0.5, anchorY: 0.5 }); // For possible future use: gunGfx return self; }); // Zombie class var Zombie = Container.expand(function () { var self = Container.call(this); // Determine zombie tier based on score at spawn var tier = Math.floor(score / 100); if (tier >= zombieColors.length) tier = zombieColors.length - 1; self.tier = tier; // Attach zombie asset, center anchor, color by tier var zombieGfx = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5, color: zombieColors[tier] }); // Target position (player center) self.targetX = 0; self.targetY = 0; self.speed = 5 + tier * 0.5; // Fixed speed, no randomness // Health increases with tier self.maxHealth = 1 + tier * 2; self.health = self.maxHealth; self.update = function () { // Move toward target var dx = self.targetX - self.x; var dy = self.targetY - 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; } }; // Take damage method self.takeDamage = function (dmg) { self.health -= dmg; if (self.health <= 0) { self.destroy(); return true; // killed } // Flash on hit LK.effects.flashObject(self, 0xffffff, 120); return false; // not dead }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222244 // Changed to a visible dark blue for better contrast }); /**** * Game Code ****/ // --- Begin: Bouncing kill line logic --- // --- Begin: 4000 kill reset logic --- if (typeof game._lastResetKill === "undefined") game._lastResetKill = 0; if (score - game._lastResetKill >= 4000) { // Save the current kill count var totalKills = score; // Reset all gameplay variables except kill count // Remove all bullets/zombies for (var i = 0; i < bullets.length; i++) bullets[i].destroy(); for (var j = 0; j < zombies.length; j++) zombies[j].destroy(); bullets = []; zombies = []; // Reset upgrades, gun, aim, etc. gunLevel = 1; lastUpgradeScore = 0; isGameOver = false; game.rightClickSpinState = false; if (game.rightClickSpinTween) { tween.kill(game.rightClickSpinTween); game.rightClickSpinTween = null; } gun.rotation = 0; aimX = gun.x; aimY = gun.y - 400; updateAimLine(aimX, aimY); // Reset spawn tick game.lastZombieSpawnTick = LK.ticks; // Reset score to 1 (kill count stays the same) score = 1; scoreTxt.setText(totalKills); // Mark last reset game._lastResetKill = totalKills; // Flash to indicate reset LK.effects.flashScreen(0x00ffff, 800); // Optionally, show a message (not required) } // --- End: 4000 kill reset logic --- // Game constants // Gun (player) asset: a box, centered // Bullet asset: small ellipse // Zombie asset: greenish box // Aiming line: thin yellow box (will be scaled) var bulletColors = [0xffe066, // yellow 0x2196f3, // blue 0xff9800, // orange 0xe91e63, // pink 0x9c27b0, // purple 0xffeb3b, // yellow2 0x795548, // brown 0x607d8b, // gray-blue 0xf44336, // red 0x00bcd4 // cyan ]; // Zombie class var zombieColors = [0x4caf50, // green 0x2196f3, // blue 0xff9800, // orange 0xe91e63, // pink 0x9c27b0, // purple 0xffeb3b, // yellow 0x795548, // brown 0x607d8b, // gray-blue 0xf44336, // red 0x00bcd4 // cyan ]; var GAME_W = 2048; var GAME_H = 2732; var PLAYER_X = GAME_W / 2; var PLAYER_Y = GAME_H / 2; // Center of the screen // Game state var bullets = []; var zombies = []; var score = 0; var isGameOver = false; // Gun upgrade state var gunLevel = 1; var lastUpgradeScore = 0; var bulletBaseSpeed = 40; var aimLineBaseLen = 700; // Create gun/player var gun = new Gun(); gun.x = PLAYER_X; gun.y = PLAYER_Y; game.addChild(gun); // Create aiming line var aimLine = new AimLine(); aimLine.x = gun.x; aimLine.y = gun.y; game.addChild(aimLine); // Score text var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Last score text (shows after game over) var lastScoreTxt = new Text2('', { size: 80, fill: 0xFFE066 }); lastScoreTxt.anchor.set(0.5, 0); lastScoreTxt.visible = false; lastScoreTxt.y = 140; LK.gui.top.addChild(lastScoreTxt); // Helper: clamp function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Helper: spawn zombie at random edge, aiming at player function spawnZombie() { // Determine zombie tier based on score at spawn var tier = Math.floor(score / 100); // Deterministic edge: cycle through edges in order (top, bottom, left, right) if (typeof spawnZombie.edgeIndex === "undefined") spawnZombie.edgeIndex = 0; var edge = spawnZombie.edgeIndex % 4; spawnZombie.edgeIndex++; var x, y; if (edge === 0) { // top x = 200; y = -80; } else if (edge === 1) { // bottom x = 200; y = GAME_H + 80; } else if (edge === 2) { // left x = -80; y = 300; } else { // right x = GAME_W + 80; y = 300; } var z = new Zombie(); z.x = x; z.y = y; z.targetX = gun.x; z.targetY = gun.y; zombies.push(z); game.addChild(z); } // Helper: fire bullet toward (tx,ty) function fireBullet(tx, ty) { // If score >= 20 and there are zombies, auto-aim at closest zombie var autoAim = score >= 20 && zombies.length > 0; var dx, dy, dist; if (autoAim) { // Find closest zombie to gun var minDist = Infinity; var closest = null; for (var i = 0; i < zombies.length; i++) { var z = zombies[i]; var zx = z.x - gun.x; var zy = z.y - gun.y; var d = Math.sqrt(zx * zx + zy * zy); if (d < minDist) { minDist = d; closest = z; } } if (closest) { dx = closest.x - gun.x; dy = closest.y - gun.y; dist = Math.sqrt(dx * dx + dy * dy); // If zombie is on top of gun, fallback to normal aim if (dist < 10) { dx = tx - gun.x; dy = ty - gun.y; dist = Math.sqrt(dx * dx + dy * dy); } } else { dx = tx - gun.x; dy = ty - gun.y; dist = Math.sqrt(dx * dx + dy * dy); } } else { dx = tx - gun.x; dy = ty - gun.y; dist = Math.sqrt(dx * dx + dy * dy); } // Removed 'don't fire if too close' check dx /= dist; dy /= dist; // Only fire a single bullet, but set its bounces based on score // Bullet cap: max 100 bullets at once if (bullets.length >= 100) { return; } var angle = Math.atan2(dy, dx); var b = new Bullet(); b.x = gun.x + Math.cos(angle) * 80; // Start a bit ahead of gun b.y = gun.y + Math.sin(angle) * 80; b.dx = Math.cos(angle); b.dy = Math.sin(angle); // Upgrade bullet speed, slow down at score >= 210 if (score >= 210) { b.speed = 18 + (gunLevel - 1) * 2; } else { b.speed = bulletBaseSpeed + (gunLevel - 1) * 10; } // Track lastX and lastY for despawn logic b.lastX = b.x; b.lastY = b.y; bullets.push(b); game.addChild(b); // Play bullet fire sound LK.getSound('bulletFire').play(); } // Update aiming line to point from gun to (aimX, aimY) function updateAimLine(aimX, aimY) { var dx = aimX - gun.x; var dy = aimY - gun.y; var dist = Math.sqrt(dx * dx + dy * dy); // Clamp min/max length var minLen = 120, maxLen = aimLineBaseLen + (gunLevel - 1) * 80; var len = clamp(dist, minLen, maxLen); // Set scaleX of line aimLine.scale.x = len / 20; // asset width is 20 // Set rotation aimLine.rotation = Math.atan2(dy, dx); // Make the gun point at the aim direction gun.rotation = Math.atan2(dy, dx); } // Initial aim position: straight up var aimX = gun.x; var aimY = gun.y - 400; updateAimLine(aimX, aimY); // Touch/mouse move: update aim game.move = function (x, y, obj) { // Clamp aim point to inside game area aimX = clamp(x, 100, GAME_W - 100); aimY = clamp(y, 100, GAME_H - 100); updateAimLine(aimX, aimY); }; // Hold-to-fire state var isFiring = false; // Touch/mouse down: start firing game.down = function (x, y, obj) { if (isGameOver) return; // Only fire on left click/tap (event.button==0 or undefined for touch) if (!obj || !obj.event || obj.event.button === undefined || obj.event.button === 0) { isFiring = true; // Fire immediately on down fireBullet(x, y); } }; // Touch/mouse up: stop firing game.up = function (x, y, obj) { isFiring = false; }; // Right click: spin gun and toggle spin state game.rightClickSpinState = false; game.rightClickSpinTween = null; game.rightClick = function (x, y, obj) { if (isGameOver) return; // Toggle spin state game.rightClickSpinState = !game.rightClickSpinState; // If already spinning, stop previous tween if (game.rightClickSpinTween) { tween.kill(game.rightClickSpinTween); game.rightClickSpinTween = null; } if (game.rightClickSpinState) { // Spin gun 2 full turns (4*PI) over 0.7s var startRot = gun.rotation; var endRot = startRot + Math.PI * 4; game.rightClickSpinTween = tween.to(gun, { rotation: endRot }, 700, { easing: "easeInOutCubic" }); } else { // Spin back to original (0) over 0.7s var curRot = gun.rotation; // Normalize to [-PI, PI] for shortest path var normRot = (curRot % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2); if (normRot > Math.PI) normRot -= Math.PI * 2; game.rightClickSpinTween = tween.to(gun, { rotation: 0 }, 700, { easing: "easeInOutCubic" }); } }; // Listen for right click (button==2) game.on('down', function (x, y, obj) { if (isGameOver) return; if (obj && obj.event && obj.event.button === 2) { game.rightClick(x, y, obj); } }); // Prevent context menu on right click if (typeof window !== "undefined" && window.addEventListener) { window.addEventListener("contextmenu", function (e) { e.preventDefault(); }); } // No drag, so up is not needed // Game update loop game.update = function () { if (isGameOver) { // Show last score text if available if (storage.lastScore !== undefined) { lastScoreTxt.setText("Last: " + storage.lastScore); lastScoreTxt.visible = true; } return; } lastScoreTxt.visible = false; // --- Begin: Bouncing kill line logic --- // Remove all previous aim lines if (!game.bounceLines) game.bounceLines = []; for (var li = 0; li < game.bounceLines.length; li++) { game.bounceLines[li].destroy(); } game.bounceLines = []; // For each bullet, create a visible line that bounces from zombie to zombie, killing all zombies in the path for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; // Make bullet invisible if (b.children && b.children.length > 0) { for (var ci = 0; ci < b.children.length; ci++) { b.children[ci].alpha = 0; } } // Track previous position for despawn logic if (typeof b.lastX === "undefined") b.lastX = b.x; if (typeof b.lastY === "undefined") b.lastY = b.y; // Build the bounce path: start at bullet, bounce to closest zombie not yet hit, repeat up to bouncesLeft, then to screen edge var pathPoints = [{ x: b.x, y: b.y }]; var hitZombies = []; var curX = b.x, curY = b.y; var bounces = typeof b.bouncesLeft !== "undefined" ? b.bouncesLeft : 1 + Math.floor(score / 10); var zombiesLeft = zombies.slice(); // Remove zombies already hit by this bullet if (b.lastHitZombies && b.lastHitZombies.length) { for (var lhz = 0; lhz < b.lastHitZombies.length; lhz++) { var idx = zombiesLeft.indexOf(b.lastHitZombies[lhz]); if (idx !== -1) zombiesLeft.splice(idx, 1); } } // Build bounce path for (var bounce = 0; bounce < bounces; bounce++) { // Find closest zombie not yet hit var minDist = Infinity, closest = null, closestIdx = -1; for (var zi = 0; zi < zombiesLeft.length; zi++) { var z = zombiesLeft[zi]; var dx = z.x - curX; var dy = z.y - curY; var d = Math.sqrt(dx * dx + dy * dy); if (d < minDist) { minDist = d; closest = z; closestIdx = zi; } } if (closest) { pathPoints.push({ x: closest.x, y: closest.y }); hitZombies.push(closest); curX = closest.x; curY = closest.y; zombiesLeft.splice(closestIdx, 1); } else { break; } } // After last bounce, extend line to screen edge in the same direction var lastPt = pathPoints[pathPoints.length - 1]; var prevPt = pathPoints.length > 1 ? pathPoints[pathPoints.length - 2] : { x: b.x, y: b.y }; var dx = lastPt.x - prevPt.x; var dy = lastPt.y - prevPt.y; var norm = Math.sqrt(dx * dx + dy * dy); if (norm === 0) { dx = 1; dy = 0; norm = 1; } dx /= norm; dy /= norm; // Find intersection with screen edge var tMax = 99999; var tx = dx > 0 ? (GAME_W - lastPt.x) / dx : dx < 0 ? (0 - lastPt.x) / dx : tMax; var ty = dy > 0 ? (GAME_H - lastPt.y) / dy : dy < 0 ? (0 - lastPt.y) / dy : tMax; var t = Math.min(Math.abs(tx), Math.abs(ty)); var edgeX = lastPt.x + dx * t; var edgeY = lastPt.y + dy * t; // Clamp to screen edgeX = clamp(edgeX, 0, GAME_W); edgeY = clamp(edgeY, 0, GAME_H); pathPoints.push({ x: edgeX, y: edgeY }); // Draw the visible bouncing line segments for (var pi = 0; pi < pathPoints.length - 1; pi++) { var p0 = pathPoints[pi]; var p1 = pathPoints[pi + 1]; var segLen = Math.sqrt((p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y)); if (segLen < 1) continue; var line = new AimLine(); line.x = p0.x; line.y = p0.y; line.scale.x = segLen / 20; line.scale.y = 0.18; line.rotation = Math.atan2(p1.y - p0.y, p1.x - p0.x); line.alpha = 1; game.addChild(line); game.bounceLines.push(line); } // Kill all zombies that intersect any segment of the line (except already killed this frame) var killedZombies = []; for (var pi = 0; pi < pathPoints.length - 1; pi++) { var p0 = pathPoints[pi]; var p1 = pathPoints[pi + 1]; var segDx = p1.x - p0.x; var segDy = p1.y - p0.y; var segLen = Math.sqrt(segDx * segDx + segDy * segDy); if (segLen < 1) continue; for (var j = zombies.length - 1; j >= 0; j--) { var z = zombies[j]; // Don't double-kill if (killedZombies.indexOf(z) !== -1) continue; // Distance from zombie center to segment var zx = z.x, zy = z.y; var t = ((zx - p0.x) * segDx + (zy - p0.y) * segDy) / (segLen * segLen); t = Math.max(0, Math.min(1, t)); var projX = p0.x + t * segDx; var projY = p0.y + t * segDy; var distToLine = Math.sqrt((zx - projX) * (zx - projX) + (zy - projY) * (zy - projY)); // Use zombie's width as hit radius var hitRadius = 60; if (distToLine <= hitRadius) { // Kill zombie var killed = false; if (typeof z.takeDamage === "function") { killed = z.takeDamage(9999); } else { z.destroy(); killed = true; } if (killed) { zombies.splice(j, 1); killedZombies.push(z); // Play zombie death sound LK.getSound('zombieDeath').play(); score += 1; scoreTxt.setText((typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score); if (score > 0 && score % 5 === 0 && score !== lastUpgradeScore) { gunLevel += 1; lastUpgradeScore = score; LK.effects.flashObject(gun, 0x3399ff, 500); } LK.effects.flashObject(gun, 0x00ff00, 200); } } } } // Remove bullet after line is drawn and zombies are killed b.destroy(); bullets.splice(i, 1); } // --- End: Bouncing kill line logic --- // Update all zombies (no artificial cap that breaks at high scores) for (var k = zombies.length - 1; k >= 0; k--) { var z = zombies[k]; // Make it easier at score >= 200: reduce zombie speed if (score >= 200) { z.speed = Math.max(2, z.speed * 0.7); } // Only update if zombie is on screen (with margin) if (z.x > -200 && z.x < GAME_W + 200 && z.y > -200 && z.y < GAME_H + 200) { z.update(); } // Check if zombie reached player (gun) if (z.intersects(gun)) { // Game over isGameOver = true; // Save score to storage as lastScore and bestScore var totalKills = (typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score; storage.lastScore = totalKills; if (!storage.bestScore || totalKills > storage.bestScore) { storage.bestScore = totalKills; } LK.effects.flashScreen(0xff0000, 1000); // Show game over and then leaderboard LK.showGameOver(function () { LK.showLeaderboard({ score: totalKills }); }); return; } } // Make it easier at score >= 200: increase bullet bounces if (score >= 200) { for (var i = 0; i < bullets.length; i++) { var b = bullets[i]; if (typeof b.bouncesLeft !== "undefined") { // Give at least 2 extra bounces at 200+ b.bouncesLeft = Math.max(b.bouncesLeft, 3 + Math.floor(score / 100)); } } } // Spawn zombies at intervals, but reduce max zombies as score increases (never limit bullets) var baseMax = 120; var tier = Math.floor(score / 100); var MAX_ZOMBIES = baseMax - tier * 15; if (MAX_ZOMBIES < 10) MAX_ZOMBIES = 10; if (score >= 1200) MAX_ZOMBIES = 10; // Cap zombies to always be less than bullet cap (60 at score >= 209) if (score >= 209) { MAX_ZOMBIES = Math.min(MAX_ZOMBIES, 40); } // Spawn zombies at a normal interval if under cap if (typeof game.lastZombieSpawnTick === "undefined") game.lastZombieSpawnTick = 0; var ZOMBIE_SPAWN_INTERVAL = 30; // spawn every 30 frames (~0.5s at 60fps) if (zombies.length < MAX_ZOMBIES && LK.ticks - game.lastZombieSpawnTick >= ZOMBIE_SPAWN_INTERVAL) { spawnZombie(); game.lastZombieSpawnTick = LK.ticks; } // Fire a bullet every frame if holding down and not game over if (isFiring && !isGameOver) { fireBullet(aimX, aimY); } // No cap on number of bullets in play }; // --- Play sick music track on game start --- LK.playMusic('musicId', { loop: true, fade: { start: 0, end: 1, duration: 1200 } }); // --- Add zoom out button to pause menu --- if (!game.zoomOutBtn) { var zoomOutBtn = new Text2("Zoom Out", { size: 90, fill: 0xFFE066 }); zoomOutBtn.anchor.set(0.5, 0.5); zoomOutBtn.interactive = true; zoomOutBtn.buttonMode = true; zoomOutBtn.x = 0; zoomOutBtn.y = 200; zoomOutBtn.down = function () { // Zoom out by scaling the game container down to 0.7x, min 0.4x, max 1x if (!game._zoomLevel) game._zoomLevel = 1; game._zoomLevel = Math.max(0.4, game._zoomLevel - 0.15); game.scale.x = game._zoomLevel; game.scale.y = game._zoomLevel; }; // Add to pause menu overlay (centered) if (LK.gui.pause && LK.gui.pause.center) { LK.gui.pause.center.addChild(zoomOutBtn); } game.zoomOutBtn = zoomOutBtn; } // Reset state on game restart game.on('reset', function () { // Remove all bullets/zombies for (var i = 0; i < bullets.length; i++) bullets[i].destroy(); for (var j = 0; j < zombies.length; j++) zombies[j].destroy(); bullets = []; zombies = []; score = 0; isGameOver = false; scoreTxt.setText((typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score); lastScoreTxt.visible = false; // Reset gun upgrades gunLevel = 1; lastUpgradeScore = 0; // Reset gun spin state game.rightClickSpinState = false; if (game.rightClickSpinTween) { tween.kill(game.rightClickSpinTween); game.rightClickSpinTween = null; } gun.rotation = 0; // Reset aim aimX = gun.x; aimY = gun.y - 400; updateAimLine(aimX, aimY); // Add leaderboard button to GUI (top right) if (!game.leaderboardBtn) { var leaderboardBtn = new Text2("🏆", { size: 100, fill: 0xFFE066 }); leaderboardBtn.anchor.set(1, 0); leaderboardBtn.interactive = true; leaderboardBtn.buttonMode = true; leaderboardBtn.x = -40; leaderboardBtn.y = 20; leaderboardBtn.down = function () { LK.showLeaderboard({ score: (typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score }); }; LK.gui.topRight.addChild(leaderboardBtn); game.leaderboardBtn = leaderboardBtn; } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Aiming line class
var AimLine = Container.expand(function () {
var self = Container.call(this);
// Attach aim line asset, anchor at left (0,0.5)
var lineGfx = self.attachAsset('aimLine', {
anchorX: 0,
anchorY: 0.5
});
// Set default scale
lineGfx.scaleY = 0.2; // Thin line
// Set color, etc. if needed
return self;
});
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Determine bullet tier based on score at spawn
var tier = Math.floor(score / 100);
if (tier >= bulletColors.length) tier = bulletColors.length - 1;
self.tier = tier;
// Attach bullet asset, center anchor, color by tier
var bulletGfx = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
color: bulletColors[tier]
});
// Direction vector (set on spawn)
self.dx = 0;
self.dy = 0;
self.speed = 40 + tier * 2; // px per frame, slight speed bonus per tier
// Damage increases with tier
self.damage = 1 + tier;
self.update = function () {
// Bullet bounce logic: number of bounces = 1 + floor(score/10)
if (typeof self.bouncesLeft === "undefined") {
self.bouncesLeft = 1 + Math.floor(score / 10);
self.lastHitZombies = [];
}
// If bullet just bounced, skip homing for this frame
if (self.justBounced) {
self.justBounced = false;
} else if (zombies && zombies.length > 0) {
// Home in on closest zombie not already hit
var minDist = Infinity;
var closest = null;
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
// Don't home to zombies already hit by this bullet
if (self.lastHitZombies.indexOf(z) !== -1) continue;
var zx = z.x - self.x;
var zy = z.y - self.y;
var d = Math.sqrt(zx * zx + zy * zy);
if (d < minDist) {
minDist = d;
closest = z;
}
}
if (closest && minDist > 1) {
// Adjust direction toward closest zombie
var dx = closest.x - self.x;
var dy = closest.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
// Smoothly adjust direction (homing)
var homingStrength = 0.18;
self.dx = (1 - homingStrength) * self.dx + homingStrength * (dx / dist);
self.dy = (1 - homingStrength) * self.dy + homingStrength * (dy / dist);
// Normalize direction
var norm = Math.sqrt(self.dx * self.dx + self.dy * self.dy);
if (norm > 0) {
self.dx /= norm;
self.dy /= norm;
}
}
}
}
// At score >= 210, slow down bullet and instantly bounce to next zombie if possible
if (score >= 210) {
// Slow down bullet
self.speed = Math.max(10, 18 + self.tier * 2);
// Instantly bounce to next zombie if bounces left and zombies available
if (typeof self.bouncesLeft === "undefined") {
self.bouncesLeft = 1 + Math.floor(score / 10);
self.lastHitZombies = [];
}
if (self.bouncesLeft > 0 && zombies && zombies.length > 1) {
// Find next closest zombie not already hit
var minDist = Infinity;
var nextTarget = null;
for (var zz = 0; zz < zombies.length; zz++) {
var z2 = zombies[zz];
if (self.lastHitZombies.indexOf(z2) !== -1) continue;
var zx = z2.x - self.x;
var zy = z2.y - self.y;
var d = Math.sqrt(zx * zx + zy * zy);
if (d < minDist) {
minDist = d;
nextTarget = z2;
}
}
if (nextTarget) {
// Set direction to next zombie
var dx = nextTarget.x - self.x;
var dy = nextTarget.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.dx = dx / dist;
self.dy = dy / dist;
self.lastHitZombies.push(nextTarget);
self.bouncesLeft--;
}
}
}
}
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// Gun/player class
var Gun = Container.expand(function () {
var self = Container.call(this);
// Attach gun asset, center anchor
var gunGfx = self.attachAsset('gun', {
anchorX: 0.5,
anchorY: 0.5
});
// For possible future use: gunGfx
return self;
});
// Zombie class
var Zombie = Container.expand(function () {
var self = Container.call(this);
// Determine zombie tier based on score at spawn
var tier = Math.floor(score / 100);
if (tier >= zombieColors.length) tier = zombieColors.length - 1;
self.tier = tier;
// Attach zombie asset, center anchor, color by tier
var zombieGfx = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5,
color: zombieColors[tier]
});
// Target position (player center)
self.targetX = 0;
self.targetY = 0;
self.speed = 5 + tier * 0.5; // Fixed speed, no randomness
// Health increases with tier
self.maxHealth = 1 + tier * 2;
self.health = self.maxHealth;
self.update = function () {
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - 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;
}
};
// Take damage method
self.takeDamage = function (dmg) {
self.health -= dmg;
if (self.health <= 0) {
self.destroy();
return true; // killed
}
// Flash on hit
LK.effects.flashObject(self, 0xffffff, 120);
return false; // not dead
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222244 // Changed to a visible dark blue for better contrast
});
/****
* Game Code
****/
// --- Begin: Bouncing kill line logic ---
// --- Begin: 4000 kill reset logic ---
if (typeof game._lastResetKill === "undefined") game._lastResetKill = 0;
if (score - game._lastResetKill >= 4000) {
// Save the current kill count
var totalKills = score;
// Reset all gameplay variables except kill count
// Remove all bullets/zombies
for (var i = 0; i < bullets.length; i++) bullets[i].destroy();
for (var j = 0; j < zombies.length; j++) zombies[j].destroy();
bullets = [];
zombies = [];
// Reset upgrades, gun, aim, etc.
gunLevel = 1;
lastUpgradeScore = 0;
isGameOver = false;
game.rightClickSpinState = false;
if (game.rightClickSpinTween) {
tween.kill(game.rightClickSpinTween);
game.rightClickSpinTween = null;
}
gun.rotation = 0;
aimX = gun.x;
aimY = gun.y - 400;
updateAimLine(aimX, aimY);
// Reset spawn tick
game.lastZombieSpawnTick = LK.ticks;
// Reset score to 1 (kill count stays the same)
score = 1;
scoreTxt.setText(totalKills);
// Mark last reset
game._lastResetKill = totalKills;
// Flash to indicate reset
LK.effects.flashScreen(0x00ffff, 800);
// Optionally, show a message (not required)
}
// --- End: 4000 kill reset logic ---
// Game constants
// Gun (player) asset: a box, centered
// Bullet asset: small ellipse
// Zombie asset: greenish box
// Aiming line: thin yellow box (will be scaled)
var bulletColors = [0xffe066,
// yellow
0x2196f3,
// blue
0xff9800,
// orange
0xe91e63,
// pink
0x9c27b0,
// purple
0xffeb3b,
// yellow2
0x795548,
// brown
0x607d8b,
// gray-blue
0xf44336,
// red
0x00bcd4 // cyan
];
// Zombie class
var zombieColors = [0x4caf50,
// green
0x2196f3,
// blue
0xff9800,
// orange
0xe91e63,
// pink
0x9c27b0,
// purple
0xffeb3b,
// yellow
0x795548,
// brown
0x607d8b,
// gray-blue
0xf44336,
// red
0x00bcd4 // cyan
];
var GAME_W = 2048;
var GAME_H = 2732;
var PLAYER_X = GAME_W / 2;
var PLAYER_Y = GAME_H / 2; // Center of the screen
// Game state
var bullets = [];
var zombies = [];
var score = 0;
var isGameOver = false;
// Gun upgrade state
var gunLevel = 1;
var lastUpgradeScore = 0;
var bulletBaseSpeed = 40;
var aimLineBaseLen = 700;
// Create gun/player
var gun = new Gun();
gun.x = PLAYER_X;
gun.y = PLAYER_Y;
game.addChild(gun);
// Create aiming line
var aimLine = new AimLine();
aimLine.x = gun.x;
aimLine.y = gun.y;
game.addChild(aimLine);
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Last score text (shows after game over)
var lastScoreTxt = new Text2('', {
size: 80,
fill: 0xFFE066
});
lastScoreTxt.anchor.set(0.5, 0);
lastScoreTxt.visible = false;
lastScoreTxt.y = 140;
LK.gui.top.addChild(lastScoreTxt);
// Helper: clamp
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Helper: spawn zombie at random edge, aiming at player
function spawnZombie() {
// Determine zombie tier based on score at spawn
var tier = Math.floor(score / 100);
// Deterministic edge: cycle through edges in order (top, bottom, left, right)
if (typeof spawnZombie.edgeIndex === "undefined") spawnZombie.edgeIndex = 0;
var edge = spawnZombie.edgeIndex % 4;
spawnZombie.edgeIndex++;
var x, y;
if (edge === 0) {
// top
x = 200;
y = -80;
} else if (edge === 1) {
// bottom
x = 200;
y = GAME_H + 80;
} else if (edge === 2) {
// left
x = -80;
y = 300;
} else {
// right
x = GAME_W + 80;
y = 300;
}
var z = new Zombie();
z.x = x;
z.y = y;
z.targetX = gun.x;
z.targetY = gun.y;
zombies.push(z);
game.addChild(z);
}
// Helper: fire bullet toward (tx,ty)
function fireBullet(tx, ty) {
// If score >= 20 and there are zombies, auto-aim at closest zombie
var autoAim = score >= 20 && zombies.length > 0;
var dx, dy, dist;
if (autoAim) {
// Find closest zombie to gun
var minDist = Infinity;
var closest = null;
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
var zx = z.x - gun.x;
var zy = z.y - gun.y;
var d = Math.sqrt(zx * zx + zy * zy);
if (d < minDist) {
minDist = d;
closest = z;
}
}
if (closest) {
dx = closest.x - gun.x;
dy = closest.y - gun.y;
dist = Math.sqrt(dx * dx + dy * dy);
// If zombie is on top of gun, fallback to normal aim
if (dist < 10) {
dx = tx - gun.x;
dy = ty - gun.y;
dist = Math.sqrt(dx * dx + dy * dy);
}
} else {
dx = tx - gun.x;
dy = ty - gun.y;
dist = Math.sqrt(dx * dx + dy * dy);
}
} else {
dx = tx - gun.x;
dy = ty - gun.y;
dist = Math.sqrt(dx * dx + dy * dy);
}
// Removed 'don't fire if too close' check
dx /= dist;
dy /= dist;
// Only fire a single bullet, but set its bounces based on score
// Bullet cap: max 100 bullets at once
if (bullets.length >= 100) {
return;
}
var angle = Math.atan2(dy, dx);
var b = new Bullet();
b.x = gun.x + Math.cos(angle) * 80; // Start a bit ahead of gun
b.y = gun.y + Math.sin(angle) * 80;
b.dx = Math.cos(angle);
b.dy = Math.sin(angle);
// Upgrade bullet speed, slow down at score >= 210
if (score >= 210) {
b.speed = 18 + (gunLevel - 1) * 2;
} else {
b.speed = bulletBaseSpeed + (gunLevel - 1) * 10;
}
// Track lastX and lastY for despawn logic
b.lastX = b.x;
b.lastY = b.y;
bullets.push(b);
game.addChild(b);
// Play bullet fire sound
LK.getSound('bulletFire').play();
}
// Update aiming line to point from gun to (aimX, aimY)
function updateAimLine(aimX, aimY) {
var dx = aimX - gun.x;
var dy = aimY - gun.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Clamp min/max length
var minLen = 120,
maxLen = aimLineBaseLen + (gunLevel - 1) * 80;
var len = clamp(dist, minLen, maxLen);
// Set scaleX of line
aimLine.scale.x = len / 20; // asset width is 20
// Set rotation
aimLine.rotation = Math.atan2(dy, dx);
// Make the gun point at the aim direction
gun.rotation = Math.atan2(dy, dx);
}
// Initial aim position: straight up
var aimX = gun.x;
var aimY = gun.y - 400;
updateAimLine(aimX, aimY);
// Touch/mouse move: update aim
game.move = function (x, y, obj) {
// Clamp aim point to inside game area
aimX = clamp(x, 100, GAME_W - 100);
aimY = clamp(y, 100, GAME_H - 100);
updateAimLine(aimX, aimY);
};
// Hold-to-fire state
var isFiring = false;
// Touch/mouse down: start firing
game.down = function (x, y, obj) {
if (isGameOver) return;
// Only fire on left click/tap (event.button==0 or undefined for touch)
if (!obj || !obj.event || obj.event.button === undefined || obj.event.button === 0) {
isFiring = true;
// Fire immediately on down
fireBullet(x, y);
}
};
// Touch/mouse up: stop firing
game.up = function (x, y, obj) {
isFiring = false;
};
// Right click: spin gun and toggle spin state
game.rightClickSpinState = false;
game.rightClickSpinTween = null;
game.rightClick = function (x, y, obj) {
if (isGameOver) return;
// Toggle spin state
game.rightClickSpinState = !game.rightClickSpinState;
// If already spinning, stop previous tween
if (game.rightClickSpinTween) {
tween.kill(game.rightClickSpinTween);
game.rightClickSpinTween = null;
}
if (game.rightClickSpinState) {
// Spin gun 2 full turns (4*PI) over 0.7s
var startRot = gun.rotation;
var endRot = startRot + Math.PI * 4;
game.rightClickSpinTween = tween.to(gun, {
rotation: endRot
}, 700, {
easing: "easeInOutCubic"
});
} else {
// Spin back to original (0) over 0.7s
var curRot = gun.rotation;
// Normalize to [-PI, PI] for shortest path
var normRot = (curRot % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2);
if (normRot > Math.PI) normRot -= Math.PI * 2;
game.rightClickSpinTween = tween.to(gun, {
rotation: 0
}, 700, {
easing: "easeInOutCubic"
});
}
};
// Listen for right click (button==2)
game.on('down', function (x, y, obj) {
if (isGameOver) return;
if (obj && obj.event && obj.event.button === 2) {
game.rightClick(x, y, obj);
}
});
// Prevent context menu on right click
if (typeof window !== "undefined" && window.addEventListener) {
window.addEventListener("contextmenu", function (e) {
e.preventDefault();
});
}
// No drag, so up is not needed
// Game update loop
game.update = function () {
if (isGameOver) {
// Show last score text if available
if (storage.lastScore !== undefined) {
lastScoreTxt.setText("Last: " + storage.lastScore);
lastScoreTxt.visible = true;
}
return;
}
lastScoreTxt.visible = false;
// --- Begin: Bouncing kill line logic ---
// Remove all previous aim lines
if (!game.bounceLines) game.bounceLines = [];
for (var li = 0; li < game.bounceLines.length; li++) {
game.bounceLines[li].destroy();
}
game.bounceLines = [];
// For each bullet, create a visible line that bounces from zombie to zombie, killing all zombies in the path
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
// Make bullet invisible
if (b.children && b.children.length > 0) {
for (var ci = 0; ci < b.children.length; ci++) {
b.children[ci].alpha = 0;
}
}
// Track previous position for despawn logic
if (typeof b.lastX === "undefined") b.lastX = b.x;
if (typeof b.lastY === "undefined") b.lastY = b.y;
// Build the bounce path: start at bullet, bounce to closest zombie not yet hit, repeat up to bouncesLeft, then to screen edge
var pathPoints = [{
x: b.x,
y: b.y
}];
var hitZombies = [];
var curX = b.x,
curY = b.y;
var bounces = typeof b.bouncesLeft !== "undefined" ? b.bouncesLeft : 1 + Math.floor(score / 10);
var zombiesLeft = zombies.slice();
// Remove zombies already hit by this bullet
if (b.lastHitZombies && b.lastHitZombies.length) {
for (var lhz = 0; lhz < b.lastHitZombies.length; lhz++) {
var idx = zombiesLeft.indexOf(b.lastHitZombies[lhz]);
if (idx !== -1) zombiesLeft.splice(idx, 1);
}
}
// Build bounce path
for (var bounce = 0; bounce < bounces; bounce++) {
// Find closest zombie not yet hit
var minDist = Infinity,
closest = null,
closestIdx = -1;
for (var zi = 0; zi < zombiesLeft.length; zi++) {
var z = zombiesLeft[zi];
var dx = z.x - curX;
var dy = z.y - curY;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < minDist) {
minDist = d;
closest = z;
closestIdx = zi;
}
}
if (closest) {
pathPoints.push({
x: closest.x,
y: closest.y
});
hitZombies.push(closest);
curX = closest.x;
curY = closest.y;
zombiesLeft.splice(closestIdx, 1);
} else {
break;
}
}
// After last bounce, extend line to screen edge in the same direction
var lastPt = pathPoints[pathPoints.length - 1];
var prevPt = pathPoints.length > 1 ? pathPoints[pathPoints.length - 2] : {
x: b.x,
y: b.y
};
var dx = lastPt.x - prevPt.x;
var dy = lastPt.y - prevPt.y;
var norm = Math.sqrt(dx * dx + dy * dy);
if (norm === 0) {
dx = 1;
dy = 0;
norm = 1;
}
dx /= norm;
dy /= norm;
// Find intersection with screen edge
var tMax = 99999;
var tx = dx > 0 ? (GAME_W - lastPt.x) / dx : dx < 0 ? (0 - lastPt.x) / dx : tMax;
var ty = dy > 0 ? (GAME_H - lastPt.y) / dy : dy < 0 ? (0 - lastPt.y) / dy : tMax;
var t = Math.min(Math.abs(tx), Math.abs(ty));
var edgeX = lastPt.x + dx * t;
var edgeY = lastPt.y + dy * t;
// Clamp to screen
edgeX = clamp(edgeX, 0, GAME_W);
edgeY = clamp(edgeY, 0, GAME_H);
pathPoints.push({
x: edgeX,
y: edgeY
});
// Draw the visible bouncing line segments
for (var pi = 0; pi < pathPoints.length - 1; pi++) {
var p0 = pathPoints[pi];
var p1 = pathPoints[pi + 1];
var segLen = Math.sqrt((p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y));
if (segLen < 1) continue;
var line = new AimLine();
line.x = p0.x;
line.y = p0.y;
line.scale.x = segLen / 20;
line.scale.y = 0.18;
line.rotation = Math.atan2(p1.y - p0.y, p1.x - p0.x);
line.alpha = 1;
game.addChild(line);
game.bounceLines.push(line);
}
// Kill all zombies that intersect any segment of the line (except already killed this frame)
var killedZombies = [];
for (var pi = 0; pi < pathPoints.length - 1; pi++) {
var p0 = pathPoints[pi];
var p1 = pathPoints[pi + 1];
var segDx = p1.x - p0.x;
var segDy = p1.y - p0.y;
var segLen = Math.sqrt(segDx * segDx + segDy * segDy);
if (segLen < 1) continue;
for (var j = zombies.length - 1; j >= 0; j--) {
var z = zombies[j];
// Don't double-kill
if (killedZombies.indexOf(z) !== -1) continue;
// Distance from zombie center to segment
var zx = z.x,
zy = z.y;
var t = ((zx - p0.x) * segDx + (zy - p0.y) * segDy) / (segLen * segLen);
t = Math.max(0, Math.min(1, t));
var projX = p0.x + t * segDx;
var projY = p0.y + t * segDy;
var distToLine = Math.sqrt((zx - projX) * (zx - projX) + (zy - projY) * (zy - projY));
// Use zombie's width as hit radius
var hitRadius = 60;
if (distToLine <= hitRadius) {
// Kill zombie
var killed = false;
if (typeof z.takeDamage === "function") {
killed = z.takeDamage(9999);
} else {
z.destroy();
killed = true;
}
if (killed) {
zombies.splice(j, 1);
killedZombies.push(z);
// Play zombie death sound
LK.getSound('zombieDeath').play();
score += 1;
scoreTxt.setText((typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score);
if (score > 0 && score % 5 === 0 && score !== lastUpgradeScore) {
gunLevel += 1;
lastUpgradeScore = score;
LK.effects.flashObject(gun, 0x3399ff, 500);
}
LK.effects.flashObject(gun, 0x00ff00, 200);
}
}
}
}
// Remove bullet after line is drawn and zombies are killed
b.destroy();
bullets.splice(i, 1);
}
// --- End: Bouncing kill line logic ---
// Update all zombies (no artificial cap that breaks at high scores)
for (var k = zombies.length - 1; k >= 0; k--) {
var z = zombies[k];
// Make it easier at score >= 200: reduce zombie speed
if (score >= 200) {
z.speed = Math.max(2, z.speed * 0.7);
}
// Only update if zombie is on screen (with margin)
if (z.x > -200 && z.x < GAME_W + 200 && z.y > -200 && z.y < GAME_H + 200) {
z.update();
}
// Check if zombie reached player (gun)
if (z.intersects(gun)) {
// Game over
isGameOver = true;
// Save score to storage as lastScore and bestScore
var totalKills = (typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score;
storage.lastScore = totalKills;
if (!storage.bestScore || totalKills > storage.bestScore) {
storage.bestScore = totalKills;
}
LK.effects.flashScreen(0xff0000, 1000);
// Show game over and then leaderboard
LK.showGameOver(function () {
LK.showLeaderboard({
score: totalKills
});
});
return;
}
}
// Make it easier at score >= 200: increase bullet bounces
if (score >= 200) {
for (var i = 0; i < bullets.length; i++) {
var b = bullets[i];
if (typeof b.bouncesLeft !== "undefined") {
// Give at least 2 extra bounces at 200+
b.bouncesLeft = Math.max(b.bouncesLeft, 3 + Math.floor(score / 100));
}
}
}
// Spawn zombies at intervals, but reduce max zombies as score increases (never limit bullets)
var baseMax = 120;
var tier = Math.floor(score / 100);
var MAX_ZOMBIES = baseMax - tier * 15;
if (MAX_ZOMBIES < 10) MAX_ZOMBIES = 10;
if (score >= 1200) MAX_ZOMBIES = 10;
// Cap zombies to always be less than bullet cap (60 at score >= 209)
if (score >= 209) {
MAX_ZOMBIES = Math.min(MAX_ZOMBIES, 40);
}
// Spawn zombies at a normal interval if under cap
if (typeof game.lastZombieSpawnTick === "undefined") game.lastZombieSpawnTick = 0;
var ZOMBIE_SPAWN_INTERVAL = 30; // spawn every 30 frames (~0.5s at 60fps)
if (zombies.length < MAX_ZOMBIES && LK.ticks - game.lastZombieSpawnTick >= ZOMBIE_SPAWN_INTERVAL) {
spawnZombie();
game.lastZombieSpawnTick = LK.ticks;
}
// Fire a bullet every frame if holding down and not game over
if (isFiring && !isGameOver) {
fireBullet(aimX, aimY);
}
// No cap on number of bullets in play
};
// --- Play sick music track on game start ---
LK.playMusic('musicId', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// --- Add zoom out button to pause menu ---
if (!game.zoomOutBtn) {
var zoomOutBtn = new Text2("Zoom Out", {
size: 90,
fill: 0xFFE066
});
zoomOutBtn.anchor.set(0.5, 0.5);
zoomOutBtn.interactive = true;
zoomOutBtn.buttonMode = true;
zoomOutBtn.x = 0;
zoomOutBtn.y = 200;
zoomOutBtn.down = function () {
// Zoom out by scaling the game container down to 0.7x, min 0.4x, max 1x
if (!game._zoomLevel) game._zoomLevel = 1;
game._zoomLevel = Math.max(0.4, game._zoomLevel - 0.15);
game.scale.x = game._zoomLevel;
game.scale.y = game._zoomLevel;
};
// Add to pause menu overlay (centered)
if (LK.gui.pause && LK.gui.pause.center) {
LK.gui.pause.center.addChild(zoomOutBtn);
}
game.zoomOutBtn = zoomOutBtn;
}
// Reset state on game restart
game.on('reset', function () {
// Remove all bullets/zombies
for (var i = 0; i < bullets.length; i++) bullets[i].destroy();
for (var j = 0; j < zombies.length; j++) zombies[j].destroy();
bullets = [];
zombies = [];
score = 0;
isGameOver = false;
scoreTxt.setText((typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score);
lastScoreTxt.visible = false;
// Reset gun upgrades
gunLevel = 1;
lastUpgradeScore = 0;
// Reset gun spin state
game.rightClickSpinState = false;
if (game.rightClickSpinTween) {
tween.kill(game.rightClickSpinTween);
game.rightClickSpinTween = null;
}
gun.rotation = 0;
// Reset aim
aimX = gun.x;
aimY = gun.y - 400;
updateAimLine(aimX, aimY);
// Add leaderboard button to GUI (top right)
if (!game.leaderboardBtn) {
var leaderboardBtn = new Text2("🏆", {
size: 100,
fill: 0xFFE066
});
leaderboardBtn.anchor.set(1, 0);
leaderboardBtn.interactive = true;
leaderboardBtn.buttonMode = true;
leaderboardBtn.x = -40;
leaderboardBtn.y = 20;
leaderboardBtn.down = function () {
LK.showLeaderboard({
score: (typeof game._lastResetKill !== "undefined" ? game._lastResetKill : 0) + score
});
};
LK.gui.topRight.addChild(leaderboardBtn);
game.leaderboardBtn = leaderboardBtn;
}
});