User prompt
make the mouse move the person
User prompt
make it so you can move
User prompt
make it so you can move
User prompt
make the balls look like pepole
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'gunTxt.style.fill = "#" + gun.color.toString(16);' Line Number: 487
Code edit (1 edits merged)
Please save this source code
User prompt
Lots of Guns
User prompt
lots of guns
User prompt
online
Initial prompt
2d shooter
/**** * 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;
}