/**** * 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 // 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 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.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" }); 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
@@ -432,10 +432,11 @@
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.width = 30;
- trail.height = 100;
+ trail.tint = 0x3843ab;
+ trail.width = 20;
+ trail.height = 70;
self.trails.push(trail);
}
}
// Update position of all trail segments
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