/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Enemy class var Enemy = Container.expand(function () { var self = Container.call(this); // Randomly choose between enemy_1 and enemy_2 var enemyAsset = Math.random() < 0.5 ? 'enemy' : 'Enemy_2'; var enemySprite = self.attachAsset(enemyAsset, { anchorX: 0.5, anchorY: 0.5 }); self.width = enemySprite.width; self.height = enemySprite.height; self.speed = 5; // Decreased speed for level 1 self.alive = true; self.lane = 0; // Track which lane this enemy is in (0-4) self.update = function () { var effectiveSpeed = self.speed; if (enemySpeedUpgrade) { effectiveSpeed *= 0.7; // 30% slower } self.y += effectiveSpeed * gameSpeed; if (self.y > GAMEPLAY_AREA_BOTTOM) { self.alpha = 0; } else { self.alpha = 1; } }; return self; }); // FireRateBoost drop var FireRateBoost = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('speedBoost', { anchorX: 0.5, anchorY: 0.5, color: 0x00bfff // blue }); self.width = sprite.width; self.height = sprite.height; self.speed = 10; self.type = 'firerate'; self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // FireStyleBoost drop var FireStyleBoost = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('speedBoost', { anchorX: 0.5, anchorY: 0.5, color: 0xff8800 // orange }); self.width = sprite.width; self.height = sprite.height; self.speed = 10; self.type = 'firestyle'; self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // Hero class var Hero = Container.expand(function () { var self = Container.call(this); var heroSprite = self.attachAsset('hero', { anchorX: 0.5, anchorY: 0.5 }); self.width = heroSprite.width; self.height = heroSprite.height; self.shootCooldown = 0; // frames until next allowed shot // Shoot method self.shoot = function () { if (self.shootCooldown > 0 || isReloading || bulletCount <= 0) return; // Calculate bullets to fire based on fire style var bulletsToFire = fireStyle; if (bulletCount < bulletsToFire) { startReload(); return; } // Fire style: 1=single, 2=double, 3=triple if (fireStyle === 1) { var bullet = new HeroBullet(); bullet.x = self.x; bullet.y = self.y - self.height / 2 - bullet.height / 2; heroBullets.push(bullet); game.addChild(bullet); } else if (fireStyle === 2) { for (var i = -1; i <= 1; i += 2) { var bullet = new HeroBullet(); bullet.x = self.x + i * 40; bullet.y = self.y - self.height / 2 - bullet.height / 2; heroBullets.push(bullet); game.addChild(bullet); } } else if (fireStyle === 3) { for (var i = -1; i <= 1; i++) { var bullet = new HeroBullet(); bullet.x = self.x + i * 40; bullet.y = self.y - self.height / 2 - bullet.height / 2; heroBullets.push(bullet); game.addChild(bullet); } } // Consume bullets bulletCount -= bulletsToFire; updateBulletDisplay(); // Remove bullet display elements based on bullets fired for (var k = 0; k < bulletsToFire; k++) { if (bulletTxt.children.length > 0) { var bulletElement = bulletTxt.children[bulletTxt.children.length - 1]; bulletTxt.removeChild(bulletElement); } } // Check if we need to reload if (bulletCount <= 0) { startReload(); } if (fireRateActive) { self.shootCooldown = 4; // much faster } else { self.shootCooldown = 20; // slower normal fire rate } LK.getSound('shoot').play(); }; // Called every tick self.update = function () { if (self.shootCooldown > 0) self.shootCooldown--; }; return self; }); // Hero bullet class var HeroBullet = Container.expand(function () { var self = Container.call(this); var bulletSprite = self.attachAsset('heroBullet', { anchorX: 0.5, anchorY: 0.5 }); // Apply bullet size upgrade if (bulletSizeUpgrade) { bulletSprite.scaleX = 1.5; bulletSprite.scaleY = 1.5; } self.width = bulletSprite.width; self.height = bulletSprite.height; self.speed = -36; // Upwards self.update = function () { self.y += self.speed; }; return self; }); // Market window class var MarketWindow = Container.expand(function () { var self = Container.call(this); // Semi-transparent background - larger market window var bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 1800, height: 1400, color: 0x000000 }); bg.alpha = 0.8; self.addChild(bg); // Market title var title = new Text2('MARKET', { size: 80, fill: 0xffffff }); title.anchor.set(0.5, 0.5); title.x = 0; title.y = -400; self.addChild(title); // Bullet size upgrade button - larger for better touch var bulletUpgradeBtn = LK.getAsset('marketButton', { anchorX: 0.5, anchorY: 0.5, color: 0x00ff00, scaleX: 2.0, scaleY: 1.5 }); bulletUpgradeBtn.x = 0; bulletUpgradeBtn.y = -200; self.addChild(bulletUpgradeBtn); var bulletUpgradeText = new Text2('BIGGER BULLETS\n50 Coins', { size: 40, fill: 0xffffff }); bulletUpgradeText.anchor.set(0.5, 0.5); bulletUpgradeText.x = 0; bulletUpgradeText.y = -200; self.addChild(bulletUpgradeText); // Enemy speed slower upgrade button - larger for better touch var speedUpgradeBtn = LK.getAsset('marketButton', { anchorX: 0.5, anchorY: 0.5, color: 0xff6600, scaleX: 2.0, scaleY: 1.5 }); speedUpgradeBtn.x = 0; speedUpgradeBtn.y = 0; self.addChild(speedUpgradeBtn); var speedUpgradeText = new Text2('SLOWER ENEMIES\n75 Coins', { size: 40, fill: 0xffffff }); speedUpgradeText.anchor.set(0.5, 0.5); speedUpgradeText.x = 0; speedUpgradeText.y = 0; self.addChild(speedUpgradeText); // Back button - larger for better touch var backBtn = LK.getAsset('marketButton', { anchorX: 0.5, anchorY: 0.5, color: 0xff0000, scaleX: 2.0, scaleY: 1.5 }); backBtn.x = 0; backBtn.y = 300; self.addChild(backBtn); var backText = new Text2('BACK', { size: 50, fill: 0xffffff }); backText.anchor.set(0.5, 0.5); backText.x = 0; backText.y = 300; self.addChild(backText); // Store button references for hit detection self.bulletUpgradeBtn = bulletUpgradeBtn; self.speedUpgradeBtn = speedUpgradeBtn; self.backBtn = backBtn; return self; }); // ShieldBoost drop var ShieldBoost = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('speedBoost', { anchorX: 0.5, anchorY: 0.5, color: 0x00ffea // cyan }); self.width = sprite.width; self.height = sprite.height; self.speed = 10; self.type = 'shield'; self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // Speed boost drop class var SpeedBoost = Container.expand(function () { var self = Container.call(this); var boostSprite = self.attachAsset('speedBoost', { anchorX: 0.5, anchorY: 0.5 }); self.width = boostSprite.width; self.height = boostSprite.height; self.speed = 10; self.type = 'speed'; // default type self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // Warning asset that appears on idle player var WarningAsset = Container.expand(function () { var self = Container.call(this); var warningSprite = self.attachAsset('laneholding', { anchorX: 0.5, anchorY: 0.5 }); self.width = warningSprite.width; self.height = warningSprite.height; self.lifeTimer = 2000; // 2 seconds self.update = function () { self.lifeTimer -= 1000 / 60; // Decrease by frame time if (self.lifeTimer <= 0) { // Check if damage cooldown is active if (damageCooldown <= 0) { // Damage player lives--; updateLivesDisplay(); LK.getSound('hit').play(); LK.effects.flashObject(hero, 0xff0000, 400); // Start damage cooldown damageCooldown = damageCooldownDuration; // Check game over if (lives <= 0) { finished = true; LK.showGameOver(); return; } } // Remove warning asset regardless of whether damage was dealt if (warningAsset) { warningAsset.destroy(); warningAsset = null; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 }); /**** * Game Code ****/ // --- Global variables --- // Hero (player) // Hero bullet // Enemy // Speed boost drop // Road (background) // Sound effects // Music // soldier: gray box // tank: green box // jungle green var hero; var heroBullets = []; var enemies = []; var boosts = []; var lives = 3; var gameTime = 0; // ms var gameDuration = 20000; // 20 seconds var gameSpeed = 1; // Multiplier, affected by boosts var boostActive = false; var boostTimer = 0; // Power-up states var fireRateActive = false; var fireRateTimer = 0; var fireStyle = 1; // 1: single, 2: double, 3: triple var fireStyleTimer = 0; var shieldActive = false; var shieldTimer = 0; var lastEnemySpawnTick = 0; var enemySpawnInterval = 48; // frames (0.8s at 60fps) var score = 0; var finished = false; var level = 1; var killTarget = 10; // Level 1: 10, Level 2: 15, Level 3: 20, etc. // Lane idle detection var lastLane = 2; // Track last lane position var laneIdleTimer = 0; // Timer for staying in same lane var idleThreshold = 3000; // 3 seconds in milliseconds var warningAsset = null; // Asset that appears on player var damageCooldown = 0; // Cooldown timer after taking damage var damageCooldownDuration = 5000; // 5 seconds cooldown after damage // Lane holding kill tracking var laneHoldingTimer = 0; // Timer for consecutive kills in same lane var laneHoldingThreshold = 3000; // 3 seconds in milliseconds var lastKillLane = -1; // Track lane of last kill (-1 means no previous kill) // Coin system var coins = storage.coins || 0; // Market system var gamePaused = false; var marketWindowVisible = false; var marketWindow = null; var marketInactivityTimer = 0; var marketInactivityTimeout = 5000; // 5 seconds in milliseconds // Upgrade states var bulletSizeUpgrade = false; var enemySpeedUpgrade = false; // Bullet system var bulletCount = 5; // Current bullets available var maxBullets = 5; // Maximum bullets in magazine var isReloading = false; var reloadTimer = 0; var reloadDuration = 2000; // 2 seconds in milliseconds function updateKillTarget() { if (level === 1) { killTarget = 10; } else if (level === 2) { killTarget = 15; } else { killTarget = 15 + (level - 2) * 5; } } updateKillTarget(); // --- Define gameplay and control areas --- var CONTROL_AREA_HEIGHT = 400; // px reserved for controller at bottom var GAMEPLAY_AREA_TOP = 0; var GAMEPLAY_AREA_BOTTOM = 2732 - CONTROL_AREA_HEIGHT; // --- Define 5 lanes --- var LANE_COUNT = 5; var ROAD_WIDTH = 900; var LANE_WIDTH = ROAD_WIDTH / LANE_COUNT; // 180px per lane var ROAD_LEFT = 2048 / 2 - ROAD_WIDTH / 2; var currentLane = 2; // Start hero in middle lane (0-4) // Get lane center X position function getLaneX(laneIndex) { return ROAD_LEFT + (laneIndex + 0.5) * LANE_WIDTH; } // Get lane index from X position function getLaneFromX(x) { var relativeX = x - ROAD_LEFT; var laneIndex = Math.floor(relativeX / LANE_WIDTH); return Math.max(0, Math.min(LANE_COUNT - 1, laneIndex)); } // --- Controller area background (army green) --- var controllerBg = LK.getAsset('centerCircle', { anchorX: 0, anchorY: 0, width: 2048, height: CONTROL_AREA_HEIGHT, color: 0x4B5320, // army green x: 0, y: 2732 - CONTROL_AREA_HEIGHT }); game.addChild(controllerBg); // --- Road background (restricted to end at controller line) --- var road = LK.getAsset('road', { anchorX: 0.5, anchorY: 0, width: 900, height: 2732 - CONTROL_AREA_HEIGHT // End at controller line }); road.x = 2048 / 2; road.y = 0; game.addChild(road); // --- Hero --- hero = new Hero(); hero.x = getLaneX(currentLane); hero.y = GAMEPLAY_AREA_BOTTOM - 150; game.addChild(hero); // --- GUI: Lives (Heart Icons) --- var heartIcons = []; for (var i = 0; i < 3; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); heart.x = 150 + i * 100; // Space hearts 100px apart heart.y = 60; heartIcons.push(heart); LK.gui.top.addChild(heart); } // --- GUI: Level/Target --- var levelTxt = new Text2('Level 1', { size: 90, fill: 0xFFFFFF }); levelTxt.anchor.set(0, 0); LK.gui.topLeft.addChild(levelTxt); var targetTxt = new Text2('Target: 10', { size: 90, fill: 0xFFFFFF }); targetTxt.anchor.set(0.5, 0); LK.gui.bottom.addChild(targetTxt); function updateLevelDisplay() { levelTxt.setText('Level ' + level); targetTxt.setText('Target: ' + killTarget); } // --- GUI: Score --- var scoreTxt = new Text2('0', { size: 90, fill: 0xFFE066 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- GUI: Coins --- var coinContainer = new Container(); var coinTxt = new Text2(coins, { size: 50, fill: 0x000000 }); coinTxt.anchor.set(0.5, 0.5); var coinIcon = LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); coinIcon.x = 0; coinIcon.y = 0; coinTxt.x = 0; coinTxt.y = 0; coinContainer.addChild(coinIcon); coinContainer.addChild(coinTxt); coinContainer.x = -100; coinContainer.y = 120; LK.gui.topRight.addChild(coinContainer); // --- GUI: Bullet Count and Reloading --- var bulletTxt = LK.getAsset('bulletCount', { anchorX: 1.0, anchorY: 0 }); LK.gui.topRight.addChild(bulletTxt); var reloadingTxt = LK.getAsset('reloadingText', { anchorX: 0.5, anchorY: 0.5 }); reloadingTxt.alpha = 0; LK.gui.center.addChild(reloadingTxt); // --- Music --- LK.playMusic('bgmusic'); // --- Helper: Update GUI --- function updateLivesDisplay() { for (var i = 0; i < heartIcons.length; i++) { heartIcons[i].alpha = i < lives ? 1 : 0.2; // Show full hearts for remaining lives, dim for lost lives } } function updateTimerDisplay() { // Timer GUI removed, nothing to update } function updateScoreDisplay() { scoreTxt.setText(score); } function updateBulletDisplay() { // Clear existing bullet display elements while (bulletTxt.children.length > 0) { bulletTxt.removeChild(bulletTxt.children[0]); } // Add bullet elements based on current bullet count for (var i = 0; i < bulletCount; i++) { var bulletElement = LK.getAsset('heroBullet', { anchorX: 0.5, anchorY: 0.5 }); bulletElement.x = -30 - i * 25; // Position bullets horizontally bulletElement.y = 0; bulletTxt.addChild(bulletElement); } } function updateCoinDisplay() { coinTxt.setText(coins); storage.coins = coins; // Save to persistent storage } function startReload() { if (!isReloading && bulletCount < maxBullets) { isReloading = true; reloadTimer = reloadDuration; reloadingTxt.alpha = 1; tween(reloadingTxt, { alpha: 0 }, { duration: reloadDuration, onFinish: function onFinish() { bulletCount = maxBullets; isReloading = false; updateBulletDisplay(); } }); } } // --- Touch controls: Move hero, shoot --- var dragHero = false; var leftArrowBtn, rightArrowBtn, fireBtn; var leftBtnPressed = false; var rightBtnPressed = false; var fireBtnPressed = false; var controllerBtnSize = 200; // Size for arrow buttons var fireBtnSize = 250; // Size for fire button // Move hero to specific lane function moveHeroToLane(newLane) { currentLane = Math.max(0, Math.min(LANE_COUNT - 1, newLane)); hero.x = getLaneX(currentLane); } // Controller buttons (left arrow, right arrow, fire) leftArrowBtn = LK.getAsset('leftArrow', { anchorX: 0.5, anchorY: 0.5 }); rightArrowBtn = LK.getAsset('rightArrow', { anchorX: 0.5, anchorY: 0.5 }); fireBtn = LK.getAsset('fireButton', { anchorX: 0.5, anchorY: 0.5 }); leftArrowBtn.alpha = 1.0; rightArrowBtn.alpha = 1.0; fireBtn.alpha = 1.0; // Place buttons side-by-side in the control area with better left-to-right spacing leftArrowBtn.x = 300; leftArrowBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2; rightArrowBtn.x = 600; rightArrowBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2; fireBtn.x = 1600; fireBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2; // Make sure controllerBg is below buttons (already added above) // Add controller buttons to game layer to make them visible game.addChild(leftArrowBtn); game.addChild(rightArrowBtn); game.addChild(fireBtn); // Market button var marketBtn = LK.getAsset('marketButton', { anchorX: 0.5, anchorY: 0.5 }); marketBtn.x = 1000; marketBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2; marketBtn.alpha = 0.8; game.addChild(marketBtn); // Market text label var marketLabel = new Text2('MARKET', { size: 30, fill: 0xffffff }); marketLabel.anchor.set(0.5, 0.5); marketLabel.x = marketBtn.x; marketLabel.y = marketBtn.y; game.addChild(marketLabel); // Make buttons more visible with higher alpha leftArrowBtn.alpha = 0.8; rightArrowBtn.alpha = 0.8; fireBtn.alpha = 0.8; // Helper: check if point is inside button function isInsideBtn(btn, x, y) { // Account for button scaling and provide generous touch areas var baseWidth = btn.width || 150; var baseHeight = btn.height || 80; var scaleX = btn.scaleX || 1; var scaleY = btn.scaleY || 1; var touchWidth = baseWidth * scaleX * 1.2; // 20% extra touch area var touchHeight = baseHeight * scaleY * 1.2; // 20% extra touch area return x >= btn.x - touchWidth / 2 && x <= btn.x + touchWidth / 2 && y >= btn.y - touchHeight / 2 && y <= btn.y + touchHeight / 2; } function showMarket() { if (!marketWindowVisible) { gamePaused = true; marketWindowVisible = true; marketInactivityTimer = 0; // Reset timer when market opens // Create and show market window marketWindow = new MarketWindow(); marketWindow.x = 2048 / 2; marketWindow.y = 2732 / 2; game.addChild(marketWindow); } } function hideMarket() { if (marketWindowVisible) { gamePaused = false; marketWindowVisible = false; // Remove market window if (marketWindow) { marketWindow.destroy(); marketWindow = null; } } } game.down = function (x, y, obj) { // If touch is on left arrow, set pressed if (isInsideBtn(leftArrowBtn, x, y)) { leftBtnPressed = true; leftArrowBtn.alpha = 0.9; return; } // If touch is on right arrow, set pressed if (isInsideBtn(rightArrowBtn, x, y)) { rightBtnPressed = true; rightArrowBtn.alpha = 0.9; return; } // If touch is on fire button, set pressed and shoot if (isInsideBtn(fireBtn, x, y)) { fireBtnPressed = true; fireBtn.alpha = 0.9; hero.shoot(); return; } // If touch is on market button, show market if (isInsideBtn(marketBtn, x, y)) { marketBtn.alpha = 0.9; showMarket(); return; } // Handle market window interactions if (marketWindowVisible && marketWindow) { // Reset inactivity timer on any interaction with market window marketInactivityTimer = 0; // Convert to market window local coordinates var localPos = marketWindow.toLocal({ x: x, y: y }); // Check bullet upgrade button - use global coordinates var bulletBtnGlobalPos = marketWindow.toGlobal(marketWindow.bulletUpgradeBtn.position); if (isInsideBtn({ x: bulletBtnGlobalPos.x, y: bulletBtnGlobalPos.y, width: marketWindow.bulletUpgradeBtn.width, height: marketWindow.bulletUpgradeBtn.height }, x, y)) { if (coins >= 50 && !bulletSizeUpgrade) { coins -= 50; bulletSizeUpgrade = true; updateCoinDisplay(); LK.effects.flashScreen(0x00ff00, 300); } return; } // Check enemy speed upgrade button - use global coordinates var speedBtnGlobalPos = marketWindow.toGlobal(marketWindow.speedUpgradeBtn.position); if (isInsideBtn({ x: speedBtnGlobalPos.x, y: speedBtnGlobalPos.y, width: marketWindow.speedUpgradeBtn.width, height: marketWindow.speedUpgradeBtn.height }, x, y)) { if (coins >= 75 && !enemySpeedUpgrade) { coins -= 75; enemySpeedUpgrade = true; updateCoinDisplay(); LK.effects.flashScreen(0x00ff00, 300); } return; } // Check back button - use global coordinates var backBtnGlobalPos = marketWindow.toGlobal(marketWindow.backBtn.position); if (isInsideBtn({ x: backBtnGlobalPos.x, y: backBtnGlobalPos.y, width: marketWindow.backBtn.width, height: marketWindow.backBtn.height }, x, y)) { hideMarket(); return; } } }; game.move = function (x, y, obj) { // Drag control disabled // If finger moves off controller, release if (!isInsideBtn(leftArrowBtn, x, y)) { leftBtnPressed = false; leftArrowBtn.alpha = 0.8; } if (!isInsideBtn(rightArrowBtn, x, y)) { rightBtnPressed = false; rightArrowBtn.alpha = 0.8; } if (!isInsideBtn(fireBtn, x, y)) { fireBtnPressed = false; fireBtn.alpha = 0.8; } if (!isInsideBtn(marketBtn, x, y)) { marketBtn.alpha = 0.8; } }; game.up = function (x, y, obj) { leftBtnPressed = false; rightBtnPressed = false; fireBtnPressed = false; leftArrowBtn.alpha = 0.8; rightArrowBtn.alpha = 0.8; fireBtn.alpha = 0.8; marketBtn.alpha = 0.8; }; // --- Enemy spawn logic --- function spawnEnemy() { // Enemies spawn randomly in one of the 5 lanes var enemy = new Enemy(); var enemyLane = Math.floor(Math.random() * LANE_COUNT); enemy.x = getLaneX(enemyLane); enemy.lane = enemyLane; // Set the enemy's lane // Spawn at the top of the gameplay area, not above the road enemy.y = GAMEPLAY_AREA_TOP - enemy.height / 2 - 10; // Randomize speed a bit enemy.speed = 6 + Math.random() * 3; enemies.push(enemy); game.addChild(enemy); } // --- Boost logic --- function spawnBoost(x, y) { // Randomly choose power-up type var r = Math.random(); var boost; if (r < 0.25) { boost = new SpeedBoost(); } else if (r < 0.5) { boost = new FireRateBoost(); } else if (r < 0.75) { boost = new FireStyleBoost(); } else { boost = new ShieldBoost(); } boost.x = x; boost.y = y; boosts.push(boost); game.addChild(boost); } // --- Game update --- game.update = function () { // Handle market inactivity timer if (marketWindowVisible) { marketInactivityTimer += 1000 / 60; // Increase by frame time if (marketInactivityTimer >= marketInactivityTimeout) { // Auto-close market after 5 seconds of inactivity tween(marketWindow, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { hideMarket(); } }); marketInactivityTimer = 0; // Reset timer to prevent multiple triggers } return; // Don't continue with game updates when paused } if (finished) return; // --- Controller movement --- if (leftBtnPressed) { moveHeroToLane(currentLane - 1); leftBtnPressed = false; // Single lane move per press leftArrowBtn.alpha = 0.8; } if (rightBtnPressed) { moveHeroToLane(currentLane + 1); rightBtnPressed = false; // Single lane move per press rightArrowBtn.alpha = 0.8; } // --- Update damage cooldown --- if (damageCooldown > 0) { damageCooldown -= 1000 / 60; // Decrease by frame time } // --- Lane idle detection --- if (currentLane === lastLane) { // Only increase idle timer if not in damage cooldown if (damageCooldown <= 0) { laneIdleTimer += 1000 / 60; // Increase by frame time (milliseconds) } // If player has been idle for 3 seconds and no warning exists and not in cooldown if (laneIdleTimer >= idleThreshold && !warningAsset && damageCooldown <= 0) { warningAsset = new WarningAsset(); warningAsset.x = hero.x; warningAsset.y = hero.y - 100; // Position above hero game.addChild(warningAsset); } } else { // Player moved to different lane, reset timer laneIdleTimer = 0; lastLane = currentLane; // Remove warning if it exists if (warningAsset) { warningAsset.destroy(); warningAsset = null; } } // --- Lane holding kill timer --- if (lastKillLane === currentLane && lastKillLane !== -1) { // Player is holding in same lane where they made their last kill laneHoldingTimer += 1000 / 60; // Increase by frame time (milliseconds) // If player has been holding for 3 seconds, reset timer and start count again if (laneHoldingTimer >= laneHoldingThreshold) { laneHoldingTimer = 0; // Reset timer to 0 and start count again } } else if (currentLane !== lastKillLane && lastKillLane !== -1) { // Player moved away from kill lane, reset holding timer laneHoldingTimer = 0; } // --- Update warning asset --- if (warningAsset) { warningAsset.update(); // Keep warning positioned above hero warningAsset.x = hero.x; warningAsset.y = hero.y - 100; } // --- Timer --- // (Timer removed, no time-based win/lose condition) updateTimerDisplay(); // --- Win condition --- // (Handled by kill target below) // --- Boost logic --- if (boostActive) { boostTimer -= 1000 / 60; if (boostTimer <= 0) { boostActive = false; gameSpeed = 1; } } if (fireRateActive) { fireRateTimer -= 1000 / 60; if (fireRateTimer <= 0) { fireRateActive = false; } } if (fireStyle > 1) { fireStyleTimer -= 1000 / 60; if (fireStyleTimer <= 0) { fireStyle = 1; } } if (shieldActive) { shieldTimer -= 1000 / 60; if (shieldTimer <= 0) { shieldActive = false; } } // --- Enemy spawn --- if (LK.ticks - lastEnemySpawnTick >= enemySpawnInterval) { spawnEnemy(); lastEnemySpawnTick = LK.ticks; } // --- Update hero --- hero.update(); // --- Update hero bullets --- for (var i = heroBullets.length - 1; i >= 0; i--) { var b = heroBullets[i]; b.update(); // Remove if off screen if (b.y < -b.height) { b.destroy(); heroBullets.splice(i, 1); } } // --- Update enemies --- for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); // Remove if off screen if (e.y > 2732 + e.height) { e.destroy(); enemies.splice(i, 1); continue; } // Check collision with hero - only if enemy is in same lane if (e.alive && e.lane === currentLane && e.intersects(hero)) { if (shieldActive) { // Ignore hit, just destroy enemy LK.effects.flashObject(hero, 0x00ffea, 200); e.alive = false; e.destroy(); enemies.splice(i, 1); continue; } // Lose a life, destroy enemy lives--; updateLivesDisplay(); LK.getSound('hit').play(); LK.effects.flashObject(hero, 0xff0000, 400); e.alive = false; e.destroy(); enemies.splice(i, 1); // Game over? if (lives <= 0) { finished = true; LK.showGameOver(); return; } continue; } // Check collision with hero bullets for (var j = heroBullets.length - 1; j >= 0; j--) { var b = heroBullets[j]; if (e.alive && b.intersects(e)) { // Enemy down e.alive = false; e.destroy(); enemies.splice(i, 1); b.destroy(); heroBullets.splice(j, 1); score++; coins++; // Award 1 coin per enemy kill updateScoreDisplay(); updateCoinDisplay(); LK.getSound('enemyDown').play(); // Lane holding kill detection if (lastKillLane === currentLane) { // Player killed enemy in same lane as previous kill, reset timer laneHoldingTimer = 0; } else { // Player switched lanes between kills, start new count laneHoldingTimer = 0; lastKillLane = currentLane; } // Win if enough enemies killed for this level if (score >= killTarget) { // Prepare for next level level++; updateKillTarget(); updateLevelDisplay(); // Reset score for next level score = 0; updateScoreDisplay(); // Restore full health when level ends lives = 3; updateLivesDisplay(); // Increase enemy spawn rate for higher difficulty enemySpawnInterval = Math.max(20, enemySpawnInterval - 3); // Flash screen to indicate level complete LK.effects.flashScreen(0x00ff00, 500); } // Chance to drop boost (30%) if (Math.random() < 0.3) { spawnBoost(e.x, e.y); } break; } } } // --- Update boosts --- for (var i = boosts.length - 1; i >= 0; i--) { var boost = boosts[i]; boost.update(); // Remove if off screen if (boost.y > 2732 + boost.height) { boost.destroy(); boosts.splice(i, 1); continue; } // Check collision with hero if (boost.intersects(hero)) { if (boost.type === 'speed') { boostActive = true; boostTimer = 2000; // 2 seconds gameSpeed = 2.2; LK.getSound('boost').play(); LK.effects.flashObject(hero, 0x44e07b, 400); } else if (boost.type === 'firerate') { fireRateActive = true; fireRateTimer = 4000; // 4 seconds LK.effects.flashObject(hero, 0x00bfff, 400); } else if (boost.type === 'firestyle') { fireStyle = Math.min(3, fireStyle + 1); // double, then triple fireStyleTimer = 4000; // 4 seconds LK.effects.flashObject(hero, 0xff8800, 400); } else if (boost.type === 'shield') { shieldActive = true; shieldTimer = 4000; // 4 seconds LK.effects.flashObject(hero, 0x00ffea, 400); } boost.destroy(); boosts.splice(i, 1); } } }; // --- Initial GUI update --- updateLivesDisplay(); updateScoreDisplay(); updateLevelDisplay(); updateBulletDisplay(); updateCoinDisplay();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Randomly choose between enemy_1 and enemy_2
var enemyAsset = Math.random() < 0.5 ? 'enemy' : 'Enemy_2';
var enemySprite = self.attachAsset(enemyAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.width = enemySprite.width;
self.height = enemySprite.height;
self.speed = 5; // Decreased speed for level 1
self.alive = true;
self.lane = 0; // Track which lane this enemy is in (0-4)
self.update = function () {
var effectiveSpeed = self.speed;
if (enemySpeedUpgrade) {
effectiveSpeed *= 0.7; // 30% slower
}
self.y += effectiveSpeed * gameSpeed;
if (self.y > GAMEPLAY_AREA_BOTTOM) {
self.alpha = 0;
} else {
self.alpha = 1;
}
};
return self;
});
// FireRateBoost drop
var FireRateBoost = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('speedBoost', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x00bfff // blue
});
self.width = sprite.width;
self.height = sprite.height;
self.speed = 10;
self.type = 'firerate';
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// FireStyleBoost drop
var FireStyleBoost = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('speedBoost', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xff8800 // orange
});
self.width = sprite.width;
self.height = sprite.height;
self.speed = 10;
self.type = 'firestyle';
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Hero class
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroSprite = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = heroSprite.width;
self.height = heroSprite.height;
self.shootCooldown = 0; // frames until next allowed shot
// Shoot method
self.shoot = function () {
if (self.shootCooldown > 0 || isReloading || bulletCount <= 0) return;
// Calculate bullets to fire based on fire style
var bulletsToFire = fireStyle;
if (bulletCount < bulletsToFire) {
startReload();
return;
}
// Fire style: 1=single, 2=double, 3=triple
if (fireStyle === 1) {
var bullet = new HeroBullet();
bullet.x = self.x;
bullet.y = self.y - self.height / 2 - bullet.height / 2;
heroBullets.push(bullet);
game.addChild(bullet);
} else if (fireStyle === 2) {
for (var i = -1; i <= 1; i += 2) {
var bullet = new HeroBullet();
bullet.x = self.x + i * 40;
bullet.y = self.y - self.height / 2 - bullet.height / 2;
heroBullets.push(bullet);
game.addChild(bullet);
}
} else if (fireStyle === 3) {
for (var i = -1; i <= 1; i++) {
var bullet = new HeroBullet();
bullet.x = self.x + i * 40;
bullet.y = self.y - self.height / 2 - bullet.height / 2;
heroBullets.push(bullet);
game.addChild(bullet);
}
}
// Consume bullets
bulletCount -= bulletsToFire;
updateBulletDisplay();
// Remove bullet display elements based on bullets fired
for (var k = 0; k < bulletsToFire; k++) {
if (bulletTxt.children.length > 0) {
var bulletElement = bulletTxt.children[bulletTxt.children.length - 1];
bulletTxt.removeChild(bulletElement);
}
}
// Check if we need to reload
if (bulletCount <= 0) {
startReload();
}
if (fireRateActive) {
self.shootCooldown = 4; // much faster
} else {
self.shootCooldown = 20; // slower normal fire rate
}
LK.getSound('shoot').play();
};
// Called every tick
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
};
return self;
});
// Hero bullet class
var HeroBullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('heroBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply bullet size upgrade
if (bulletSizeUpgrade) {
bulletSprite.scaleX = 1.5;
bulletSprite.scaleY = 1.5;
}
self.width = bulletSprite.width;
self.height = bulletSprite.height;
self.speed = -36; // Upwards
self.update = function () {
self.y += self.speed;
};
return self;
});
// Market window class
var MarketWindow = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background - larger market window
var bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 1800,
height: 1400,
color: 0x000000
});
bg.alpha = 0.8;
self.addChild(bg);
// Market title
var title = new Text2('MARKET', {
size: 80,
fill: 0xffffff
});
title.anchor.set(0.5, 0.5);
title.x = 0;
title.y = -400;
self.addChild(title);
// Bullet size upgrade button - larger for better touch
var bulletUpgradeBtn = LK.getAsset('marketButton', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x00ff00,
scaleX: 2.0,
scaleY: 1.5
});
bulletUpgradeBtn.x = 0;
bulletUpgradeBtn.y = -200;
self.addChild(bulletUpgradeBtn);
var bulletUpgradeText = new Text2('BIGGER BULLETS\n50 Coins', {
size: 40,
fill: 0xffffff
});
bulletUpgradeText.anchor.set(0.5, 0.5);
bulletUpgradeText.x = 0;
bulletUpgradeText.y = -200;
self.addChild(bulletUpgradeText);
// Enemy speed slower upgrade button - larger for better touch
var speedUpgradeBtn = LK.getAsset('marketButton', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xff6600,
scaleX: 2.0,
scaleY: 1.5
});
speedUpgradeBtn.x = 0;
speedUpgradeBtn.y = 0;
self.addChild(speedUpgradeBtn);
var speedUpgradeText = new Text2('SLOWER ENEMIES\n75 Coins', {
size: 40,
fill: 0xffffff
});
speedUpgradeText.anchor.set(0.5, 0.5);
speedUpgradeText.x = 0;
speedUpgradeText.y = 0;
self.addChild(speedUpgradeText);
// Back button - larger for better touch
var backBtn = LK.getAsset('marketButton', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xff0000,
scaleX: 2.0,
scaleY: 1.5
});
backBtn.x = 0;
backBtn.y = 300;
self.addChild(backBtn);
var backText = new Text2('BACK', {
size: 50,
fill: 0xffffff
});
backText.anchor.set(0.5, 0.5);
backText.x = 0;
backText.y = 300;
self.addChild(backText);
// Store button references for hit detection
self.bulletUpgradeBtn = bulletUpgradeBtn;
self.speedUpgradeBtn = speedUpgradeBtn;
self.backBtn = backBtn;
return self;
});
// ShieldBoost drop
var ShieldBoost = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('speedBoost', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x00ffea // cyan
});
self.width = sprite.width;
self.height = sprite.height;
self.speed = 10;
self.type = 'shield';
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Speed boost drop class
var SpeedBoost = Container.expand(function () {
var self = Container.call(this);
var boostSprite = self.attachAsset('speedBoost', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = boostSprite.width;
self.height = boostSprite.height;
self.speed = 10;
self.type = 'speed'; // default type
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Warning asset that appears on idle player
var WarningAsset = Container.expand(function () {
var self = Container.call(this);
var warningSprite = self.attachAsset('laneholding', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = warningSprite.width;
self.height = warningSprite.height;
self.lifeTimer = 2000; // 2 seconds
self.update = function () {
self.lifeTimer -= 1000 / 60; // Decrease by frame time
if (self.lifeTimer <= 0) {
// Check if damage cooldown is active
if (damageCooldown <= 0) {
// Damage player
lives--;
updateLivesDisplay();
LK.getSound('hit').play();
LK.effects.flashObject(hero, 0xff0000, 400);
// Start damage cooldown
damageCooldown = damageCooldownDuration;
// Check game over
if (lives <= 0) {
finished = true;
LK.showGameOver();
return;
}
}
// Remove warning asset regardless of whether damage was dealt
if (warningAsset) {
warningAsset.destroy();
warningAsset = null;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111
});
/****
* Game Code
****/
// --- Global variables ---
// Hero (player)
// Hero bullet
// Enemy
// Speed boost drop
// Road (background)
// Sound effects
// Music
// soldier: gray box
// tank: green box
// jungle green
var hero;
var heroBullets = [];
var enemies = [];
var boosts = [];
var lives = 3;
var gameTime = 0; // ms
var gameDuration = 20000; // 20 seconds
var gameSpeed = 1; // Multiplier, affected by boosts
var boostActive = false;
var boostTimer = 0;
// Power-up states
var fireRateActive = false;
var fireRateTimer = 0;
var fireStyle = 1; // 1: single, 2: double, 3: triple
var fireStyleTimer = 0;
var shieldActive = false;
var shieldTimer = 0;
var lastEnemySpawnTick = 0;
var enemySpawnInterval = 48; // frames (0.8s at 60fps)
var score = 0;
var finished = false;
var level = 1;
var killTarget = 10; // Level 1: 10, Level 2: 15, Level 3: 20, etc.
// Lane idle detection
var lastLane = 2; // Track last lane position
var laneIdleTimer = 0; // Timer for staying in same lane
var idleThreshold = 3000; // 3 seconds in milliseconds
var warningAsset = null; // Asset that appears on player
var damageCooldown = 0; // Cooldown timer after taking damage
var damageCooldownDuration = 5000; // 5 seconds cooldown after damage
// Lane holding kill tracking
var laneHoldingTimer = 0; // Timer for consecutive kills in same lane
var laneHoldingThreshold = 3000; // 3 seconds in milliseconds
var lastKillLane = -1; // Track lane of last kill (-1 means no previous kill)
// Coin system
var coins = storage.coins || 0;
// Market system
var gamePaused = false;
var marketWindowVisible = false;
var marketWindow = null;
var marketInactivityTimer = 0;
var marketInactivityTimeout = 5000; // 5 seconds in milliseconds
// Upgrade states
var bulletSizeUpgrade = false;
var enemySpeedUpgrade = false;
// Bullet system
var bulletCount = 5; // Current bullets available
var maxBullets = 5; // Maximum bullets in magazine
var isReloading = false;
var reloadTimer = 0;
var reloadDuration = 2000; // 2 seconds in milliseconds
function updateKillTarget() {
if (level === 1) {
killTarget = 10;
} else if (level === 2) {
killTarget = 15;
} else {
killTarget = 15 + (level - 2) * 5;
}
}
updateKillTarget();
// --- Define gameplay and control areas ---
var CONTROL_AREA_HEIGHT = 400; // px reserved for controller at bottom
var GAMEPLAY_AREA_TOP = 0;
var GAMEPLAY_AREA_BOTTOM = 2732 - CONTROL_AREA_HEIGHT;
// --- Define 5 lanes ---
var LANE_COUNT = 5;
var ROAD_WIDTH = 900;
var LANE_WIDTH = ROAD_WIDTH / LANE_COUNT; // 180px per lane
var ROAD_LEFT = 2048 / 2 - ROAD_WIDTH / 2;
var currentLane = 2; // Start hero in middle lane (0-4)
// Get lane center X position
function getLaneX(laneIndex) {
return ROAD_LEFT + (laneIndex + 0.5) * LANE_WIDTH;
}
// Get lane index from X position
function getLaneFromX(x) {
var relativeX = x - ROAD_LEFT;
var laneIndex = Math.floor(relativeX / LANE_WIDTH);
return Math.max(0, Math.min(LANE_COUNT - 1, laneIndex));
}
// --- Controller area background (army green) ---
var controllerBg = LK.getAsset('centerCircle', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: CONTROL_AREA_HEIGHT,
color: 0x4B5320,
// army green
x: 0,
y: 2732 - CONTROL_AREA_HEIGHT
});
game.addChild(controllerBg);
// --- Road background (restricted to end at controller line) ---
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0,
width: 900,
height: 2732 - CONTROL_AREA_HEIGHT // End at controller line
});
road.x = 2048 / 2;
road.y = 0;
game.addChild(road);
// --- Hero ---
hero = new Hero();
hero.x = getLaneX(currentLane);
hero.y = GAMEPLAY_AREA_BOTTOM - 150;
game.addChild(hero);
// --- GUI: Lives (Heart Icons) ---
var heartIcons = [];
for (var i = 0; i < 3; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = 150 + i * 100; // Space hearts 100px apart
heart.y = 60;
heartIcons.push(heart);
LK.gui.top.addChild(heart);
}
// --- GUI: Level/Target ---
var levelTxt = new Text2('Level 1', {
size: 90,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelTxt);
var targetTxt = new Text2('Target: 10', {
size: 90,
fill: 0xFFFFFF
});
targetTxt.anchor.set(0.5, 0);
LK.gui.bottom.addChild(targetTxt);
function updateLevelDisplay() {
levelTxt.setText('Level ' + level);
targetTxt.setText('Target: ' + killTarget);
}
// --- GUI: Score ---
var scoreTxt = new Text2('0', {
size: 90,
fill: 0xFFE066
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- GUI: Coins ---
var coinContainer = new Container();
var coinTxt = new Text2(coins, {
size: 50,
fill: 0x000000
});
coinTxt.anchor.set(0.5, 0.5);
var coinIcon = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
coinIcon.x = 0;
coinIcon.y = 0;
coinTxt.x = 0;
coinTxt.y = 0;
coinContainer.addChild(coinIcon);
coinContainer.addChild(coinTxt);
coinContainer.x = -100;
coinContainer.y = 120;
LK.gui.topRight.addChild(coinContainer);
// --- GUI: Bullet Count and Reloading ---
var bulletTxt = LK.getAsset('bulletCount', {
anchorX: 1.0,
anchorY: 0
});
LK.gui.topRight.addChild(bulletTxt);
var reloadingTxt = LK.getAsset('reloadingText', {
anchorX: 0.5,
anchorY: 0.5
});
reloadingTxt.alpha = 0;
LK.gui.center.addChild(reloadingTxt);
// --- Music ---
LK.playMusic('bgmusic');
// --- Helper: Update GUI ---
function updateLivesDisplay() {
for (var i = 0; i < heartIcons.length; i++) {
heartIcons[i].alpha = i < lives ? 1 : 0.2; // Show full hearts for remaining lives, dim for lost lives
}
}
function updateTimerDisplay() {
// Timer GUI removed, nothing to update
}
function updateScoreDisplay() {
scoreTxt.setText(score);
}
function updateBulletDisplay() {
// Clear existing bullet display elements
while (bulletTxt.children.length > 0) {
bulletTxt.removeChild(bulletTxt.children[0]);
}
// Add bullet elements based on current bullet count
for (var i = 0; i < bulletCount; i++) {
var bulletElement = LK.getAsset('heroBullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletElement.x = -30 - i * 25; // Position bullets horizontally
bulletElement.y = 0;
bulletTxt.addChild(bulletElement);
}
}
function updateCoinDisplay() {
coinTxt.setText(coins);
storage.coins = coins; // Save to persistent storage
}
function startReload() {
if (!isReloading && bulletCount < maxBullets) {
isReloading = true;
reloadTimer = reloadDuration;
reloadingTxt.alpha = 1;
tween(reloadingTxt, {
alpha: 0
}, {
duration: reloadDuration,
onFinish: function onFinish() {
bulletCount = maxBullets;
isReloading = false;
updateBulletDisplay();
}
});
}
}
// --- Touch controls: Move hero, shoot ---
var dragHero = false;
var leftArrowBtn, rightArrowBtn, fireBtn;
var leftBtnPressed = false;
var rightBtnPressed = false;
var fireBtnPressed = false;
var controllerBtnSize = 200; // Size for arrow buttons
var fireBtnSize = 250; // Size for fire button
// Move hero to specific lane
function moveHeroToLane(newLane) {
currentLane = Math.max(0, Math.min(LANE_COUNT - 1, newLane));
hero.x = getLaneX(currentLane);
}
// Controller buttons (left arrow, right arrow, fire)
leftArrowBtn = LK.getAsset('leftArrow', {
anchorX: 0.5,
anchorY: 0.5
});
rightArrowBtn = LK.getAsset('rightArrow', {
anchorX: 0.5,
anchorY: 0.5
});
fireBtn = LK.getAsset('fireButton', {
anchorX: 0.5,
anchorY: 0.5
});
leftArrowBtn.alpha = 1.0;
rightArrowBtn.alpha = 1.0;
fireBtn.alpha = 1.0;
// Place buttons side-by-side in the control area with better left-to-right spacing
leftArrowBtn.x = 300;
leftArrowBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2;
rightArrowBtn.x = 600;
rightArrowBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2;
fireBtn.x = 1600;
fireBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2;
// Make sure controllerBg is below buttons (already added above)
// Add controller buttons to game layer to make them visible
game.addChild(leftArrowBtn);
game.addChild(rightArrowBtn);
game.addChild(fireBtn);
// Market button
var marketBtn = LK.getAsset('marketButton', {
anchorX: 0.5,
anchorY: 0.5
});
marketBtn.x = 1000;
marketBtn.y = 2732 - CONTROL_AREA_HEIGHT / 2;
marketBtn.alpha = 0.8;
game.addChild(marketBtn);
// Market text label
var marketLabel = new Text2('MARKET', {
size: 30,
fill: 0xffffff
});
marketLabel.anchor.set(0.5, 0.5);
marketLabel.x = marketBtn.x;
marketLabel.y = marketBtn.y;
game.addChild(marketLabel);
// Make buttons more visible with higher alpha
leftArrowBtn.alpha = 0.8;
rightArrowBtn.alpha = 0.8;
fireBtn.alpha = 0.8;
// Helper: check if point is inside button
function isInsideBtn(btn, x, y) {
// Account for button scaling and provide generous touch areas
var baseWidth = btn.width || 150;
var baseHeight = btn.height || 80;
var scaleX = btn.scaleX || 1;
var scaleY = btn.scaleY || 1;
var touchWidth = baseWidth * scaleX * 1.2; // 20% extra touch area
var touchHeight = baseHeight * scaleY * 1.2; // 20% extra touch area
return x >= btn.x - touchWidth / 2 && x <= btn.x + touchWidth / 2 && y >= btn.y - touchHeight / 2 && y <= btn.y + touchHeight / 2;
}
function showMarket() {
if (!marketWindowVisible) {
gamePaused = true;
marketWindowVisible = true;
marketInactivityTimer = 0; // Reset timer when market opens
// Create and show market window
marketWindow = new MarketWindow();
marketWindow.x = 2048 / 2;
marketWindow.y = 2732 / 2;
game.addChild(marketWindow);
}
}
function hideMarket() {
if (marketWindowVisible) {
gamePaused = false;
marketWindowVisible = false;
// Remove market window
if (marketWindow) {
marketWindow.destroy();
marketWindow = null;
}
}
}
game.down = function (x, y, obj) {
// If touch is on left arrow, set pressed
if (isInsideBtn(leftArrowBtn, x, y)) {
leftBtnPressed = true;
leftArrowBtn.alpha = 0.9;
return;
}
// If touch is on right arrow, set pressed
if (isInsideBtn(rightArrowBtn, x, y)) {
rightBtnPressed = true;
rightArrowBtn.alpha = 0.9;
return;
}
// If touch is on fire button, set pressed and shoot
if (isInsideBtn(fireBtn, x, y)) {
fireBtnPressed = true;
fireBtn.alpha = 0.9;
hero.shoot();
return;
}
// If touch is on market button, show market
if (isInsideBtn(marketBtn, x, y)) {
marketBtn.alpha = 0.9;
showMarket();
return;
}
// Handle market window interactions
if (marketWindowVisible && marketWindow) {
// Reset inactivity timer on any interaction with market window
marketInactivityTimer = 0;
// Convert to market window local coordinates
var localPos = marketWindow.toLocal({
x: x,
y: y
});
// Check bullet upgrade button - use global coordinates
var bulletBtnGlobalPos = marketWindow.toGlobal(marketWindow.bulletUpgradeBtn.position);
if (isInsideBtn({
x: bulletBtnGlobalPos.x,
y: bulletBtnGlobalPos.y,
width: marketWindow.bulletUpgradeBtn.width,
height: marketWindow.bulletUpgradeBtn.height
}, x, y)) {
if (coins >= 50 && !bulletSizeUpgrade) {
coins -= 50;
bulletSizeUpgrade = true;
updateCoinDisplay();
LK.effects.flashScreen(0x00ff00, 300);
}
return;
}
// Check enemy speed upgrade button - use global coordinates
var speedBtnGlobalPos = marketWindow.toGlobal(marketWindow.speedUpgradeBtn.position);
if (isInsideBtn({
x: speedBtnGlobalPos.x,
y: speedBtnGlobalPos.y,
width: marketWindow.speedUpgradeBtn.width,
height: marketWindow.speedUpgradeBtn.height
}, x, y)) {
if (coins >= 75 && !enemySpeedUpgrade) {
coins -= 75;
enemySpeedUpgrade = true;
updateCoinDisplay();
LK.effects.flashScreen(0x00ff00, 300);
}
return;
}
// Check back button - use global coordinates
var backBtnGlobalPos = marketWindow.toGlobal(marketWindow.backBtn.position);
if (isInsideBtn({
x: backBtnGlobalPos.x,
y: backBtnGlobalPos.y,
width: marketWindow.backBtn.width,
height: marketWindow.backBtn.height
}, x, y)) {
hideMarket();
return;
}
}
};
game.move = function (x, y, obj) {
// Drag control disabled
// If finger moves off controller, release
if (!isInsideBtn(leftArrowBtn, x, y)) {
leftBtnPressed = false;
leftArrowBtn.alpha = 0.8;
}
if (!isInsideBtn(rightArrowBtn, x, y)) {
rightBtnPressed = false;
rightArrowBtn.alpha = 0.8;
}
if (!isInsideBtn(fireBtn, x, y)) {
fireBtnPressed = false;
fireBtn.alpha = 0.8;
}
if (!isInsideBtn(marketBtn, x, y)) {
marketBtn.alpha = 0.8;
}
};
game.up = function (x, y, obj) {
leftBtnPressed = false;
rightBtnPressed = false;
fireBtnPressed = false;
leftArrowBtn.alpha = 0.8;
rightArrowBtn.alpha = 0.8;
fireBtn.alpha = 0.8;
marketBtn.alpha = 0.8;
};
// --- Enemy spawn logic ---
function spawnEnemy() {
// Enemies spawn randomly in one of the 5 lanes
var enemy = new Enemy();
var enemyLane = Math.floor(Math.random() * LANE_COUNT);
enemy.x = getLaneX(enemyLane);
enemy.lane = enemyLane; // Set the enemy's lane
// Spawn at the top of the gameplay area, not above the road
enemy.y = GAMEPLAY_AREA_TOP - enemy.height / 2 - 10;
// Randomize speed a bit
enemy.speed = 6 + Math.random() * 3;
enemies.push(enemy);
game.addChild(enemy);
}
// --- Boost logic ---
function spawnBoost(x, y) {
// Randomly choose power-up type
var r = Math.random();
var boost;
if (r < 0.25) {
boost = new SpeedBoost();
} else if (r < 0.5) {
boost = new FireRateBoost();
} else if (r < 0.75) {
boost = new FireStyleBoost();
} else {
boost = new ShieldBoost();
}
boost.x = x;
boost.y = y;
boosts.push(boost);
game.addChild(boost);
}
// --- Game update ---
game.update = function () {
// Handle market inactivity timer
if (marketWindowVisible) {
marketInactivityTimer += 1000 / 60; // Increase by frame time
if (marketInactivityTimer >= marketInactivityTimeout) {
// Auto-close market after 5 seconds of inactivity
tween(marketWindow, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
hideMarket();
}
});
marketInactivityTimer = 0; // Reset timer to prevent multiple triggers
}
return; // Don't continue with game updates when paused
}
if (finished) return;
// --- Controller movement ---
if (leftBtnPressed) {
moveHeroToLane(currentLane - 1);
leftBtnPressed = false; // Single lane move per press
leftArrowBtn.alpha = 0.8;
}
if (rightBtnPressed) {
moveHeroToLane(currentLane + 1);
rightBtnPressed = false; // Single lane move per press
rightArrowBtn.alpha = 0.8;
}
// --- Update damage cooldown ---
if (damageCooldown > 0) {
damageCooldown -= 1000 / 60; // Decrease by frame time
}
// --- Lane idle detection ---
if (currentLane === lastLane) {
// Only increase idle timer if not in damage cooldown
if (damageCooldown <= 0) {
laneIdleTimer += 1000 / 60; // Increase by frame time (milliseconds)
}
// If player has been idle for 3 seconds and no warning exists and not in cooldown
if (laneIdleTimer >= idleThreshold && !warningAsset && damageCooldown <= 0) {
warningAsset = new WarningAsset();
warningAsset.x = hero.x;
warningAsset.y = hero.y - 100; // Position above hero
game.addChild(warningAsset);
}
} else {
// Player moved to different lane, reset timer
laneIdleTimer = 0;
lastLane = currentLane;
// Remove warning if it exists
if (warningAsset) {
warningAsset.destroy();
warningAsset = null;
}
}
// --- Lane holding kill timer ---
if (lastKillLane === currentLane && lastKillLane !== -1) {
// Player is holding in same lane where they made their last kill
laneHoldingTimer += 1000 / 60; // Increase by frame time (milliseconds)
// If player has been holding for 3 seconds, reset timer and start count again
if (laneHoldingTimer >= laneHoldingThreshold) {
laneHoldingTimer = 0; // Reset timer to 0 and start count again
}
} else if (currentLane !== lastKillLane && lastKillLane !== -1) {
// Player moved away from kill lane, reset holding timer
laneHoldingTimer = 0;
}
// --- Update warning asset ---
if (warningAsset) {
warningAsset.update();
// Keep warning positioned above hero
warningAsset.x = hero.x;
warningAsset.y = hero.y - 100;
}
// --- Timer ---
// (Timer removed, no time-based win/lose condition)
updateTimerDisplay();
// --- Win condition ---
// (Handled by kill target below)
// --- Boost logic ---
if (boostActive) {
boostTimer -= 1000 / 60;
if (boostTimer <= 0) {
boostActive = false;
gameSpeed = 1;
}
}
if (fireRateActive) {
fireRateTimer -= 1000 / 60;
if (fireRateTimer <= 0) {
fireRateActive = false;
}
}
if (fireStyle > 1) {
fireStyleTimer -= 1000 / 60;
if (fireStyleTimer <= 0) {
fireStyle = 1;
}
}
if (shieldActive) {
shieldTimer -= 1000 / 60;
if (shieldTimer <= 0) {
shieldActive = false;
}
}
// --- Enemy spawn ---
if (LK.ticks - lastEnemySpawnTick >= enemySpawnInterval) {
spawnEnemy();
lastEnemySpawnTick = LK.ticks;
}
// --- Update hero ---
hero.update();
// --- Update hero bullets ---
for (var i = heroBullets.length - 1; i >= 0; i--) {
var b = heroBullets[i];
b.update();
// Remove if off screen
if (b.y < -b.height) {
b.destroy();
heroBullets.splice(i, 1);
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
// Remove if off screen
if (e.y > 2732 + e.height) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check collision with hero - only if enemy is in same lane
if (e.alive && e.lane === currentLane && e.intersects(hero)) {
if (shieldActive) {
// Ignore hit, just destroy enemy
LK.effects.flashObject(hero, 0x00ffea, 200);
e.alive = false;
e.destroy();
enemies.splice(i, 1);
continue;
}
// Lose a life, destroy enemy
lives--;
updateLivesDisplay();
LK.getSound('hit').play();
LK.effects.flashObject(hero, 0xff0000, 400);
e.alive = false;
e.destroy();
enemies.splice(i, 1);
// Game over?
if (lives <= 0) {
finished = true;
LK.showGameOver();
return;
}
continue;
}
// Check collision with hero bullets
for (var j = heroBullets.length - 1; j >= 0; j--) {
var b = heroBullets[j];
if (e.alive && b.intersects(e)) {
// Enemy down
e.alive = false;
e.destroy();
enemies.splice(i, 1);
b.destroy();
heroBullets.splice(j, 1);
score++;
coins++; // Award 1 coin per enemy kill
updateScoreDisplay();
updateCoinDisplay();
LK.getSound('enemyDown').play();
// Lane holding kill detection
if (lastKillLane === currentLane) {
// Player killed enemy in same lane as previous kill, reset timer
laneHoldingTimer = 0;
} else {
// Player switched lanes between kills, start new count
laneHoldingTimer = 0;
lastKillLane = currentLane;
}
// Win if enough enemies killed for this level
if (score >= killTarget) {
// Prepare for next level
level++;
updateKillTarget();
updateLevelDisplay();
// Reset score for next level
score = 0;
updateScoreDisplay();
// Restore full health when level ends
lives = 3;
updateLivesDisplay();
// Increase enemy spawn rate for higher difficulty
enemySpawnInterval = Math.max(20, enemySpawnInterval - 3);
// Flash screen to indicate level complete
LK.effects.flashScreen(0x00ff00, 500);
}
// Chance to drop boost (30%)
if (Math.random() < 0.3) {
spawnBoost(e.x, e.y);
}
break;
}
}
}
// --- Update boosts ---
for (var i = boosts.length - 1; i >= 0; i--) {
var boost = boosts[i];
boost.update();
// Remove if off screen
if (boost.y > 2732 + boost.height) {
boost.destroy();
boosts.splice(i, 1);
continue;
}
// Check collision with hero
if (boost.intersects(hero)) {
if (boost.type === 'speed') {
boostActive = true;
boostTimer = 2000; // 2 seconds
gameSpeed = 2.2;
LK.getSound('boost').play();
LK.effects.flashObject(hero, 0x44e07b, 400);
} else if (boost.type === 'firerate') {
fireRateActive = true;
fireRateTimer = 4000; // 4 seconds
LK.effects.flashObject(hero, 0x00bfff, 400);
} else if (boost.type === 'firestyle') {
fireStyle = Math.min(3, fireStyle + 1); // double, then triple
fireStyleTimer = 4000; // 4 seconds
LK.effects.flashObject(hero, 0xff8800, 400);
} else if (boost.type === 'shield') {
shieldActive = true;
shieldTimer = 4000; // 4 seconds
LK.effects.flashObject(hero, 0x00ffea, 400);
}
boost.destroy();
boosts.splice(i, 1);
}
}
};
// --- Initial GUI update ---
updateLivesDisplay();
updateScoreDisplay();
updateLevelDisplay();
updateBulletDisplay();
updateCoinDisplay();
Let's remove background and resize it bigger
Make it view from sky and change color of rifle to black and brown
Change towards to right
change it mecha-style heart for hero lives. In-Game asset. 2d. High contrast. No shadows. mechaart
fire button for tank game controller. Fire button in mecha style. In-Game asset. 2d. High contrast. No shadows
green line with army style. In-Game asset. 2d. High contrast. No shadows
make shorter horizontal wing
make it vertical
remove dollar emblem from it
exclamantation. In-Game asset. 2d. High contrast. No shadows
black market which sells weapon. In-Game asset. 2d. High contrast. No shadows
blur brown