/****
* 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;
}
}
}
}
}; /****
* 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;
}
}
}
}
};
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