Code edit (5 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Everytime you update a Text2 text, please set again stroke and strokeThickness
User prompt
1. The bubbles at the same size seem able to move at different X speed. Please fix. 2. The hitbox of the harpoon seems too large. Reduce.
Code edit (9 edits merged)
Please save this source code
User prompt
After the tween on Bubbles popped has finished scaling, please readd the stroke and stroke fill ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
After the tween on Bubbles popped has finished scaling, please reset the stroke and stroke fill ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
But Gettign Started, the updated Bubbles Popped, etc - don't have a stroke? Please go over All Text2 and make sure all have stroke black
User prompt
Please add black stroke to all the Text2
User prompt
For some reasons, the speed / velocity of the split bubbles seems not consistent. That is, sometime the split bubbles have one velocity, others another velocity. The velocity should be consistent and predictable
User prompt
Make sure you destroy the harpoon and the trail before you split the bubbles so that it does not trigger more than one split
User prompt
When a bubble is split into two, make sure both halves start at the same y and act simmetrically, one going down left another down right, but with the same starting point and same conditions of speed, velocity, gravity, etc.
Code edit (1 edits merged)
Please save this source code
/**** * 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.bounceFactor = 1; // Energy loss on bounce (30% loss) //self.friction = 0.98; // Air/ground friction self.friction = 0; // 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); // Use completely fixed values for split bubbles to ensure consistency var upwardSpeed = -8; // Fixed upward velocity // Fixed horizontal speed regardless of bubble size for consistent behavior var horizontalSpeed = 8; // Fixed horizontal speed for all bubble sizes // Create left bubble var leftBubble = new Bubble(); leftBubble.x = self.x; leftBubble.y = self.y; leftBubble.size = halfSize; leftBubble.speedX = -horizontalSpeed; // Move left leftBubble.speed = upwardSpeed; // Same jump up speed as right bubble 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 = horizontalSpeed; // Move right with same speed magnitude rightBubble.speed = upwardSpeed; // Same jump up speed as left bubble 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 // Calculate bounce height based on bubble size // Larger bubbles bounce higher (500-800px), smaller bubbles bounce lower (300-500px) var bounceHeight = 400 + self.size * 1000; // Size 1.0 = 800px, Size 0.3 = 450px // Calculate velocity needed to reach the desired height var sizeBasedVelocity = -Math.sqrt(2 * self.gravity * bounceHeight); var naturalBounce = -self.speed * 0.7; // Original dampening logic (70% of original speed) // Use the stronger of the two values to ensure minimum bounce height self.speed = Math.min(naturalBounce, sizeBasedVelocity); // 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 with reduced base width var baseWidth = 30; // Reduced base tolerance for normal bubbles (from 50 to 30) var tolerance; if (sizeFactor <= 0.3) { tolerance = 100; // Less generous for small bubbles (from 150 to 100) } else if (sizeFactor < 1.0) { // Scale tolerance inversely with bubble size, but with reduced overall values tolerance = baseWidth * (1.75 - sizeFactor * 0.75); } else { tolerance = baseWidth; // Reduced 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.tint = 0x3843ab; trail.width = 20; trail.height = 70; 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; // 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", stroke: 0xFFFFFF, strokeThickness: 3 }); 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", stroke: 0xffffff, strokeThickness: 4 }); 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", stroke: 0xffffff, strokeThickness: 6 }); 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; } // First destroy the harpoon and trails to prevent multiple collisions if (harpoon.trails) { harpoon.trails.forEach(function (trail) { trail.destroy(); }); } harpoon.destroy(); harpoons.splice(j, 1); // Remove harpoon from the array // 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(); 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, onFinish: function onFinish() { // Reapply stroke after scale animation completes scoreTxt.stroke = 0xffffff; scoreTxt.strokeThickness = 4; } }); } }); 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", stroke: 0xffffff, strokeThickness: 8 }); var toastTextBg = new Text2('', { size: 255, fill: 0xFF00AA, font: "'Comic Sans MS', cursive, sans-serif", stroke: 0xffffff, strokeThickness: 10 }); 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, onFinish: function onFinish() { // Reapply stroke after scale animation completes scoreTxt.stroke = 0xffffff; scoreTxt.strokeThickness = 4; } }); } }); } break; } } } } };
===================================================================
--- original.js
+++ change.js
@@ -624,9 +624,9 @@
var difficultyIndicator = new Text2('Difficulty: Easy', {
size: 24,
fill: 0x00FF00,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xFFFFFF,
strokeThickness: 3
});
difficultyIndicator.anchor.set(0, 0.5);
difficultyIndicator.x = 20;
@@ -709,9 +709,9 @@
var announcement = new Text2(difficultyText, {
size: 100,
fill: textColor,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xffffff,
strokeThickness: 6
});
announcement.anchor.set(0.5, 0.5);
announcement.x = 2048 / 2;
@@ -1057,9 +1057,9 @@
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reapply stroke after scale animation completes
- scoreTxt.stroke = 0x000000;
+ scoreTxt.stroke = 0xffffff;
scoreTxt.strokeThickness = 4;
}
});
}
@@ -1125,16 +1125,16 @@
var toastText = new Text2('', {
size: 250,
fill: 0xFFC0CB,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xffffff,
strokeThickness: 8
});
var toastTextBg = new Text2('', {
size: 255,
fill: 0xFF00AA,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xffffff,
strokeThickness: 10
});
toastText.anchor.set(0.5, 0.5);
toastText.x = 2048 / 2;
@@ -1256,9 +1256,9 @@
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reapply stroke after scale animation completes
- scoreTxt.stroke = 0x000000;
+ scoreTxt.stroke = 0xffffff;
scoreTxt.strokeThickness = 4;
}
});
}
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
A Boom! explosion, pixel retro style. In-Game asset. 2d. High contrast. No shadows