Code edit (17 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Check if you are going left and if so, mirror the player. You can do that just by checking x before and x after. If it's smaller, it's going left. If it's going right, don't mirror.
User prompt
Make sure that harpoons intersecting with powerboxes activate the powerboxes (collect them) for the user
User prompt
Some trail segments seem to not be destroyed, especially those created after a powerbox.
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'destroy')' in or related to this line: 'harpoon.trail.destroy();' Line Number: 1031
Code edit (1 edits merged)
Please save this source code
User prompt
The trail now is 1 and it's scaled (self.trail.height) going from the startY to self.y. Instead, please divide that height by 100 and generate 1 trail per each 100 pixels.
/**** * 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 - 200; // 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) { player.x = x; if (x < 2048 / 2 && player.scaleX > 0 || x >= 2048 / 2 && player.scaleX < 0) { player.scaleX *= -1; // Mirror the player image } }; 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]; if (box.intersects(harpoon)) { // Store reference to box before destroying it var boxToApplyEffect = box; // Destroy elements box.destroy(); LK.getSound('powerup').play(); harpoon.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 - 200;
// 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) {
player.x = x;
if (x < 2048 / 2 && player.scaleX > 0 || x >= 2048 / 2 && player.scaleX < 0) {
player.scaleX *= -1; // Mirror the player image
}
};
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];
if (box.intersects(harpoon)) {
// Store reference to box before destroying it
var boxToApplyEffect = box;
// Destroy elements
box.destroy();
LK.getSound('powerup').play();
harpoon.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 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 paper lantern, retro pixel style. In-Game asset. 2d. High contrast. No shadows
fireworks, retro pixel style, colorful. In-Game asset. 2d. High contrast. No shadows
A chinese pixel background of an arcade game, sunset, retro style,. In-Game asset. 2d. High contrast. No shadows