Code edit (5 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Everytime you update a Text2 text, please set again stroke and strokeThickness
User prompt
1. The bubbles at the same size seem able to move at different X speed. Please fix. 2. The hitbox of the harpoon seems too large. Reduce.
Code edit (9 edits merged)
Please save this source code
User prompt
After the tween on Bubbles popped has finished scaling, please readd the stroke and stroke fill ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
After the tween on Bubbles popped has finished scaling, please reset the stroke and stroke fill ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
But Gettign Started, the updated Bubbles Popped, etc - don't have a stroke? Please go over All Text2 and make sure all have stroke black
User prompt
Please add black stroke to all the Text2
User prompt
For some reasons, the speed / velocity of the split bubbles seems not consistent. That is, sometime the split bubbles have one velocity, others another velocity. The velocity should be consistent and predictable
User prompt
Make sure you destroy the harpoon and the trail before you split the bubbles so that it does not trigger more than one split
User prompt
When a bubble is split into two, make sure both halves start at the same y and act simmetrically, one going down left another down right, but with the same starting point and same conditions of speed, velocity, gravity, etc.
Code edit (1 edits merged)
Please save this source code
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Box class
var Box = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.update = function () {
self.y += self.speed;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box1 class
var Box1 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box1', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 5;
self.update = function () {
self.y += self.speed;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box2 class
var Box2 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box2', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 7;
self.update = function () {
self.y += self.speed;
if (self.y > 2732) {
self.destroy();
}
};
});
// Box3 class
var Box3 = Container.expand(function () {
var self = Container.call(this);
var boxGraphics = self.attachAsset('box3', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
// Increase the scale to enlarge the bounding box
scaleY: 1.2
});
self.speed = 9;
self.update = function () {
self.y += self.speed;
if (self.y > 2732) {
self.destroy();
}
};
});
// The assets will be automatically created and loaded by the LK engine.
// Bubble class
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1 // Start fully visible instead of transparent
});
// Generate random color for the bubble
var randomColor = Math.random() * 0xFFFFFF;
bubbleGraphics.tint = randomColor;
// Allow for different bubble sizes (original size = 1.0)
self.size = 1.0;
// Update bubble size based on the size property
bubbleGraphics.scale.set(self.size);
// Physics constants for more consistent behavior
self.gravity = 0.2; // Gravity constant
// self.bounceFactor = 0.7; // Energy loss on bounce (30% loss)
self.bounceFactor = 1; // Energy loss on bounce (30% loss)
//self.friction = 0.98; // Air/ground friction
self.friction = 0; // Air/ground friction
self.speed = 3; // Vertical speed (significantly reduced initial speed)
// Track last position for collision detection
self.lastY = 0;
self.lastIntersecting = false;
// Method to split the bubble into two smaller ones
self.split = function () {
// Only split if bubble is not too small
// Note: We're keeping the same threshold (0.3) to determine when bubbles stop splitting
if (self.size <= 0.3) {
// Track that this is a small bubble that can't be split anymore
console.log("Bubble too small to split, size=" + self.size);
return false;
}
// Create two new smaller bubbles
var halfSize = self.size * 0.5;
console.log("Splitting bubble: original size=" + self.size + ", new size=" + halfSize);
// Use completely fixed values for split bubbles to ensure consistency
var upwardSpeed = -8; // Fixed upward velocity
// Fixed horizontal speed regardless of bubble size for consistent behavior
var horizontalSpeed = 8; // Fixed horizontal speed for all bubble sizes
// Create left bubble
var leftBubble = new Bubble();
leftBubble.x = self.x;
leftBubble.y = self.y;
leftBubble.size = halfSize;
leftBubble.speedX = -horizontalSpeed; // Move left
leftBubble.speed = upwardSpeed; // Same jump up speed as right bubble
leftBubble.lastY = leftBubble.y;
leftBubble.lastIntersecting = false;
if (leftBubble.children && leftBubble.children.length > 0) {
leftBubble.children[0].scale.set(halfSize);
// Generate a new random color for split bubble
leftBubble.children[0].tint = Math.random() * 0xFFFFFF;
}
// Create right bubble
var rightBubble = new Bubble();
rightBubble.x = self.x;
rightBubble.y = self.y;
rightBubble.size = halfSize;
rightBubble.speedX = horizontalSpeed; // Move right with same speed magnitude
rightBubble.speed = upwardSpeed; // Same jump up speed as left bubble
rightBubble.lastY = rightBubble.y;
rightBubble.lastIntersecting = false;
if (rightBubble.children && rightBubble.children.length > 0) {
rightBubble.children[0].scale.set(halfSize);
// Generate a new random color for split bubble
rightBubble.children[0].tint = Math.random() * 0xFFFFFF;
}
// Add both bubbles to the game
game.addChild(leftBubble);
game.addChild(rightBubble);
return true;
};
self.update = function () {
// Track last position before moving
self.lastY = self.y;
self.lastIntersecting = self.intersects(player);
self.speed += 0.2; // Further reduced gravity acceleration from 0.3 to 0.2
self.y += self.speed;
self.x += self.speedX; // Update horizontal position based on speedX
// Bounce off the left and right margins with a small boost to ensure movement
if (self.x <= 100 && self.speedX < 0 || self.x >= 1948 && self.speedX > 0) {
self.speedX *= -1.1; // Reverse horizontal direction with a slight boost to ensure movement
}
// Bounce off the ground (bottom of the screen)
if (self.y >= 2732 - 100) {
// Account for bubble size/radius
self.y = 2732 - 100; // Reset position to prevent sinking below ground
// Calculate bounce height based on bubble size
// Larger bubbles bounce higher (500-800px), smaller bubbles bounce lower (300-500px)
var bounceHeight = 400 + self.size * 1000; // Size 1.0 = 800px, Size 0.3 = 450px
// Calculate velocity needed to reach the desired height
var sizeBasedVelocity = -Math.sqrt(2 * self.gravity * bounceHeight);
var naturalBounce = -self.speed * 0.7; // Original dampening logic (70% of original speed)
// Use the stronger of the two values to ensure minimum bounce height
self.speed = Math.min(naturalBounce, sizeBasedVelocity);
// Add a small random horizontal impulse on bounce for more natural movement
self.speedX += (Math.random() - 0.5) * 2;
// Add a slight color flash when bouncing (if not a rainbow bubble)
if (!self.isRainbow && self.children && self.children.length > 0) {
var bubbleGraphics = self.children[0];
// Store original tint
var originalTint = bubbleGraphics.tint;
// Slightly lighten the bubble on bounce for visual feedback
var brighterTint = 0xFFFFFF;
// Flash to brighter color
tween(bubbleGraphics, {
tint: brighterTint
}, {
duration: 100,
onFinish: function onFinish() {
// Return to original tint
tween(bubbleGraphics, {
tint: originalTint
}, {
duration: 300
});
}
});
}
}
// Removed tint application when reaching a certain Y position
// Bubble class update method where player collision is detected
if (!self.lastIntersecting && self.intersects(player)) {
// Make the player blink three times with rainbow colors when hit by a bubble
if (player.children && player.children.length > 0 && !player.isDying) {
var rainbowTint = function rainbowTint() {
// Convert HSL to RGB (simplified version)
rainbowHue = (rainbowHue + 20) % 360; // Faster color change
// Convert hue (0-360) to RGB (0-255)
var h = rainbowHue / 60;
var c = 1; // Chroma
var x = c * (1 - Math.abs(h % 2 - 1));
var r, g, b;
if (h < 1) {
r = c;
g = x;
b = 0;
} else if (h < 2) {
r = x;
g = c;
b = 0;
} else if (h < 3) {
r = 0;
g = c;
b = x;
} else if (h < 4) {
r = 0;
g = x;
b = c;
} else if (h < 5) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
// Convert to RGB format
return (Math.floor(r * 255) << 16) + (Math.floor(g * 255) << 8) + Math.floor(b * 255);
}; // First blink (1/3)
var playerGraphics = player.children[0];
// Save original tint
var originalTint = playerGraphics.tint || 0xFFFFFF;
// Create rainbow animation function for player blinking
var rainbowHue = 0;
playerGraphics.tint = rainbowTint(); // Initial rainbow color
tween(playerGraphics, {
alpha: 0
}, {
duration: 100,
onFinish: function onFinish() {
playerGraphics.tint = rainbowTint(); // New rainbow color
tween(playerGraphics, {
alpha: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Second blink (2/3)
playerGraphics.tint = rainbowTint(); // New rainbow color
tween(playerGraphics, {
alpha: 0
}, {
duration: 100,
onFinish: function onFinish() {
playerGraphics.tint = rainbowTint(); // New rainbow color
tween(playerGraphics, {
alpha: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Third blink (3/3)
playerGraphics.tint = rainbowTint(); // New rainbow color
tween(playerGraphics, {
alpha: 0
}, {
duration: 100,
onFinish: function onFinish() {
playerGraphics.tint = rainbowTint(); // Final rainbow color
tween(playerGraphics, {
alpha: 1
}, {
duration: 100,
onFinish: function onFinish() {
// Reset tint to original color
playerGraphics.tint = originalTint;
}
});
}
});
}
});
}
});
}
});
}
});
}
// Instead of destroying the bubble, try to split it
var wasSplit = self.split();
// Always destroy the original bubble after splitting or if too small to split
self.destroy();
lives -= 1;
// Remove a heart icon when a life is lost
if (hearts.length > lives) {
var heartToRemove = hearts.pop();
if (heartToRemove) {
tween(heartToRemove.scale, {
x: 0,
y: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
heartToRemove.destroy();
}
});
}
}
if (lives < 0) {
// Trigger player death animation before showing game over
if (player && !player.isDying) {
player.die();
// Delay game over screen until animation completes
LK.setTimeout(function () {
LK.showGameOver();
}, 2000); // Wait for death animation to complete
} else {
LK.showGameOver();
}
}
}
};
});
// Explosion class
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
tween(explosionGraphics, {
scaleX: explosionGraphics.scaleX + 1,
scaleY: explosionGraphics.scaleY + 1
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
self.destroy();
}
});
self.update = function () {
// The explosion will disappear after a while
if (self.alpha > 0) {
self.alpha -= 0.02;
} else {
self.destroy();
}
};
});
// Harpoon class
var Harpoon = Container.expand(function () {
var self = Container.call(this);
var harpoonGraphics = self.attachAsset('harpoon', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -20;
self.originalSpeed = -20;
self.maxDistance = 2732 * 0.85; // Maximum distance - 85% of screen height
self.startY = 0; // Will store the starting Y position
self.trails = [];
// Apply a quick acceleration tween to give a nice launch effect
tween(self, {
speed: self.speed * 1.2 // Accelerate to 1.2x speed
}, {
duration: 200,
easing: tween.easeOut
});
// Function to check if point is on line (trail)
self.isPointOnLine = function (point, bubbleSize) {
// The trail is a vertical line from the harpoon down to the player
// Get bubble size factor (if not provided, default to 1)
var sizeFactor = bubbleSize || 1.0;
// Special handling for the smallest bubbles - much more generous
if (sizeFactor <= 0.25) {
// For very small bubbles, use a very large fixed tolerance
var tolerance = 100; // Extremely generous for the tiniest bubbles
// More lenient y-range check for tiny bubbles
var minY = self.y - 100; // Allow collision even slightly above the harpoon
var maxY = 2732 + 100; // Allow collision even slightly below the bottom
// Very generous x-range check for tiny bubbles
return Math.abs(point.x - self.x) < tolerance && point.y >= minY && point.y <= maxY;
}
// For all other bubble sizes, use scaled tolerance with reduced base width
var baseWidth = 30; // Reduced base tolerance for normal bubbles (from 50 to 30)
var tolerance;
if (sizeFactor <= 0.3) {
tolerance = 100; // Less generous for small bubbles (from 150 to 100)
} else if (sizeFactor < 1.0) {
// Scale tolerance inversely with bubble size, but with reduced overall values
tolerance = baseWidth * (1.75 - sizeFactor * 0.75);
} else {
tolerance = baseWidth; // Reduced normal tolerance for regular bubbles
}
// Check if point is on main harpoon or any trail segment
var yCheck = point.y >= self.y && point.y <= 2732;
// First check against harpoon itself
if (Math.abs(point.x - self.x) < tolerance && yCheck) {
return true;
}
// Then check against each trail segment
for (var i = 0; i < self.trails.length; i++) {
var trail = self.trails[i];
if (Math.abs(point.x - self.x) < tolerance && point.y >= trail.y - trail.height / 2 && point.y <= trail.y + trail.height / 2) {
return true;
}
}
return false;
};
self.update = function () {
self.y += self.speed;
// Store initial position on first update
if (self.startY === 0) {
self.startY = self.y;
}
// Calculate total trail distance
var totalDistance = self.startY - self.y;
// Create trail segments for every 100 pixels
var segmentCount = Math.floor(totalDistance / 100);
var currentTrailCount = self.trails.length;
// Create new trail segments if needed
if (segmentCount > currentTrailCount) {
for (var i = currentTrailCount; i < segmentCount; i++) {
var trail = game.addChild(new Trail());
trail.x = self.x;
trail.y = self.y + 50 + i * 100;
trail.tint = 0x3843ab;
trail.width = 20;
trail.height = 70;
self.trails.push(trail);
}
}
// Update position of all trail segments
for (var i = 0; i < self.trails.length; i++) {
var trail = self.trails[i];
trail.y = self.y + 50 + i * 100;
}
// Destroy when reaching max distance or going off-screen
if (self.y < 0 || self.startY - self.y > self.maxDistance) {
self.destroy();
self.trails.forEach(function (trail) {
trail.destroy();
});
}
};
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.isDying = false;
self.deathSpeed = 0;
self.deathRotation = 0;
self.lastX = 0; // Track the last X position to detect direction of movement
self.update = function () {
// Handle death animation if player is dying
if (self.isDying) {
// Accelerate the fall
self.deathSpeed += 0.5;
self.y += self.deathSpeed;
// Flip the player while falling (rotate vertically)
self.deathRotation += 0.05;
self.rotation = self.deathRotation;
// Add slight horizontal movement for more natural fall
self.x += Math.sin(self.deathRotation * 2) * 3;
// Slowly make the player more transparent
if (self.alpha > 0.1) {
self.alpha -= 0.01;
}
// Check if player has fallen off the screen
if (self.y > 2732 + 500) {
self.isDying = false;
self.destroy();
}
return;
}
// Normal movement when not dying
if (self.direction && self.direction === 'left') {
self.x -= self.speed;
} else if (self.direction === 'right') {
self.x += self.speed;
}
// Check if player has moved since last update and in which direction
if (self.x !== self.lastX && !self.isDying) {
// If moving right (x increasing), make sure scale is positive (not mirrored)
if (self.x > self.lastX && self.scaleX < 0) {
self.scaleX *= -1; // Flip to face right
}
// If moving left (x decreasing), make sure scale is negative (mirrored)
else if (self.x < self.lastX && self.scaleX > 0) {
self.scaleX *= -1; // Flip to face left
}
}
// Update lastX for next frame comparison
self.lastX = self.x;
};
self.die = function () {
if (self.isDying) {
return;
} // Prevent multiple death animations
self.isDying = true;
self.deathSpeed = 5;
// Play death sound (if available)
if (LK.getSound('explosion')) {
LK.getSound('explosion').play();
}
};
self.shoot = function () {
if (self.isDying) {
return;
} // Cannot shoot while dying
var harpoon = new Harpoon();
harpoon.x = player.x;
harpoon.y = player.y;
// Trails will be created automatically in the harpoon update method
game.addChild(harpoon);
LK.getSound('crossbow').play();
};
});
// PowerUpText class
var PowerUpText = Container.expand(function () {
var self = Container.call(this);
var textGraphics = self.attachAsset('PowerUpText', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y -= 2;
if (self.y < 0) {
self.destroy();
}
};
});
// Trail class
var Trail = Container.expand(function () {
var self = Container.call(this);
var trailGraphics = self.attachAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
alpha: 0.9,
// Slightly transparent
scaleY: 0.15 // Default scale for segment
});
// Custom setter for the height property
Object.defineProperty(self, 'height', {
get: function get() {
return trailGraphics.scaleY * 100; // Convert scale to height
},
set: function set(value) {
trailGraphics.scaleY = value / 100; // Convert height to scale
}
});
self.update = function () {
// Trail segments are now controlled by the harpoon
// They don't move independently
if (self.y > 2732) {
self.destroy();
}
};
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFFFFFFF // Init game with black background
});
/****
* Game Code
****/
var background = game.attachAsset('Landscape', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.setChildIndex(background, 0);
var player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 180;
player.lastX = player.x; // Initialize lastX to current position
game.addChild(player); // Add player after trail to ensure correct rendering order
game.move = function (x, y, obj) {
if (player && !player.isDying) {
// Track the last position before moving (done in player.update)
player.x = x;
// Flipping is now handled in player.update based on movement direction
}
};
var score = 0;
var lives = 3;
var lastDifficultyLevel = 0; // Track the last level for showing difficulty announcements
// Create score background
var scoreBackground = new Container();
var scoreBgGraphics = scoreBackground.attachAsset('scoreBg', {
anchorX: 0.5,
anchorY: 0.1,
scaleX: 5,
scaleY: 5,
alpha: 1
});
// Create difficulty indicator
var difficultyIndicator = new Text2('Difficulty: Easy', {
size: 24,
fill: 0x00FF00,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xFFFFFF,
strokeThickness: 3
});
difficultyIndicator.anchor.set(0, 0.5);
difficultyIndicator.x = 20;
difficultyIndicator.y = 50;
LK.gui.topLeft.addChild(difficultyIndicator);
// Function to update difficulty indicator based on score
function updateDifficultyIndicator() {
var level = Math.floor(score / 10);
var difficultyText = 'Difficulty: ';
var textColor = 0x00FF00;
if (level === 0) {
difficultyText += 'Very Easy';
textColor = 0x00FF00;
} else if (level === 1) {
difficultyText += 'Easy';
textColor = 0x66FF00;
} else if (level === 2) {
difficultyText += 'Medium';
textColor = 0xFFFF00;
} else if (level === 3) {
difficultyText += 'Challenging';
textColor = 0xFF9900;
} else if (level === 4) {
difficultyText += 'Hard';
textColor = 0xFF6600;
} else {
difficultyText += 'Expert';
textColor = 0xFF0000;
}
difficultyIndicator.setText(difficultyText);
difficultyIndicator.fill = textColor;
}
scoreBackground.addChild(scoreBgGraphics);
scoreBackground.x = 0;
scoreBackground.y = 0;
LK.gui.top.addChild(scoreBackground);
// Create score text
var scoreTxt = new Text2('Bubbles popped: 0', {
size: 30,
fill: 0x007bff,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xffffff,
strokeThickness: 4
});
scoreTxt.anchor.set(0.5, -0.1);
scoreBackground.addChild(scoreTxt);
// Function to show difficulty level change announcement
function showDifficultyAnnouncement(level) {
var difficultyText = '';
var textColor = 0xFFFFFF;
switch (level) {
case 1:
difficultyText = "Warming Up!";
textColor = 0x00FF00;
break;
case 2:
difficultyText = "Getting Started!";
textColor = 0xFFFF00;
break;
case 3:
difficultyText = "Picking Up Pace!";
textColor = 0xFF9900;
break;
case 4:
difficultyText = "More Bubbles!";
textColor = 0xFF6600;
break;
case 5:
difficultyText = "Getting Challenging!";
textColor = 0xFF3300;
break;
default:
if (level > 5) {
difficultyText = "Level " + (level - 5) + "!";
textColor = 0xFF0000;
}
}
if (difficultyText) {
// Create text for announcement
var announcement = new Text2(difficultyText, {
size: 100,
fill: textColor,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xffffff,
strokeThickness: 6
});
announcement.anchor.set(0.5, 0.5);
announcement.x = 2048 / 2;
announcement.y = 2732 / 2;
announcement.alpha = 0;
game.addChild(announcement);
// Fade in
tween(announcement, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for a moment
LK.setTimeout(function () {
// Fade out
tween(announcement, {
alpha: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
announcement.destroy();
}
});
}, 1500);
}
});
// Play a sound for level up
LK.getSound('powerup').play();
}
}
var hearts = [];
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * (i + 1) * 50,
// Position hearts with some spacing
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
var lastShot = -999;
game.down = function (x, y, obj) {
if (LK.ticks - lastShot > 10 && player && !player.isDying) {
player.shoot();
lastShot = LK.ticks;
}
};
// Start the music 'chinese' upon starting the game
LK.playMusic('arcade');
game.update = function () {
// Create boxes array before using it
var boxes = game.children.filter(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
});
// Dynamic bubble spawn rate based on score
// Base spawn interval is much slower at start (300 ticks) and only gradually decreases based on score
// This makes the start of the game much easier
var baseInterval = 300; // Start with very slow spawn rate (5 seconds between bubbles)
var minInterval = 60; // Minimum spawn interval is now higher (1 second)
var reductionPerPoint = 2; // Slower progression - only reduce by 2 ticks per point
var intervalReductionThreshold = 10; // Only start making game harder after scoring 10 points
// Calculate spawn interval with much gentler progression
var scoreBasedReduction = Math.max(0, score - intervalReductionThreshold) * reductionPerPoint;
var spawnInterval = Math.max(minInterval, baseInterval - scoreBasedReduction);
// Only spawn a bubble if current tick matches spawn interval
if (LK.ticks % spawnInterval === 0) {
var newBubble = new Bubble();
newBubble.x = Math.random() * 2048;
newBubble.y = 0;
// Reduced horizontal speed for more predictable trajectories
var minSpeed = 3; // Reduced from 5
var maxSpeed = 6; // Reduced from 8
var randomSpeed = minSpeed + Math.random() * (maxSpeed - minSpeed);
newBubble.speedX = Math.random() < 0.5 ? -randomSpeed : randomSpeed;
// Give bubbles a smaller initial vertical speed for gentler physics
newBubble.speed = 1 + Math.random() * 2; // Reduced from 2 + random * 2
// Set bounce count to track how many times the bubble has bounced
newBubble.bounceCount = 0;
newBubble.maxBounces = 3 + Math.floor(Math.random() * 3);
// Initialize tracking properties for the bubble
newBubble.lastY = newBubble.y;
newBubble.lastIntersecting = false;
// For special bubbles (every 10th bubble), create a rainbow effect
if (score > 30 && Math.random() < 0.1) {
// Set the initial tint
var hue = 0;
// Store the original tint for reference
newBubble.originalTint = newBubble.children[0].tint;
// Flag this bubble as a special rainbow bubble
newBubble.isRainbow = true;
// Start the rainbow animation
newBubble.rainbowTween = function () {
// Convert HSL to RGB (simplified version)
hue = (hue + 1) % 360;
// Convert hue (0-360) to RGB (0-255)
var h = hue / 60;
var c = 1; // Chroma
var x = c * (1 - Math.abs(h % 2 - 1));
var r, g, b;
if (h < 1) {
r = c;
g = x;
b = 0;
} else if (h < 2) {
r = x;
g = c;
b = 0;
} else if (h < 3) {
r = 0;
g = c;
b = x;
} else if (h < 4) {
r = 0;
g = x;
b = c;
} else if (h < 5) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
// Convert to RGB format
var rgb = (Math.floor(r * 255) << 16) + (Math.floor(g * 255) << 8) + Math.floor(b * 255);
// Apply the color
if (newBubble.children && newBubble.children.length > 0) {
newBubble.children[0].tint = rgb;
}
// Continue the animation next frame
newBubble.rainbowTimeout = LK.setTimeout(newBubble.rainbowTween, 50);
};
// Start the rainbow animation
newBubble.rainbowTween();
}
game.addChild(newBubble);
// Log difficulty progression for debugging
if (score > 0 && score % 10 === 0 && LK.ticks % spawnInterval === 0) {
console.log("Score: " + score + ", New spawn interval: " + spawnInterval + " ms (" + (spawnInterval / 60).toFixed(1) + " seconds between bubbles)");
}
}
// Make sure to initialize all arrays at the beginning
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
// boxes array is now initialized at the beginning of update function
// to prevent 'Cannot read properties of undefined' error
var harpoons = game.children.filter(function (child) {
return child instanceof Harpoon;
});
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
// Collision handling is now done in the Bubble class update method
for (var j = 0; j < harpoons.length; j++) {
var harpoon = harpoons[j];
// Check if bubble collides with harpoon OR with harpoon's trail
var hitsHarpoon = bubble.intersects(harpoon);
var hitsTrail = harpoon.isPointOnLine({
x: bubble.x,
y: bubble.y
}, bubble.size);
// For very small bubbles, do additional detection with shifted positions to improve hit detection
if (!hitsTrail && bubble.size <= 0.25) {
// Try multiple points in a wider pattern around the bubble for tiny bubbles
// Use more test points with larger offsets
var offset = 20; // Increase test point spread
var testPoints = [
// Square pattern around center
{
x: bubble.x - offset,
y: bubble.y
}, {
x: bubble.x + offset,
y: bubble.y
}, {
x: bubble.x,
y: bubble.y - offset
}, {
x: bubble.x,
y: bubble.y + offset
},
// Diagonal test points
{
x: bubble.x - offset,
y: bubble.y - offset
}, {
x: bubble.x + offset,
y: bubble.y - offset
}, {
x: bubble.x - offset,
y: bubble.y + offset
}, {
x: bubble.x + offset,
y: bubble.y + offset
},
// Even wider points for extreme edge cases
{
x: bubble.x - offset * 1.5,
y: bubble.y
}, {
x: bubble.x + offset * 1.5,
y: bubble.y
}];
// Check each test point
for (var p = 0; p < testPoints.length; p++) {
if (harpoon.isPointOnLine(testPoints[p], bubble.size)) {
hitsTrail = true;
// Log when a tiny bubble is hit
console.log("Small bubble hit by trail: size=" + bubble.size + ", x=" + bubble.x + ", harpoon.x=" + harpoon.x + ", point=" + testPoints[p].x + "," + testPoints[p].y);
break;
}
}
}
if (hitsHarpoon || hitsTrail) {
// Clean up rainbow animation if this is a rainbow bubble
if (bubble.isRainbow && bubble.rainbowTimeout) {
LK.clearTimeout(bubble.rainbowTimeout);
bubble.rainbowTimeout = null;
}
// Destroy small bubbles immediately when hit by the trail
if (bubble.size <= 0.25) {
// Show a small explosion effect
var explosion = new Explosion();
explosion.x = bubble.x;
explosion.y = bubble.y;
// explosion.scale.set(bubble.size);
game.addChild(explosion);
// Play explosion sound
LK.getSound('explosion').play();
// Show Explosion asset for tiny bubbles (instead of Boom! text)
var explosion = new Explosion();
explosion.x = bubble.x;
explosion.y = bubble.y;
// Adjust explosion size based on bubble size
explosion.scale.set(bubble.size * 1.5); // Slightly larger than bubble for visual effect
game.addChild(explosion);
// Update score
score += 1;
scoreTxt.setText("Bubbles popped: " + score.toString());
// Destroy bubble and harpoon
bubble.destroy();
bubbles.splice(i, 1);
// Check if harpoon.trail exists before destroying it
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
} else if (harpoon.trail) {
harpoon.trail.destroy();
}
harpoon.destroy();
harpoons.splice(j, 1);
continue; // Skip further checks for this bubble
}
// Log small bubble collisions for debugging
if (bubble.size <= 0.3) {
// Check for trail hits on small bubbles, store the hit location for verification
if (hitsTrail && !hitsHarpoon) {
// Create temporary visual indicator at hit point (for debug)
var debugHitSpot = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0.8,
tint: 0xFF0000 // Red to make it visible
});
debugHitSpot.x = bubble.x;
debugHitSpot.y = bubble.y;
game.addChild(debugHitSpot);
// Remove the debug hit spot after a short duration
LK.setTimeout(function () {
debugHitSpot.destroy();
}, 300);
// Add log message for each trail segment
if (harpoon.trails) {
for (var t = 0; t < harpoon.trails.length; t++) {
var trail = harpoon.trails[t];
console.log("Trail segment " + t + ": x=" + trail.x + ", y=" + trail.y + ", height=" + trail.height);
}
}
}
}
// Store current position and data for explosion
var bubbleX = bubble.x;
var bubbleY = bubble.y;
var bubbleSize = bubble.size || 1.0;
var bubbleSpeedX = bubble.speedX;
// Clean up rainbow animation if this is a rainbow bubble
if (bubble.isRainbow && bubble.rainbowTimeout) {
LK.clearTimeout(bubble.rainbowTimeout);
bubble.rainbowTimeout = null;
}
// First destroy the harpoon and trails to prevent multiple collisions
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
harpoons.splice(j, 1); // Remove harpoon from the array
// Try to split the bubble
var wasSplit = false;
if (bubbleSize > 0.3) {
// Call split method to create two smaller bubbles
wasSplit = bubble.split();
var boxes = game.children.filter(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
});
// Remove the original bubble
bubble.destroy();
// Play explosion sound
LK.getSound('explosion').play();
bubbles.splice(i, 1); // Remove bubble from the array
// Update score and UI
score += 1;
scoreTxt.setText("Bubbles popped: " + score.toString());
// Check if we've reached a new difficulty level (every 10 points)
var currentDifficultyLevel = Math.floor(score / 10);
if (currentDifficultyLevel > lastDifficultyLevel) {
lastDifficultyLevel = currentDifficultyLevel;
// Show difficulty level announcement
showDifficultyAnnouncement(currentDifficultyLevel);
// Update the difficulty indicator
updateDifficultyIndicator();
}
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reapply stroke after scale animation completes
scoreTxt.stroke = 0xffffff;
scoreTxt.strokeThickness = 4;
}
});
}
});
harpoons.splice(j, 1); // Remove harpoon from the array
// Create an explosion at the intersection point
var explosion = new Explosion();
explosion.x = bubbleX;
explosion.y = bubbleY;
game.addChild(explosion);
// Create a box at the intersection point with a 10% probability, but only if bubble wasn't split
// and if there are no other boxes in the game
if (!wasSplit && !game.children.some(function (child) {
return child instanceof Box || child instanceof Box1 || child instanceof Box2 || child instanceof Box3;
})) {
if (Math.random() < 1) {
var boxType = Math.floor(Math.random() * 4);
var box;
switch (boxType) {
case 0:
box = new Box();
break;
case 1:
box = new Box1();
break;
case 2:
box = new Box2();
break;
case 3:
box = new Box3();
break;
}
box.x = bubbleX;
box.y = bubbleY;
game.addChild(box);
}
}
break; // Break out of the loop since we're deleting the bubble
}
}
}
for (var k = 0; k < boxes.length; k++) {
var box = boxes[k];
for (var l = 0; l < harpoons.length; l++) {
var harpoon = harpoons[l];
// Check if box collides with harpoon OR with harpoon's trail
var hitsHarpoon = box.intersects(harpoon);
var hitsTrail = harpoon.isPointOnLine({
x: box.x,
y: box.y
}, 1.0); // Using size 1.0 for boxes to ensure consistent behavior
if (hitsHarpoon || hitsTrail) {
box.destroy();
LK.getSound('powerup').play();
// Destroy all trail segments
if (harpoon.trails) {
harpoon.trails.forEach(function (trail) {
trail.destroy();
});
}
harpoon.destroy();
// Display toast text based on the type of box destroyed
var toastText = new Text2('', {
size: 250,
fill: 0xFFC0CB,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xffffff,
strokeThickness: 8
});
var toastTextBg = new Text2('', {
size: 255,
fill: 0xFF00AA,
font: "'Comic Sans MS', cursive, sans-serif",
stroke: 0xffffff,
strokeThickness: 10
});
toastText.anchor.set(0.5, 0.5);
toastText.x = 2048 / 2;
toastText.y = 2732 / 2;
toastTextBg.anchor.set(0.5, 0.5);
toastTextBg.x = 2048 / 2;
toastTextBg.y = 2732 / 2;
game.addChild(toastText);
game.addChild(toastTextBg);
if (box instanceof Box1) {
toastText.setText("Smash!");
toastTextBg.setText("Smash!");
} else if (box instanceof Box2) {
toastText.setText("Destruction!");
toastTextBg.setText("Destruction!");
} else if (box instanceof Box) {
toastText.setText("Less madness!");
toastTextBg.setText("Less madness!");
} else if (box instanceof Box3) {
toastText.setText("Life up!");
toastTextBg.setText("Life up!");
}
// Tween the toast text to fade out and destroy after 2 seconds
tween(toastText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastText.destroy();
}
});
tween(toastTextBg, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
toastTextBg.destroy();
}
});
boxes.splice(k, 1);
harpoons.splice(l, 1);
// Check if the destroyed box is an instance of Box1
if (box instanceof Box1) {
// Create six additional harpoons and trails
for (var i = 1; i <= 3; i++) {
var leftHarpoon = new Harpoon();
leftHarpoon.x = player.x - i * 150;
leftHarpoon.y = player.y;
game.addChild(leftHarpoon);
var rightHarpoon = new Harpoon();
rightHarpoon.x = player.x + i * 150;
rightHarpoon.y = player.y;
game.addChild(rightHarpoon);
// Set a timeout to remove the additional harpoons after 5 seconds
LK.setTimeout(function (lh, rh) {
if (lh.trails) {
lh.trails.forEach(function (trail) {
trail.destroy();
});
}
lh.destroy();
if (rh.trails) {
rh.trails.forEach(function (trail) {
trail.destroy();
});
}
rh.destroy();
}.bind(null, leftHarpoon, rightHarpoon), 5000);
}
}
// Check if the destroyed box is an instance of Box and reduce bubble speed
if (box instanceof Box) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
bubbles.forEach(function (bubble) {
bubble.speed /= 2;
});
LK.setTimeout(function () {
bubbles.forEach(function (bubble) {
bubble.speed *= 2;
});
}, 5000);
}
// Check if the destroyed box is an instance of Box3 and lives are less than 3
if (box instanceof Box3 && lives < 3) {
lives += 1;
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: -1 * lives * 50,
y: 50
});
LK.gui.topRight.addChild(heart);
hearts.push(heart);
}
// Check if the destroyed box is an instance of Box2
if (box instanceof Box2) {
var bubbles = game.children.filter(function (child) {
return child instanceof Bubble;
});
var bubblesDestroyed = bubbles.length;
bubbles.forEach(function (bubble) {
bubble.destroy();
});
score += bubblesDestroyed;
scoreTxt.setText("Balloons popped: " + score.toString());
tween(scoreTxt.scale, {
x: 2,
y: 2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reapply stroke after scale animation completes
scoreTxt.stroke = 0xffffff;
scoreTxt.strokeThickness = 4;
}
});
}
});
}
break;
}
}
}
}
}; ===================================================================
--- original.js
+++ change.js
@@ -624,9 +624,9 @@
var difficultyIndicator = new Text2('Difficulty: Easy', {
size: 24,
fill: 0x00FF00,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xFFFFFF,
strokeThickness: 3
});
difficultyIndicator.anchor.set(0, 0.5);
difficultyIndicator.x = 20;
@@ -709,9 +709,9 @@
var announcement = new Text2(difficultyText, {
size: 100,
fill: textColor,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xffffff,
strokeThickness: 6
});
announcement.anchor.set(0.5, 0.5);
announcement.x = 2048 / 2;
@@ -1057,9 +1057,9 @@
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reapply stroke after scale animation completes
- scoreTxt.stroke = 0x000000;
+ scoreTxt.stroke = 0xffffff;
scoreTxt.strokeThickness = 4;
}
});
}
@@ -1125,16 +1125,16 @@
var toastText = new Text2('', {
size: 250,
fill: 0xFFC0CB,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xffffff,
strokeThickness: 8
});
var toastTextBg = new Text2('', {
size: 255,
fill: 0xFF00AA,
font: "'Comic Sans MS', cursive, sans-serif",
- stroke: 0x000000,
+ stroke: 0xffffff,
strokeThickness: 10
});
toastText.anchor.set(0.5, 0.5);
toastText.x = 2048 / 2;
@@ -1256,9 +1256,9 @@
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reapply stroke after scale animation completes
- scoreTxt.stroke = 0x000000;
+ scoreTxt.stroke = 0xffffff;
scoreTxt.strokeThickness = 4;
}
});
}
a green cross, icon, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a sand clock pixel style.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a bomb, pixel style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
a pixel harpoon, vertical and looking up, retro like in pang games.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
A banner to show a message, pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
two harpoons looking up, retro, pixel. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
A Mount fuji background with a big rainbow crossing from side to side in the sky, pixel style, colourful. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A Boom! explosion, pixel retro style. In-Game asset. 2d. High contrast. No shadows