/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Box class var Box = Container.expand(function () { var self = Container.call(this); var boxGraphics = self.attachAsset('box', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, // Increase the scale to enlarge the bounding box scaleY: 1.2 }); self.speed = 5; self.lastIntersecting = false; // Track last intersection state with player self.update = function () { self.y += self.speed; // Check for player intersection transitions var currentIntersecting = self.intersects(player); if (!self.lastIntersecting && currentIntersecting) { // Flash the box briefly to indicate collection is possible tween(boxGraphics, { tint: 0xFFFF00, scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(boxGraphics, { tint: 0xFFFFFF, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeIn }); } }); } self.lastIntersecting = currentIntersecting; if (self.y > 2732) { self.destroy(); } }; }); // Box1 class var Box1 = Container.expand(function () { var self = Container.call(this); var boxGraphics = self.attachAsset('box1', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, // Increase the scale to enlarge the bounding box scaleY: 1.2 }); self.speed = 5; self.lastIntersecting = false; // Track last intersection state with player self.update = function () { self.y += self.speed; // Check for player intersection transitions var currentIntersecting = self.intersects(player); if (!self.lastIntersecting && currentIntersecting) { // Flash the box briefly to indicate collection is possible tween(boxGraphics, { tint: 0xFF00FF, scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(boxGraphics, { tint: 0xFFFFFF, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeIn }); } }); } self.lastIntersecting = currentIntersecting; if (self.y > 2732) { self.destroy(); } }; }); // Box2 class var Box2 = Container.expand(function () { var self = Container.call(this); var boxGraphics = self.attachAsset('box2', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, // Increase the scale to enlarge the bounding box scaleY: 1.2 }); self.speed = 7; self.lastIntersecting = false; // Track last intersection state with player self.update = function () { self.y += self.speed; // Check for player intersection transitions var currentIntersecting = self.intersects(player); if (!self.lastIntersecting && currentIntersecting) { // Flash the box briefly to indicate collection is possible tween(boxGraphics, { tint: 0x00FFFF, scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(boxGraphics, { tint: 0xFFFFFF, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeIn }); } }); } self.lastIntersecting = currentIntersecting; if (self.y > 2732) { self.destroy(); } }; }); // Box3 class var Box3 = Container.expand(function () { var self = Container.call(this); var boxGraphics = self.attachAsset('box3', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, // Increase the scale to enlarge the bounding box scaleY: 1.2 }); self.speed = 9; self.lastIntersecting = false; // Track last intersection state with player self.update = function () { self.y += self.speed; // Check for player intersection transitions var currentIntersecting = self.intersects(player); if (!self.lastIntersecting && currentIntersecting) { // Flash the box briefly to indicate collection is possible with heart color tween(boxGraphics, { tint: 0xFF0000, scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(boxGraphics, { tint: 0xFFFFFF, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeIn }); } }); } self.lastIntersecting = currentIntersecting; if (self.y > 2732) { self.destroy(); } }; }); // The assets will be automatically created and loaded by the LK engine. // Bubble class var Bubble = Container.expand(function () { var self = Container.call(this); var bubbleGraphics = self.attachAsset('bubble', { anchorX: 0.5, anchorY: 0.5, alpha: 0 // Start transparent }); // Make the bubble opaque after 1 second tween(bubbleGraphics, { alpha: 1 }, { duration: 1000 }); self.speed = -10; // Negative speed to move upward self.lastY = undefined; // Track last position for intersection checks self.hasHitTop = false; // Flag to track if lantern has gone off screen at top self.update = function () { if (self.lastY === undefined) { self.lastY = self.y; } self.y += self.speed; if (self.y < 1200) { // Start tinting when approaching the top tween(self, { tint: 0xFF0000 }, { duration: 500, easing: tween.linear }); } // Check if the lantern is going off the top of the screen without being destroyed if (self.lastY >= 0 && self.y < 0 && !self.hasHitTop) { self.hasHitTop = true; // Mark as hit top to prevent multiple life deductions // Player didn't destroy the lantern in time lives -= 1; // Create explosion effect at the top where the lantern left var explosion = new Explosion(); explosion.x = self.x; explosion.y = 10; // Just at the edge of the screen game.addChild(explosion); // Play explosion sound LK.getSound('explosion').play(); // Remove a heart icon when a life is lost if (hearts.length > lives) { var heartToRemove = hearts.pop(); if (heartToRemove) { tween(heartToRemove.scale, { x: 0, y: 0 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { heartToRemove.destroy(); } }); } } if (lives < 0) { // Handle player death with dramatic effect handlePlayerDeath(); return; } self.destroy(); } // Handle going completely off screen // Note: Not removing the lantern when it intersects with player // as per the requirement that player collision shouldn't remove lives if (self.y < -200) { self.destroy(); } self.lastY = self.y; }; }); // Explosion class with color customization var Explosion = Container.expand(function () { var self = Container.call(this); var explosionGraphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); // Extend self with tint property accessor/mutator to allow tinting the explosion Object.defineProperty(self, 'tint', { get: function get() { return explosionGraphics.tint; }, set: function set(value) { explosionGraphics.tint = value; } }); // Apply explosion animation tween(explosionGraphics, { scaleX: explosionGraphics.scaleX + 1, scaleY: explosionGraphics.scaleY + 1 }, { duration: 1000, easing: tween.bounceOut, onFinish: function onFinish() { self.destroy(); } }); self.update = function () { // The explosion will disappear after a while if (self.alpha > 0) { self.alpha -= 0.005; } else { self.destroy(); } }; }); // Harpoon class var Harpoon = Container.expand(function () { var self = Container.call(this); var harpoonGraphics = self.attachAsset('harpoon', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -30; // Reduced speed for slower movement self.originalSpeed = -30; self.maxDistance = 2732 * 0.85; // Increased maximum distance - 85% of screen height self.startY = 0; // Will store the starting Y position self.trails = []; // Apply a quick acceleration tween to give a nice launch effect tween(self, { speed: self.speed * 1.5 // Accelerate to 1.5x the slower speed }, { duration: 200, easing: tween.easeOut }); self.update = function () { // Store initial position on first update if (self.startY === 0) { self.startY = self.y; } self.y += self.speed; // Calculate total trail distance var totalDistance = self.startY - self.y; // Create trail segments for every 100 pixels var segmentCount = Math.floor(totalDistance / 100); var currentTrailCount = self.trails.length; // Create new trail segments if needed if (segmentCount > currentTrailCount) { for (var i = currentTrailCount; i < segmentCount; i++) { var trail = game.addChild(new Trail()); trail.x = self.x; trail.y = self.y + 50 + i * 100; trail.height = 100; self.trails.push(trail); } } // Update position of all trail segments for (var i = 0; i < self.trails.length; i++) { var trail = self.trails[i]; trail.y = self.y + 50 + i * 100; } // Destroy when reaching max distance or going off-screen if (self.y < 0 || self.startY - self.y > self.maxDistance) { self.destroy(); self.trails.forEach(function (trail) { trail.destroy(); }); } }; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; self.update = function () { if (self.direction && self.direction === 'left') { self.x -= self.speed; } else if (self.direction === 'right') { self.x += self.speed; } }; self.shoot = function () { var harpoon = new Harpoon(); harpoon.x = player.x; harpoon.y = player.y; // Create a more dramatic scale effect for shooting with extended range harpoon.scale.x = 0.7; harpoon.scale.y = 1.3; // Flag normal harpoons (from player) to allow them to create boxes harpoon.isPlayerCreated = true; // Apply a tween to normalize the scale while launching, with longer stretch for better visual feedback tween(harpoon.scale, { x: 1, y: 1 }, { duration: 250, easing: tween.easeOut }); // Trails will be created automatically in the harpoon update method game.addChild(harpoon); LK.getSound('crossbow').play(); }; }); // PlayerDeathEffect class - creates a dramatic death sequence with rainbow colors var PlayerDeathEffect = Container.expand(function () { var self = Container.call(this); // Create the main container for all death effect elements var effectContainer = new Container(); self.addChild(effectContainer); // Rainbow colors array (red, orange, yellow, green, blue, indigo, violet) var rainbowColors = [ // Indigo 0x9400D3, // Violet 0xfaadad, // Red 0xFF7F00, // Orange 0xFFFF00, // Yellow 0x00FF00, // Green 0x0000FF, // Blue 0x4B0082]; // Add a full-screen overlay for dramatic effect var overlay = LK.getAsset('deadBg', { anchorX: 0, anchorY: 0, scaleX: 20.48, // Full width (2048px) scaleY: 27.32, // Full height (2732px) tint: rainbowColors[0], // Start with red alpha: 0 }); effectContainer.addChild(overlay); // Add dramatic text var gameOverText = new Text2('GAME OVER', { size: 150, fill: 0xFFFFFF, font: "'Comic Sans MS', cursive, sans-serif" }); gameOverText.anchor.set(0.5, 0.5); gameOverText.x = 2048 / 2; gameOverText.y = 2732 / 2; gameOverText.alpha = 0; gameOverText.scale.set(0.1); effectContainer.addChild(gameOverText); // Function to create a rainbow tint sequence function createRainbowTintSequence(target, startIndex, duration) { var colorIndex = startIndex % rainbowColors.length; var nextColorIndex = (colorIndex + 1) % rainbowColors.length; tween(target, { tint: rainbowColors[nextColorIndex] }, { duration: duration, easing: tween.linear, onFinish: function onFinish() { createRainbowTintSequence(target, nextColorIndex, duration); } }); } // Add rainbow flickering effect self.startEffect = function () { // Pulse the overlay with rainbow colors tween(overlay, { alpha: 0.5 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { // Start the rainbow color sequence createRainbowTintSequence(overlay, 0, 300); } }); // Show and scale the text tween(gameOverText, { alpha: 1, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.elasticOut }); // Create explosion effects at random positions with rainbow colors for (var i = 0; i < 5; i++) { LK.setTimeout(function (colorIndex) { var explosion = new Explosion(); explosion.x = Math.random() * 2048; explosion.y = Math.random() * 2732; // Give each explosion a different color from the rainbow explosion.tint = rainbowColors[colorIndex % rainbowColors.length]; effectContainer.addChild(explosion); // Play explosion sound LK.getSound('explosion').play(); }.bind(null, i), 300 * i); } // Show actual game over screen after the effect LK.setTimeout(function () { self.destroy(); LK.showGameOver(); }, 2500); // Show game over after 2.5 seconds }; return self; }); // PowerUpText class var PowerUpText = Container.expand(function () { var self = Container.call(this); var textGraphics = self.attachAsset('PowerUpText', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { self.y -= 2; if (self.y < 0) { self.destroy(); } }; }); // Trail class var Trail = Container.expand(function () { var self = Container.call(this); var trailGraphics = self.attachAsset('line', { anchorX: 0.5, anchorY: 0.5, width: 18, alpha: 0.9, // Slightly transparent to enhance visual effect with faster movement scaleY: 1.5 // Scale the trail to support longer reach }); // The trail doesn't need its own update movement since it's positioned relative to the harpoon self.update = function () { // Trail no longer moves independently // Its position is now fully controlled by the Harpoon if (self.y > 2732) { self.destroy(); } }; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xFFFFFFFF // Init game with black background }); /**** * Game Code ****/ var background = game.attachAsset('Landscape', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.setChildIndex(background, 0); // Add a warning zone at the top to indicate the danger area var warningZone = new Container(); var warningGraphics = LK.getAsset('scoreBg', { anchorX: 0, anchorY: 0, scaleX: 20.48, // Make it span the entire width (2048px) scaleY: 1.5, tint: 0xFF3333, // Red tint to indicate danger alpha: 0.3 // Semi-transparent }); warningZone.addChild(warningGraphics); warningZone.x = 0; warningZone.y = 0; game.addChild(warningZone); var player = new Player(); player.x = 2048 / 2; player.y = 2732 - 180; game.addChild(player); // Add player after trail to ensure correct rendering order // Add a description text for the warning zone var warningText = new Text2('Lanterns escaping at the top will cost a life!', { size: 30, fill: 0xFFFFFF, font: "'Comic Sans MS', cursive, sans-serif" }); warningText.anchor.set(0.5, 0); warningText.x = 2048 / 2; warningText.y = 130; game.addChild(warningText); // Make the warning text pulse to draw attention var _pulseWarning = function pulseWarning() { tween(warningText.scale, { x: 1.2, y: 1.2 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(warningText.scale, { x: 1.0, y: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: _pulseWarning }); } }); }; _pulseWarning(); game.move = function (x, y, obj) { // Store previous x for direction detection if (player.lastX === undefined) { player.lastX = player.x; } player.x = x; // Check if moving left or right if (player.x < player.lastX) { // Moving left, mirror if (player.scaleX > 0) { player.scaleX *= -1; } } else if (player.x > player.lastX) { // Moving right, un-mirror if (player.scaleX < 0) { player.scaleX *= -1; } } player.lastX = player.x; }; var score = 0; var lives = 3; var scoreBackground = new Container(); var scoreBgGraphics = scoreBackground.attachAsset('scoreBg', { anchorX: 0.5, anchorY: 0.1, scaleX: 5, scaleY: 5, alpha: 1 }); scoreBackground.addChild(scoreBgGraphics); scoreBackground.x = 0; scoreBackground.y = 0; LK.gui.top.addChild(scoreBackground); var scoreTxt = new Text2('Lanterns destroyed: 0', { size: 30, fill: 0xFF3659, font: "'Comic Sans MS', cursive, sans-serif" }); scoreTxt.anchor.set(0.5, -0.1); scoreBackground.addChild(scoreTxt); var hearts = []; for (var i = 0; i < lives; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: -1 * (i + 1) * 50, // Position hearts with some spacing y: 50 }); LK.gui.topRight.addChild(heart); hearts.push(heart); } // Function to apply box effects based on box type function applyBoxEffect(box) { // Check if the collected box is an instance of Box1 if (box instanceof Box1) { // If we already have box1 harpoons, destroy them first var existingHarpoons = game.children.filter(function (child) { return child instanceof Harpoon && child.isFromBox1 === true; }); existingHarpoons.forEach(function (harpoon) { if (harpoon.trails) { harpoon.trails.forEach(function (trail) { trail.destroy(); }); } harpoon.destroy(); }); // Create six additional harpoons and trails for (var i = 1; i <= 3; i++) { var leftHarpoon = new Harpoon(); leftHarpoon.x = player.x - i * 150; leftHarpoon.y = player.y; // Mark harpoons created by box power-up leftHarpoon.isPlayerCreated = false; leftHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up game.addChild(leftHarpoon); var rightHarpoon = new Harpoon(); rightHarpoon.x = player.x + i * 150; rightHarpoon.y = player.y; // Mark harpoons created by box power-up rightHarpoon.isPlayerCreated = false; rightHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up game.addChild(rightHarpoon); // Set a timeout to remove the additional harpoons after 5 seconds LK.setTimeout(function (lh, rh) { if (lh.trails) { lh.trails.forEach(function (trail) { trail.destroy(); }); } lh.destroy(); if (rh.trails) { rh.trails.forEach(function (trail) { trail.destroy(); }); } rh.destroy(); }.bind(null, leftHarpoon, rightHarpoon), 5000); } } // Check if the collected box is an instance of Box and reduce bubble speed if (box instanceof Box) { var bubbles = game.children.filter(function (child) { return child instanceof Bubble; }); bubbles.forEach(function (bubble) { bubble.speed /= 2; }); LK.setTimeout(function () { bubbles.forEach(function (bubble) { bubble.speed *= 2; }); }, 5000); } // Check if the collected box is an instance of Box3 and lives are less than 3 if (box instanceof Box3 && lives < 3) { lives += 1; var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: -1 * lives * 50, y: 50 }); LK.gui.topRight.addChild(heart); hearts.push(heart); } // Check if the collected box is an instance of Box2 if (box instanceof Box2) { var bubbles = game.children.filter(function (child) { return child instanceof Bubble; }); var bubblesDestroyed = bubbles.length; bubbles.forEach(function (bubble) { bubble.destroy(); }); score += bubblesDestroyed; scoreTxt.setText("Lanterns destroyed: " + score.toString()); tween(scoreTxt.scale, { x: 2, y: 2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(scoreTxt.scale, { x: 1, y: 1 }, { duration: 500, easing: tween.easeInOut }); } }); } // Create toast message based on box type showToastForBox(box); } // Function to show toast message for box effects function showToastForBox(box) { var toastText = new Text2('', { size: 250, fill: 0xFFC0CB, font: "'Comic Sans MS', cursive, sans-serif" }); var toastTextBg = new Text2('', { size: 255, fill: 0xFF00AA, font: "'Comic Sans MS', cursive, sans-serif" }); toastText.anchor.set(0.5, 0.5); toastText.x = 2048 / 2; toastText.y = 2732 / 2; toastTextBg.anchor.set(0.5, 0.5); toastTextBg.x = 2048 / 2; toastTextBg.y = 2732 / 2; game.addChild(toastText); game.addChild(toastTextBg); if (box instanceof Box1) { toastText.setText("Smash!"); toastTextBg.setText("Smash!"); } else if (box instanceof Box2) { toastText.setText("Destruction!"); toastTextBg.setText("Destruction!"); } else if (box instanceof Box) { toastText.setText("Less madness!"); toastTextBg.setText("Less madness!"); } else if (box instanceof Box3) { toastText.setText("Life up!"); toastTextBg.setText("Life up!"); } // Tween the toast text to fade out and destroy after 2 seconds tween(toastText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { toastText.destroy(); } }); tween(toastTextBg, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { toastTextBg.destroy(); } }); } // Function to handle player death sequence with rainbow colors function handlePlayerDeath() { // Rainbow colors array var rainbowColors = [0xFF0000, // Red 0xFF7F00, // Orange 0xFFFF00, // Yellow 0x00FF00, // Green 0x0000FF, // Blue 0x4B0082, // Indigo 0x9400D3 // Violet ]; // Create dramatic death effect var deathEffect = new PlayerDeathEffect(); game.addChild(deathEffect); deathEffect.startEffect(); // Mark game as over to stop spawning bubbles game.isGameOver = true; // Create a function for sequential rainbow color animation function playerRainbowSequence(colorIndex) { if (colorIndex >= rainbowColors.length) { // One full cycle completed, start fading out tween(player, { alpha: 0, tint: 0xFFFFFF }, { duration: 500, easing: tween.easeOut }); return; } tween(player, { alpha: 0.3, tint: rainbowColors[colorIndex] }, { duration: 200, easing: tween.easeInOut, onFinish: function onFinish() { playerRainbowSequence(colorIndex + 1); } }); } // Start the rainbow sequence playerRainbowSequence(0); } var lastShot = -999; game.down = function (x, y, obj) { if (LK.ticks - lastShot > 10) { player.shoot(); lastShot = LK.ticks; } }; // Start the music 'chinese' upon starting the game LK.playMusic('chinese'); game.update = function () { // Initialize game.isGameOver if it doesn't exist if (game.isGameOver === undefined) { game.isGameOver = false; } // Only spawn new bubbles if game isn't over if (!game.isGameOver && LK.ticks % 50 == 0) { var newBubble = new Bubble(); newBubble.x = Math.random() * 2048; newBubble.y = 2732 * 0.75; // Start at 25% from bottom of screen game.addChild(newBubble); var randomTween = { delta: Math.random() * (300 - 100) + 100, // Random delta between 100 and 300 duration: Math.random() * (1500 - 800) + 800, // Random duration between 800 and 1500 easing: [tween.easeIn, tween.easeOut, tween.elasticIn, tween.elasticOut, tween.bounceIn, tween.bounceOut, tween.easeInOut, tween.bounceInOut, tween.elasticInOut][Math.floor(Math.random() * 9)] // Random easing }; tween(newBubble, { x: Math.max(0, Math.min(2048, newBubble.x + randomTween.delta)) }, { duration: randomTween.duration, easing: randomTween.easing, onFinish: function onFinish() { tween(newBubble, { x: Math.max(0, Math.min(2048, newBubble.x - randomTween.delta)) }, { duration: randomTween.duration, easing: randomTween.easing }); } }); } var bubbles = game.children.filter(function (child) { return child instanceof Bubble; }); var harpoons = game.children.filter(function (child) { return child instanceof Harpoon; }); for (var i = 0; i < bubbles.length; i++) { var bubble = bubbles[i]; // Check for player intersection but don't damage the player // We allow lanterns to pass through the player harmlessly if (bubble.intersects(player)) { // Don't destroy the bubble or harm the player // This allows the bubble to continue moving up // It will only damage the player if it exits off the top without being destroyed } for (var j = 0; j < harpoons.length; j++) { var harpoon = harpoons[j]; // Enhanced collision detection for longer harpoon reach and multiple trail segments var collides = bubble.intersects(harpoon); if (!collides && harpoon.trails && harpoon.trails.length > 0) { for (var t = 0; t < harpoon.trails.length; t++) { var trail = harpoon.trails[t]; if (Math.abs(bubble.x - harpoon.x) < 50 && Math.abs(bubble.y - trail.y) < 50) { collides = true; break; } } } if (collides) { bubble.destroy(); LK.getSound('explosion').play(); if (harpoon.trails) { harpoon.trails.forEach(function (trail) { trail.destroy(); }); } harpoon.destroy(); bubbles.splice(i, 1); // Remove bubble from the array score += 1; scoreTxt.setText("Lanterns destroyed: " + score.toString()); tween(scoreTxt.scale, { x: 2, y: 2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(scoreTxt.scale, { x: 1, y: 1 }, { duration: 500, easing: tween.easeInOut }); } }); harpoons.splice(j, 1); // Remove harpoon from the array // Create an explosion at the intersection point var explosion = new Explosion(); explosion.x = bubble.x; explosion.y = bubble.y; game.addChild(explosion); // Create a box at the intersection point with a 5% probability (half the previous 10%) // Only spawn boxes when balloons are popped by player harpoons, not by explosion effects if (!game.children.some(function (child) { return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3; })) { // Only create boxes when bubble is popped by a player-created harpoon, not by power-up harpoons // Check explicitly that the harpoon was created by the player, not by any box power-up if (Math.random() < 0.5 && harpoon instanceof Harpoon && harpoon.isPlayerCreated === true) { var boxType = Math.floor(Math.random() * 4); var box; switch (boxType) { case 0: box = new Box(); break; case 1: box = new Box1(); break; case 2: box = new Box2(); break; case 3: box = new Box3(); break; } box.x = bubble.x; box.y = bubble.y; game.addChild(box); } } } } var boxes = game.children.filter(function (child) { return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3; }); for (var k = 0; k < boxes.length; k++) { var box = boxes[k]; // Check if player collects/intersects the box if (box.intersects(player)) { // Create collection animation tween(box.scale, { x: 0, y: 0 }, { duration: 300, easing: tween.easeInOut, onFinish: function (boxToDestroy) { boxToDestroy.destroy(); }.bind(null, box) }); // Play pickup sound LK.getSound('powerup').play(); // Apply the power-up effect based on box type using centralized handler applyBoxEffect(box); // Remove box from tracking array boxes.splice(k, 1); // Break out of the loop since we've handled the box break; } // Check for harpoon intersecting with boxes for (var l = 0; l < harpoons.length; l++) { var harpoon = harpoons[l]; var collides = box.intersects(harpoon); // Check trail segments for collision as well if (!collides && harpoon.trails && harpoon.trails.length > 0) { for (var t = 0; t < harpoon.trails.length; t++) { var trail = harpoon.trails[t]; if (trail && Math.abs(box.x - harpoon.x) < 50 && Math.abs(box.y - trail.y) < 50) { collides = true; break; } } } if (collides) { // Store reference to box before destroying it var boxToApplyEffect = box; // Destroy elements box.destroy(); LK.getSound('powerup').play(); // Safely destroy trails for harpoons created before we started using multiple trails if (harpoon.trail) { harpoon.trail.destroy(); } // Safely destroy trail segments for current harpoons if (harpoon.trails && harpoon.trails.length > 0) { harpoon.trails.forEach(function (trail) { if (trail) { trail.destroy(); } }); } harpoon.destroy(); // Apply the power-up effect based on box type using centralized handler applyBoxEffect(boxToApplyEffect); // Remove from arrays boxes.splice(k, 1); harpoons.splice(l, 1); break; } } } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Box class
var Box = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0xFFFF00,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box1 class
var Box1 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0xFF00FF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box2 class
var Box2 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box2', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 7;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible
tween(boxGraphics, {
tint: 0x00FFFF,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box3 class
var Box3 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box3', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 9;
self.lastIntersecting = false; // Track last intersection state with player
self.update = function () {
self.y += self.speed;
// Check for player intersection transitions
var currentIntersecting = self.intersects(player);
if (!self.lastIntersecting && currentIntersecting) {
// Flash the box briefly to indicate collection is possible with heart color
tween(boxGraphics, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boxGraphics, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
self.lastIntersecting = currentIntersecting;
if (self.y > 2732) {
self.destroy();
}
};
});
// The assets will be automatically created and loaded by the LK engine.
// Bubble class
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0 // Start transparent
});
// Make the bubble opaque after 1 second
tween(bubbleGraphics, {
alpha: 1
}, {
duration: 1000
});
self.speed = -10; // Negative speed to move upward
self.lastY = undefined; // Track last position for intersection checks
self.hasHitTop = false; // Flag to track if lantern has gone off screen at top
self.update = function () {
if (self.lastY === undefined) {
self.lastY = self.y;
}
self.y += self.speed;
if (self.y < 1200) {
// Start tinting when approaching the top
tween(self, {
tint: 0xFF0000
}, {
duration: 500,
easing: tween.linear
});
}
// Check if the lantern is going off the top of the screen without being destroyed
if (self.lastY >= 0 && self.y < 0 && !self.hasHitTop) {
self.hasHitTop = true; // Mark as hit top to prevent multiple life deductions
// Player didn't destroy the lantern in time
lives -= 1;
// Create explosion effect at the top where the lantern left
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = 10; // Just at the edge of the screen
game.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
// Remove a heart icon when a life is lost
if (hearts.length > lives) {
var heartToRemove = hearts.pop();
if (heartToRemove) {
tween(heartToRemove.scale, {
x: 0,
y: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
heartToRemove.destroy();
}
});
}
}
if (lives < 0) {
// Handle player death with dramatic effect
handlePlayerDeath();
return;
}
self.destroy();
}
// Handle going completely off screen
// Note: Not removing the lantern when it intersects with player
// as per the requirement that player collision shouldn't remove lives
if (self.y < -200) {
self.destroy();
}
self.lastY = self.y;
};
});
// Explosion class with color customization
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
// Extend self with tint property accessor/mutator to allow tinting the explosion
Object.defineProperty(self, 'tint', {
get: function get() {
return explosionGraphics.tint;
},
set: function set(value) {
explosionGraphics.tint = value;
}
});
// Apply explosion animation
tween(explosionGraphics, {
scaleX: explosionGraphics.scaleX + 1,
scaleY: explosionGraphics.scaleY + 1
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
self.destroy();
}
});
self.update = function () {
// The explosion will disappear after a while
if (self.alpha > 0) {
self.alpha -= 0.005;
} else {
self.destroy();
}
};
});
// Harpoon class
var Harpoon = Container.expand(function () {
var self = Container.call(this);
var harpoonGraphics = self.attachAsset('harpoon', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -30; // Reduced speed for slower movement
self.originalSpeed = -30;
self.maxDistance = 2732 * 0.85; // Increased maximum distance - 85% of screen height
self.startY = 0; // Will store the starting Y position
self.trails = [];
// Apply a quick acceleration tween to give a nice launch effect
tween(self, {
speed: self.speed * 1.5 // Accelerate to 1.5x the slower speed
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Store initial position on first update
if (self.startY === 0) {
self.startY = self.y;
}
self.y += self.speed;
// Calculate total trail distance
var totalDistance = self.startY - self.y;
// Create trail segments for every 100 pixels
var segmentCount = Math.floor(totalDistance / 100);
var currentTrailCount = self.trails.length;
// Create new trail segments if needed
if (segmentCount > currentTrailCount) {
for (var i = currentTrailCount; i < segmentCount; i++) {
var trail = game.addChild(new Trail());
trail.x = self.x;
trail.y = self.y + 50 + i * 100;
trail.height = 100;
self.trails.push(trail);
}
}
// Update position of all trail segments
for (var i = 0; i < self.trails.length; i++) {
var trail = self.trails[i];
trail.y = self.y + 50 + i * 100;
}
// Destroy when reaching max distance or going off-screen
if (self.y < 0 || self.startY - self.y > self.maxDistance) {
self.destroy();
self.trails.forEach(function (trail) {
trail.destroy();
});
}
};
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.update = function () {
if (self.direction && self.direction === 'left') {
self.x -= self.speed;
} else if (self.direction === 'right') {
self.x += self.speed;
}
};
self.shoot = function () {
var harpoon = new Harpoon();
harpoon.x = player.x;
harpoon.y = player.y;
// Create a more dramatic scale effect for shooting with extended range
harpoon.scale.x = 0.7;
harpoon.scale.y = 1.3;
// Flag normal harpoons (from player) to allow them to create boxes
harpoon.isPlayerCreated = true;
// Apply a tween to normalize the scale while launching, with longer stretch for better visual feedback
tween(harpoon.scale, {
x: 1,
y: 1
}, {
duration: 250,
easing: tween.easeOut
});
// Trails will be created automatically in the harpoon update method
game.addChild(harpoon);
LK.getSound('crossbow').play();
};
});
// PlayerDeathEffect class - creates a dramatic death sequence with rainbow colors
var PlayerDeathEffect = Container.expand(function () {
var self = Container.call(this);
// Create the main container for all death effect elements
var effectContainer = new Container();
self.addChild(effectContainer);
// Rainbow colors array (red, orange, yellow, green, blue, indigo, violet)
var rainbowColors = [
// Indigo
0x9400D3,
// Violet
0xfaadad,
// Red
0xFF7F00,
// Orange
0xFFFF00,
// Yellow
0x00FF00,
// Green
0x0000FF,
// Blue
0x4B0082];
// Add a full-screen overlay for dramatic effect
var overlay = LK.getAsset('deadBg', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
// Full width (2048px)
scaleY: 27.32,
// Full height (2732px)
tint: rainbowColors[0],
// Start with red
alpha: 0
});
effectContainer.addChild(overlay);
// Add dramatic text
var gameOverText = new Text2('GAME OVER', {
size: 150,
fill: 0xFFFFFF,
font: "'Comic Sans MS', cursive, sans-serif"
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 2732 / 2;
gameOverText.alpha = 0;
gameOverText.scale.set(0.1);
effectContainer.addChild(gameOverText);
// Function to create a rainbow tint sequence
function createRainbowTintSequence(target, startIndex, duration) {
var colorIndex = startIndex % rainbowColors.length;
var nextColorIndex = (colorIndex + 1) % rainbowColors.length;
tween(target, {
tint: rainbowColors[nextColorIndex]
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
createRainbowTintSequence(target, nextColorIndex, duration);
}
});
}
// Add rainbow flickering effect
self.startEffect = function () {
// Pulse the overlay with rainbow colors
tween(overlay, {
alpha: 0.5
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
// Start the rainbow color sequence
createRainbowTintSequence(overlay, 0, 300);
}
});
// Show and scale the text
tween(gameOverText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.elasticOut
});
// Create explosion effects at random positions with rainbow colors
for (var i = 0; i < 5; i++) {
LK.setTimeout(function (colorIndex) {
var explosion = new Explosion();
explosion.x = Math.random() * 2048;
explosion.y = Math.random() * 2732;
// Give each explosion a different color from the rainbow
explosion.tint = rainbowColors[colorIndex % rainbowColors.length];
effectContainer.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
}.bind(null, i), 300 * i);
}
// Show actual game over screen after the effect
LK.setTimeout(function () {
self.destroy();
LK.showGameOver();
}, 2500); // Show game over after 2.5 seconds
};
return self;
});
// PowerUpText class
var PowerUpText = Container.expand(function () {
var self = Container.call(this);
var textGraphics = self.attachAsset('PowerUpText', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y -= 2;
if (self.y < 0) {
self.destroy();
}
};
});
// Trail class
var Trail = Container.expand(function () {
var self = Container.call(this);
var trailGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
alpha: 0.9,
// Slightly transparent to enhance visual effect with faster movement
scaleY: 1.5 // Scale the trail to support longer reach
});
// The trail doesn't need its own update movement since it's positioned relative to the harpoon
self.update = function () {
// Trail no longer moves independently
// Its position is now fully controlled by the Harpoon
if (self.y > 2732) {
self.destroy();
}
};
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFFFFFFF // Init game with black background
});
/****
* Game Code
****/
var background = game.attachAsset('Landscape', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.setChildIndex(background, 0);
// Add a warning zone at the top to indicate the danger area
var warningZone = new Container();
var warningGraphics = LK.getAsset('scoreBg', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
// Make it span the entire width (2048px)
scaleY: 1.5,
tint: 0xFF3333,
// Red tint to indicate danger
alpha: 0.3 // Semi-transparent
});
warningZone.addChild(warningGraphics);
warningZone.x = 0;
warningZone.y = 0;
game.addChild(warningZone);
var player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 180;
game.addChild(player); // Add player after trail to ensure correct rendering order
// Add a description text for the warning zone
var warningText = new Text2('Lanterns escaping at the top will cost a life!', {
size: 30,
fill: 0xFFFFFF,
font: "'Comic Sans MS', cursive, sans-serif"
});
warningText.anchor.set(0.5, 0);
warningText.x = 2048 / 2;
warningText.y = 130;
game.addChild(warningText);
// Make the warning text pulse to draw attention
var _pulseWarning = function pulseWarning() {
tween(warningText.scale, {
x: 1.2,
y: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(warningText.scale, {
x: 1.0,
y: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: _pulseWarning
});
}
});
};
_pulseWarning();
game.move = function (x, y, obj) {
// Store previous x for direction detection
if (player.lastX === undefined) {
player.lastX = player.x;
}
player.x = x;
// Check if moving left or right
if (player.x < player.lastX) {
// Moving left, mirror
if (player.scaleX > 0) {
player.scaleX *= -1;
}
} else if (player.x > player.lastX) {
// Moving right, un-mirror
if (player.scaleX < 0) {
player.scaleX *= -1;
}
}
player.lastX = player.x;
};
var score = 0;
var lives = 3;
var scoreBackground = new Container();
var scoreBgGraphics = scoreBackground.attachAsset('scoreBg', {
anchorX: 0.5,
anchorY: 0.1,
scaleX: 5,
scaleY: 5,
alpha: 1
});
scoreBackground.addChild(scoreBgGraphics);
scoreBackground.x = 0;
scoreBackground.y = 0;
LK.gui.top.addChild(scoreBackground);
var scoreTxt = new Text2('Lanterns destroyed: 0', {
size: 30,
fill: 0xFF3659,
font: "'Comic Sans MS', cursive, sans-serif"
});
scoreTxt.anchor.set(0.5, -0.1);
scoreBackground.addChild(scoreTxt);
var hearts = [];
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * (i + 1) * 50,
// Position hearts with some spacing
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Function to apply box effects based on box type
function applyBoxEffect(box) {
// Check if the collected box is an instance of Box1
if (box instanceof Box1) {
// If we already have box1 harpoons, destroy them first
var existingHarpoons = game.children.filter(function (child) {
return child instanceof Harpoon && child.isFromBox1 === true;
});
existingHarpoons.forEach(function (harpoon) {
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
});
// Create six additional harpoons and trails
for (var i = 1; i <= 3; i++) {
var leftHarpoon = new Harpoon();
leftHarpoon.x = player.x - i * 150;
leftHarpoon.y = player.y;
// Mark harpoons created by box power-up
leftHarpoon.isPlayerCreated = false;
leftHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up
game.addChild(leftHarpoon);
var rightHarpoon = new Harpoon();
rightHarpoon.x = player.x + i * 150;
rightHarpoon.y = player.y;
// Mark harpoons created by box power-up
rightHarpoon.isPlayerCreated = false;
rightHarpoon.isFromBox1 = true; // Mark specifically as from Box1 power-up
game.addChild(rightHarpoon);
// Set a timeout to remove the additional harpoons after 5 seconds
LK.setTimeout(function (lh, rh) {
if (lh.trails) {
lh.trails.forEach(function (trail) {
trail.destroy();
});
}
lh.destroy();
if (rh.trails) {
rh.trails.forEach(function (trail) {
trail.destroy();
});
}
rh.destroy();
}.bind(null, leftHarpoon, rightHarpoon), 5000);
}
}
// Check if the collected box is an instance of Box and reduce bubble speed
if (box instanceof Box) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
bubbles.forEach(function (bubble) {
bubble.speed /= 2;
});
LK.setTimeout(function () {
bubbles.forEach(function (bubble) {
bubble.speed *= 2;
});
}, 5000);
}
// Check if the collected box is an instance of Box3 and lives are less than 3
if (box instanceof Box3 && lives < 3) {
lives += 1;
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * lives * 50,
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Check if the collected box is an instance of Box2
if (box instanceof Box2) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var bubblesDestroyed = bubbles.length;
bubbles.forEach(function (bubble) {
bubble.destroy();
});
score += bubblesDestroyed;
scoreTxt.setText("Lanterns destroyed: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
// Create toast message based on box type
showToastForBox(box);
}
// Function to show toast message for box effects
function showToastForBox(box) {
var toastText = new Text2('', {
size: 250,
fill: 0xFFC0CB,
font: "'Comic Sans MS', cursive, sans-serif"
});
var toastTextBg = new Text2('', {
size: 255,
fill: 0xFF00AA,
font: "'Comic Sans MS', cursive, sans-serif"
});
toastText.anchor.set(0.5, 0.5);
toastText.x = 2048 / 2;
toastText.y = 2732 / 2;
toastTextBg.anchor.set(0.5, 0.5);
toastTextBg.x = 2048 / 2;
toastTextBg.y = 2732 / 2;
game.addChild(toastText);
game.addChild(toastTextBg);
if (box instanceof Box1) {
toastText.setText("Smash!");
toastTextBg.setText("Smash!");
} else if (box instanceof Box2) {
toastText.setText("Destruction!");
toastTextBg.setText("Destruction!");
} else if (box instanceof Box) {
toastText.setText("Less madness!");
toastTextBg.setText("Less madness!");
} else if (box instanceof Box3) {
toastText.setText("Life up!");
toastTextBg.setText("Life up!");
}
// Tween the toast text to fade out and destroy after 2 seconds
tween(toastText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastText.destroy();
}
});
tween(toastTextBg, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastTextBg.destroy();
}
});
}
// Function to handle player death sequence with rainbow colors
function handlePlayerDeath() {
// Rainbow colors array
var rainbowColors = [0xFF0000,
// Red
0xFF7F00,
// Orange
0xFFFF00,
// Yellow
0x00FF00,
// Green
0x0000FF,
// Blue
0x4B0082,
// Indigo
0x9400D3 // Violet
];
// Create dramatic death effect
var deathEffect = new PlayerDeathEffect();
game.addChild(deathEffect);
deathEffect.startEffect();
// Mark game as over to stop spawning bubbles
game.isGameOver = true;
// Create a function for sequential rainbow color animation
function playerRainbowSequence(colorIndex) {
if (colorIndex >= rainbowColors.length) {
// One full cycle completed, start fading out
tween(player, {
alpha: 0,
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
return;
}
tween(player, {
alpha: 0.3,
tint: rainbowColors[colorIndex]
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
playerRainbowSequence(colorIndex + 1);
}
});
}
// Start the rainbow sequence
playerRainbowSequence(0);
}
var lastShot = -999;
game.down = function (x, y, obj) {
if (LK.ticks - lastShot > 10) {
player.shoot();
lastShot = LK.ticks;
}
};
// Start the music 'chinese' upon starting the game
LK.playMusic('chinese');
game.update = function () {
// Initialize game.isGameOver if it doesn't exist
if (game.isGameOver === undefined) {
game.isGameOver = false;
}
// Only spawn new bubbles if game isn't over
if (!game.isGameOver && LK.ticks % 50 == 0) {
var newBubble = new Bubble();
newBubble.x = Math.random() * 2048;
newBubble.y = 2732 * 0.75; // Start at 25% from bottom of screen
game.addChild(newBubble);
var randomTween = {
delta: Math.random() * (300 - 100) + 100,
// Random delta between 100 and 300
duration: Math.random() * (1500 - 800) + 800,
// Random duration between 800 and 1500
easing: [tween.easeIn, tween.easeOut, tween.elasticIn, tween.elasticOut, tween.bounceIn, tween.bounceOut, tween.easeInOut, tween.bounceInOut, tween.elasticInOut][Math.floor(Math.random() * 9)] // Random easing
};
tween(newBubble, {
x: Math.max(0, Math.min(2048, newBubble.x + randomTween.delta))
}, {
duration: randomTween.duration,
easing: randomTween.easing,
onFinish: function onFinish() {
tween(newBubble, {
x: Math.max(0, Math.min(2048, newBubble.x - randomTween.delta))
}, {
duration: randomTween.duration,
easing: randomTween.easing
});
}
});
}
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var harpoons = game.children.filter(function (child) {
return child instanceof Harpoon;
});
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
// Check for player intersection but don't damage the player
// We allow lanterns to pass through the player harmlessly
if (bubble.intersects(player)) {
// Don't destroy the bubble or harm the player
// This allows the bubble to continue moving up
// It will only damage the player if it exits off the top without being destroyed
}
for (var j = 0; j < harpoons.length; j++) {
var harpoon = harpoons[j];
// Enhanced collision detection for longer harpoon reach and multiple trail segments
var collides = bubble.intersects(harpoon);
if (!collides && harpoon.trails && harpoon.trails.length > 0) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
if (Math.abs(bubble.x - harpoon.x) < 50 && Math.abs(bubble.y - trail.y) < 50) {
collides = true;
break;
}
}
}
if (collides) {
bubble.destroy();
LK.getSound('explosion').play();
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
bubbles.splice(i, 1); // Remove bubble from the array
score += 1;
scoreTxt.setText("Lanterns destroyed: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
harpoons.splice(j, 1); // Remove harpoon from the array
// Create an explosion at the intersection point
var explosion = new Explosion();
explosion.x = bubble.x;
explosion.y = bubble.y;
game.addChild(explosion);
// Create a box at the intersection point with a 5% probability (half the previous 10%)
// Only spawn boxes when balloons are popped by player harpoons, not by explosion effects
if (!game.children.some(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
})) {
// Only create boxes when bubble is popped by a player-created harpoon, not by power-up harpoons
// Check explicitly that the harpoon was created by the player, not by any box power-up
if (Math.random() < 0.5 && harpoon instanceof Harpoon && harpoon.isPlayerCreated === true) {
var boxType = Math.floor(Math.random() * 4);
var box;
switch (boxType) {
case 0:
box = new Box();
break;
case 1:
box = new Box1();
break;
case 2:
box = new Box2();
break;
case 3:
box = new Box3();
break;
}
box.x = bubble.x;
box.y = bubble.y;
game.addChild(box);
}
}
}
}
var boxes = game.children.filter(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
});
for (var k = 0; k < boxes.length; k++) {
var box = boxes[k];
// Check if player collects/intersects the box
if (box.intersects(player)) {
// Create collection animation
tween(box.scale, {
x: 0,
y: 0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function (boxToDestroy) {
boxToDestroy.destroy();
}.bind(null, box)
});
// Play pickup sound
LK.getSound('powerup').play();
// Apply the power-up effect based on box type using centralized handler
applyBoxEffect(box);
// Remove box from tracking array
boxes.splice(k, 1);
// Break out of the loop since we've handled the box
break;
}
// Check for harpoon intersecting with boxes
for (var l = 0; l < harpoons.length; l++) {
var harpoon = harpoons[l];
var collides = box.intersects(harpoon);
// Check trail segments for collision as well
if (!collides && harpoon.trails && harpoon.trails.length > 0) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
if (trail && Math.abs(box.x - harpoon.x) < 50 && Math.abs(box.y - trail.y) < 50) {
collides = true;
break;
}
}
}
if (collides) {
// Store reference to box before destroying it
var boxToApplyEffect = box;
// Destroy elements
box.destroy();
LK.getSound('powerup').play();
// Safely destroy trails for harpoons created before we started using multiple trails
if (harpoon.trail) {
harpoon.trail.destroy();
}
// Safely destroy trail segments for current harpoons
if (harpoon.trails && harpoon.trails.length > 0) {
harpoon.trails.forEach(function (trail) {
if (trail) {
trail.destroy();
}
});
}
harpoon.destroy();
// Apply the power-up effect based on box type using centralized handler
applyBoxEffect(boxToApplyEffect);
// Remove from arrays
boxes.splice(k, 1);
harpoons.splice(l, 1);
break;
}
}
}
}
};
A pixel background wallpaper of china. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a green cross, icon, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a sand clock pixel style.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a bomb, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a pixel harpoon, vertical and looking up, retro like in pang games.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
A banner to show a message, pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
two harpoons looking up, retro, pixel. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
A chinese flying lamp, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows