Code edit (2 edits merged)
Please save this source code
User prompt
The bubbles always bounce with the same height, and that height depends on the bubble size. The bigger the bubble, the higher it bounces.
User prompt
Bubbles should never stuck flat on the ground, they should have a minimum bouncing that is at least of 300 px from the ground
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: 963
User prompt
I don't want the trail to be 1 element, I want it to consist of several segments. Copy the trail by segments implementation from this code: /**** * 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) { 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]; 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; } } } } }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Modify how the player is mirrored on x. I want to detect if you go right, then it should not be mirrored. If you go lef,t it should. You can detect if goes right or left by checking last position on x versus current position.
User prompt
When the player dies, please add an animation like falling down out the screen, maybe mirroring on y
User prompt
When the player dies, please add an animation like falling down out the screen, maybe mirroring on y
Code edit (2 edits merged)
Please save this source code
User prompt
When the player blinks after being hit, make it blink to rainbow colors instead of red
User prompt
When the player blinks after being hit, make it blink to rainbow colors instead of read.
Code edit (1 edits merged)
Please save this source code
User prompt
When creating the balls, please tint them with a random color ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'difficultyIndicator.style.fill = textColor;' Line Number: 473
User prompt
Sorry I meant not a text2 Boom! but showing the "Explosion" asset.
User prompt
"Boom!" is not showed when destroying the tiniest bubbles
Code edit (3 edits merged)
Please save this source code
User prompt
There is something wrong in the ball split logic when hitting the player: a split is created but the parent bubble from which the split was created is not destroyed.
User prompt
When the bubbles hit the player, they should not be destroyed, but split.
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'difficultyIndicator.style.fill = textColor;' Line Number: 470
User prompt
The trail collision with small bubbles (0.25) works, but the bubbles don't seem to be destroyed. Please destroy them on collisions with the train. Don't change anything else. I've got many of these: Bubble hit detection SUCCESS: size=0.25, x=411.20283351168956, y=2452.2476231932624, harpoon.x=503.71250000000003, harpoon.y=912, hitType=trail
User prompt
Nope. Trailstill does not pop the tiniest balls.
/**** * 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.update = function () { self.y += self.speed; 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.update = function () { self.y += self.speed; 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.update = function () { self.y += self.speed; 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.update = function () { self.y += self.speed; 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: 1 // Start fully visible instead of transparent }); // Generate random color for the bubble var randomColor = Math.random() * 0xFFFFFF; bubbleGraphics.tint = randomColor; // Allow for different bubble sizes (original size = 1.0) self.size = 1.0; // Update bubble size based on the size property bubbleGraphics.scale.set(self.size); // Physics constants for more consistent behavior self.gravity = 0.2; // Gravity constant self.bounceFactor = 0.7; // Energy loss on bounce (30% loss) self.friction = 0.98; // Air/ground friction self.speed = 3; // Vertical speed (significantly reduced initial speed) // Track last position for collision detection self.lastY = 0; self.lastIntersecting = false; // Method to split the bubble into two smaller ones self.split = function () { // Only split if bubble is not too small // Note: We're keeping the same threshold (0.3) to determine when bubbles stop splitting if (self.size <= 0.3) { // Track that this is a small bubble that can't be split anymore console.log("Bubble too small to split, size=" + self.size); return false; } // Create two new smaller bubbles var halfSize = self.size * 0.5; console.log("Splitting bubble: original size=" + self.size + ", new size=" + halfSize); // Create left bubble var leftBubble = new Bubble(); leftBubble.x = self.x; leftBubble.y = self.y; leftBubble.size = halfSize; leftBubble.speedX = -Math.abs(self.speedX) * 1.2; // Move left, slightly faster leftBubble.speed = -5 - Math.random() * 3; // Jump up leftBubble.lastY = leftBubble.y; leftBubble.lastIntersecting = false; if (leftBubble.children && leftBubble.children.length > 0) { leftBubble.children[0].scale.set(halfSize); // Generate a new random color for split bubble leftBubble.children[0].tint = Math.random() * 0xFFFFFF; } // Create right bubble var rightBubble = new Bubble(); rightBubble.x = self.x; rightBubble.y = self.y; rightBubble.size = halfSize; rightBubble.speedX = Math.abs(self.speedX) * 1.2; // Move right, slightly faster rightBubble.speed = -5 - Math.random() * 3; // Jump up rightBubble.lastY = rightBubble.y; rightBubble.lastIntersecting = false; if (rightBubble.children && rightBubble.children.length > 0) { rightBubble.children[0].scale.set(halfSize); // Generate a new random color for split bubble rightBubble.children[0].tint = Math.random() * 0xFFFFFF; } // Add both bubbles to the game game.addChild(leftBubble); game.addChild(rightBubble); return true; }; self.update = function () { // Track last position before moving self.lastY = self.y; self.lastIntersecting = self.intersects(player); self.speed += 0.2; // Further reduced gravity acceleration from 0.3 to 0.2 self.y += self.speed; self.x += self.speedX; // Update horizontal position based on speedX // Bounce off the left and right margins with a small boost to ensure movement if (self.x <= 100 && self.speedX < 0 || self.x >= 1948 && self.speedX > 0) { self.speedX *= -1.1; // Reverse horizontal direction with a slight boost to ensure movement } // Bounce off the ground (bottom of the screen) if (self.y >= 2732 - 100) { // Account for bubble size/radius self.y = 2732 - 100; // Reset position to prevent sinking below ground self.speed = -self.speed * 0.7; // Reverse vertical direction with dampening (70% of original speed) // Add a small random horizontal impulse on bounce for more natural movement self.speedX += (Math.random() - 0.5) * 2; // Add a slight color flash when bouncing (if not a rainbow bubble) if (!self.isRainbow && self.children && self.children.length > 0) { var bubbleGraphics = self.children[0]; // Store original tint var originalTint = bubbleGraphics.tint; // Slightly lighten the bubble on bounce for visual feedback var brighterTint = 0xFFFFFF; // Flash to brighter color tween(bubbleGraphics, { tint: brighterTint }, { duration: 100, onFinish: function onFinish() { // Return to original tint tween(bubbleGraphics, { tint: originalTint }, { duration: 300 }); } }); } } // Removed tint application when reaching a certain Y position // Bubble class update method where player collision is detected if (!self.lastIntersecting && self.intersects(player)) { // Make the player blink three times with rainbow colors when hit by a bubble if (player.children && player.children.length > 0 && !player.isDying) { var rainbowTint = function rainbowTint() { // Convert HSL to RGB (simplified version) rainbowHue = (rainbowHue + 20) % 360; // Faster color change // Convert hue (0-360) to RGB (0-255) var h = rainbowHue / 60; var c = 1; // Chroma var x = c * (1 - Math.abs(h % 2 - 1)); var r, g, b; if (h < 1) { r = c; g = x; b = 0; } else if (h < 2) { r = x; g = c; b = 0; } else if (h < 3) { r = 0; g = c; b = x; } else if (h < 4) { r = 0; g = x; b = c; } else if (h < 5) { r = x; g = 0; b = c; } else { r = c; g = 0; b = x; } // Convert to RGB format return (Math.floor(r * 255) << 16) + (Math.floor(g * 255) << 8) + Math.floor(b * 255); }; // First blink (1/3) var playerGraphics = player.children[0]; // Save original tint var originalTint = playerGraphics.tint || 0xFFFFFF; // Create rainbow animation function for player blinking var rainbowHue = 0; playerGraphics.tint = rainbowTint(); // Initial rainbow color tween(playerGraphics, { alpha: 0 }, { duration: 100, onFinish: function onFinish() { playerGraphics.tint = rainbowTint(); // New rainbow color tween(playerGraphics, { alpha: 1 }, { duration: 100, onFinish: function onFinish() { // Second blink (2/3) playerGraphics.tint = rainbowTint(); // New rainbow color tween(playerGraphics, { alpha: 0 }, { duration: 100, onFinish: function onFinish() { playerGraphics.tint = rainbowTint(); // New rainbow color tween(playerGraphics, { alpha: 1 }, { duration: 100, onFinish: function onFinish() { // Third blink (3/3) playerGraphics.tint = rainbowTint(); // New rainbow color tween(playerGraphics, { alpha: 0 }, { duration: 100, onFinish: function onFinish() { playerGraphics.tint = rainbowTint(); // Final rainbow color tween(playerGraphics, { alpha: 1 }, { duration: 100, onFinish: function onFinish() { // Reset tint to original color playerGraphics.tint = originalTint; } }); } }); } }); } }); } }); } }); } // Instead of destroying the bubble, try to split it var wasSplit = self.split(); // Always destroy the original bubble after splitting or if too small to split self.destroy(); lives -= 1; // 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) { // Trigger player death animation before showing game over if (player && !player.isDying) { player.die(); // Delay game over screen until animation completes LK.setTimeout(function () { LK.showGameOver(); }, 2000); // Wait for death animation to complete } else { LK.showGameOver(); } } } }; }); // Explosion class var Explosion = Container.expand(function () { var self = Container.call(this); var explosionGraphics = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); 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.02; } 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 = -20; self.originalSpeed = -20; self.maxDistance = 2732 * 0.85; // 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.2 // Accelerate to 1.2x speed }, { duration: 200, easing: tween.easeOut }); // Function to check if point is on line (trail) self.isPointOnLine = function (point, bubbleSize) { // The trail is a vertical line from the harpoon down to the player // Get bubble size factor (if not provided, default to 1) var sizeFactor = bubbleSize || 1.0; // Special handling for the smallest bubbles - much more generous if (sizeFactor <= 0.25) { // For very small bubbles, use a very large fixed tolerance var tolerance = 100; // Extremely generous for the tiniest bubbles // More lenient y-range check for tiny bubbles var minY = self.y - 100; // Allow collision even slightly above the harpoon var maxY = 2732 + 100; // Allow collision even slightly below the bottom // Very generous x-range check for tiny bubbles return Math.abs(point.x - self.x) < tolerance && point.y >= minY && point.y <= maxY; } // For all other bubble sizes, use scaled tolerance var baseWidth = 50; // Base tolerance for normal bubbles var tolerance; if (sizeFactor <= 0.3) { tolerance = 150; // Still very generous for small bubbles } else if (sizeFactor < 1.0) { // Scale tolerance inversely with bubble size tolerance = baseWidth * (2 - sizeFactor); } else { tolerance = baseWidth; // Normal tolerance for regular bubbles } // Check if point is on main harpoon or any trail segment var yCheck = point.y >= self.y && point.y <= 2732; // First check against harpoon itself if (Math.abs(point.x - self.x) < tolerance && yCheck) { return true; } // Then check against each trail segment for (var i = 0; i < self.trails.length; i++) { var trail = self.trails[i]; if (Math.abs(point.x - self.x) < tolerance && point.y >= trail.y - trail.height / 2 && point.y <= trail.y + trail.height / 2) { return true; } } return false; }; self.update = function () { self.y += self.speed; // Store initial position on first update if (self.startY === 0) { self.startY = self.y; } // 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.isDying = false; self.deathSpeed = 0; self.deathRotation = 0; self.lastX = 0; // Track the last X position to detect direction of movement self.update = function () { // Handle death animation if player is dying if (self.isDying) { // Accelerate the fall self.deathSpeed += 0.5; self.y += self.deathSpeed; // Flip the player while falling (rotate vertically) self.deathRotation += 0.05; self.rotation = self.deathRotation; // Add slight horizontal movement for more natural fall self.x += Math.sin(self.deathRotation * 2) * 3; // Slowly make the player more transparent if (self.alpha > 0.1) { self.alpha -= 0.01; } // Check if player has fallen off the screen if (self.y > 2732 + 500) { self.isDying = false; self.destroy(); } return; } // Normal movement when not dying if (self.direction && self.direction === 'left') { self.x -= self.speed; } else if (self.direction === 'right') { self.x += self.speed; } // Check if player has moved since last update and in which direction if (self.x !== self.lastX && !self.isDying) { // If moving right (x increasing), make sure scale is positive (not mirrored) if (self.x > self.lastX && self.scaleX < 0) { self.scaleX *= -1; // Flip to face right } // If moving left (x decreasing), make sure scale is negative (mirrored) else if (self.x < self.lastX && self.scaleX > 0) { self.scaleX *= -1; // Flip to face left } } // Update lastX for next frame comparison self.lastX = self.x; }; self.die = function () { if (self.isDying) { return; } // Prevent multiple death animations self.isDying = true; self.deathSpeed = 5; // Play death sound (if available) if (LK.getSound('explosion')) { LK.getSound('explosion').play(); } }; self.shoot = function () { if (self.isDying) { return; } // Cannot shoot while dying var harpoon = new Harpoon(); harpoon.x = player.x; harpoon.y = player.y - 200; // Trails will be created automatically in the harpoon update method game.addChild(harpoon); LK.getSound('crossbow').play(); }; }); // 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 scaleY: 0.15 // Default scale for segment }); // Custom setter for the height property Object.defineProperty(self, 'height', { get: function get() { return trailGraphics.scaleY * 100; // Convert scale to height }, set: function set(value) { trailGraphics.scaleY = value / 100; // Convert height to scale } }); self.update = function () { // Trail segments are now controlled by the harpoon // They don't move independently 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); var player = new Player(); player.x = 2048 / 2; player.y = 2732 - 180; player.lastX = player.x; // Initialize lastX to current position game.addChild(player); // Add player after trail to ensure correct rendering order game.move = function (x, y, obj) { if (player && !player.isDying) { // Track the last position before moving (done in player.update) player.x = x; // Flipping is now handled in player.update based on movement direction } }; var score = 0; var lives = 3; var lastDifficultyLevel = 0; // Track the last level for showing difficulty announcements // Create score background var scoreBackground = new Container(); var scoreBgGraphics = scoreBackground.attachAsset('scoreBg', { anchorX: 0.5, anchorY: 0.1, scaleX: 5, scaleY: 5, alpha: 1 }); // Create difficulty indicator var difficultyIndicator = new Text2('Difficulty: Easy', { size: 24, fill: 0x00FF00, font: "'Comic Sans MS', cursive, sans-serif" }); difficultyIndicator.anchor.set(0, 0.5); difficultyIndicator.x = 20; difficultyIndicator.y = 50; LK.gui.topLeft.addChild(difficultyIndicator); // Function to update difficulty indicator based on score function updateDifficultyIndicator() { var level = Math.floor(score / 10); var difficultyText = 'Difficulty: '; var textColor = 0x00FF00; if (level === 0) { difficultyText += 'Very Easy'; textColor = 0x00FF00; } else if (level === 1) { difficultyText += 'Easy'; textColor = 0x66FF00; } else if (level === 2) { difficultyText += 'Medium'; textColor = 0xFFFF00; } else if (level === 3) { difficultyText += 'Challenging'; textColor = 0xFF9900; } else if (level === 4) { difficultyText += 'Hard'; textColor = 0xFF6600; } else { difficultyText += 'Expert'; textColor = 0xFF0000; } difficultyIndicator.setText(difficultyText); difficultyIndicator.fill = textColor; } scoreBackground.addChild(scoreBgGraphics); scoreBackground.x = 0; scoreBackground.y = 0; LK.gui.top.addChild(scoreBackground); // Create score text var scoreTxt = new Text2('Bubbles popped: 0', { size: 30, fill: 0x007bff, font: "'Comic Sans MS', cursive, sans-serif" }); scoreTxt.anchor.set(0.5, -0.1); scoreBackground.addChild(scoreTxt); // Function to show difficulty level change announcement function showDifficultyAnnouncement(level) { var difficultyText = ''; var textColor = 0xFFFFFF; switch (level) { case 1: difficultyText = "Warming Up!"; textColor = 0x00FF00; break; case 2: difficultyText = "Getting Started!"; textColor = 0xFFFF00; break; case 3: difficultyText = "Picking Up Pace!"; textColor = 0xFF9900; break; case 4: difficultyText = "More Bubbles!"; textColor = 0xFF6600; break; case 5: difficultyText = "Getting Challenging!"; textColor = 0xFF3300; break; default: if (level > 5) { difficultyText = "Level " + (level - 5) + "!"; textColor = 0xFF0000; } } if (difficultyText) { // Create text for announcement var announcement = new Text2(difficultyText, { size: 100, fill: textColor, font: "'Comic Sans MS', cursive, sans-serif" }); announcement.anchor.set(0.5, 0.5); announcement.x = 2048 / 2; announcement.y = 2732 / 2; announcement.alpha = 0; game.addChild(announcement); // Fade in tween(announcement, { alpha: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Hold for a moment LK.setTimeout(function () { // Fade out tween(announcement, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { announcement.destroy(); } }); }, 1500); } }); // Play a sound for level up LK.getSound('powerup').play(); } } 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); } var lastShot = -999; game.down = function (x, y, obj) { if (LK.ticks - lastShot > 10 && player && !player.isDying) { player.shoot(); lastShot = LK.ticks; } }; // Start the music 'chinese' upon starting the game LK.playMusic('arcade'); game.update = function () { // Create boxes array before using it var boxes = game.children.filter(function (child) { return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3; }); // Dynamic bubble spawn rate based on score // Base spawn interval is much slower at start (300 ticks) and only gradually decreases based on score // This makes the start of the game much easier var baseInterval = 300; // Start with very slow spawn rate (5 seconds between bubbles) var minInterval = 60; // Minimum spawn interval is now higher (1 second) var reductionPerPoint = 2; // Slower progression - only reduce by 2 ticks per point var intervalReductionThreshold = 10; // Only start making game harder after scoring 10 points // Calculate spawn interval with much gentler progression var scoreBasedReduction = Math.max(0, score - intervalReductionThreshold) * reductionPerPoint; var spawnInterval = Math.max(minInterval, baseInterval - scoreBasedReduction); // Only spawn a bubble if current tick matches spawn interval if (LK.ticks % spawnInterval === 0) { var newBubble = new Bubble(); newBubble.x = Math.random() * 2048; newBubble.y = 0; // Reduced horizontal speed for more predictable trajectories var minSpeed = 3; // Reduced from 5 var maxSpeed = 6; // Reduced from 8 var randomSpeed = minSpeed + Math.random() * (maxSpeed - minSpeed); newBubble.speedX = Math.random() < 0.5 ? -randomSpeed : randomSpeed; // Give bubbles a smaller initial vertical speed for gentler physics newBubble.speed = 1 + Math.random() * 2; // Reduced from 2 + random * 2 // Set bounce count to track how many times the bubble has bounced newBubble.bounceCount = 0; newBubble.maxBounces = 3 + Math.floor(Math.random() * 3); // Initialize tracking properties for the bubble newBubble.lastY = newBubble.y; newBubble.lastIntersecting = false; // For special bubbles (every 10th bubble), create a rainbow effect if (score > 30 && Math.random() < 0.1) { // Set the initial tint var hue = 0; // Store the original tint for reference newBubble.originalTint = newBubble.children[0].tint; // Flag this bubble as a special rainbow bubble newBubble.isRainbow = true; // Start the rainbow animation newBubble.rainbowTween = function () { // Convert HSL to RGB (simplified version) hue = (hue + 1) % 360; // Convert hue (0-360) to RGB (0-255) var h = hue / 60; var c = 1; // Chroma var x = c * (1 - Math.abs(h % 2 - 1)); var r, g, b; if (h < 1) { r = c; g = x; b = 0; } else if (h < 2) { r = x; g = c; b = 0; } else if (h < 3) { r = 0; g = c; b = x; } else if (h < 4) { r = 0; g = x; b = c; } else if (h < 5) { r = x; g = 0; b = c; } else { r = c; g = 0; b = x; } // Convert to RGB format var rgb = (Math.floor(r * 255) << 16) + (Math.floor(g * 255) << 8) + Math.floor(b * 255); // Apply the color if (newBubble.children && newBubble.children.length > 0) { newBubble.children[0].tint = rgb; } // Continue the animation next frame newBubble.rainbowTimeout = LK.setTimeout(newBubble.rainbowTween, 50); }; // Start the rainbow animation newBubble.rainbowTween(); } game.addChild(newBubble); // Log difficulty progression for debugging if (score > 0 && score % 10 === 0 && LK.ticks % spawnInterval === 0) { console.log("Score: " + score + ", New spawn interval: " + spawnInterval + " ms (" + (spawnInterval / 60).toFixed(1) + " seconds between bubbles)"); } } // Make sure to initialize all arrays at the beginning var bubbles = game.children.filter(function (child) { return child instanceof Bubble; }); // boxes array is now initialized at the beginning of update function // to prevent 'Cannot read properties of undefined' error var harpoons = game.children.filter(function (child) { return child instanceof Harpoon; }); for (var i = 0; i < bubbles.length; i++) { var bubble = bubbles[i]; // Collision handling is now done in the Bubble class update method for (var j = 0; j < harpoons.length; j++) { var harpoon = harpoons[j]; // Check if bubble collides with harpoon OR with harpoon's trail var hitsHarpoon = bubble.intersects(harpoon); var hitsTrail = harpoon.isPointOnLine({ x: bubble.x, y: bubble.y }, bubble.size); // For very small bubbles, do additional detection with shifted positions to improve hit detection if (!hitsTrail && bubble.size <= 0.25) { // Try multiple points in a wider pattern around the bubble for tiny bubbles // Use more test points with larger offsets var offset = 20; // Increase test point spread var testPoints = [ // Square pattern around center { x: bubble.x - offset, y: bubble.y }, { x: bubble.x + offset, y: bubble.y }, { x: bubble.x, y: bubble.y - offset }, { x: bubble.x, y: bubble.y + offset }, // Diagonal test points { x: bubble.x - offset, y: bubble.y - offset }, { x: bubble.x + offset, y: bubble.y - offset }, { x: bubble.x - offset, y: bubble.y + offset }, { x: bubble.x + offset, y: bubble.y + offset }, // Even wider points for extreme edge cases { x: bubble.x - offset * 1.5, y: bubble.y }, { x: bubble.x + offset * 1.5, y: bubble.y }]; // Check each test point for (var p = 0; p < testPoints.length; p++) { if (harpoon.isPointOnLine(testPoints[p], bubble.size)) { hitsTrail = true; // Log when a tiny bubble is hit console.log("Small bubble hit by trail: size=" + bubble.size + ", x=" + bubble.x + ", harpoon.x=" + harpoon.x + ", point=" + testPoints[p].x + "," + testPoints[p].y); break; } } } if (hitsHarpoon || hitsTrail) { // Clean up rainbow animation if this is a rainbow bubble if (bubble.isRainbow && bubble.rainbowTimeout) { LK.clearTimeout(bubble.rainbowTimeout); bubble.rainbowTimeout = null; } // Destroy small bubbles immediately when hit by the trail if (bubble.size <= 0.25) { // Show a small explosion effect var explosion = new Explosion(); explosion.x = bubble.x; explosion.y = bubble.y; // explosion.scale.set(bubble.size); game.addChild(explosion); // Play explosion sound LK.getSound('explosion').play(); // Show Explosion asset for tiny bubbles (instead of Boom! text) var explosion = new Explosion(); explosion.x = bubble.x; explosion.y = bubble.y; // Adjust explosion size based on bubble size explosion.scale.set(bubble.size * 1.5); // Slightly larger than bubble for visual effect game.addChild(explosion); // Update score score += 1; scoreTxt.setText("Bubbles popped: " + score.toString()); // Destroy bubble and harpoon bubble.destroy(); bubbles.splice(i, 1); // Check if harpoon.trail exists before destroying it if (harpoon.trails) { harpoon.trails.forEach(function (trail) { trail.destroy(); }); } else if (harpoon.trail) { harpoon.trail.destroy(); } harpoon.destroy(); harpoons.splice(j, 1); continue; // Skip further checks for this bubble } // Log small bubble collisions for debugging if (bubble.size <= 0.3) { // Check for trail hits on small bubbles, store the hit location for verification if (hitsTrail && !hitsHarpoon) { // Create temporary visual indicator at hit point (for debug) var debugHitSpot = LK.getAsset('bubble', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.1, scaleY: 0.1, alpha: 0.8, tint: 0xFF0000 // Red to make it visible }); debugHitSpot.x = bubble.x; debugHitSpot.y = bubble.y; game.addChild(debugHitSpot); // Remove the debug hit spot after a short duration LK.setTimeout(function () { debugHitSpot.destroy(); }, 300); // Add log message for each trail segment if (harpoon.trails) { for (var t = 0; t < harpoon.trails.length; t++) { var trail = harpoon.trails[t]; console.log("Trail segment " + t + ": x=" + trail.x + ", y=" + trail.y + ", height=" + trail.height); } } } } // Store current position and data for explosion var bubbleX = bubble.x; var bubbleY = bubble.y; var bubbleSize = bubble.size || 1.0; var bubbleSpeedX = bubble.speedX; // Clean up rainbow animation if this is a rainbow bubble if (bubble.isRainbow && bubble.rainbowTimeout) { LK.clearTimeout(bubble.rainbowTimeout); bubble.rainbowTimeout = null; } // Try to split the bubble var wasSplit = false; if (bubbleSize > 0.3) { // Call split method to create two smaller bubbles wasSplit = bubble.split(); var boxes = game.children.filter(function (child) { return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3; }); // Remove the original bubble bubble.destroy(); // Play explosion sound LK.getSound('explosion').play(); // Remove the harpoon and all its trail segments if (harpoon.trails) { harpoon.trails.forEach(function (trail) { trail.destroy(); }); } harpoon.destroy(); bubbles.splice(i, 1); // Remove bubble from the array // Update score and UI score += 1; scoreTxt.setText("Bubbles popped: " + score.toString()); // Check if we've reached a new difficulty level (every 10 points) var currentDifficultyLevel = Math.floor(score / 10); if (currentDifficultyLevel > lastDifficultyLevel) { lastDifficultyLevel = currentDifficultyLevel; // Show difficulty level announcement showDifficultyAnnouncement(currentDifficultyLevel); // Update the difficulty indicator updateDifficultyIndicator(); } 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 = bubbleX; explosion.y = bubbleY; game.addChild(explosion); // Create a box at the intersection point with a 10% probability, but only if bubble wasn't split // and if there are no other boxes in the game if (!wasSplit && !game.children.some(function (child) { return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3; })) { if (Math.random() < 1) { 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 = bubbleX; box.y = bubbleY; game.addChild(box); } } break; // Break out of the loop since we're deleting the bubble } } } for (var k = 0; k < boxes.length; k++) { var box = boxes[k]; for (var l = 0; l < harpoons.length; l++) { var harpoon = harpoons[l]; // Check if box collides with harpoon OR with harpoon's trail var hitsHarpoon = box.intersects(harpoon); var hitsTrail = harpoon.isPointOnLine({ x: box.x, y: box.y }, 1.0); // Using size 1.0 for boxes to ensure consistent behavior if (hitsHarpoon || hitsTrail) { box.destroy(); LK.getSound('powerup').play(); // Destroy all trail segments if (harpoon.trails) { harpoon.trails.forEach(function (trail) { trail.destroy(); }); } harpoon.destroy(); // Display toast text based on the type of box destroyed 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(); } }); boxes.splice(k, 1); harpoons.splice(l, 1); // Check if the destroyed box is an instance of Box1 if (box instanceof Box1) { // 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; game.addChild(leftHarpoon); var rightHarpoon = new Harpoon(); rightHarpoon.x = player.x + i * 150; rightHarpoon.y = player.y; 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 destroyed 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 destroyed 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 destroyed 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("Balloons popped: " + 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 }); } }); } break; } } } } };
===================================================================
--- original.js
+++ change.js
@@ -951,10 +951,8 @@
continue; // Skip further checks for this bubble
}
// Log small bubble collisions for debugging
if (bubble.size <= 0.3) {
- // Enhanced logging with more details
- console.log("Bubble hit detection SUCCESS: size=" + bubble.size + ", x=" + bubble.x + ", y=" + bubble.y + ", harpoon.x=" + harpoon.x + ", harpoon.y=" + harpoon.y + ", hitType=" + (hitsHarpoon ? "direct" : "trail"));
// Check for trail hits on small bubbles, store the hit location for verification
if (hitsTrail && !hitsHarpoon) {
// Create temporary visual indicator at hit point (for debug)
var debugHitSpot = LK.getAsset('bubble', {
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 Mount fuji background with a big rainbow crossing from side to side in the sky, pixel style, colourful. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows