/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bullet class var Bullet = Container.expand(function () { var self = Container.call(this); self.radius = 20; self.speed = 30; self.dir = -Math.PI / 2; self.damage = 1; self.fromPlayer = true; self.bulletAsset = 'bullet_basic'; self.sprite = null; self.init = function (asset, speed, dir, damage, fromPlayer) { self.bulletAsset = asset; self.speed = speed; self.dir = dir; self.damage = damage; self.fromPlayer = fromPlayer; if (self.sprite) self.removeChild(self.sprite); // Use realistic bullet image asset if available, fallback to shape if not var assetId = asset; if (asset === 'bullet_basic' || asset === 'bullet_basic_real') assetId = 'bullet_basic_real';else if (asset === 'bullet_big' || asset === 'bullet_big_real') assetId = 'bullet_big_real';else if (asset === 'bullet_rapid' || asset === 'bullet_rapid_real') assetId = 'bullet_rapid_real';else if (asset === 'bullet_spread' || asset === 'bullet_spread_real') assetId = 'bullet_spread_real'; self.sprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.sprite.width / 2; }; self.update = function () { self.x += Math.cos(self.dir) * self.speed; self.y += Math.sin(self.dir) * self.speed; }; return self; }); // Normal enemy (person-like, red shirt) var Enemy = Container.expand(function () { var self = Container.call(this); // Head var head = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 28, height: 28, y: -32, color: 0xffe0b2, shape: 'ellipse' }); // Eyes var leye = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 5, height: 5, x: -6, y: -36, color: 0x222222, shape: 'ellipse' }); var reye = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 5, height: 5, x: 6, y: -36, color: 0x222222, shape: 'ellipse' }); // Body var body = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 22, height: 38, y: -18, color: 0xc0392b, shape: 'box' }); // Left arm var larm = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 7, height: 24, x: -16, y: -12, color: 0xffe0b2, shape: 'box' }); // Right arm var rarm = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 7, height: 24, x: 16, y: -12, color: 0xffe0b2, shape: 'box' }); // Left leg var lleg = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 7, height: 22, x: -7, y: 20, color: 0x222222, shape: 'box' }); // Right leg var rleg = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 7, height: 22, x: 7, y: 20, color: 0x222222, shape: 'box' }); self.radius = 28; self.hp = 2; self.enemyType = "normal"; self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5; self.speed = 4 + Math.random() * 2; self.update = function () { self.x += Math.cos(self.dir) * self.speed; self.y += Math.sin(self.dir) * self.speed; }; return self; }); // Fast enemy (person-like, blue shirt, smaller) var EnemyFast = Container.expand(function () { var self = Container.call(this); // Head var head = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 18, height: 18, y: -20, color: 0xffe0b2, shape: 'ellipse' }); // Eyes var leye = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 3, height: 3, x: -4, y: -22, color: 0x222222, shape: 'ellipse' }); var reye = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 3, height: 3, x: 4, y: -22, color: 0x222222, shape: 'ellipse' }); // Body var body = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 13, height: 24, y: -10, color: 0x2980b9, shape: 'box' }); // Left arm var larm = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 4, height: 14, x: -10, y: -6, color: 0xffe0b2, shape: 'box' }); // Right arm var rarm = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 4, height: 14, x: 10, y: -6, color: 0xffe0b2, shape: 'box' }); // Left leg var lleg = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 4, height: 12, x: -4, y: 12, color: 0x222222, shape: 'box' }); // Right leg var rleg = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 4, height: 12, x: 4, y: 12, color: 0x222222, shape: 'box' }); self.radius = 18; self.hp = 1; self.enemyType = "fast"; self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5; self.speed = 8 + Math.random() * 2.5; self.update = function () { self.x += Math.cos(self.dir) * self.speed; self.y += Math.sin(self.dir) * self.speed; }; return self; }); // Tank enemy (person-like, green shirt, big) var EnemyTank = Container.expand(function () { var self = Container.call(this); // Head var head = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 38, height: 38, y: -48, color: 0xffe0b2, shape: 'ellipse' }); // Eyes var leye = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 7, height: 7, x: -10, y: -54, color: 0x222222, shape: 'ellipse' }); var reye = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 7, height: 7, x: 10, y: -54, color: 0x222222, shape: 'ellipse' }); // Body var body = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 32, height: 60, y: -20, color: 0x27ae60, shape: 'box' }); // Left arm var larm = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 36, x: -26, y: -10, color: 0xffe0b2, shape: 'box' }); // Right arm var rarm = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 36, x: 26, y: -10, color: 0xffe0b2, shape: 'box' }); // Left leg var lleg = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 28, x: -10, y: 44, color: 0x222222, shape: 'box' }); // Right leg var rleg = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 28, x: 10, y: 44, color: 0x222222, shape: 'box' }); self.radius = 38; self.hp = 6; self.enemyType = "tank"; self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5; self.speed = 2 + Math.random() * 1.2; self.update = function () { self.x += Math.cos(self.dir) * self.speed; self.y += Math.sin(self.dir) * self.speed; }; return self; }); // GunDrop class var GunDrop = Container.expand(function () { var self = Container.call(this); self.gun = null; var dropSprite = self.attachAsset('gun_drop', { anchorX: 0.5, anchorY: 0.5 }); self.radius = dropSprite.width / 2; self.setGun = function (gun) { self.gun = gun; dropSprite.tint = gun.color; }; self.update = function () { self.y += 8; }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); // Head var head = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, width: 54, height: 54, y: -44, color: 0xffe0b2 // skin color }); // Face (simple: eyes and mouth) var leye = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, width: 8, height: 8, x: -12, y: -44 - 6, color: 0x222222, shape: 'ellipse' }); var reye = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, width: 8, height: 8, x: 12, y: -44 - 6, color: 0x222222, shape: 'ellipse' }); var mouth = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, width: 18, height: 6, x: 0, y: -44 + 12, color: 0x8d5524, shape: 'ellipse' }); // Body var body = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.0, width: 28, height: 64, y: -17, color: 0x2980b9, shape: 'box' }); // Left arm var larm = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 40, x: -24, y: -8, color: 0x27ae60, shape: 'box' }); // Right arm var rarm = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 40, x: 24, y: -8, color: 0x27ae60, shape: 'box' }); // Gun sprite (player) - now using a realistic gun image var gunSprite = self.attachAsset('gun_real_player_img', { anchorX: 0.2, anchorY: 0.5, width: 64, height: 32, x: 36, y: 12 }); rarm.addChild(gunSprite); // Left leg var lleg = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 40, x: -10, y: 48, color: 0xf1c40f, shape: 'box' }); // Right leg var rleg = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.0, width: 12, height: 40, x: 10, y: 48, color: 0xf1c40f, shape: 'box' }); self.radius = 54; // head radius self.invulnTicks = 0; self.flashTween = null; // Animation state for realism self.animTick = Math.random() * 1000; self.lastX = 0; self.lastY = 0; self.update = function () { // Animate limbs for realism based on movement var dx = self.x - (self.lastX || self.x); var dy = self.y - (self.lastY || self.y); var speed = Math.sqrt(dx * dx + dy * dy); self.animTick += speed * 0.7 + 1; var phase = self.animTick * 0.09; var walkSwing = Math.sin(phase) * 0.7; var walkSwingLeg = Math.sin(phase + Math.PI / 2) * 0.7; // Arms swing larm.rotation = walkSwing * 0.5; rarm.rotation = -walkSwing * 0.5; // Legs swing lleg.rotation = -walkSwingLeg * 0.5; rleg.rotation = walkSwingLeg * 0.5; // Head bob head.y = -44 + Math.sin(phase) * 2; // Store last position for next frame self.lastX = self.x; self.lastY = self.y; }; self.setInvulnerable = function (ticks) { self.invulnTicks = ticks; if (self.flashTween) tween.stop(head, { alpha: true }); head.alpha = 0.5; self.flashTween = tween(head, { alpha: 1 }, { duration: 200, easing: tween.linear, onFinish: function onFinish() { tween(head, { alpha: 0.5 }, { duration: 200, easing: tween.linear, onFinish: function onFinish() { if (self.invulnTicks > 0) self.setInvulnerable(self.invulnTicks - 1);else head.alpha = 1; } }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c20 }); /**** * Game Code ****/ // Realistic bullet image assets // --- POLISH: Animated background --- var bgPolish = LK.getAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, x: 2048 / 2, y: 2732 / 2, color: 0x232a34, alpha: 0.13 }); game.addChildAt(bgPolish, 0); var bgPolish2 = LK.getAsset('enemy', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, x: 2048 / 2, y: 2732 / 2, color: 0x3a506b, alpha: 0.09 }); game.addChildAt(bgPolish2, 1); var bgPolishTick = 0; game.update = function (origUpdate) { return function () { bgPolishTick++; bgPolish.rotation = Math.sin(bgPolishTick / 300) * 0.04; bgPolish2.rotation = -Math.sin(bgPolishTick / 400) * 0.03; if (origUpdate) origUpdate.apply(this, arguments); }; }(game.update); // --- END POLISH --- // Game state // Guns (shapes for now, will be replaced by images automatically) // Sounds // Gun definitions var GUNS = [{ id: 'basic', name: 'Basic Gun', bulletAsset: 'bullet_basic_real', fireRate: 18, // ticks between shots bulletSpeed: 32, spread: 0, bulletsPerShot: 1, damage: 1, color: 0xf1c40f }, { id: 'spread', name: 'Spread Shot', bulletAsset: 'bullet_spread_real', fireRate: 28, bulletSpeed: 26, spread: 0.25, bulletsPerShot: 3, damage: 1, color: 0x8e44ad }, { id: 'rapid', name: 'Rapid Fire', bulletAsset: 'bullet_rapid_real', fireRate: 7, bulletSpeed: 24, spread: 0.08, bulletsPerShot: 1, damage: 1, color: 0x16a085 }, { id: 'big', name: 'Big Blaster', bulletAsset: 'bullet_big_real', fireRate: 30, bulletSpeed: 20, spread: 0, bulletsPerShot: 1, damage: 3, color: 0xff9800 }]; // Helper: get random gun (not in excludeIds) function getRandomGun(excludeIds) { var pool = []; for (var i = 0; i < GUNS.length; ++i) { if (!excludeIds || excludeIds.indexOf(GUNS[i].id) === -1) pool.push(GUNS[i]); if (enemyGunFireTick > 0) { enemyGunFireTick--; } } ; if (pool.length === 0) pool = GUNS; return pool[Math.floor(Math.random() * pool.length)]; } var player = null; var enemies = []; var bullets = []; var gunDrops = []; var score = 0; var scoreTxt = null; var gunTxt = null; var wave = 1; var ticksToNextWave = 0; var playerGuns = []; var currentGunIndex = 0; var fireCooldown = 0; var dragging = false; var dragOffsetX = 0; var dragOffsetY = 0; var lastTouchX = 0; var lastTouchY = 0; var enemySpeedMultiplier = 1; // NEW: multiplier for enemy speed var enemySpeedupTicks = 0; // NEW: ticks counter for speedup var gameArea = { x: 0, y: 0, w: 2048, h: 2732 - 200 }; // leave some space at bottom for gun UI // Setup UI scoreTxt = new Text2('0', { size: 120, fill: '#fff' }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // (Wave UI removed) gunTxt = new Text2('', { size: 70, fill: '#fff' }); gunTxt.anchor.set(0.5, 0); LK.gui.bottom.addChild(gunTxt); // Inventory UI var inventoryUI = []; function updateInventoryUI() { // Remove old UI for (var i = 0; i < inventoryUI.length; ++i) { if (inventoryUI[i].parent) inventoryUI[i].parent.removeChild(inventoryUI[i]); } inventoryUI = []; // Show all guns in inventory at the bottom, horizontally var spacing = 180; var startX = (2048 - (playerGuns.length - 1) * spacing) / 2; var y = 2732 - 120; for (var i = 0; i < playerGuns.length; ++i) { var gun = playerGuns[i]; var assetId = gun.bulletAsset; // Use gun image for inventory, fallback to bullet asset if not available var gunAsset = null; if (gun.id === 'basic') gunAsset = LK.getAsset('gun_real_player_img', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90 });else if (gun.id === 'spread') gunAsset = LK.getAsset('gun_real_player_img', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90 });else if (gun.id === 'rapid') gunAsset = LK.getAsset('gun_real_player_img', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90 });else if (gun.id === 'big') gunAsset = LK.getAsset('gun_real_player_img', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90 });else gunAsset = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90 }); gunAsset.x = startX + i * spacing; gunAsset.y = y; // Highlight current gun gunAsset.alpha = i === currentGunIndex ? 1 : 0.5; LK.gui.bottom.addChild(gunAsset); inventoryUI.push(gunAsset); // POLISH: pop effect for selected gun if (i === currentGunIndex) { gunAsset.scaleX = 1.25; gunAsset.scaleY = 1.25; tween(gunAsset, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.easeOutBack }); } } } // Initialize player player = new Player(); player.x = 2048 / 2; player.y = 2732 - 350; game.addChild(player); // POLISH: player spawn flash LK.effects.flashObject(player, 0xffffff, 300); // Give player a starting gun playerGuns = [GUNS[0]]; currentGunIndex = 0; updateGunUI(); updateInventoryUI(); // Spawn first wave spawnWave(wave); // Touch controls game.down = function (x, y, obj) { // Only start drag if touch is on player var dx = x - player.x; var dy = y - player.y; if (dx * dx + dy * dy < player.radius * player.radius * 1.5) { dragging = true; dragOffsetX = player.x - x; dragOffsetY = player.y - y; lastTouchX = x; lastTouchY = y; } }; game.move = function (x, y, obj) { if (dragging) { // Move player directly to the touch/mouse position, clamped to game area var nx = x; var ny = y; if (nx < gameArea.x + player.radius) nx = gameArea.x + player.radius; if (nx > gameArea.x + gameArea.w - player.radius) nx = gameArea.x + gameArea.w - player.radius; if (ny < gameArea.y + player.radius) ny = gameArea.y + player.radius; if (ny > gameArea.y + gameArea.h - player.radius) ny = gameArea.y + gameArea.h - player.radius; player.x = nx; player.y = ny; lastTouchX = x; lastTouchY = y; } }; game.up = function (x, y, obj) { dragging = false; }; // Tap bottom of screen to switch gun game.on('down', function (x, y, obj) { if (y > 2732 - 200) { // Switch gun if (playerGuns.length > 1) { currentGunIndex = (currentGunIndex + 1) % playerGuns.length; updateGunUI(); updateInventoryUI(); LK.getSound('gunchange').play(); // Clamp player to game area in case new gun is larger if (player.x < gameArea.x + player.radius) player.x = gameArea.x + player.radius; if (player.x > gameArea.x + gameArea.w - player.radius) player.x = gameArea.x + gameArea.w - player.radius; if (player.y < gameArea.y + player.radius) player.y = gameArea.y + player.radius; if (player.y > gameArea.y + gameArea.h - player.radius) player.y = gameArea.y + gameArea.h - player.radius; } } }); // Enemy gun fire rate control var enemyGunFireRate = 80; // Higher = slower, default 80 ticks between shots (was much faster before) var enemyGunFireTick = 0; // NEW: variables to control enemy gun speedup var enemyGunFireRateMin = 20; // Minimum allowed fire rate (fastest) var enemyGunFireRateSpeedupTicks = 0; // Ticks counter for gun speedup // Main update loop game.update = function () { // Player invulnerability if (player.invulnTicks > 0) player.invulnTicks--; // Firing fireCooldown--; if (dragging && fireCooldown <= 0) { var gun = playerGuns[currentGunIndex]; fireGun(gun); fireCooldown = gun.fireRate; } // Update bullets for (var i = bullets.length - 1; i >= 0; --i) { var b = bullets[i]; b.update(); // Remove if out of bounds if (b.x < -100 || b.x > 2148 || b.y < -100 || b.y > 2832) { b.destroy(); bullets.splice(i, 1); continue; } // Collisions if (b.fromPlayer) { // Hit enemy for (var j = enemies.length - 1; j >= 0; --j) { var e = enemies[j]; var dx = b.x - e.x; var dy = b.y - e.y; var dist = dx * dx + dy * dy; if (dist < (b.radius + e.radius) * (b.radius + e.radius)) { // Bullet hits enemy: deal damage e.hp -= b.damage; LK.getSound('enemyhit').play(); b.destroy(); bullets.splice(i, 1); if (e.hp <= 0) { // Enemy dies spawnGunDrop(e.x, e.y); // Kill animation: flash enemy red and scale up, then destroy tween(e, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 320, easing: tween.easeOutCubic, onFinish: function onFinish() { e.destroy(); } }); enemies.splice(j, 1); score += 10; scoreTxt.setText(score); // POLISH: score pop scoreTxt.scaleX = 1.3; scoreTxt.scaleY = 1.3; tween(scoreTxt, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.easeOutBack }); // Win condition: player wins at 500 points if (score >= 500) { LK.effects.flashScreen(0x00ff00, 1000); LK.showYouWin(); return; } } break; } } } else { // Enemy bullet hits player var dx = b.x - player.x; var dy = b.y - player.y; var dist = dx * dx + dy * dy; if (dist < (b.radius + player.radius) * (b.radius + player.radius)) { if (player.invulnTicks <= 0) { LK.effects.flashScreen(0xff0000, 600); player.setInvulnerable(40); b.destroy(); bullets.splice(i, 1); // Game over LK.showGameOver(); return; } } } } // Update enemies // Slowly increase enemy speed multiplier every 120 ticks (~2 seconds) enemySpeedupTicks++; if (enemySpeedupTicks >= 120) { enemySpeedupTicks = 0; enemySpeedMultiplier += 0.03; // Increase speed multiplier slowly if (enemySpeedMultiplier > 3) enemySpeedMultiplier = 3; // Cap to avoid going too fast } // NEW: Slowly speed up enemy gun by decreasing fire rate every 180 ticks (~3 seconds) enemyGunFireRateSpeedupTicks++; if (enemyGunFireRateSpeedupTicks >= 180) { enemyGunFireRateSpeedupTicks = 0; if (enemyGunFireRate > enemyGunFireRateMin) { enemyGunFireRate -= 2; // Decrease fire rate (speed up gun) if (enemyGunFireRate < enemyGunFireRateMin) enemyGunFireRate = enemyGunFireRateMin; } } for (var i = enemies.length - 1; i >= 0; --i) { var e = enemies[i]; // Apply speed multiplier for this frame var origSpeed = e.speed; e.speed = origSpeed * enemySpeedMultiplier; e.update(); e.speed = origSpeed; // Restore original speed for next frame // Remove if out of bounds if (e.x < -120 || e.x > 2168 || e.y > 2832) { e.destroy(); enemies.splice(i, 1); continue; } // Enemy collides with player var dx = e.x - player.x; var dy = e.y - player.y; var dist = dx * dx + dy * dy; if (dist < (e.radius + player.radius) * (e.radius + player.radius)) { if (player.invulnTicks <= 0) { LK.effects.flashScreen(0xff0000, 600); player.setInvulnerable(40); // Game over LK.showGameOver(); return; } } // Enemy fires at player (throttled by enemyGunFireRate) if (enemyGunFireTick <= 0) { if (Math.random() < 0.008 + wave * 0.001) { var dir = Math.atan2(player.y - e.y, player.x - e.x); var eb = new Bullet(); eb.init('bullet_basic', 18 + Math.random() * 6, dir, 1, false); eb.x = e.x; eb.y = e.y; bullets.push(eb); game.addChild(eb); enemyGunFireTick = enemyGunFireRate; } } } // Update gun drops for (var i = gunDrops.length - 1; i >= 0; --i) { var g = gunDrops[i]; g.update(); // Remove if out of bounds if (g.y > 2832) { g.destroy(); gunDrops.splice(i, 1); continue; } // Pickup var dx = g.x - player.x; var dy = g.y - player.y; var dist = dx * dx + dy * dy; if (dist < (g.radius + player.radius) * (g.radius + player.radius)) { // Add gun if not already owned if (!hasGun(g.gun.id)) { playerGuns.push(g.gun); currentGunIndex = playerGuns.length - 1; updateGunUI(); updateInventoryUI(); LK.getSound('pickup').play(); // Visual feedback for pickup LK.effects.flashObject(player, 0x00ff00, 400); } g.destroy(); gunDrops.splice(i, 1); } } // Next wave if (enemies.length === 0 && gunDrops.length === 0 && ticksToNextWave <= 0) { ticksToNextWave = 60; } if (ticksToNextWave > 0) { ticksToNextWave--; if (ticksToNextWave === 0) { wave++; spawnWave(wave); // (Wave UI update/animation removed) } } }; // Helper: fire gun function fireGun(gun) { for (var i = 0; i < gun.bulletsPerShot; ++i) { var b = new Bullet(); var spread = gun.spread * (Math.random() - 0.5); var dir = -Math.PI / 2 + spread + (gun.bulletsPerShot > 1 ? (i - (gun.bulletsPerShot - 1) / 2) * gun.spread : 0); b.init(gun.bulletAsset, gun.bulletSpeed, dir, gun.damage, true); b.x = player.x; b.y = player.y - player.radius; bullets.push(b); game.addChild(b); } LK.getSound('shoot').play(); } // Helper: spawn wave function spawnWave(w) { var n = 3 + Math.floor(w * 1.5); for (var i = 0; i < n; ++i) { // Choose enemy type: more tanks and fast enemies as wave increases var typeRand = Math.random(); var e; if (w > 2 && typeRand < Math.min(0.18 + w * 0.01, 0.33)) { // Tank enemy e = new EnemyTank(); e.hp = 6 + Math.floor(w / 2); e.speed = 2 + Math.random() * 0.8 + w * 0.04; } else if (w > 1 && typeRand < Math.min(0.45 + w * 0.01, 0.6)) { // Fast enemy e = new EnemyFast(); e.hp = 1 + Math.floor(w / 4); e.speed = 8 + Math.random() * 2.5 + w * 0.12; } else { // Normal enemy e = new Enemy(); e.hp = 2 + Math.floor(w / 2); e.speed = 2 + Math.random() * 1.5 + w * 0.08; } e.x = 200 + Math.random() * (2048 - 400); e.y = -100 - Math.random() * 200; // Add enemy to the game scene so it is visible game.addChild(e); enemies.push(e); // POLISH: spawn flash LK.effects.flashObject(e, 0xffffff, 200); } } // Helper: spawn gun drop function spawnGunDrop(x, y) { // 50% chance to drop a gun, only if player doesn't have all guns if (playerGuns.length >= GUNS.length) return; if (Math.random() < 0.5) { var exclude = []; for (var i = 0; i < playerGuns.length; ++i) exclude.push(playerGuns[i].id); var gun = getRandomGun(exclude); var g = new GunDrop(); g.x = x; g.y = y; g.setGun(gun); gunDrops.push(g); game.addChild(g); } } // Helper: update gun UI function updateGunUI() { var gun = playerGuns[currentGunIndex]; gunTxt.setText("Gun: " + gun.name + " (Tap here to switch)"); gunTxt.setStyle({ fill: "#" + gun.color.toString(16) }); updateInventoryUI(); } // Helper: has gun function hasGun(id) { for (var i = 0; i < playerGuns.length; ++i) if (playerGuns[i].id === id) return true; return false; }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.radius = 20;
self.speed = 30;
self.dir = -Math.PI / 2;
self.damage = 1;
self.fromPlayer = true;
self.bulletAsset = 'bullet_basic';
self.sprite = null;
self.init = function (asset, speed, dir, damage, fromPlayer) {
self.bulletAsset = asset;
self.speed = speed;
self.dir = dir;
self.damage = damage;
self.fromPlayer = fromPlayer;
if (self.sprite) self.removeChild(self.sprite);
// Use realistic bullet image asset if available, fallback to shape if not
var assetId = asset;
if (asset === 'bullet_basic' || asset === 'bullet_basic_real') assetId = 'bullet_basic_real';else if (asset === 'bullet_big' || asset === 'bullet_big_real') assetId = 'bullet_big_real';else if (asset === 'bullet_rapid' || asset === 'bullet_rapid_real') assetId = 'bullet_rapid_real';else if (asset === 'bullet_spread' || asset === 'bullet_spread_real') assetId = 'bullet_spread_real';
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.sprite.width / 2;
};
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Normal enemy (person-like, red shirt)
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 28,
height: 28,
y: -32,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: 5,
x: -6,
y: -36,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: 5,
x: 6,
y: -36,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 22,
height: 38,
y: -18,
color: 0xc0392b,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 24,
x: -16,
y: -12,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 24,
x: 16,
y: -12,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 22,
x: -7,
y: 20,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 22,
x: 7,
y: 20,
color: 0x222222,
shape: 'box'
});
self.radius = 28;
self.hp = 2;
self.enemyType = "normal";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 4 + Math.random() * 2;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Fast enemy (person-like, blue shirt, smaller)
var EnemyFast = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 18,
y: -20,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 3,
height: 3,
x: -4,
y: -22,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 3,
height: 3,
x: 4,
y: -22,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 13,
height: 24,
y: -10,
color: 0x2980b9,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 14,
x: -10,
y: -6,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 14,
x: 10,
y: -6,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 12,
x: -4,
y: 12,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 12,
x: 4,
y: 12,
color: 0x222222,
shape: 'box'
});
self.radius = 18;
self.hp = 1;
self.enemyType = "fast";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 8 + Math.random() * 2.5;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Tank enemy (person-like, green shirt, big)
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 38,
height: 38,
y: -48,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 7,
height: 7,
x: -10,
y: -54,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 7,
height: 7,
x: 10,
y: -54,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 32,
height: 60,
y: -20,
color: 0x27ae60,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 36,
x: -26,
y: -10,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 36,
x: 26,
y: -10,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 28,
x: -10,
y: 44,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 28,
x: 10,
y: 44,
color: 0x222222,
shape: 'box'
});
self.radius = 38;
self.hp = 6;
self.enemyType = "tank";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 2 + Math.random() * 1.2;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// GunDrop class
var GunDrop = Container.expand(function () {
var self = Container.call(this);
self.gun = null;
var dropSprite = self.attachAsset('gun_drop', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = dropSprite.width / 2;
self.setGun = function (gun) {
self.gun = gun;
dropSprite.tint = gun.color;
};
self.update = function () {
self.y += 8;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 54,
height: 54,
y: -44,
color: 0xffe0b2 // skin color
});
// Face (simple: eyes and mouth)
var leye = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8,
x: -12,
y: -44 - 6,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8,
x: 12,
y: -44 - 6,
color: 0x222222,
shape: 'ellipse'
});
var mouth = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 6,
x: 0,
y: -44 + 12,
color: 0x8d5524,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 28,
height: 64,
y: -17,
color: 0x2980b9,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: -24,
y: -8,
color: 0x27ae60,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: 24,
y: -8,
color: 0x27ae60,
shape: 'box'
});
// Gun sprite (player) - now using a realistic gun image
var gunSprite = self.attachAsset('gun_real_player_img', {
anchorX: 0.2,
anchorY: 0.5,
width: 64,
height: 32,
x: 36,
y: 12
});
rarm.addChild(gunSprite);
// Left leg
var lleg = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: -10,
y: 48,
color: 0xf1c40f,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: 10,
y: 48,
color: 0xf1c40f,
shape: 'box'
});
self.radius = 54; // head radius
self.invulnTicks = 0;
self.flashTween = null;
// Animation state for realism
self.animTick = Math.random() * 1000;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
// Animate limbs for realism based on movement
var dx = self.x - (self.lastX || self.x);
var dy = self.y - (self.lastY || self.y);
var speed = Math.sqrt(dx * dx + dy * dy);
self.animTick += speed * 0.7 + 1;
var phase = self.animTick * 0.09;
var walkSwing = Math.sin(phase) * 0.7;
var walkSwingLeg = Math.sin(phase + Math.PI / 2) * 0.7;
// Arms swing
larm.rotation = walkSwing * 0.5;
rarm.rotation = -walkSwing * 0.5;
// Legs swing
lleg.rotation = -walkSwingLeg * 0.5;
rleg.rotation = walkSwingLeg * 0.5;
// Head bob
head.y = -44 + Math.sin(phase) * 2;
// Store last position for next frame
self.lastX = self.x;
self.lastY = self.y;
};
self.setInvulnerable = function (ticks) {
self.invulnTicks = ticks;
if (self.flashTween) tween.stop(head, {
alpha: true
});
head.alpha = 0.5;
self.flashTween = tween(head, {
alpha: 1
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
tween(head, {
alpha: 0.5
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
if (self.invulnTicks > 0) self.setInvulnerable(self.invulnTicks - 1);else head.alpha = 1;
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// Realistic bullet image assets
// --- POLISH: Animated background ---
var bgPolish = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
color: 0x232a34,
alpha: 0.13
});
game.addChildAt(bgPolish, 0);
var bgPolish2 = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
color: 0x3a506b,
alpha: 0.09
});
game.addChildAt(bgPolish2, 1);
var bgPolishTick = 0;
game.update = function (origUpdate) {
return function () {
bgPolishTick++;
bgPolish.rotation = Math.sin(bgPolishTick / 300) * 0.04;
bgPolish2.rotation = -Math.sin(bgPolishTick / 400) * 0.03;
if (origUpdate) origUpdate.apply(this, arguments);
};
}(game.update);
// --- END POLISH ---
// Game state
// Guns (shapes for now, will be replaced by images automatically)
// Sounds
// Gun definitions
var GUNS = [{
id: 'basic',
name: 'Basic Gun',
bulletAsset: 'bullet_basic_real',
fireRate: 18,
// ticks between shots
bulletSpeed: 32,
spread: 0,
bulletsPerShot: 1,
damage: 1,
color: 0xf1c40f
}, {
id: 'spread',
name: 'Spread Shot',
bulletAsset: 'bullet_spread_real',
fireRate: 28,
bulletSpeed: 26,
spread: 0.25,
bulletsPerShot: 3,
damage: 1,
color: 0x8e44ad
}, {
id: 'rapid',
name: 'Rapid Fire',
bulletAsset: 'bullet_rapid_real',
fireRate: 7,
bulletSpeed: 24,
spread: 0.08,
bulletsPerShot: 1,
damage: 1,
color: 0x16a085
}, {
id: 'big',
name: 'Big Blaster',
bulletAsset: 'bullet_big_real',
fireRate: 30,
bulletSpeed: 20,
spread: 0,
bulletsPerShot: 1,
damage: 3,
color: 0xff9800
}];
// Helper: get random gun (not in excludeIds)
function getRandomGun(excludeIds) {
var pool = [];
for (var i = 0; i < GUNS.length; ++i) {
if (!excludeIds || excludeIds.indexOf(GUNS[i].id) === -1) pool.push(GUNS[i]);
if (enemyGunFireTick > 0) {
enemyGunFireTick--;
}
}
;
if (pool.length === 0) pool = GUNS;
return pool[Math.floor(Math.random() * pool.length)];
}
var player = null;
var enemies = [];
var bullets = [];
var gunDrops = [];
var score = 0;
var scoreTxt = null;
var gunTxt = null;
var wave = 1;
var ticksToNextWave = 0;
var playerGuns = [];
var currentGunIndex = 0;
var fireCooldown = 0;
var dragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastTouchX = 0;
var lastTouchY = 0;
var enemySpeedMultiplier = 1; // NEW: multiplier for enemy speed
var enemySpeedupTicks = 0; // NEW: ticks counter for speedup
var gameArea = {
x: 0,
y: 0,
w: 2048,
h: 2732 - 200
}; // leave some space at bottom for gun UI
// Setup UI
scoreTxt = new Text2('0', {
size: 120,
fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// (Wave UI removed)
gunTxt = new Text2('', {
size: 70,
fill: '#fff'
});
gunTxt.anchor.set(0.5, 0);
LK.gui.bottom.addChild(gunTxt);
// Inventory UI
var inventoryUI = [];
function updateInventoryUI() {
// Remove old UI
for (var i = 0; i < inventoryUI.length; ++i) {
if (inventoryUI[i].parent) inventoryUI[i].parent.removeChild(inventoryUI[i]);
}
inventoryUI = [];
// Show all guns in inventory at the bottom, horizontally
var spacing = 180;
var startX = (2048 - (playerGuns.length - 1) * spacing) / 2;
var y = 2732 - 120;
for (var i = 0; i < playerGuns.length; ++i) {
var gun = playerGuns[i];
var assetId = gun.bulletAsset;
// Use gun image for inventory, fallback to bullet asset if not available
var gunAsset = null;
if (gun.id === 'basic') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'spread') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'rapid') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'big') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else gunAsset = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});
gunAsset.x = startX + i * spacing;
gunAsset.y = y;
// Highlight current gun
gunAsset.alpha = i === currentGunIndex ? 1 : 0.5;
LK.gui.bottom.addChild(gunAsset);
inventoryUI.push(gunAsset);
// POLISH: pop effect for selected gun
if (i === currentGunIndex) {
gunAsset.scaleX = 1.25;
gunAsset.scaleY = 1.25;
tween(gunAsset, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOutBack
});
}
}
}
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 350;
game.addChild(player);
// POLISH: player spawn flash
LK.effects.flashObject(player, 0xffffff, 300);
// Give player a starting gun
playerGuns = [GUNS[0]];
currentGunIndex = 0;
updateGunUI();
updateInventoryUI();
// Spawn first wave
spawnWave(wave);
// Touch controls
game.down = function (x, y, obj) {
// Only start drag if touch is on player
var dx = x - player.x;
var dy = y - player.y;
if (dx * dx + dy * dy < player.radius * player.radius * 1.5) {
dragging = true;
dragOffsetX = player.x - x;
dragOffsetY = player.y - y;
lastTouchX = x;
lastTouchY = y;
}
};
game.move = function (x, y, obj) {
if (dragging) {
// Move player directly to the touch/mouse position, clamped to game area
var nx = x;
var ny = y;
if (nx < gameArea.x + player.radius) nx = gameArea.x + player.radius;
if (nx > gameArea.x + gameArea.w - player.radius) nx = gameArea.x + gameArea.w - player.radius;
if (ny < gameArea.y + player.radius) ny = gameArea.y + player.radius;
if (ny > gameArea.y + gameArea.h - player.radius) ny = gameArea.y + gameArea.h - player.radius;
player.x = nx;
player.y = ny;
lastTouchX = x;
lastTouchY = y;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Tap bottom of screen to switch gun
game.on('down', function (x, y, obj) {
if (y > 2732 - 200) {
// Switch gun
if (playerGuns.length > 1) {
currentGunIndex = (currentGunIndex + 1) % playerGuns.length;
updateGunUI();
updateInventoryUI();
LK.getSound('gunchange').play();
// Clamp player to game area in case new gun is larger
if (player.x < gameArea.x + player.radius) player.x = gameArea.x + player.radius;
if (player.x > gameArea.x + gameArea.w - player.radius) player.x = gameArea.x + gameArea.w - player.radius;
if (player.y < gameArea.y + player.radius) player.y = gameArea.y + player.radius;
if (player.y > gameArea.y + gameArea.h - player.radius) player.y = gameArea.y + gameArea.h - player.radius;
}
}
});
// Enemy gun fire rate control
var enemyGunFireRate = 80; // Higher = slower, default 80 ticks between shots (was much faster before)
var enemyGunFireTick = 0;
// NEW: variables to control enemy gun speedup
var enemyGunFireRateMin = 20; // Minimum allowed fire rate (fastest)
var enemyGunFireRateSpeedupTicks = 0; // Ticks counter for gun speedup
// Main update loop
game.update = function () {
// Player invulnerability
if (player.invulnTicks > 0) player.invulnTicks--;
// Firing
fireCooldown--;
if (dragging && fireCooldown <= 0) {
var gun = playerGuns[currentGunIndex];
fireGun(gun);
fireCooldown = gun.fireRate;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; --i) {
var b = bullets[i];
b.update();
// Remove if out of bounds
if (b.x < -100 || b.x > 2148 || b.y < -100 || b.y > 2832) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Collisions
if (b.fromPlayer) {
// Hit enemy
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
var dx = b.x - e.x;
var dy = b.y - e.y;
var dist = dx * dx + dy * dy;
if (dist < (b.radius + e.radius) * (b.radius + e.radius)) {
// Bullet hits enemy: deal damage
e.hp -= b.damage;
LK.getSound('enemyhit').play();
b.destroy();
bullets.splice(i, 1);
if (e.hp <= 0) {
// Enemy dies
spawnGunDrop(e.x, e.y);
// Kill animation: flash enemy red and scale up, then destroy
tween(e, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 320,
easing: tween.easeOutCubic,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(j, 1);
score += 10;
scoreTxt.setText(score);
// POLISH: score pop
scoreTxt.scaleX = 1.3;
scoreTxt.scaleY = 1.3;
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOutBack
});
// Win condition: player wins at 500 points
if (score >= 500) {
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
return;
}
}
break;
}
}
} else {
// Enemy bullet hits player
var dx = b.x - player.x;
var dy = b.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (b.radius + player.radius) * (b.radius + player.radius)) {
if (player.invulnTicks <= 0) {
LK.effects.flashScreen(0xff0000, 600);
player.setInvulnerable(40);
b.destroy();
bullets.splice(i, 1);
// Game over
LK.showGameOver();
return;
}
}
}
}
// Update enemies
// Slowly increase enemy speed multiplier every 120 ticks (~2 seconds)
enemySpeedupTicks++;
if (enemySpeedupTicks >= 120) {
enemySpeedupTicks = 0;
enemySpeedMultiplier += 0.03; // Increase speed multiplier slowly
if (enemySpeedMultiplier > 3) enemySpeedMultiplier = 3; // Cap to avoid going too fast
}
// NEW: Slowly speed up enemy gun by decreasing fire rate every 180 ticks (~3 seconds)
enemyGunFireRateSpeedupTicks++;
if (enemyGunFireRateSpeedupTicks >= 180) {
enemyGunFireRateSpeedupTicks = 0;
if (enemyGunFireRate > enemyGunFireRateMin) {
enemyGunFireRate -= 2; // Decrease fire rate (speed up gun)
if (enemyGunFireRate < enemyGunFireRateMin) enemyGunFireRate = enemyGunFireRateMin;
}
}
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
// Apply speed multiplier for this frame
var origSpeed = e.speed;
e.speed = origSpeed * enemySpeedMultiplier;
e.update();
e.speed = origSpeed; // Restore original speed for next frame
// Remove if out of bounds
if (e.x < -120 || e.x > 2168 || e.y > 2832) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Enemy collides with player
var dx = e.x - player.x;
var dy = e.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (e.radius + player.radius) * (e.radius + player.radius)) {
if (player.invulnTicks <= 0) {
LK.effects.flashScreen(0xff0000, 600);
player.setInvulnerable(40);
// Game over
LK.showGameOver();
return;
}
}
// Enemy fires at player (throttled by enemyGunFireRate)
if (enemyGunFireTick <= 0) {
if (Math.random() < 0.008 + wave * 0.001) {
var dir = Math.atan2(player.y - e.y, player.x - e.x);
var eb = new Bullet();
eb.init('bullet_basic', 18 + Math.random() * 6, dir, 1, false);
eb.x = e.x;
eb.y = e.y;
bullets.push(eb);
game.addChild(eb);
enemyGunFireTick = enemyGunFireRate;
}
}
}
// Update gun drops
for (var i = gunDrops.length - 1; i >= 0; --i) {
var g = gunDrops[i];
g.update();
// Remove if out of bounds
if (g.y > 2832) {
g.destroy();
gunDrops.splice(i, 1);
continue;
}
// Pickup
var dx = g.x - player.x;
var dy = g.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (g.radius + player.radius) * (g.radius + player.radius)) {
// Add gun if not already owned
if (!hasGun(g.gun.id)) {
playerGuns.push(g.gun);
currentGunIndex = playerGuns.length - 1;
updateGunUI();
updateInventoryUI();
LK.getSound('pickup').play();
// Visual feedback for pickup
LK.effects.flashObject(player, 0x00ff00, 400);
}
g.destroy();
gunDrops.splice(i, 1);
}
}
// Next wave
if (enemies.length === 0 && gunDrops.length === 0 && ticksToNextWave <= 0) {
ticksToNextWave = 60;
}
if (ticksToNextWave > 0) {
ticksToNextWave--;
if (ticksToNextWave === 0) {
wave++;
spawnWave(wave);
// (Wave UI update/animation removed)
}
}
};
// Helper: fire gun
function fireGun(gun) {
for (var i = 0; i < gun.bulletsPerShot; ++i) {
var b = new Bullet();
var spread = gun.spread * (Math.random() - 0.5);
var dir = -Math.PI / 2 + spread + (gun.bulletsPerShot > 1 ? (i - (gun.bulletsPerShot - 1) / 2) * gun.spread : 0);
b.init(gun.bulletAsset, gun.bulletSpeed, dir, gun.damage, true);
b.x = player.x;
b.y = player.y - player.radius;
bullets.push(b);
game.addChild(b);
}
LK.getSound('shoot').play();
}
// Helper: spawn wave
function spawnWave(w) {
var n = 3 + Math.floor(w * 1.5);
for (var i = 0; i < n; ++i) {
// Choose enemy type: more tanks and fast enemies as wave increases
var typeRand = Math.random();
var e;
if (w > 2 && typeRand < Math.min(0.18 + w * 0.01, 0.33)) {
// Tank enemy
e = new EnemyTank();
e.hp = 6 + Math.floor(w / 2);
e.speed = 2 + Math.random() * 0.8 + w * 0.04;
} else if (w > 1 && typeRand < Math.min(0.45 + w * 0.01, 0.6)) {
// Fast enemy
e = new EnemyFast();
e.hp = 1 + Math.floor(w / 4);
e.speed = 8 + Math.random() * 2.5 + w * 0.12;
} else {
// Normal enemy
e = new Enemy();
e.hp = 2 + Math.floor(w / 2);
e.speed = 2 + Math.random() * 1.5 + w * 0.08;
}
e.x = 200 + Math.random() * (2048 - 400);
e.y = -100 - Math.random() * 200;
// Add enemy to the game scene so it is visible
game.addChild(e);
enemies.push(e);
// POLISH: spawn flash
LK.effects.flashObject(e, 0xffffff, 200);
}
}
// Helper: spawn gun drop
function spawnGunDrop(x, y) {
// 50% chance to drop a gun, only if player doesn't have all guns
if (playerGuns.length >= GUNS.length) return;
if (Math.random() < 0.5) {
var exclude = [];
for (var i = 0; i < playerGuns.length; ++i) exclude.push(playerGuns[i].id);
var gun = getRandomGun(exclude);
var g = new GunDrop();
g.x = x;
g.y = y;
g.setGun(gun);
gunDrops.push(g);
game.addChild(g);
}
}
// Helper: update gun UI
function updateGunUI() {
var gun = playerGuns[currentGunIndex];
gunTxt.setText("Gun: " + gun.name + " (Tap here to switch)");
gunTxt.setStyle({
fill: "#" + gun.color.toString(16)
});
updateInventoryUI();
}
// Helper: has gun
function hasGun(id) {
for (var i = 0; i < playerGuns.length; ++i) if (playerGuns[i].id === id) return true;
return false;
}