/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Background elements that scrolls to create movement illusion var BackgroundTile = Container.expand(function () { var self = Container.call(this); var tileGraphics = self.attachAsset('backgroundTile', { anchorX: 0.5, anchorY: 0 }); self.speed = 2; //{6} // Start with slower speed self.update = function () { // Don't move if game is over if (!game.isGameOver) { self.y += self.speed; // Reset position when it goes out of view if (self.y >= 2732) { // Calculate the exact position needed for seamless tiling // Find the tile that's highest up (lowest y value) var highestTile = self; var highestY = self.y; for (var i = 0; i < backgroundTiles.length; i++) { var tile = backgroundTiles[i]; if (tile !== self && tile.y < highestY) { highestTile = tile; highestY = tile.y; } } // Position this tile directly above the highest tile self.y = highestY - tileGraphics.height; } } }; return self; }); // Game variables // Barrier obstacle var Barrier = Container.expand(function () { var self = Container.call(this); var barrierGraphics = self.attachAsset('barrier', { anchorX: 0.5, anchorY: 0.5 }); // Barrier properties self.health = 5; self.speed = 2; //{i} // Start with slower speed // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Take damage self.takeDamage = function () { self.health--; if (self.health <= 0) { LK.getSound('explosion').play(); LK.setScore(LK.getScore() + 2); return true; // Barrier destroyed } return false; // Barrier still intact }; // Update called every frame self.update = function () { self.lastX = self.x; self.lastY = self.y; // Don't move if game is over if (!game.isGameOver) { // Move downward self.y += self.speed; } }; return self; }); // Enemy class var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics; // Enemy properties self.health = 1; self.speed = 2; self.shootChance = 1; // 1% chance per frame self.lane = 0; self.type = 'basic'; // basic, shooter, tank // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Initialize enemy with type self.init = function (type, lane) { self.type = type; self.lane = lane; // Remove previous graphics if exists if (enemyGraphics) { self.removeChild(enemyGraphics); } if (type === 'basic') { // Shots per second // Add small grow and shrink animation var _startBasicEnemyAnimation = function startBasicEnemyAnimation() { tween(enemyGraphics, { scaleX: 1.05, scaleY: 1.05 }, { duration: 350, easing: tween.easeInOut, onFinish: function onFinish() { tween(enemyGraphics, { scaleX: 1, scaleY: 1 }, { duration: 350, easing: tween.easing, onFinish: _startBasicEnemyAnimation }); } }); }; // Start the animation enemyGraphics = self.attachAsset('enemyBasic', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.speed = 3; //{P} // Slower starting speed self.shootRate = 200; _startBasicEnemyAnimation(); } else if (type === 'shooter') { // Moderate fire rate // Add small grow and shrink animation var _startShooterEnemyAnimation = function startShooterEnemyAnimation() { tween(enemyGraphics, { scaleX: 1.05, scaleY: 1.05 }, { duration: 350, easing: tween.easeInOut, onFinish: function onFinish() { tween(enemyGraphics, { scaleX: 1, scaleY: 1 }, { duration: 350, easing: tween.easeInOut, onFinish: _startShooterEnemyAnimation }); } }); }; // Start the animation enemyGraphics = self.attachAsset('enemyShooter', { anchorX: 0.5, anchorY: 0.5 }); self.health = 1; self.speed = 2.5; // Slower starting speed self.shootRate = 160; _startShooterEnemyAnimation(); } else if (type === 'tank') { enemyGraphics = self.attachAsset('enemyTank', { anchorX: 0.5, anchorY: 0.5 }); self.health = 30; self.speed = 2; // Slower starting speed self.shootRate = 400; // Slow fire rate } }; // Shoot method for enemies self.shootRate = 200; // Base shoot rate (bullets per frame at 60fps) self.shootCooldown = 0; self.tryShoot = function () { // Create bullet with a rate based on shootRate and normalized for 60fps // shootRate is # of frames between shots at 60 FPS // Higher shootRate = slower shooting // Lower shootRate = faster shooting if (Math.random() < 1 / self.shootRate * 60 * LK.deltaTime) { // Play enemy shoot sound LK.getSound('enemyShoot').play(); var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y + 60; // Set bullet speed based on enemy type and game difficulty var baseSpeed = 5; var typeBonus = 0; // Different enemy types get different bullet speed bonuses if (self.type === 'shooter') typeBonus = 1; if (self.type === 'tank') typeBonus = 0.5; // Apply difficulty bonus var difficultyBonus = game.difficulty ? game.difficulty * 0.3 : 0; bullet.speed = baseSpeed + typeBonus + difficultyBonus; game.addChild(bullet); enemyBullets.push(bullet); } }; // Take damage self.takeDamage = function () { self.health--; if (self.health <= 0) { LK.getSound('enemyDie').play(); LK.setScore(LK.getScore() + 1); return true; // Enemy destroyed } return false; // Enemy still alive }; // Update called every frame self.update = function () { self.lastX = self.x; self.lastY = self.y; // Don't move or shoot if game is over if (!game.isGameOver) { // Move downward self.y += self.speed; // Update shooting cooldown if (self.shootCooldown > 0) { self.shootCooldown -= LK.deltaTime; } // Try to shoot if we're a shooting type if ((self.type === 'shooter' || self.type === 'tank' || self.type === 'basic') && self.y > 0) { self.tryShoot(); } } }; return self; }); // Enemy bullet var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Don't move if game is over if (!game.isGameOver) { // Move bullet downward using delta time var frameRate = 60; // Base frame rate var speedMultiplier = LK.deltaTime * frameRate; // Adjust speed based on actual frame time // Adjust bullet speed based on current game difficulty var difficultySpeedBonus = game.difficulty ? game.difficulty * 0.5 : 0; self.y += (self.speed + difficultySpeedBonus) * speedMultiplier; } }; return self; }); // Hole obstacle class var Hole = Container.expand(function () { var self = Container.call(this); // Create hole image var holeGraphics = self.attachAsset('hole', { anchorX: 0.5, anchorY: 0.5 }); // Hole properties self.health = 999; self.speed = 2; // Start with slower speed // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Take damage self.takeDamage = function () { // Reduce hole health by 1 self.health--; // Check if hole is completely destroyed if (self.health <= 0) { LK.getSound('explosion').play(); LK.setScore(LK.getScore() + 3); // More points than barriers return true; // Hole destroyed } return false; // Hole still intact }; // Update called every frame self.update = function () { self.lastX = self.x; self.lastY = self.y; // Don't move if game is over if (!game.isGameOver) { // Move downward self.y += self.speed; } }; return self; }); // Lane definitions for positioning var Lane = Container.expand(function () { var self = Container.call(this); var laneGraphics = self.attachAsset('lane', { anchorX: 0.5, anchorY: 0 }); return self; }); // Main Menu Screen var MenuScreen = Container.expand(function () { var self = Container.call(this); // Define Vector2 positions for centered elements var gameIconPosition = { x: 2048 / 2, y: 2732 / 2 - 700 }; // Center of screen horizontally, 400px from top var playButtonPosition = { x: 2048 / 2, y: 2732 / 2 }; // Center of screen horizontally, 900px from top // Define Vector2 scales for elements var gameIconScale = { x: 3.0, y: 3.0 }; // Default scale for game icon var playButtonScale = { x: 3.0, y: 3.0 }; // Default scale for play button // Add background image var menuBackground = self.attachAsset('menuBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); // Create game title graphic var gameIcon = self.attachAsset('gameIcon', { anchorX: 0.5, anchorY: 0.5, x: gameIconPosition.x, y: gameIconPosition.y, scaleX: gameIconScale.x, scaleY: gameIconScale.y }); // Add grow and shrink animation to game icon function startIconAnimation() { tween(gameIcon, { scaleX: gameIconScale.x * 1.1, scaleY: gameIconScale.y * 1.1 }, { duration: 1200, easing: tween.easeInOut, onFinish: function onFinish() { tween(gameIcon, { scaleX: gameIconScale.x * 0.95, scaleY: gameIconScale.y * 0.95 }, { duration: 1200, easing: tween.easeInOut, onFinish: startIconAnimation }); } }); } // Start the animation startIconAnimation(); // Create play button with animation var playButton = self.attachAsset('playButton', { anchorX: 0.5, anchorY: 0.5, x: playButtonPosition.x, y: playButtonPosition.y, scaleX: playButtonScale.x, scaleY: playButtonScale.y }); // Add swipe instruction text var swipeText = new Text2('Swipe to move left or right', { size: 60, fill: 0xFFFFFF }); swipeText.anchor.set(0.5, 0.5); swipeText.x = playButtonPosition.x; swipeText.y = playButtonPosition.y + 200; // Position below play button self.addChild(swipeText); // Add highscore display to menu screen if (storage.highScore && storage.highScore > 0) { var highScoreText = new Text2('HIGHSCORE: ' + storage.highScore, { size: 80, fill: 0xFFD700 }); highScoreText.anchor.set(0.5, 0.5); highScoreText.x = playButtonPosition.x; highScoreText.y = playButtonPosition.y + 350; // Position below swipe instructions self.addChild(highScoreText); } // Add pulse animation to play button function startPulseAnimation() { tween(playButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(playButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: startPulseAnimation }); } }); } // Start the pulse animation startPulseAnimation(); // Handle touch events on play button playButton.down = function (x, y, obj) { tween(playButton, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); }; playButton.up = function (x, y, obj) { tween(playButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, onFinish: function onFinish() { // Hide menu and set gameInitialized to true self.visible = false; gameInitialized = true; // This will trigger the game initialization } }); }; return self; }); // Function to update health display // Player class var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Add small grow and shrink animation function startPlayerPulseAnimation() { tween(playerGraphics, { scaleX: 1.05, scaleY: 1.05 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(playerGraphics, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeInOut, onFinish: startPlayerPulseAnimation }); } }); } // Start the animation startPlayerPulseAnimation(); // Player properties self.currentLane = 1; // 0=left, 1=center, 2=right self.shootCooldown = 0; self.shootRate = 50; // Configurable shoot rate (bullets per second) self.bulletType = 'normal'; // normal, double, triple self.powerUpTimer = 0; self.isAlive = true; self.maxHealth = 3; // Maximum health points self.health = self.maxHealth; // Current health // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Method to handle damage and health reduction self.takeDamage = function () { if (!self.isAlive) { return; } // Don't take damage if already dead self.health--; // Subtract health by 1 // Update health display updateHealthDisplay(); // Turn player sprite red briefly to indicate damage tween(playerGraphics, { tint: 0xFF0000 }, { duration: 200, onFinish: function onFinish() { tween(playerGraphics, { tint: 0xFFFFFF }, { duration: 100 }); } }); // Check if player is dead if (self.health <= 0) { self.isAlive = false; // Stop player's grow and shrink animation tween.stop(playerGraphics); // Change player image to dead image // Remove current graphics self.removeChild(playerGraphics); // Add dead player image playerGraphics = self.attachAsset('playerDead', { anchorX: 0.5, anchorY: 0.5 }); // Flash screen red for game over LK.effects.flashScreen(0xff0000, 1000); // Set a global variable to indicate game is over but waiting game.isGameOver = true; // Play game over sound effect LK.getSound('gameOver').play(); // Update highscore if current score is higher var currentScore = LK.getScore(); if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Wait 3 seconds before showing game over LK.setTimeout(function () { LK.showGameOver(); }, 3000); } return self.health <= 0; // Return true if player died }; // Movement handling self.moveTo = function (laneIndex) { if (laneIndex >= 0 && laneIndex <= 2) { self.currentLane = laneIndex; } }; // Shoot method self.shoot = function () { // Use the configurable shootRate variable instead of hard-coded value if (self.shootCooldown <= 0) { // Only play sound if game is not over if (!game.isGameOver) { LK.getSound('shoot').play(); } if (self.bulletType === 'normal') { // Create a single bullet var bullet = new PlayerBullet(); bullet.x = self.x; bullet.y = self.y - 60; // Make bullet invisible if game is over if (game.isGameOver) { bullet.alpha = 0; } game.addChild(bullet); playerBullets.push(bullet); } else if (self.bulletType === 'double') { // Create two side-by-side bullets var bullet1 = new PlayerBullet(); bullet1.x = self.x - 30; bullet1.y = self.y - 60; // Make bullet invisible if game is over if (game.isGameOver) { bullet1.alpha = 0; } game.addChild(bullet1); playerBullets.push(bullet1); var bullet2 = new PlayerBullet(); bullet2.x = self.x + 30; bullet2.y = self.y - 60; // Make bullet invisible if game is over if (game.isGameOver) { bullet2.alpha = 0; } game.addChild(bullet2); playerBullets.push(bullet2); } else if (self.bulletType === 'triple') { // Create three bullets - one forward and two diagonal var bulletCenter = new PlayerBullet(); bulletCenter.x = self.x; bulletCenter.y = self.y - 60; // Make bullet invisible if game is over if (game.isGameOver) { bulletCenter.alpha = 0; } game.addChild(bulletCenter); playerBullets.push(bulletCenter); var bulletLeft = new PlayerBullet(); bulletLeft.x = self.x - 30; bulletLeft.y = self.y - 60; bulletLeft.angle = -15; // Diagonal left // Make bullet invisible if game is over if (game.isGameOver) { bulletLeft.alpha = 0; } game.addChild(bulletLeft); playerBullets.push(bulletLeft); var bulletRight = new PlayerBullet(); bulletRight.x = self.x + 30; bulletRight.y = self.y - 60; bulletRight.angle = 15; // Diagonal right // Make bullet invisible if game is over if (game.isGameOver) { bulletRight.alpha = 0; } game.addChild(bulletRight); playerBullets.push(bulletRight); } // Set cooldown based on the configurable shootRate variable self.shootCooldown = self.shootRate / 60; // Normalize to 60 fps } }; // Power-up activation self.activatePowerUp = function (type) { LK.getSound('powerUp').play(); if (type === 'fastShoot') { self.setShootRate(25); // Set faster shooting rate self.bulletType = 'normal'; // Keep normal bullet type } else { self.bulletType = type; self.setShootRate(50); // Reset to default rate } self.powerUpTimer = 600; // 5 seconds at 60fps }; // Method to change the shoot rate self.setShootRate = function (rate) { if (rate > 0) { self.shootRate = rate; } }; // Update called every frame self.update = function () { self.lastX = self.x; self.lastY = self.y; // Handle cooldowns directly (not converting from frames) if (self.shootCooldown > 0) { self.shootCooldown -= LK.deltaTime; } // Power-up timer using delta time if (self.powerUpTimer > 0) { self.powerUpTimer -= 1; // Decrease by 1 each frame at 60fps if (self.powerUpTimer <= 0) { self.bulletType = 'normal'; self.setShootRate(50); // Reset shooting rate to default } } // Auto-shoot self.shoot(); }; return self; }); // Player bullet var PlayerBullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 20; self.angle = 0; // 0 = straight, positive/negative for diagonal // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Don't move if game is over if (!game.isGameOver) { // Move bullet based on angle using delta time var radians = self.angle * (Math.PI / 180); var frameRate = 60; // Base frame rate var speedMultiplier = LK.deltaTime * frameRate; // Adjust speed based on actual frame time self.x += Math.sin(radians) * self.speed * speedMultiplier; self.y -= Math.cos(radians) * self.speed * speedMultiplier; } }; return self; }); // Power-up class var PowerUp = Container.expand(function () { var self = Container.call(this); // Start with a placeholder that will be replaced in init() var powerUpGraphics = self.attachAsset('doublePowerUp', { anchorX: 0.5, anchorY: 0.5 }); // Power-up properties self.type = 'double'; // double, triple self.speed = 2; //{2h} // Start with slower speed // Last position tracking for collision detection self.lastX = 0; self.lastY = 0; self.lastWasIntersecting = false; // Initialize power-up with type self.init = function (type) { self.type = type; // Remove existing graphics self.removeChild(powerUpGraphics); // Use appropriate image based on power-up type if (type === 'double') { powerUpGraphics = self.attachAsset('doublePowerUp', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'triple') { powerUpGraphics = self.attachAsset('triplePowerUp', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'fastShoot') { powerUpGraphics = self.attachAsset('fastShootPowerUp', { anchorX: 0.5, anchorY: 0.5 }); } // Add pulsating animation to the power-up function startPulseAnimation() { // Grow slightly tween(powerUpGraphics, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { // Shrink back tween(powerUpGraphics, { scaleX: 0.9, scaleY: 0.9 }, { duration: 800, easing: tween.easeInOut, onFinish: startPulseAnimation }); } }); } // Start the animation startPulseAnimation(); }; // Update called every frame self.update = function () { self.lastX = self.x; self.lastY = self.y; // Don't move if game is over if (!game.isGameOver) { // Move downward self.y += self.speed; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Import tween plugin // Player, enemies and bullets // Environment and obstacles // Power-ups // Sounds // Background // Game variables function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) { return Array.from(r); } } function _arrayWithoutHoles(r) { if (Array.isArray(r)) { return _arrayLikeToArray(r); } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } var menuScreen; var gameInitialized = false; var highScore = storage.highScore || 0; // Add deltaTime calculation if it's not provided by LK engine // Function to update health display function updateHealthDisplay() { // Vector2 variable for hearts position var heartsPosition = { x: 2048 / 2, y: 220 }; // Clear existing health images for (var i = 0; i < healthImages.length; i++) { healthImages[i].destroy(); } healthImages = []; // Create new health images based on current health for (var i = 0; i < player.health; i++) { var heartImage = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Position hearts centered at heart position with spacing heartImage.x = heartsPosition.x - ((player.health - 1) / 2 - i) * 80; // Center hearts around heartsPosition.x heartImage.y = heartsPosition.y; game.addChild(heartImage); healthImages.push(heartImage); } } if (typeof LK.deltaTime === 'undefined') { LK.lastTime = Date.now(); LK.deltaTime = 1 / 60; // Default to 60fps LK.targetFPS = 60; // Update deltaTime before each frame LK.on('tick', function () { var now = Date.now(); LK.deltaTime = Math.min(0.1, (now - LK.lastTime) / 1000); // Cap at 0.1s to prevent huge jumps LK.lastTime = now; // Lock to 60fps if (LK.deltaTime < 1 / LK.targetFPS) { LK.deltaTime = 1 / LK.targetFPS; } }); } var player; var lanes = []; var lanePositions = [512, 1024, 1536]; // The three lanes X positions var playerBullets = []; var enemies = []; var enemyBullets = []; var barriers = []; var holes = []; // Array to track hole obstacles var powerUps = []; var backgroundTiles = []; var healthImages = []; var gameSpeed = 2; // Start with slower speed var difficulty = 0.5; // Start with lower difficulty var spawnTimer = 0; var barrierTimer = 0; var holeTimer = 0; // Timer for spawning holes var powerUpTimer = 0; var difficultyIncreaseRate = 0.05; // How fast difficulty increases var maxDifficulty = 6; // Cap on maximum difficulty // Touch tracking for swipe detection var touchStartX = null; var touchStartY = null; // Initialize the game elements function initGame() { // Mark game as initialized gameInitialized = true; // Hide menu if it exists if (menuScreen) { menuScreen.visible = false; } // Switch from menu music to game music when game starts LK.stopMusic(); // Stop menu music first // Play background music with loop LK.playMusic('bgMusic', { fade: { start: 0, end: 0.7, duration: 1000 } }); // Set up background var bgTileHeight = LK.getAsset('backgroundTile', {}).height; var numTilesNeeded = Math.ceil(2732 / bgTileHeight) + 1; // One extra for seamless scrolling for (var i = 0; i < numTilesNeeded; i++) { var bgTile = new BackgroundTile(); bgTile.x = 1024; // Center of screen bgTile.y = i * bgTileHeight; // Position tiles exactly adjacent to each other backgroundTiles.push(bgTile); game.addChild(bgTile); } // Create the lane markers for (var i = 0; i < 2; i++) { var lane = new Lane(); lane.x = lanePositions[i] + (lanePositions[1] - lanePositions[0]) / 2; // Position between lanes lane.y = 0; lane.alpha = 0.3; lanes.push(lane); game.addChild(lane); } // Create the player player = new Player(); player.x = lanePositions[1]; // Start in center lane player.y = 2732 - 300; // Near bottom of screen player.health = player.maxHealth; // Ensure health is set to maximum game.addChild(player); // Initialize score display var scoreBackground = game.addChild(LK.getAsset('scoreBackground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 80, alpha: 0.7 })); var scoreTxt = new Text2('0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0.5); scoreTxt.x = 1024; scoreTxt.y = 80; game.addChild(scoreTxt); // Create highscore display var highScoreTxt = new Text2('HIGH: ' + highScore, { size: 40, fill: 0xFFFF00 }); highScoreTxt.anchor.set(0.5, 0.5); highScoreTxt.x = 1024; highScoreTxt.y = 130; game.addChild(highScoreTxt); function updateHealthDisplay() { // Vector2 variable for hearts position var heartsPosition = { x: 2048 / 2, y: 220 }; // Clear existing health images for (var i = 0; i < healthImages.length; i++) { healthImages[i].destroy(); } healthImages = []; // Create new health images based on current health for (var i = 0; i < player.health; i++) { var heartImage = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); // Position hearts centered at heart position with spacing heartImage.x = heartsPosition.x - ((player.health - 1) / 2 - i) * 80; // Center hearts around heartsPosition.x heartImage.y = heartsPosition.y; game.addChild(heartImage); healthImages.push(heartImage); } } // Call updateHealthDisplay to initialize health display updateHealthDisplay(); // Update score display every second LK.setInterval(function () { var currentScore = LK.getScore(); scoreTxt.setText("SCORE: " + currentScore); // Update highscore if current score is higher if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; highScoreTxt.setText("HIGH: " + highScore); } }, 1000); } // Spawn a new enemy with type based on difficulty function spawnEnemy() { // Check which lanes already have enemies var occupiedLanes = [false, false, false]; for (var i = 0; i < enemies.length; i++) { // Check if enemy is in any part of the visible screen (not just spawning area) // This ensures only one enemy per lane until it's off screen or dead occupiedLanes[enemies[i].lane] = true; } // Get available lanes var availableLanes = []; for (var i = 0; i < 3; i++) { if (!occupiedLanes[i]) { availableLanes.push(i); } } // If no lanes available, return without spawning if (availableLanes.length === 0) { return; } // Choose a random available lane var laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; var enemyType = 'basic'; // Determine enemy type distribution based on current difficulty var typeRoll = Math.random(); if (difficulty < 2) { // Early game - mostly basic enemies (100%) enemyType = 'basic'; } else if (difficulty < 3) { // Mid game - introduce shooters (30% shooter, 70% basic) if (typeRoll < 0.3) { enemyType = 'shooter'; } } else if (difficulty < 4) { // Late-mid game - introduce tanks (20% shooter, 10% tank, 70% basic) if (typeRoll < 0.2) { enemyType = 'shooter'; } else if (typeRoll < 0.3) { enemyType = 'tank'; } } else { // Late game - more challenging mix (30% shooter, 15% tank, 55% basic) if (typeRoll < 0.3) { enemyType = 'shooter'; } else if (typeRoll < 0.45) { enemyType = 'tank'; } } var enemy = new Enemy(); enemy.init(enemyType, laneIndex); enemy.x = lanePositions[laneIndex]; enemy.y = -100; game.addChild(enemy); enemies.push(enemy); } // Spawn a barrier across all lanes function spawnBarrier() { // Check if there are any obstacles (holes, barriers or enemies) at the top of the screen var obstaclesNearTop = false; // Check holes near top for (var i = 0; i < holes.length; i++) { if (holes[i].y < 300) { obstaclesNearTop = true; break; } } // Check barriers near top for (var i = 0; i < barriers.length; i++) { if (barriers[i].y < 300) { obstaclesNearTop = true; break; } } // Check enemies near top for (var i = 0; i < enemies.length; i++) { if (enemies[i].y < 300) { obstaclesNearTop = true; break; } } // If there are obstacles near the top, don't spawn a barrier if (obstaclesNearTop) { return; } var barrier = new Barrier(); barrier.x = 1024; // Center of screen barrier.y = -100; game.addChild(barrier); barriers.push(barrier); } // Spawn a hole in a random lane function spawnHole() { // Check which lanes already have obstacles (holes, barriers or enemies) var occupiedLanes = [false, false, false]; // Check holes for (var i = 0; i < holes.length; i++) { // Find which lane this hole is in for (var j = 0; j < 3; j++) { if (Math.abs(holes[i].x - lanePositions[j]) < 50) { occupiedLanes[j] = true; break; } } } // Check barriers - they block all lanes if (barriers.length > 0) { for (var i = 0; i < barriers.length; i++) { if (barriers[i].y < 300) { // Only consider barriers near the top // Barriers block all lanes occupiedLanes = [true, true, true]; break; } } } // Get available lanes var availableLanes = []; for (var i = 0; i < 3; i++) { if (!occupiedLanes[i]) { availableLanes.push(i); } } // If no lanes available, return without spawning if (availableLanes.length === 0) { return; } // Choose a random available lane var laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; var hole = new Hole(); hole.x = lanePositions[laneIndex]; hole.y = -100; hole.lane = laneIndex; // Track which lane this hole belongs to // First add background to the game var allChildren = _toConsumableArray(game.children); game.removeChildren(); // Add background tiles first for (var i = 0; i < backgroundTiles.length; i++) { var bgTile = null; for (var j = 0; j < allChildren.length; j++) { if (allChildren[j] === backgroundTiles[i]) { bgTile = allChildren[j]; allChildren.splice(j, 1); break; } } if (bgTile) { game.addChild(bgTile); } } // Add hole right after background game.addChild(hole); // Add all other elements on top for (var i = 0; i < allChildren.length; i++) { game.addChild(allChildren[i]); } holes.push(hole); } // Spawn a power-up function spawnPowerUp() { // Check which lanes already have obstacles (holes, barriers or enemies) var occupiedLanes = [false, false, false]; // Check holes for (var i = 0; i < holes.length; i++) { // Find which lane this hole is in for (var j = 0; j < 3; j++) { if (Math.abs(holes[i].x - lanePositions[j]) < 50 && holes[i].y < 300) { occupiedLanes[j] = true; break; } } } // Check enemies for (var i = 0; i < enemies.length; i++) { if (enemies[i].y < 300) { occupiedLanes[enemies[i].lane] = true; } } // Check barriers - they block all lanes for (var i = 0; i < barriers.length; i++) { if (barriers[i].y < 300) { occupiedLanes = [true, true, true]; break; } } // Get available lanes var availableLanes = []; for (var i = 0; i < 3; i++) { if (!occupiedLanes[i]) { availableLanes.push(i); } } // If no lanes available, return without spawning if (availableLanes.length === 0) { return; } // Choose a random available lane var laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; var randomValue = Math.random(); var powerUpType; if (randomValue < 0.33) { powerUpType = 'double'; } else if (randomValue < 0.66) { powerUpType = 'triple'; } else { powerUpType = 'fastShoot'; // Add fast shoot power-up type } var powerUp = new PowerUp(); powerUp.init(powerUpType); powerUp.x = lanePositions[laneIndex]; powerUp.y = -100; powerUp.lane = laneIndex; // Track which lane this power-up belongs to game.addChild(powerUp); powerUps.push(powerUp); } // Increase difficulty over time function updateDifficulty() { // More gradual difficulty increase based on score var targetDifficulty = Math.min(maxDifficulty, 0.5 + LK.getScore() / 20); // Smooth difficulty transition instead of sudden jumps difficulty += (targetDifficulty - difficulty) * difficultyIncreaseRate; // Store difficulty in game scope so bullets can access it game.difficulty = difficulty; // Calculate game speed based on smoothed difficulty var targetSpeed = 2 + Math.min(difficulty, 4); gameSpeed += (targetSpeed - gameSpeed) * 0.05; // Update game elements with new speed for (var i = 0; i < backgroundTiles.length; i++) { backgroundTiles[i].speed = gameSpeed; } for (var i = 0; i < enemies.length; i++) { var speedModifier = enemies[i].type === 'basic' ? 1 : 0; var targetEnemySpeed = gameSpeed + speedModifier; enemies[i].speed = targetEnemySpeed; } for (var i = 0; i < barriers.length; i++) { barriers[i].speed = gameSpeed; } for (var i = 0; i < holes.length; i++) { holes[i].speed = gameSpeed; } for (var i = 0; i < powerUps.length; i++) { powerUps[i].speed = gameSpeed; } } // Handle touch input for lane changing game.down = function (x, y, obj) { // Store touch start position for swipe detection game.touchStartX = x; game.touchStartY = y; }; // Handle touch move events for swipe detection game.move = function (x, y, obj) { // Skip if no touch start position is recorded if (!game.touchStartX) { return; } }; // Handle touch up events for swipe detection game.up = function (x, y, obj) { // Skip if no touch start position is recorded if (!game.touchStartX) { return; } // Calculate horizontal distance moved var deltaX = x - game.touchStartX; var deltaY = y - game.touchStartY; // Only process horizontal swipes (ignore vertical) if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) { if (deltaX < 0 && player.currentLane > 0) { // Swipe left - move to left lane player.moveTo(player.currentLane - 1); } else if (deltaX > 0 && player.currentLane < 2) { // Swipe right - move to right lane player.moveTo(player.currentLane + 1); } } // Reset touch start position game.touchStartX = null; game.touchStartY = null; }; // Main update function game.update = function () { // Check if game is over and waiting to show game over screen if (game.isGameOver) { // If this is the first frame after game over, stop all animations if (!game.animationsStopped) { game.animationsStopped = true; // Stop player animations if (player) { tween.stop(player.children[0]); // Stop player animations } // Stop all enemy animations for (var i = 0; i < enemies.length; i++) { if (enemies[i] && enemies[i].children[0]) { tween.stop(enemies[i].children[0]); // Stop enemy animations } } // Set all movable objects' speed to 0 for (var i = 0; i < enemies.length; i++) { enemies[i].speed = 0; } for (var i = 0; i < enemyBullets.length; i++) { enemyBullets[i].speed = 0; } for (var i = 0; i < playerBullets.length; i++) { playerBullets[i].speed = 0; } for (var i = 0; i < barriers.length; i++) { barriers[i].speed = 0; } for (var i = 0; i < holes.length; i++) { holes[i].speed = 0; } for (var i = 0; i < powerUps.length; i++) { powerUps[i].speed = 0; } for (var i = 0; i < backgroundTiles.length; i++) { backgroundTiles[i].speed = 0; } } // Don't update any movement when game is over return; } // Show menu on first update if (!menuScreen && !gameInitialized) { // Create menu screen menuScreen = new MenuScreen(); game.addChild(menuScreen); // Play menu music LK.playMusic('menuMusic', { loop: true, // Make sure it loops fade: { start: 0, end: 0.5, duration: 1000 } }); return; } // Check if we're still in menu mode (not gameInitialized) if (!gameInitialized) { // We're in menu mode, don't run any game logic return; } // Game already initialized, ensure player exists if (gameInitialized && !player) { initGame(); return; } // Update player position based on current lane if (player && typeof player.currentLane !== 'undefined') { var targetX = lanePositions[player.currentLane]; player.x += (targetX - player.x) * 0.8; // Much faster movement response (was 0.2) } // Update all game elements if (player && typeof player.update === 'function') { player.update(); } // Update background tiles for (var i = 0; i < backgroundTiles.length; i++) { backgroundTiles[i].update(); } // Update and check player bullets for (var i = playerBullets.length - 1; i >= 0; i--) { var bullet = playerBullets[i]; bullet.update(); // Remove bullets that go off screen if (bullet.y < -50) { bullet.destroy(); playerBullets.splice(i, 1); continue; } // Check for collisions with enemies var hitEnemy = false; for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (!bullet.lastWasIntersecting && bullet.intersects(enemy)) { bullet.lastWasIntersecting = true; if (enemy.takeDamage()) { enemy.destroy(); enemies.splice(j, 1); } hitEnemy = true; break; } } // Check for collisions with barriers if (!hitEnemy) { for (var j = barriers.length - 1; j >= 0; j--) { var barrier = barriers[j]; if (!bullet.lastWasIntersecting && bullet.intersects(barrier)) { bullet.lastWasIntersecting = true; if (barrier.takeDamage()) { barrier.destroy(); barriers.splice(j, 1); } hitEnemy = true; break; } } } // Remove bullet if it hit something if (hitEnemy) { bullet.destroy(); playerBullets.splice(i, 1); } } // Update and check enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var bullet = enemyBullets[i]; bullet.update(); // Remove bullets that go off screen if (bullet.y > 2732 + 50) { bullet.destroy(); enemyBullets.splice(i, 1); continue; } // Check for collision with player if (!bullet.lastWasIntersecting && bullet.intersects(player) && player.isAlive) { bullet.lastWasIntersecting = true; // Player hit by enemy bullet - use the takeDamage function player.takeDamage(); bullet.destroy(); enemyBullets.splice(i, 1); break; } } // Update and check enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemy.update(); // Remove enemies that go off screen if (enemy.y > 2732 + 100) { enemy.destroy(); enemies.splice(i, 1); continue; } // Check for collision with player if (!enemy.lastWasIntersecting && enemy.intersects(player) && player.isAlive) { enemy.lastWasIntersecting = true; // Player hit by enemy - use the takeDamage function player.takeDamage(); break; } } // Update and check barriers for (var i = barriers.length - 1; i >= 0; i--) { var barrier = barriers[i]; barrier.update(); // Remove barriers that go off screen if (barrier.y > 2732 + 100) { barrier.destroy(); barriers.splice(i, 1); continue; } // Check for collision with player if (!barrier.lastWasIntersecting && barrier.intersects(player) && player.isAlive) { barrier.lastWasIntersecting = true; // Player hit by barrier - use the takeDamage function player.takeDamage(); break; } } // Update and check holes for (var i = holes.length - 1; i >= 0; i--) { var hole = holes[i]; hole.update(); // Remove holes that go off screen if (hole.y > 2732 + 100) { hole.destroy(); holes.splice(i, 1); continue; } // Check for collision with player if (!hole.lastWasIntersecting && hole.intersects(player) && player.isAlive) { hole.lastWasIntersecting = true; // Player hit by hole - use the takeDamage function player.takeDamage(); break; } } // Check for player bullets hitting holes - bullets pass through for (var i = playerBullets.length - 1; i >= 0; i--) { var bullet = playerBullets[i]; for (var j = holes.length - 1; j >= 0; j--) { var hole = holes[j]; // When a bullet intersects a hole if (bullet.intersects(hole)) { // Only process the collision once (when initially intersecting) if (!bullet.lastWasIntersecting) { // Apply damage to the hole but don't destroy the bullet if (hole.takeDamage()) { hole.destroy(); holes.splice(j, 1); } } // Mark bullet as intersecting with the hole bullet.lastWasIntersecting = true; break; } } // Reset lastWasIntersecting when the bullet no longer intersects any hole var intersectsAnyHole = false; for (var j = 0; j < holes.length; j++) { if (bullet.intersects(holes[j])) { intersectsAnyHole = true; break; } } if (!intersectsAnyHole) { bullet.lastWasIntersecting = false; } } // Update and check power-ups for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; powerUp.update(); // Remove power-ups that go off screen if (powerUp.y > 2732 + 100) { powerUp.destroy(); powerUps.splice(i, 1); continue; } // Check for collision with player if (!powerUp.lastWasIntersecting && powerUp.intersects(player)) { powerUp.lastWasIntersecting = true; // Player got power-up player.activatePowerUp(powerUp.type); // Restore 1 health point when getting a power-up, up to max health if (player.health < player.maxHealth) { player.health++; // Update health display updateHealthDisplay(); } powerUp.destroy(); powerUps.splice(i, 1); } } // Function to manage spawn rates for different game elements function manageSpawnRates() { var frameStep = 60 * LK.deltaTime; // Update all spawn timers spawnTimer -= frameStep; barrierTimer -= frameStep; holeTimer -= frameStep; powerUpTimer -= frameStep; // Spawn enemies with rates dependent on difficulty if (spawnTimer <= 0) { spawnEnemy(); // Base spawn rate that changes with difficulty var baseEnemyRate = Math.max(60, 120 - difficulty * 8); // Adjust rate based on current difficulty level if (difficulty < 2) { // Early game - slower enemy spawns spawnTimer = baseEnemyRate * 1.2; } else if (difficulty < 4) { // Mid game - normal enemy spawns spawnTimer = baseEnemyRate; } else { // Late game - faster enemy spawns spawnTimer = baseEnemyRate * 0.8; } } // Spawn barriers with difficulty-based threshold if (barrierTimer <= 0) { // Different difficulty thresholds for barriers if (difficulty >= 0) { spawnBarrier(); // Adjust barrier spawn rate based on difficulty barrierTimer = Math.max(300, 400 - difficulty * 15); } else { // Reset timer without spawning if below threshold barrierTimer = 200; } } // Spawn holes with difficulty-based threshold if (holeTimer <= 0) { if (difficulty >= 0) { spawnHole(); // Adjust hole spawn rate based on difficulty holeTimer = Math.max(250, 350 - difficulty * 20); } else { // Reset timer without spawning if below threshold holeTimer = 150; } } // Spawn power-ups with variable timing if (powerUpTimer <= 0) { spawnPowerUp(); // Power-ups more frequent as game progresses but with a minimum interval powerUpTimer = Math.max(450, 600 + Math.max(0, 300 - difficulty * 50)); } } // Call the spawn rate manager manageSpawnRates(); // Update difficulty based on score updateDifficulty(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Background elements that scrolls to create movement illusion
var BackgroundTile = Container.expand(function () {
var self = Container.call(this);
var tileGraphics = self.attachAsset('backgroundTile', {
anchorX: 0.5,
anchorY: 0
});
self.speed = 2; //{6} // Start with slower speed
self.update = function () {
// Don't move if game is over
if (!game.isGameOver) {
self.y += self.speed;
// Reset position when it goes out of view
if (self.y >= 2732) {
// Calculate the exact position needed for seamless tiling
// Find the tile that's highest up (lowest y value)
var highestTile = self;
var highestY = self.y;
for (var i = 0; i < backgroundTiles.length; i++) {
var tile = backgroundTiles[i];
if (tile !== self && tile.y < highestY) {
highestTile = tile;
highestY = tile.y;
}
}
// Position this tile directly above the highest tile
self.y = highestY - tileGraphics.height;
}
}
};
return self;
});
// Game variables
// Barrier obstacle
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphics = self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5
});
// Barrier properties
self.health = 5;
self.speed = 2; //{i} // Start with slower speed
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Take damage
self.takeDamage = function () {
self.health--;
if (self.health <= 0) {
LK.getSound('explosion').play();
LK.setScore(LK.getScore() + 2);
return true; // Barrier destroyed
}
return false; // Barrier still intact
};
// Update called every frame
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Don't move if game is over
if (!game.isGameOver) {
// Move downward
self.y += self.speed;
}
};
return self;
});
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics;
// Enemy properties
self.health = 1;
self.speed = 2;
self.shootChance = 1; // 1% chance per frame
self.lane = 0;
self.type = 'basic'; // basic, shooter, tank
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Initialize enemy with type
self.init = function (type, lane) {
self.type = type;
self.lane = lane;
// Remove previous graphics if exists
if (enemyGraphics) {
self.removeChild(enemyGraphics);
}
if (type === 'basic') {
// Shots per second
// Add small grow and shrink animation
var _startBasicEnemyAnimation = function startBasicEnemyAnimation() {
tween(enemyGraphics, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 350,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(enemyGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 350,
easing: tween.easing,
onFinish: _startBasicEnemyAnimation
});
}
});
}; // Start the animation
enemyGraphics = self.attachAsset('enemyBasic', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.speed = 3; //{P} // Slower starting speed
self.shootRate = 200;
_startBasicEnemyAnimation();
} else if (type === 'shooter') {
// Moderate fire rate
// Add small grow and shrink animation
var _startShooterEnemyAnimation = function startShooterEnemyAnimation() {
tween(enemyGraphics, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 350,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(enemyGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 350,
easing: tween.easeInOut,
onFinish: _startShooterEnemyAnimation
});
}
});
}; // Start the animation
enemyGraphics = self.attachAsset('enemyShooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.speed = 2.5; // Slower starting speed
self.shootRate = 160;
_startShooterEnemyAnimation();
} else if (type === 'tank') {
enemyGraphics = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 30;
self.speed = 2; // Slower starting speed
self.shootRate = 400; // Slow fire rate
}
};
// Shoot method for enemies
self.shootRate = 200; // Base shoot rate (bullets per frame at 60fps)
self.shootCooldown = 0;
self.tryShoot = function () {
// Create bullet with a rate based on shootRate and normalized for 60fps
// shootRate is # of frames between shots at 60 FPS
// Higher shootRate = slower shooting
// Lower shootRate = faster shooting
if (Math.random() < 1 / self.shootRate * 60 * LK.deltaTime) {
// Play enemy shoot sound
LK.getSound('enemyShoot').play();
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 60;
// Set bullet speed based on enemy type and game difficulty
var baseSpeed = 5;
var typeBonus = 0;
// Different enemy types get different bullet speed bonuses
if (self.type === 'shooter') typeBonus = 1;
if (self.type === 'tank') typeBonus = 0.5;
// Apply difficulty bonus
var difficultyBonus = game.difficulty ? game.difficulty * 0.3 : 0;
bullet.speed = baseSpeed + typeBonus + difficultyBonus;
game.addChild(bullet);
enemyBullets.push(bullet);
}
};
// Take damage
self.takeDamage = function () {
self.health--;
if (self.health <= 0) {
LK.getSound('enemyDie').play();
LK.setScore(LK.getScore() + 1);
return true; // Enemy destroyed
}
return false; // Enemy still alive
};
// Update called every frame
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Don't move or shoot if game is over
if (!game.isGameOver) {
// Move downward
self.y += self.speed;
// Update shooting cooldown
if (self.shootCooldown > 0) {
self.shootCooldown -= LK.deltaTime;
}
// Try to shoot if we're a shooting type
if ((self.type === 'shooter' || self.type === 'tank' || self.type === 'basic') && self.y > 0) {
self.tryShoot();
}
}
};
return self;
});
// Enemy bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Don't move if game is over
if (!game.isGameOver) {
// Move bullet downward using delta time
var frameRate = 60; // Base frame rate
var speedMultiplier = LK.deltaTime * frameRate; // Adjust speed based on actual frame time
// Adjust bullet speed based on current game difficulty
var difficultySpeedBonus = game.difficulty ? game.difficulty * 0.5 : 0;
self.y += (self.speed + difficultySpeedBonus) * speedMultiplier;
}
};
return self;
});
// Hole obstacle class
var Hole = Container.expand(function () {
var self = Container.call(this);
// Create hole image
var holeGraphics = self.attachAsset('hole', {
anchorX: 0.5,
anchorY: 0.5
});
// Hole properties
self.health = 999;
self.speed = 2; // Start with slower speed
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Take damage
self.takeDamage = function () {
// Reduce hole health by 1
self.health--;
// Check if hole is completely destroyed
if (self.health <= 0) {
LK.getSound('explosion').play();
LK.setScore(LK.getScore() + 3); // More points than barriers
return true; // Hole destroyed
}
return false; // Hole still intact
};
// Update called every frame
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Don't move if game is over
if (!game.isGameOver) {
// Move downward
self.y += self.speed;
}
};
return self;
});
// Lane definitions for positioning
var Lane = Container.expand(function () {
var self = Container.call(this);
var laneGraphics = self.attachAsset('lane', {
anchorX: 0.5,
anchorY: 0
});
return self;
});
// Main Menu Screen
var MenuScreen = Container.expand(function () {
var self = Container.call(this);
// Define Vector2 positions for centered elements
var gameIconPosition = {
x: 2048 / 2,
y: 2732 / 2 - 700
}; // Center of screen horizontally, 400px from top
var playButtonPosition = {
x: 2048 / 2,
y: 2732 / 2
}; // Center of screen horizontally, 900px from top
// Define Vector2 scales for elements
var gameIconScale = {
x: 3.0,
y: 3.0
}; // Default scale for game icon
var playButtonScale = {
x: 3.0,
y: 3.0
}; // Default scale for play button
// Add background image
var menuBackground = self.attachAsset('menuBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
// Create game title graphic
var gameIcon = self.attachAsset('gameIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: gameIconPosition.x,
y: gameIconPosition.y,
scaleX: gameIconScale.x,
scaleY: gameIconScale.y
});
// Add grow and shrink animation to game icon
function startIconAnimation() {
tween(gameIcon, {
scaleX: gameIconScale.x * 1.1,
scaleY: gameIconScale.y * 1.1
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gameIcon, {
scaleX: gameIconScale.x * 0.95,
scaleY: gameIconScale.y * 0.95
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: startIconAnimation
});
}
});
}
// Start the animation
startIconAnimation();
// Create play button with animation
var playButton = self.attachAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5,
x: playButtonPosition.x,
y: playButtonPosition.y,
scaleX: playButtonScale.x,
scaleY: playButtonScale.y
});
// Add swipe instruction text
var swipeText = new Text2('Swipe to move left or right', {
size: 60,
fill: 0xFFFFFF
});
swipeText.anchor.set(0.5, 0.5);
swipeText.x = playButtonPosition.x;
swipeText.y = playButtonPosition.y + 200; // Position below play button
self.addChild(swipeText);
// Add highscore display to menu screen
if (storage.highScore && storage.highScore > 0) {
var highScoreText = new Text2('HIGHSCORE: ' + storage.highScore, {
size: 80,
fill: 0xFFD700
});
highScoreText.anchor.set(0.5, 0.5);
highScoreText.x = playButtonPosition.x;
highScoreText.y = playButtonPosition.y + 350; // Position below swipe instructions
self.addChild(highScoreText);
}
// Add pulse animation to play button
function startPulseAnimation() {
tween(playButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: startPulseAnimation
});
}
});
}
// Start the pulse animation
startPulseAnimation();
// Handle touch events on play button
playButton.down = function (x, y, obj) {
tween(playButton, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
playButton.up = function (x, y, obj) {
tween(playButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
onFinish: function onFinish() {
// Hide menu and set gameInitialized to true
self.visible = false;
gameInitialized = true; // This will trigger the game initialization
}
});
};
return self;
});
// Function to update health display
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add small grow and shrink animation
function startPlayerPulseAnimation() {
tween(playerGraphics, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playerGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: startPlayerPulseAnimation
});
}
});
}
// Start the animation
startPlayerPulseAnimation();
// Player properties
self.currentLane = 1; // 0=left, 1=center, 2=right
self.shootCooldown = 0;
self.shootRate = 50; // Configurable shoot rate (bullets per second)
self.bulletType = 'normal'; // normal, double, triple
self.powerUpTimer = 0;
self.isAlive = true;
self.maxHealth = 3; // Maximum health points
self.health = self.maxHealth; // Current health
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Method to handle damage and health reduction
self.takeDamage = function () {
if (!self.isAlive) {
return;
} // Don't take damage if already dead
self.health--; // Subtract health by 1
// Update health display
updateHealthDisplay();
// Turn player sprite red briefly to indicate damage
tween(playerGraphics, {
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(playerGraphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
// Check if player is dead
if (self.health <= 0) {
self.isAlive = false;
// Stop player's grow and shrink animation
tween.stop(playerGraphics);
// Change player image to dead image
// Remove current graphics
self.removeChild(playerGraphics);
// Add dead player image
playerGraphics = self.attachAsset('playerDead', {
anchorX: 0.5,
anchorY: 0.5
});
// Flash screen red for game over
LK.effects.flashScreen(0xff0000, 1000);
// Set a global variable to indicate game is over but waiting
game.isGameOver = true;
// Play game over sound effect
LK.getSound('gameOver').play();
// Update highscore if current score is higher
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Wait 3 seconds before showing game over
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
}
return self.health <= 0; // Return true if player died
};
// Movement handling
self.moveTo = function (laneIndex) {
if (laneIndex >= 0 && laneIndex <= 2) {
self.currentLane = laneIndex;
}
};
// Shoot method
self.shoot = function () {
// Use the configurable shootRate variable instead of hard-coded value
if (self.shootCooldown <= 0) {
// Only play sound if game is not over
if (!game.isGameOver) {
LK.getSound('shoot').play();
}
if (self.bulletType === 'normal') {
// Create a single bullet
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y - 60;
// Make bullet invisible if game is over
if (game.isGameOver) {
bullet.alpha = 0;
}
game.addChild(bullet);
playerBullets.push(bullet);
} else if (self.bulletType === 'double') {
// Create two side-by-side bullets
var bullet1 = new PlayerBullet();
bullet1.x = self.x - 30;
bullet1.y = self.y - 60;
// Make bullet invisible if game is over
if (game.isGameOver) {
bullet1.alpha = 0;
}
game.addChild(bullet1);
playerBullets.push(bullet1);
var bullet2 = new PlayerBullet();
bullet2.x = self.x + 30;
bullet2.y = self.y - 60;
// Make bullet invisible if game is over
if (game.isGameOver) {
bullet2.alpha = 0;
}
game.addChild(bullet2);
playerBullets.push(bullet2);
} else if (self.bulletType === 'triple') {
// Create three bullets - one forward and two diagonal
var bulletCenter = new PlayerBullet();
bulletCenter.x = self.x;
bulletCenter.y = self.y - 60;
// Make bullet invisible if game is over
if (game.isGameOver) {
bulletCenter.alpha = 0;
}
game.addChild(bulletCenter);
playerBullets.push(bulletCenter);
var bulletLeft = new PlayerBullet();
bulletLeft.x = self.x - 30;
bulletLeft.y = self.y - 60;
bulletLeft.angle = -15; // Diagonal left
// Make bullet invisible if game is over
if (game.isGameOver) {
bulletLeft.alpha = 0;
}
game.addChild(bulletLeft);
playerBullets.push(bulletLeft);
var bulletRight = new PlayerBullet();
bulletRight.x = self.x + 30;
bulletRight.y = self.y - 60;
bulletRight.angle = 15; // Diagonal right
// Make bullet invisible if game is over
if (game.isGameOver) {
bulletRight.alpha = 0;
}
game.addChild(bulletRight);
playerBullets.push(bulletRight);
}
// Set cooldown based on the configurable shootRate variable
self.shootCooldown = self.shootRate / 60; // Normalize to 60 fps
}
};
// Power-up activation
self.activatePowerUp = function (type) {
LK.getSound('powerUp').play();
if (type === 'fastShoot') {
self.setShootRate(25); // Set faster shooting rate
self.bulletType = 'normal'; // Keep normal bullet type
} else {
self.bulletType = type;
self.setShootRate(50); // Reset to default rate
}
self.powerUpTimer = 600; // 5 seconds at 60fps
};
// Method to change the shoot rate
self.setShootRate = function (rate) {
if (rate > 0) {
self.shootRate = rate;
}
};
// Update called every frame
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Handle cooldowns directly (not converting from frames)
if (self.shootCooldown > 0) {
self.shootCooldown -= LK.deltaTime;
}
// Power-up timer using delta time
if (self.powerUpTimer > 0) {
self.powerUpTimer -= 1; // Decrease by 1 each frame at 60fps
if (self.powerUpTimer <= 0) {
self.bulletType = 'normal';
self.setShootRate(50); // Reset shooting rate to default
}
}
// Auto-shoot
self.shoot();
};
return self;
});
// Player bullet
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 20;
self.angle = 0; // 0 = straight, positive/negative for diagonal
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Don't move if game is over
if (!game.isGameOver) {
// Move bullet based on angle using delta time
var radians = self.angle * (Math.PI / 180);
var frameRate = 60; // Base frame rate
var speedMultiplier = LK.deltaTime * frameRate; // Adjust speed based on actual frame time
self.x += Math.sin(radians) * self.speed * speedMultiplier;
self.y -= Math.cos(radians) * self.speed * speedMultiplier;
}
};
return self;
});
// Power-up class
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Start with a placeholder that will be replaced in init()
var powerUpGraphics = self.attachAsset('doublePowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
// Power-up properties
self.type = 'double'; // double, triple
self.speed = 2; //{2h} // Start with slower speed
// Last position tracking for collision detection
self.lastX = 0;
self.lastY = 0;
self.lastWasIntersecting = false;
// Initialize power-up with type
self.init = function (type) {
self.type = type;
// Remove existing graphics
self.removeChild(powerUpGraphics);
// Use appropriate image based on power-up type
if (type === 'double') {
powerUpGraphics = self.attachAsset('doublePowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'triple') {
powerUpGraphics = self.attachAsset('triplePowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'fastShoot') {
powerUpGraphics = self.attachAsset('fastShootPowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add pulsating animation to the power-up
function startPulseAnimation() {
// Grow slightly
tween(powerUpGraphics, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Shrink back
tween(powerUpGraphics, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: startPulseAnimation
});
}
});
}
// Start the animation
startPulseAnimation();
};
// Update called every frame
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Don't move if game is over
if (!game.isGameOver) {
// Move downward
self.y += self.speed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Import tween plugin
// Player, enemies and bullets
// Environment and obstacles
// Power-ups
// Sounds
// Background
// Game variables
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) {
return _arrayLikeToArray(r, a);
}
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) {
return Array.from(r);
}
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) {
return _arrayLikeToArray(r);
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) {
n[e] = r[e];
}
return n;
}
var menuScreen;
var gameInitialized = false;
var highScore = storage.highScore || 0;
// Add deltaTime calculation if it's not provided by LK engine
// Function to update health display
function updateHealthDisplay() {
// Vector2 variable for hearts position
var heartsPosition = {
x: 2048 / 2,
y: 220
};
// Clear existing health images
for (var i = 0; i < healthImages.length; i++) {
healthImages[i].destroy();
}
healthImages = [];
// Create new health images based on current health
for (var i = 0; i < player.health; i++) {
var heartImage = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Position hearts centered at heart position with spacing
heartImage.x = heartsPosition.x - ((player.health - 1) / 2 - i) * 80; // Center hearts around heartsPosition.x
heartImage.y = heartsPosition.y;
game.addChild(heartImage);
healthImages.push(heartImage);
}
}
if (typeof LK.deltaTime === 'undefined') {
LK.lastTime = Date.now();
LK.deltaTime = 1 / 60; // Default to 60fps
LK.targetFPS = 60;
// Update deltaTime before each frame
LK.on('tick', function () {
var now = Date.now();
LK.deltaTime = Math.min(0.1, (now - LK.lastTime) / 1000); // Cap at 0.1s to prevent huge jumps
LK.lastTime = now;
// Lock to 60fps
if (LK.deltaTime < 1 / LK.targetFPS) {
LK.deltaTime = 1 / LK.targetFPS;
}
});
}
var player;
var lanes = [];
var lanePositions = [512, 1024, 1536]; // The three lanes X positions
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var barriers = [];
var holes = []; // Array to track hole obstacles
var powerUps = [];
var backgroundTiles = [];
var healthImages = [];
var gameSpeed = 2; // Start with slower speed
var difficulty = 0.5; // Start with lower difficulty
var spawnTimer = 0;
var barrierTimer = 0;
var holeTimer = 0; // Timer for spawning holes
var powerUpTimer = 0;
var difficultyIncreaseRate = 0.05; // How fast difficulty increases
var maxDifficulty = 6; // Cap on maximum difficulty
// Touch tracking for swipe detection
var touchStartX = null;
var touchStartY = null;
// Initialize the game elements
function initGame() {
// Mark game as initialized
gameInitialized = true;
// Hide menu if it exists
if (menuScreen) {
menuScreen.visible = false;
}
// Switch from menu music to game music when game starts
LK.stopMusic(); // Stop menu music first
// Play background music with loop
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
// Set up background
var bgTileHeight = LK.getAsset('backgroundTile', {}).height;
var numTilesNeeded = Math.ceil(2732 / bgTileHeight) + 1; // One extra for seamless scrolling
for (var i = 0; i < numTilesNeeded; i++) {
var bgTile = new BackgroundTile();
bgTile.x = 1024; // Center of screen
bgTile.y = i * bgTileHeight; // Position tiles exactly adjacent to each other
backgroundTiles.push(bgTile);
game.addChild(bgTile);
}
// Create the lane markers
for (var i = 0; i < 2; i++) {
var lane = new Lane();
lane.x = lanePositions[i] + (lanePositions[1] - lanePositions[0]) / 2; // Position between lanes
lane.y = 0;
lane.alpha = 0.3;
lanes.push(lane);
game.addChild(lane);
}
// Create the player
player = new Player();
player.x = lanePositions[1]; // Start in center lane
player.y = 2732 - 300; // Near bottom of screen
player.health = player.maxHealth; // Ensure health is set to maximum
game.addChild(player);
// Initialize score display
var scoreBackground = game.addChild(LK.getAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 80,
alpha: 0.7
}));
var scoreTxt = new Text2('0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0.5);
scoreTxt.x = 1024;
scoreTxt.y = 80;
game.addChild(scoreTxt);
// Create highscore display
var highScoreTxt = new Text2('HIGH: ' + highScore, {
size: 40,
fill: 0xFFFF00
});
highScoreTxt.anchor.set(0.5, 0.5);
highScoreTxt.x = 1024;
highScoreTxt.y = 130;
game.addChild(highScoreTxt);
function updateHealthDisplay() {
// Vector2 variable for hearts position
var heartsPosition = {
x: 2048 / 2,
y: 220
};
// Clear existing health images
for (var i = 0; i < healthImages.length; i++) {
healthImages[i].destroy();
}
healthImages = [];
// Create new health images based on current health
for (var i = 0; i < player.health; i++) {
var heartImage = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Position hearts centered at heart position with spacing
heartImage.x = heartsPosition.x - ((player.health - 1) / 2 - i) * 80; // Center hearts around heartsPosition.x
heartImage.y = heartsPosition.y;
game.addChild(heartImage);
healthImages.push(heartImage);
}
}
// Call updateHealthDisplay to initialize health display
updateHealthDisplay();
// Update score display every second
LK.setInterval(function () {
var currentScore = LK.getScore();
scoreTxt.setText("SCORE: " + currentScore);
// Update highscore if current score is higher
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
highScoreTxt.setText("HIGH: " + highScore);
}
}, 1000);
}
// Spawn a new enemy with type based on difficulty
function spawnEnemy() {
// Check which lanes already have enemies
var occupiedLanes = [false, false, false];
for (var i = 0; i < enemies.length; i++) {
// Check if enemy is in any part of the visible screen (not just spawning area)
// This ensures only one enemy per lane until it's off screen or dead
occupiedLanes[enemies[i].lane] = true;
}
// Get available lanes
var availableLanes = [];
for (var i = 0; i < 3; i++) {
if (!occupiedLanes[i]) {
availableLanes.push(i);
}
}
// If no lanes available, return without spawning
if (availableLanes.length === 0) {
return;
}
// Choose a random available lane
var laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
var enemyType = 'basic';
// Determine enemy type distribution based on current difficulty
var typeRoll = Math.random();
if (difficulty < 2) {
// Early game - mostly basic enemies (100%)
enemyType = 'basic';
} else if (difficulty < 3) {
// Mid game - introduce shooters (30% shooter, 70% basic)
if (typeRoll < 0.3) {
enemyType = 'shooter';
}
} else if (difficulty < 4) {
// Late-mid game - introduce tanks (20% shooter, 10% tank, 70% basic)
if (typeRoll < 0.2) {
enemyType = 'shooter';
} else if (typeRoll < 0.3) {
enemyType = 'tank';
}
} else {
// Late game - more challenging mix (30% shooter, 15% tank, 55% basic)
if (typeRoll < 0.3) {
enemyType = 'shooter';
} else if (typeRoll < 0.45) {
enemyType = 'tank';
}
}
var enemy = new Enemy();
enemy.init(enemyType, laneIndex);
enemy.x = lanePositions[laneIndex];
enemy.y = -100;
game.addChild(enemy);
enemies.push(enemy);
}
// Spawn a barrier across all lanes
function spawnBarrier() {
// Check if there are any obstacles (holes, barriers or enemies) at the top of the screen
var obstaclesNearTop = false;
// Check holes near top
for (var i = 0; i < holes.length; i++) {
if (holes[i].y < 300) {
obstaclesNearTop = true;
break;
}
}
// Check barriers near top
for (var i = 0; i < barriers.length; i++) {
if (barriers[i].y < 300) {
obstaclesNearTop = true;
break;
}
}
// Check enemies near top
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].y < 300) {
obstaclesNearTop = true;
break;
}
}
// If there are obstacles near the top, don't spawn a barrier
if (obstaclesNearTop) {
return;
}
var barrier = new Barrier();
barrier.x = 1024; // Center of screen
barrier.y = -100;
game.addChild(barrier);
barriers.push(barrier);
}
// Spawn a hole in a random lane
function spawnHole() {
// Check which lanes already have obstacles (holes, barriers or enemies)
var occupiedLanes = [false, false, false];
// Check holes
for (var i = 0; i < holes.length; i++) {
// Find which lane this hole is in
for (var j = 0; j < 3; j++) {
if (Math.abs(holes[i].x - lanePositions[j]) < 50) {
occupiedLanes[j] = true;
break;
}
}
}
// Check barriers - they block all lanes
if (barriers.length > 0) {
for (var i = 0; i < barriers.length; i++) {
if (barriers[i].y < 300) {
// Only consider barriers near the top
// Barriers block all lanes
occupiedLanes = [true, true, true];
break;
}
}
}
// Get available lanes
var availableLanes = [];
for (var i = 0; i < 3; i++) {
if (!occupiedLanes[i]) {
availableLanes.push(i);
}
}
// If no lanes available, return without spawning
if (availableLanes.length === 0) {
return;
}
// Choose a random available lane
var laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
var hole = new Hole();
hole.x = lanePositions[laneIndex];
hole.y = -100;
hole.lane = laneIndex; // Track which lane this hole belongs to
// First add background to the game
var allChildren = _toConsumableArray(game.children);
game.removeChildren();
// Add background tiles first
for (var i = 0; i < backgroundTiles.length; i++) {
var bgTile = null;
for (var j = 0; j < allChildren.length; j++) {
if (allChildren[j] === backgroundTiles[i]) {
bgTile = allChildren[j];
allChildren.splice(j, 1);
break;
}
}
if (bgTile) {
game.addChild(bgTile);
}
}
// Add hole right after background
game.addChild(hole);
// Add all other elements on top
for (var i = 0; i < allChildren.length; i++) {
game.addChild(allChildren[i]);
}
holes.push(hole);
}
// Spawn a power-up
function spawnPowerUp() {
// Check which lanes already have obstacles (holes, barriers or enemies)
var occupiedLanes = [false, false, false];
// Check holes
for (var i = 0; i < holes.length; i++) {
// Find which lane this hole is in
for (var j = 0; j < 3; j++) {
if (Math.abs(holes[i].x - lanePositions[j]) < 50 && holes[i].y < 300) {
occupiedLanes[j] = true;
break;
}
}
}
// Check enemies
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].y < 300) {
occupiedLanes[enemies[i].lane] = true;
}
}
// Check barriers - they block all lanes
for (var i = 0; i < barriers.length; i++) {
if (barriers[i].y < 300) {
occupiedLanes = [true, true, true];
break;
}
}
// Get available lanes
var availableLanes = [];
for (var i = 0; i < 3; i++) {
if (!occupiedLanes[i]) {
availableLanes.push(i);
}
}
// If no lanes available, return without spawning
if (availableLanes.length === 0) {
return;
}
// Choose a random available lane
var laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
var randomValue = Math.random();
var powerUpType;
if (randomValue < 0.33) {
powerUpType = 'double';
} else if (randomValue < 0.66) {
powerUpType = 'triple';
} else {
powerUpType = 'fastShoot'; // Add fast shoot power-up type
}
var powerUp = new PowerUp();
powerUp.init(powerUpType);
powerUp.x = lanePositions[laneIndex];
powerUp.y = -100;
powerUp.lane = laneIndex; // Track which lane this power-up belongs to
game.addChild(powerUp);
powerUps.push(powerUp);
}
// Increase difficulty over time
function updateDifficulty() {
// More gradual difficulty increase based on score
var targetDifficulty = Math.min(maxDifficulty, 0.5 + LK.getScore() / 20);
// Smooth difficulty transition instead of sudden jumps
difficulty += (targetDifficulty - difficulty) * difficultyIncreaseRate;
// Store difficulty in game scope so bullets can access it
game.difficulty = difficulty;
// Calculate game speed based on smoothed difficulty
var targetSpeed = 2 + Math.min(difficulty, 4);
gameSpeed += (targetSpeed - gameSpeed) * 0.05;
// Update game elements with new speed
for (var i = 0; i < backgroundTiles.length; i++) {
backgroundTiles[i].speed = gameSpeed;
}
for (var i = 0; i < enemies.length; i++) {
var speedModifier = enemies[i].type === 'basic' ? 1 : 0;
var targetEnemySpeed = gameSpeed + speedModifier;
enemies[i].speed = targetEnemySpeed;
}
for (var i = 0; i < barriers.length; i++) {
barriers[i].speed = gameSpeed;
}
for (var i = 0; i < holes.length; i++) {
holes[i].speed = gameSpeed;
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].speed = gameSpeed;
}
}
// Handle touch input for lane changing
game.down = function (x, y, obj) {
// Store touch start position for swipe detection
game.touchStartX = x;
game.touchStartY = y;
};
// Handle touch move events for swipe detection
game.move = function (x, y, obj) {
// Skip if no touch start position is recorded
if (!game.touchStartX) {
return;
}
};
// Handle touch up events for swipe detection
game.up = function (x, y, obj) {
// Skip if no touch start position is recorded
if (!game.touchStartX) {
return;
}
// Calculate horizontal distance moved
var deltaX = x - game.touchStartX;
var deltaY = y - game.touchStartY;
// Only process horizontal swipes (ignore vertical)
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
if (deltaX < 0 && player.currentLane > 0) {
// Swipe left - move to left lane
player.moveTo(player.currentLane - 1);
} else if (deltaX > 0 && player.currentLane < 2) {
// Swipe right - move to right lane
player.moveTo(player.currentLane + 1);
}
}
// Reset touch start position
game.touchStartX = null;
game.touchStartY = null;
};
// Main update function
game.update = function () {
// Check if game is over and waiting to show game over screen
if (game.isGameOver) {
// If this is the first frame after game over, stop all animations
if (!game.animationsStopped) {
game.animationsStopped = true;
// Stop player animations
if (player) {
tween.stop(player.children[0]); // Stop player animations
}
// Stop all enemy animations
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] && enemies[i].children[0]) {
tween.stop(enemies[i].children[0]); // Stop enemy animations
}
}
// Set all movable objects' speed to 0
for (var i = 0; i < enemies.length; i++) {
enemies[i].speed = 0;
}
for (var i = 0; i < enemyBullets.length; i++) {
enemyBullets[i].speed = 0;
}
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].speed = 0;
}
for (var i = 0; i < barriers.length; i++) {
barriers[i].speed = 0;
}
for (var i = 0; i < holes.length; i++) {
holes[i].speed = 0;
}
for (var i = 0; i < powerUps.length; i++) {
powerUps[i].speed = 0;
}
for (var i = 0; i < backgroundTiles.length; i++) {
backgroundTiles[i].speed = 0;
}
}
// Don't update any movement when game is over
return;
}
// Show menu on first update
if (!menuScreen && !gameInitialized) {
// Create menu screen
menuScreen = new MenuScreen();
game.addChild(menuScreen);
// Play menu music
LK.playMusic('menuMusic', {
loop: true,
// Make sure it loops
fade: {
start: 0,
end: 0.5,
duration: 1000
}
});
return;
}
// Check if we're still in menu mode (not gameInitialized)
if (!gameInitialized) {
// We're in menu mode, don't run any game logic
return;
}
// Game already initialized, ensure player exists
if (gameInitialized && !player) {
initGame();
return;
}
// Update player position based on current lane
if (player && typeof player.currentLane !== 'undefined') {
var targetX = lanePositions[player.currentLane];
player.x += (targetX - player.x) * 0.8; // Much faster movement response (was 0.2)
}
// Update all game elements
if (player && typeof player.update === 'function') {
player.update();
}
// Update background tiles
for (var i = 0; i < backgroundTiles.length; i++) {
backgroundTiles[i].update();
}
// Update and check player bullets
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
bullet.update();
// Remove bullets that go off screen
if (bullet.y < -50) {
bullet.destroy();
playerBullets.splice(i, 1);
continue;
}
// Check for collisions with enemies
var hitEnemy = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (!bullet.lastWasIntersecting && bullet.intersects(enemy)) {
bullet.lastWasIntersecting = true;
if (enemy.takeDamage()) {
enemy.destroy();
enemies.splice(j, 1);
}
hitEnemy = true;
break;
}
}
// Check for collisions with barriers
if (!hitEnemy) {
for (var j = barriers.length - 1; j >= 0; j--) {
var barrier = barriers[j];
if (!bullet.lastWasIntersecting && bullet.intersects(barrier)) {
bullet.lastWasIntersecting = true;
if (barrier.takeDamage()) {
barrier.destroy();
barriers.splice(j, 1);
}
hitEnemy = true;
break;
}
}
}
// Remove bullet if it hit something
if (hitEnemy) {
bullet.destroy();
playerBullets.splice(i, 1);
}
}
// Update and check enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
bullet.update();
// Remove bullets that go off screen
if (bullet.y > 2732 + 50) {
bullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check for collision with player
if (!bullet.lastWasIntersecting && bullet.intersects(player) && player.isAlive) {
bullet.lastWasIntersecting = true;
// Player hit by enemy bullet - use the takeDamage function
player.takeDamage();
bullet.destroy();
enemyBullets.splice(i, 1);
break;
}
}
// Update and check enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.update();
// Remove enemies that go off screen
if (enemy.y > 2732 + 100) {
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check for collision with player
if (!enemy.lastWasIntersecting && enemy.intersects(player) && player.isAlive) {
enemy.lastWasIntersecting = true;
// Player hit by enemy - use the takeDamage function
player.takeDamage();
break;
}
}
// Update and check barriers
for (var i = barriers.length - 1; i >= 0; i--) {
var barrier = barriers[i];
barrier.update();
// Remove barriers that go off screen
if (barrier.y > 2732 + 100) {
barrier.destroy();
barriers.splice(i, 1);
continue;
}
// Check for collision with player
if (!barrier.lastWasIntersecting && barrier.intersects(player) && player.isAlive) {
barrier.lastWasIntersecting = true;
// Player hit by barrier - use the takeDamage function
player.takeDamage();
break;
}
}
// Update and check holes
for (var i = holes.length - 1; i >= 0; i--) {
var hole = holes[i];
hole.update();
// Remove holes that go off screen
if (hole.y > 2732 + 100) {
hole.destroy();
holes.splice(i, 1);
continue;
}
// Check for collision with player
if (!hole.lastWasIntersecting && hole.intersects(player) && player.isAlive) {
hole.lastWasIntersecting = true;
// Player hit by hole - use the takeDamage function
player.takeDamage();
break;
}
}
// Check for player bullets hitting holes - bullets pass through
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
for (var j = holes.length - 1; j >= 0; j--) {
var hole = holes[j];
// When a bullet intersects a hole
if (bullet.intersects(hole)) {
// Only process the collision once (when initially intersecting)
if (!bullet.lastWasIntersecting) {
// Apply damage to the hole but don't destroy the bullet
if (hole.takeDamage()) {
hole.destroy();
holes.splice(j, 1);
}
}
// Mark bullet as intersecting with the hole
bullet.lastWasIntersecting = true;
break;
}
}
// Reset lastWasIntersecting when the bullet no longer intersects any hole
var intersectsAnyHole = false;
for (var j = 0; j < holes.length; j++) {
if (bullet.intersects(holes[j])) {
intersectsAnyHole = true;
break;
}
}
if (!intersectsAnyHole) {
bullet.lastWasIntersecting = false;
}
}
// Update and check power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
powerUp.update();
// Remove power-ups that go off screen
if (powerUp.y > 2732 + 100) {
powerUp.destroy();
powerUps.splice(i, 1);
continue;
}
// Check for collision with player
if (!powerUp.lastWasIntersecting && powerUp.intersects(player)) {
powerUp.lastWasIntersecting = true;
// Player got power-up
player.activatePowerUp(powerUp.type);
// Restore 1 health point when getting a power-up, up to max health
if (player.health < player.maxHealth) {
player.health++;
// Update health display
updateHealthDisplay();
}
powerUp.destroy();
powerUps.splice(i, 1);
}
}
// Function to manage spawn rates for different game elements
function manageSpawnRates() {
var frameStep = 60 * LK.deltaTime;
// Update all spawn timers
spawnTimer -= frameStep;
barrierTimer -= frameStep;
holeTimer -= frameStep;
powerUpTimer -= frameStep;
// Spawn enemies with rates dependent on difficulty
if (spawnTimer <= 0) {
spawnEnemy();
// Base spawn rate that changes with difficulty
var baseEnemyRate = Math.max(60, 120 - difficulty * 8);
// Adjust rate based on current difficulty level
if (difficulty < 2) {
// Early game - slower enemy spawns
spawnTimer = baseEnemyRate * 1.2;
} else if (difficulty < 4) {
// Mid game - normal enemy spawns
spawnTimer = baseEnemyRate;
} else {
// Late game - faster enemy spawns
spawnTimer = baseEnemyRate * 0.8;
}
}
// Spawn barriers with difficulty-based threshold
if (barrierTimer <= 0) {
// Different difficulty thresholds for barriers
if (difficulty >= 0) {
spawnBarrier();
// Adjust barrier spawn rate based on difficulty
barrierTimer = Math.max(300, 400 - difficulty * 15);
} else {
// Reset timer without spawning if below threshold
barrierTimer = 200;
}
}
// Spawn holes with difficulty-based threshold
if (holeTimer <= 0) {
if (difficulty >= 0) {
spawnHole();
// Adjust hole spawn rate based on difficulty
holeTimer = Math.max(250, 350 - difficulty * 20);
} else {
// Reset timer without spawning if below threshold
holeTimer = 150;
}
}
// Spawn power-ups with variable timing
if (powerUpTimer <= 0) {
spawnPowerUp();
// Power-ups more frequent as game progresses but with a minimum interval
powerUpTimer = Math.max(450, 600 + Math.max(0, 300 - difficulty * 50));
}
}
// Call the spawn rate manager
manageSpawnRates();
// Update difficulty based on score
updateDifficulty();
};
an 16bit 20x40 yellow bullet. In-Game asset. 2d. High contrast. No shadows
a tank from a top down view in realistic 16bit 90s retro game style. the tank should have its bazooka point straight forward, it should be seen from above so no wheels should be seen. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
make it redish like a ruby
make it so there are 2 bullets side by side
make it so that there are 3 bullets, the one on the left inclined diagonally to the left, the one in the middle forward and the one on the right inclined diagonally to the right
make it so that there are two bullets one above the other like this 2 dots, :
an 16bit 20x40 red heart. In-Game asset. 2d. High contrast. No shadows
make it just an round hole with spikes in it seen from above
create an simple retangular play button image writen "play" in it, make it in a 16bit style. In-Game asset. 2d. High contrast. No shadows
Create an title image written "Bullet Rush" in it, add some elements of war like bulets and make it in a semirealistic 16bit style. In-Game asset. 2d. High contrast. No shadows
make soldier laying in with belly up dead