User prompt
update with: // In the Bubble class, modify the autoPop method: self.autoPop = function() { var points = Math.floor(self.getBP() * 0.5); // Half points for auto-pop // Only add points and display once game.addBP(points, self.x, self.y); self.destroy(); return; };
User prompt
increase the size of the point pop up
User prompt
increase more
User prompt
increase more
User prompt
add half points to autopop
User prompt
add a point popup display to autopop as well to go with the half points
User prompt
the popup display for autopop is being called many times, fix so its only called once
User prompt
the BP display should update when points are added
User prompt
update with: // Inside game.addBP function, after the points text animation: if (game.combo > 0) { var comboText = new Text2("x" + (game.combo + 1), { size: 48, // Increased from 24 for better visibility fill: 0xFFA500, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); comboText.anchorX = 1; comboText.anchorY = 0; comboText.x = bpText.x - 10; // Position it to the left of BP counter comboText.y = bpText.y + 50; // Position it below BP counter game.addChild(comboText); tween(comboText, { alpha: 0, y: comboText.y - 50 // Float upward as it fades }, { duration: 500, onFinish: function onFinish() { comboText.destroy(); } }); } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
update as needed with: if (game.combo > 0) { var comboText = new Text2("x" + (game.combo + 1), { size: 96, // Much bigger fill: 0xFFA500, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); comboText.anchorX = 0.5; comboText.anchorY = 0; comboText.x = game.width / 2; // Center of screen comboText.y = 20; // Near top game.addChild(comboText); tween(comboText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { comboText.destroy(); } }); } ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
update with: self.autoPop = function() { if (!self.autoPopDisplayed) { var points = Math.floor(self.getBP() * 0.5); // Half points for auto-pop game.addBP(points, self.x, self.y, true); // Added true flag for autoPop self.autoPopDisplayed = true; } self.destroy(); return; };
User prompt
update with: game.addBP = function(points, x, y, isAutoPop) { var currentTime = LK.ticks; // Only update combo if it's not an auto-pop if (!isAutoPop) { if (currentTime - game.lastPopTime < game.COMBO_WINDOW) { game.combo++; points *= 1 + game.combo * 0.1; // 10% bonus per combo } else { game.combo = 0; } game.lastPopTime = currentTime; } game.bp += Math.floor(points); bpText.setText(formatBP(game.bp) + " BP"); // Only show combo if it's a manual pop and we have a combo if (!isAutoPop && game.combo > 0) { var comboText = new Text2("x" + (game.combo + 1), { size: 96, fill: 0xFFA500, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); comboText.anchorX = 0.5; comboText.anchorY = 0; comboText.x = game.width / 2; comboText.y = 20; game.addChild(comboText); tween(comboText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { comboText.destroy(); } }); } }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
update with: game.addBP = function(points, x, y, isAutoPop) { var currentTime = LK.ticks; // Only update combo if it's not an auto-pop if (!isAutoPop) { if (currentTime - game.lastPopTime < game.COMBO_WINDOW) { game.combo++; points *= 1 + game.combo * 0.1; // 10% bonus per combo } else { game.combo = 0; } game.lastPopTime = currentTime; } game.bp += Math.floor(points); bpText.setText(formatBP(game.bp) + " BP"); // Always show point text regardless of auto or manual pop var pointText = new Text2("+" + Math.floor(points), { size: 80, fill: 0xFFFF00, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); pointText.anchorX = 0.5; pointText.anchorY = 0.5; pointText.x = x; pointText.y = y; game.addChild(pointText); tween(pointText, { y: pointText.y - 100, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { pointText.destroy(); } }); // Only show combo text if it's a manual pop and we have a combo if (!isAutoPop && game.combo > 0) { var comboText = new Text2("x" + (game.combo + 1), { size: 96, fill: 0xFFA500, stroke: 0x000000, strokeThickness: 4, fontWeight: 'bold' }); comboText.anchorX = 0.5; comboText.anchorY = 0; comboText.x = game.width / 2; comboText.y = 20; game.addChild(comboText); tween(comboText, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { comboText.destroy(); } }); } }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
update bubble with: var points = self.getBP(); game.addBP(points, self.x, self.y, false); // Pass position and flag for manual pop
User prompt
set background asset as background, one layer behind puffermask
User prompt
increase size of bp display
Code edit (2 edits merged)
Please save this source code
User prompt
use impact font and make bp display bold
User prompt
use impact font for bp pop ups
Code edit (1 edits merged)
Please save this source code
User prompt
set bubble alpha to 0.9
User prompt
update with: game.lastMouthWidth = 0; game.baselineWidth = null; game.BLOW_THRESHOLD = 0.6; // Mouth must be 60% narrower than baseline to count as blowing game.calibrationFrames = 0; game.CALIBRATION_PERIOD = 60; // 1 second to establish baseline
User prompt
update with: // Replace the if(facekit.mouthOpen) section in game.update if (facekit.mouthCenter && facekit.mouthLeft && facekit.mouthRight) { // Calculate current mouth width var currentWidth = Math.abs(facekit.mouthLeft.x - facekit.mouthRight.x); // Establish baseline during calibration if (game.calibrationFrames < game.CALIBRATION_PERIOD) { if (!game.baselineWidth) game.baselineWidth = currentWidth; game.baselineWidth = game.baselineWidth * 0.95 + currentWidth * 0.05; game.calibrationFrames++; return; } // Check for blowing gesture (pursed lips) var widthRatio = currentWidth / game.baselineWidth; var isBlowing = widthRatio < game.BLOW_THRESHOLD; if (isBlowing) { // Existing bubble growing logic var spawnX = playerMask.x; var spawnY = playerMask.y + playerMask.height * 0.15; if (!game.growingBubble) { game.growingBubble = new Bubble(); game.growingBubble.size = 25; game.addChild(game.growingBubble); game.bubbles.push(game.growingBubble); } if (game.growingBubble) { game.growingBubble.x = spawnX; game.growingBubble.y = spawnY; game.growingBubble.size = Math.min( game.growingBubble.size + game.growthRate, game.maxBubbleSize ); game.growingBubble.verticalVelocity = 0; game.growingBubble.driftX = 0; } } else if (game.growingBubble) { // Release bubble when stopped blowing game.growingBubble.verticalVelocity = -12; game.growingBubble.driftX = (Math.random() * 2 - 1) * 2.5; game.growingBubble = null; } game.lastMouthWidth = currentWidth; }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Bubble class to represent each bubble in the game
var Bubble = Container.expand(function () {
var self = Container.call(this);
self.lifetime = 0;
self.hasSplit = false;
self.splitHeight = null;
self.AUTO_POP_SIZE = 40;
self.MIN_SPLIT_SIZE = 30;
self.lastPopTime = 0; // Add timestamp tracking
var sprite = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.size = 100;
self.initLifetime = function () {
self.maxLifetime = Math.floor(Math.random() * 960 + 1440);
self.maxLifetime *= Math.min(1, self.size / 100);
};
self.initLifetime();
// Subtle size-based variance plus small random factor
var speedMultiplier = 120 / self.size * (0.9 + Math.random() * 0.2); // Just 10% variance
self.floatSpeed = 50 * speedMultiplier / 60;
self.driftX = (Math.random() * 20 - 10) / 60; // Normal drift variance
self.verticalVelocity = 0;
self.down = function (e) {
var currentTime = Date.now();
if (currentTime - self.lastPopTime < 100) {
return true;
}
self.lastPopTime = currentTime;
var index = game.bubbles.indexOf(self);
if (index > -1) {
game.bubbles.splice(index, 1);
}
var points = self.getBP();
// Pass bubble position to addBP
game.addBP(points, self.x, self.y);
if (self.size > 60 && !self.justSplit) {
var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6);
for (var i = 0; i < 2; i++) {
spawnSplitBubble(self.x, self.y, newSize, i === 0 ? -1 : 1);
}
}
self.destroy();
return true;
};
self.getBP = function () {
return Math.floor(Math.pow(self.size, 2) * 0.1);
};
self.update = function () {
self.lifetime++;
// Add subtle drift variation
if (self.lifetime % 60 === 0) {
// Every second
self.driftX += (Math.random() - 0.5) * 0.3; // Add small random drift
}
// Increase overall horizontal movement
self.x += self.driftX * 1.2; // 20% more horizontal movement
// Auto-pop or split when lifetime exceeded
if (self.lifetime > self.maxLifetime) {
// Just check size and not already split
if (self.size > 60 && !self.hasSplit) {
self.hasSplit = true;
var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6);
for (var i = 0; i < 2; i++) {
var split = spawnSplitBubble(self.x, self.y, newSize, i === 0 ? -1 : 1, true);
split.maxLifetime *= 0.7; // Shorter lifetime for split bubbles
}
self.destroy();
return;
}
// If too small to split, just pop
self.autoPop();
return;
}
// Clear off-screen bubbles
if (self.y < -self.size) {
self.destroy();
return;
}
self.justSplit = false; // Clear the flag after first update
// More gradual vertical speed transition
if (self.verticalVelocity < self.floatSpeed) {
self.verticalVelocity += 0.08; // More gentle transition
}
self.y -= self.verticalVelocity;
// Gradually reduce horizontal speed after split
if (Math.abs(self.driftX) > (Math.random() * 20 - 10) / 60) {
self.driftX *= 0.98; // Slowly return to normal drift speed
}
self.x += self.driftX;
// Bounce off edges
if (self.x < self.size) {
self.x = self.size;
self.driftX = Math.abs(self.driftX);
} else if (self.x > game.width - self.size) {
self.x = game.width - self.size;
self.driftX = -Math.abs(self.driftX);
}
var scale = self.size / sprite.width;
sprite.scaleX = scale;
sprite.scaleY = scale;
};
self.autoPop = function () {
var points = Math.floor(self.getBP() * 0.5); // Half points for auto-pop
// Only add points and display once
game.addBP(points, self.x, self.y);
self.destroy();
return;
};
return self;
});
// Pufferfish mask that follows face
var pufferMask = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('pufferfish', {
anchorX: 0.5,
anchorY: 0.5
});
var targetX = 0;
var targetY = 0;
var smoothingFactor = 0.12;
var prevX = null;
var prevY = null;
var targetRotation = 0;
var rotationSmoothingFactor = 0.1;
var targetTilt = 0;
var tiltSmoothingFactor = 0.11; // Reduced from 0.08 for smoother movement
var tiltScaleFactor = 0.09; // Reduced from 0.15 for less tilt
var scaleHistory = new Array(5).fill(0); // Keep last 5 scale values
var scaleIndex = 0;
var baseScale = 1;
var minScale = 0.1;
var maxScale = 3;
self.update = function () {
// Adjust scale based on face size
if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
var eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
var newScale = eyeDistance / 500;
// Update rolling average
scaleHistory[scaleIndex] = newScale;
scaleIndex = (scaleIndex + 1) % scaleHistory.length;
// Calculate average scale
var avgScale = scaleHistory.reduce(function (a, b) {
return a + b;
}, 0) / scaleHistory.length;
// More gentle smoothing
sprite.scaleX = sprite.scaleX * 0.85 + avgScale * 0.15;
sprite.scaleY = sprite.scaleY * 0.85 + avgScale * 0.15;
}
// Follow nose position for main face tracking
if (facekit.noseTip) {
targetX = facekit.noseTip.x;
targetY = facekit.noseTip.y;
// Initialize previous positions if not set
if (prevX === null) {
prevX = targetX;
prevY = targetY;
}
// Weighted average between previous and target position
var newX = prevX * (1 - smoothingFactor) + targetX * smoothingFactor;
var newY = prevY * (1 - smoothingFactor) + targetY * smoothingFactor;
self.x = newX;
self.y = newY;
// Update previous positions
prevX = newX;
prevY = newY;
}
if (facekit.leftEye && facekit.rightEye) {
targetTilt = calculateFaceTilt() * tiltScaleFactor; // Scale down the tilt
// Reduce max rotation to ±15 degrees
targetTilt = Math.max(-15, Math.min(15, targetTilt));
self.rotation += (targetTilt - self.rotation) * tiltSmoothingFactor;
}
};
function calculateFaceTilt() {
if (facekit.leftEye && facekit.rightEye && facekit.mouthCenter) {
// Calculate midpoint between eyes
var eyeMidX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeMidY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
// Calculate angle between eye midpoint and mouth, negated to fix direction
var dx = facekit.mouthCenter.x - eyeMidX;
var dy = facekit.mouthCenter.y - eyeMidY;
var angle = -(Math.atan2(dx, dy) * (180 / Math.PI));
// Reduced max angle to ±15 degrees and lowered multiplier
return Math.max(-15, Math.min(15, angle * 0.15));
}
return 0; // Default to straight when face points aren't available
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Light blue background to represent the sky
});
/****
* Game Code
****/
var playerMask = new pufferMask();
game.addChild(playerMask);
// Initialize game variables
game.growingBubble = null;
game.maxBubbleSize = 160; // Increased by 30%
game.growthRate = 1.6; // Slightly increased to match new max size
game.MAX_BUBBLES = 125;
game.baseSpawnRate = 180; // Every 3 seconds
function spawnSplitBubble(parentX, parentY, size, direction) {
var isAutoPop = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var bubble = new Bubble();
bubble.x = parentX;
bubble.y = parentY;
bubble.size = size;
bubble.initLifetime(); // Recalculate after size is set
bubble.justSplit = true;
var speedMultiplier = 120 / size * (0.9 + Math.random() * 0.2);
if (isAutoPop) {
// Pure upward/sideways motion for natural splits
bubble.verticalVelocity = bubble.floatSpeed; // Start at float speed
bubble.driftX = direction * (Math.random() * 0.8 + 0.5);
} else {
// Manual pop physics
bubble.verticalVelocity = -(Math.random() * 2 + 4);
bubble.driftX = direction * (Math.random() * 1.5 + 2);
}
game.addChild(bubble);
game.bubbles.push(bubble);
return bubble;
}
game.bubbles = []; // Track bubble array
// Initialize game variables
//<Assets used in the game will automatically appear here>
game.bp = 0; // Track total BP
game.bp = 0; // Initialize BP to 0
game.lastBPDisplay = 0; // Initialize last BP display to 0
game.combo = 0;
game.lastPopTime = 0;
game.COMBO_WINDOW = 60; // 1 second in frames
function formatBP(value) {
var units = ['', 'K', 'M', 'B', 'T'];
var unitIndex = 0;
while (value >= 1000 && unitIndex < units.length - 1) {
value /= 1000;
unitIndex++;
}
return Math.floor(value * 10) / 10 + units[unitIndex];
}
// Create BP display text (add near game initialization)
var bpText = new Text2("0 BP", {
size: 96,
// Increased from 64
fill: 0xFFFFFF,
fontWeight: 'bold'
});
bpText.anchorX = 1;
bpText.anchorY = 0;
bpText.x = game.width - 40; // Increased margin from 20 to 40
bpText.y = 40; // Increased from 20 to 40
game.addChild(bpText);
game.addBP = function (points, x, y) {
game.bp += Math.floor(points);
var currentBP = Math.floor(game.bp);
if (currentBP !== game.lastBPDisplay) {
game.lastBPDisplay = currentBP;
bpText.text = formatBP(currentBP) + " BP";
}
// Only create pop-up text if coordinates are provided
if (x !== undefined && y !== undefined) {
var pointText = new Text2("+" + Math.floor(points), {
size: 72,
fill: 0xFFFF00,
fontWeight: 'bold'
});
pointText.anchorX = 0.5;
pointText.anchorY = 0.5;
pointText.x = x;
pointText.y = y;
game.addChild(pointText);
tween(pointText, {
y: pointText.y - 150,
alpha: 0
}, {
duration: 90,
// Increased from 45 to 90 (1.5 seconds)
onFinish: function onFinish() {
pointText.destroy();
}
});
}
};
game.update = function () {
// Add logic to grow and release bubbles based on facekit mouth state
if (facekit.mouthOpen) {
// Calculate spawn position relative to pufferfish mask
var spawnX = playerMask.x; // Center of mask
var spawnY = playerMask.y + playerMask.height * 0.15; // 40% from bottom
// Start or continue growing bubble
if (!game.growingBubble) {
game.growingBubble = new Bubble();
game.growingBubble.size = 25; // Keep minimum starting size
game.addChild(game.growingBubble);
game.bubbles.push(game.growingBubble);
}
// Update growing bubble position and size
if (game.growingBubble) {
game.growingBubble.x = spawnX;
game.growingBubble.y = spawnY;
game.growingBubble.size = Math.min(game.growingBubble.size + game.growthRate, game.maxBubbleSize);
game.growingBubble.verticalVelocity = 0;
game.growingBubble.driftX = 0;
}
} else if (game.growingBubble) {
// Stronger initial downward velocity when released
game.growingBubble.verticalVelocity = -12; // Increased from -5
game.growingBubble.driftX = (Math.random() * 2 - 1) * 2.5; // Slightly increased drift
game.growingBubble = null;
}
// Update all children (bubbles)
// Only spawn if under max bubbles
if (game.bubbles.length < game.MAX_BUBBLES) {
if (LK.ticks % game.baseSpawnRate == 0) {
var x = Math.random() * (game.width - 200) + 100;
spawnSplitBubble(x, game.height + 100, 100, 0);
}
}
// Clean up destroyed bubbles from array
game.bubbles = game.bubbles.filter(function (bubble) {
return !bubble.destroyed;
});
// Update all children (bubbles)
for (var i = game.bubbles.length - 1; i >= 0; i--) {
var bubble = game.bubbles[i];
if (bubble.update) {
bubble.update();
}
}
};
// Handle touch/mouse events for the game
game.down = function (x, y, obj) {
var popped = false; // Track if we've popped any bubble
for (var i = game.bubbles.length - 1; i >= 0; i--) {
var bubble = game.bubbles[i];
// Calculate distance between click and bubble center
var dx = x - bubble.x;
var dy = y - bubble.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only pop if we haven't popped anything this click
if (!popped && distance <= bubble.size / 2 + 10 && bubble.down) {
bubble.down();
popped = true;
break; // Exit loop after first pop
}
}
};
;
A treasure chest with gold coins. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A golden skull with diamonds for eyes. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A golden necklace with a ruby pendant. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A filled in white circle.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A yellow star. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a game logo for a game called 'Bubble Blower Tycoon' about a happy purple pufferfish with yellow fins and spines that builds an underwater empire of bubbles. Cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
an SVG of the word 'Start'. word should be yellow and the font should look like its made out of bubbles. cartoon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
bubblelow
Sound effect
backgroundmusic
Music
bubblehigh
Sound effect
bubble1
Sound effect
bubble2
Sound effect
bubble3
Sound effect
bubble4
Sound effect
blowing
Sound effect
bubbleshoot
Sound effect
fishtank
Sound effect
menuopen
Sound effect
upgrade
Sound effect
jellyfish
Sound effect
titlemusic
Music
startbutton
Sound effect