User prompt
aftearr kill 4000 everything restes to kill 1 but the kill count stays the same and every 4000 kills it happelns agian
User prompt
give a zoom out button in the pouse menu
User prompt
remove the don'y fire if too close
User prompt
make the gun pint at the mouse
User prompt
mkae a bllet fire sound hwne you shoot and a zombine death sound when they die
User prompt
mkae a sick song for the game
User prompt
svae the score and when you lose keep it up ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
bullets make a line and evrey zombine in that lione dies and the line is insialbe and it bounces forme ebery zomibe in the diratdt of the next zomibe with another line to the edaer of th3e sceeen
User prompt
zombines spawn normally not every frame
User prompt
no spawn 1 bullet every framw if you hold it
User prompt
holding spawn 1 every frame
User prompt
bullets cap at 100
User prompt
zombines sould be capped less then bullets
User prompt
set bullet cap to ifyou auto click every millisecond at 209 and remove randomness
User prompt
bullets go to fast at 210 make bullets insitstly bounce abnd slow thgem down so they can hit zombines
User prompt
mkae it easiter at 200
User prompt
stop addd extra bullets they bounce an antra time insteed
User prompt
instead of adding extare bullets evry 10 kills bullets bounce betwtten emnemierys 1 more ebery 10 kills
User prompt
fix bullets stop sapwning at 150
User prompt
reudce max zomibes and reudce lab withou limiting bullets and when a bullet hit a zomibite it desapwns
User prompt
fix bullets stop spawning at 160
User prompt
Add colored zombies that have more health every 100 kills, and add every 100 kills, you get a new colored buttet that does more damage and when new zombibes appear you have less max zombibes
User prompt
fix bullets stop effacting zomibes and stop spawning at 170
User prompt
fix when it stop sapwning bullets
User prompt
fix stop spawniong bullets bug at 120
/**** * Plugins ****/ var tween = LK.import("@upit/tween.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); // Attach bullet asset, center anchor var bulletGfx = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Direction vector (set on spawn) self.dx = 0; self.dy = 0; self.speed = 40; // px per frame self.update = function () { // After 20 kills, home in on closest zombie if (score >= 20 && zombies && zombies.length > 0) { // Find closest zombie to bullet var minDist = Infinity; var closest = null; for (var i = 0; i < zombies.length; i++) { var z = zombies[i]; 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; // 0=none, 1=instant turn 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; } } } } 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); // Attach zombie asset, center anchor var zombieGfx = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); // Target position (player center) self.targetX = 0; self.targetY = 0; self.speed = 4 + Math.random() * 2; // Slight speed variation 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; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222244 // Changed to a visible dark blue for better contrast }); /**** * Game Code ****/ // 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 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); // 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() { var z = new Zombie(); // Pick random edge: 0=top, 1=bottom, 2=left, 3=right var edge = Math.floor(Math.random() * 4); var x, y; if (edge === 0) { // top x = 200 + Math.random() * (GAME_W - 400); y = -80; } else if (edge === 1) { // bottom x = 200 + Math.random() * (GAME_W - 400); y = GAME_H + 80; } else if (edge === 2) { // left x = -80; y = 300 + Math.random() * (GAME_H - 600); } else { // right x = GAME_W + 80; y = 300 + Math.random() * (GAME_H - 600); } z.x = x; z.y = y; z.targetX = gun.x; z.targetY = gun.y; // Stop zombie speed increase at 120 kills if (score > 120) { z.speed = 4 + Math.random() * 2; } else { // Optionally, you could have a speed formula that increases up to 120 kills, but current code is fixed // If you want to increase speed up to 120 kills, you could do: // z.speed = (4 + Math.random() * 2) + (score / 120) * SPEED_INCREASE; } 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); } if (dist < 10) return; // Don't fire if too close dx /= dist; dy /= dist; // Calculate number of bullets to fire: 1 + 1 per 10 kills (score), stop gaining extra after 120 kills var extraBullets = Math.floor(Math.min(score, 120) / 10); var numBullets = 1 + extraBullets; // Always allow at least 1 bullet to fire, even after 120 kills if (numBullets < 1) numBullets = 1; // Fire bullets in a spread (if more than 1) var spreadAngle = Math.PI / 12; // 15 degrees total spread for max bullets for (var n = 0; n < numBullets; n++) { var angle = Math.atan2(dy, dx); // Center bullet is straight, others are spread left/right var offset = 0; if (numBullets > 1) { offset = (n - (numBullets - 1) / 2) * (spreadAngle / Math.max(1, numBullets - 1)); } var bulletAngle = angle + offset; var b = new Bullet(); b.x = gun.x + Math.cos(bulletAngle) * 80; // Start a bit ahead of gun b.y = gun.y + Math.sin(bulletAngle) * 80; b.dx = Math.cos(bulletAngle); b.dy = Math.sin(bulletAngle); // Upgrade bullet speed, but cap at 120 kills var cappedScore = Math.min(score, 120); b.speed = bulletBaseSpeed + (gunLevel - 1) * 10; if (score > 120) { // After 120 kills, bullet speed does not increase further 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); } } // 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); } // 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); }; // Touch/mouse down: fire bullet 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) { fireBullet(x, y); } }; // 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) return; // Update bullets, but limit per frame to reduce lag var MAX_BULLETS_UPDATE = 120; var bulletsToUpdate = Math.min(bullets.length, MAX_BULLETS_UPDATE); for (var i = bullets.length - 1; i >= bullets.length - bulletsToUpdate && i >= 0; i--) { var b = bullets[i]; // Track previous position for despawn logic if (typeof b.lastX === "undefined") b.lastX = b.x; if (typeof b.lastY === "undefined") b.lastY = b.y; // Only update if bullet is on screen (with margin) if (b.x > -200 && b.x < GAME_W + 200 && b.y > -200 && b.y < GAME_H + 200) { b.update(); } // Despawn bullet if it just left the screen bounds if (b.lastX >= -100 && b.x < -100 || b.lastX <= GAME_W + 100 && b.x > GAME_W + 100 || b.lastY >= -100 && b.y < -100 || b.lastY <= GAME_H + 100 && b.y > GAME_H + 100) { b.destroy(); bullets.splice(i, 1); continue; } // Update lastX/lastY for next frame b.lastX = b.x; b.lastY = b.y; // Check collision with zombies, but limit checks per bullet for lag reduction var MAX_ZOMBIE_COLLISION_CHECKS = 40; var zombiesChecked = 0; for (var j = zombies.length - 1; j >= 0; j--) { if (zombiesChecked++ >= MAX_ZOMBIE_COLLISION_CHECKS) break; var z = zombies[j]; if (b.intersects(z)) { // Hit! z.destroy(); zombies.splice(j, 1); b.destroy(); bullets.splice(i, 1); // Score up score += 1; scoreTxt.setText(score); // Gun upgrade every 5 kills if (score > 0 && score % 5 === 0 && score !== lastUpgradeScore) { gunLevel += 1; lastUpgradeScore = score; // Optionally flash gun blue to show upgrade LK.effects.flashObject(gun, 0x3399ff, 500); } // Flash zombie green LK.effects.flashObject(gun, 0x00ff00, 200); break; } } } // Update zombies, but limit per frame to reduce lag var MAX_ZOMBIES_UPDATE = 120; var zombiesToUpdate = Math.min(zombies.length, MAX_ZOMBIES_UPDATE); for (var k = zombies.length - 1; k >= zombies.length - zombiesToUpdate && k >= 0; k--) { var z = zombies[k]; // 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; LK.effects.flashScreen(0xff0000, 1000); // Show game over and then leaderboard LK.showGameOver(function () { LK.showLeaderboard({ score: score }); }); return; } } // Spawn zombies at intervals, but limit max zombies for lag reduction var MAX_ZOMBIES = Math.min(120, 1 + Math.floor(score / 10) * 10); // Cap at 120 at 120 kills if (score >= 1200) MAX_ZOMBIES = 120; if (LK.ticks % 60 === 0) { // Every 1s // Increase spawn rate as score increases var toSpawn = 1 + Math.floor(score / 10); for (var s = 0; s < toSpawn; s++) { if (zombies.length < MAX_ZOMBIES) { spawnZombie(); } } } // No cap on number of bullets in play }; // 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(score); // 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: score }); }; LK.gui.topRight.addChild(leaderboardBtn); game.leaderboardBtn = leaderboardBtn; } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.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);
// Attach bullet asset, center anchor
var bulletGfx = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Direction vector (set on spawn)
self.dx = 0;
self.dy = 0;
self.speed = 40; // px per frame
self.update = function () {
// After 20 kills, home in on closest zombie
if (score >= 20 && zombies && zombies.length > 0) {
// Find closest zombie to bullet
var minDist = Infinity;
var closest = null;
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
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; // 0=none, 1=instant turn
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;
}
}
}
}
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);
// Attach zombie asset, center anchor
var zombieGfx = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position (player center)
self.targetX = 0;
self.targetY = 0;
self.speed = 4 + Math.random() * 2; // Slight speed variation
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;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222244 // Changed to a visible dark blue for better contrast
});
/****
* Game Code
****/
// 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 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);
// 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() {
var z = new Zombie();
// Pick random edge: 0=top, 1=bottom, 2=left, 3=right
var edge = Math.floor(Math.random() * 4);
var x, y;
if (edge === 0) {
// top
x = 200 + Math.random() * (GAME_W - 400);
y = -80;
} else if (edge === 1) {
// bottom
x = 200 + Math.random() * (GAME_W - 400);
y = GAME_H + 80;
} else if (edge === 2) {
// left
x = -80;
y = 300 + Math.random() * (GAME_H - 600);
} else {
// right
x = GAME_W + 80;
y = 300 + Math.random() * (GAME_H - 600);
}
z.x = x;
z.y = y;
z.targetX = gun.x;
z.targetY = gun.y;
// Stop zombie speed increase at 120 kills
if (score > 120) {
z.speed = 4 + Math.random() * 2;
} else {
// Optionally, you could have a speed formula that increases up to 120 kills, but current code is fixed
// If you want to increase speed up to 120 kills, you could do:
// z.speed = (4 + Math.random() * 2) + (score / 120) * SPEED_INCREASE;
}
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);
}
if (dist < 10) return; // Don't fire if too close
dx /= dist;
dy /= dist;
// Calculate number of bullets to fire: 1 + 1 per 10 kills (score), stop gaining extra after 120 kills
var extraBullets = Math.floor(Math.min(score, 120) / 10);
var numBullets = 1 + extraBullets;
// Always allow at least 1 bullet to fire, even after 120 kills
if (numBullets < 1) numBullets = 1;
// Fire bullets in a spread (if more than 1)
var spreadAngle = Math.PI / 12; // 15 degrees total spread for max bullets
for (var n = 0; n < numBullets; n++) {
var angle = Math.atan2(dy, dx);
// Center bullet is straight, others are spread left/right
var offset = 0;
if (numBullets > 1) {
offset = (n - (numBullets - 1) / 2) * (spreadAngle / Math.max(1, numBullets - 1));
}
var bulletAngle = angle + offset;
var b = new Bullet();
b.x = gun.x + Math.cos(bulletAngle) * 80; // Start a bit ahead of gun
b.y = gun.y + Math.sin(bulletAngle) * 80;
b.dx = Math.cos(bulletAngle);
b.dy = Math.sin(bulletAngle);
// Upgrade bullet speed, but cap at 120 kills
var cappedScore = Math.min(score, 120);
b.speed = bulletBaseSpeed + (gunLevel - 1) * 10;
if (score > 120) {
// After 120 kills, bullet speed does not increase further
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);
}
}
// 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);
}
// 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);
};
// Touch/mouse down: fire bullet
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) {
fireBullet(x, y);
}
};
// 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) return;
// Update bullets, but limit per frame to reduce lag
var MAX_BULLETS_UPDATE = 120;
var bulletsToUpdate = Math.min(bullets.length, MAX_BULLETS_UPDATE);
for (var i = bullets.length - 1; i >= bullets.length - bulletsToUpdate && i >= 0; i--) {
var b = bullets[i];
// Track previous position for despawn logic
if (typeof b.lastX === "undefined") b.lastX = b.x;
if (typeof b.lastY === "undefined") b.lastY = b.y;
// Only update if bullet is on screen (with margin)
if (b.x > -200 && b.x < GAME_W + 200 && b.y > -200 && b.y < GAME_H + 200) {
b.update();
}
// Despawn bullet if it just left the screen bounds
if (b.lastX >= -100 && b.x < -100 || b.lastX <= GAME_W + 100 && b.x > GAME_W + 100 || b.lastY >= -100 && b.y < -100 || b.lastY <= GAME_H + 100 && b.y > GAME_H + 100) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Update lastX/lastY for next frame
b.lastX = b.x;
b.lastY = b.y;
// Check collision with zombies, but limit checks per bullet for lag reduction
var MAX_ZOMBIE_COLLISION_CHECKS = 40;
var zombiesChecked = 0;
for (var j = zombies.length - 1; j >= 0; j--) {
if (zombiesChecked++ >= MAX_ZOMBIE_COLLISION_CHECKS) break;
var z = zombies[j];
if (b.intersects(z)) {
// Hit!
z.destroy();
zombies.splice(j, 1);
b.destroy();
bullets.splice(i, 1);
// Score up
score += 1;
scoreTxt.setText(score);
// Gun upgrade every 5 kills
if (score > 0 && score % 5 === 0 && score !== lastUpgradeScore) {
gunLevel += 1;
lastUpgradeScore = score;
// Optionally flash gun blue to show upgrade
LK.effects.flashObject(gun, 0x3399ff, 500);
}
// Flash zombie green
LK.effects.flashObject(gun, 0x00ff00, 200);
break;
}
}
}
// Update zombies, but limit per frame to reduce lag
var MAX_ZOMBIES_UPDATE = 120;
var zombiesToUpdate = Math.min(zombies.length, MAX_ZOMBIES_UPDATE);
for (var k = zombies.length - 1; k >= zombies.length - zombiesToUpdate && k >= 0; k--) {
var z = zombies[k];
// 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;
LK.effects.flashScreen(0xff0000, 1000);
// Show game over and then leaderboard
LK.showGameOver(function () {
LK.showLeaderboard({
score: score
});
});
return;
}
}
// Spawn zombies at intervals, but limit max zombies for lag reduction
var MAX_ZOMBIES = Math.min(120, 1 + Math.floor(score / 10) * 10); // Cap at 120 at 120 kills
if (score >= 1200) MAX_ZOMBIES = 120;
if (LK.ticks % 60 === 0) {
// Every 1s
// Increase spawn rate as score increases
var toSpawn = 1 + Math.floor(score / 10);
for (var s = 0; s < toSpawn; s++) {
if (zombies.length < MAX_ZOMBIES) {
spawnZombie();
}
}
}
// No cap on number of bullets in play
};
// 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(score);
// 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: score
});
};
LK.gui.topRight.addChild(leaderboardBtn);
game.leaderboardBtn = leaderboardBtn;
}
});