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; }
User prompt
update with: // Add near other game initialization variables game.mouthState = { isOpen: false, openWidth: 0, currentWidth: 0, blowCooldown: 0, COOLDOWN_FRAMES: 15 // Prevent rapid-fire blowing }; // Replace the mouth detection section in game.update if (facekit.mouthCenter && facekit.mouthLeft && facekit.mouthRight) { var currentWidth = Math.abs(facekit.mouthLeft.x - facekit.mouthRight.x); var currentHeight = Math.abs(facekit.mouthTop.y - facekit.mouthBottom.y); var mouthRatio = currentHeight / currentWidth; // Track mouth states if (mouthRatio > 0.7) { // Mouth is clearly open game.mouthState.isOpen = true; game.mouthState.openWidth = currentWidth; } else if (game.mouthState.isOpen && mouthRatio < 0.3 && game.mouthState.blowCooldown === 0) { // Transition from open to nearly closed - this is a "blow" game.mouthState.isOpen = false; game.mouthState.blowCooldown = game.mouthState.COOLDOWN_FRAMES; // Create and launch bubble var bubble = new Bubble(); bubble.x = playerMask.x; bubble.y = playerMask.y + playerMask.height * 0.15; bubble.size = Math.min(100, game.mouthState.openWidth); bubble.verticalVelocity = -8; bubble.driftX = (Math.random() * 2 - 1) * 2; game.addChild(bubble); game.bubbles.push(bubble); } // Update cooldown if (game.mouthState.blowCooldown > 0) { game.mouthState.blowCooldown--; } } ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
update as needed with: // Add near other game initialization variables game.mouthState = { isOpen: false, openWidth: 0, currentWidth: 0, blowCooldown: 0, COOLDOWN_FRAMES: 15 // Prevent rapid-fire blowing }; // Replace the mouth detection section in game.update if (facekit.mouthCenter && facekit.mouthLeft && facekit.mouthRight) { var currentWidth = Math.abs(facekit.mouthLeft.x - facekit.mouthRight.x); var currentHeight = Math.abs(facekit.mouthTop.y - facekit.mouthBottom.y); var mouthRatio = currentHeight / currentWidth; // Track mouth states if (mouthRatio > 0.7) { // Mouth is clearly open game.mouthState.isOpen = true; game.mouthState.openWidth = currentWidth; } else if (game.mouthState.isOpen && mouthRatio < 0.3 && game.mouthState.blowCooldown === 0) { // Transition from open to nearly closed - this is a "blow" game.mouthState.isOpen = false; game.mouthState.blowCooldown = game.mouthState.COOLDOWN_FRAMES; // Create and launch bubble var bubble = new Bubble(); bubble.x = playerMask.x; bubble.y = playerMask.y + playerMask.height * 0.15; bubble.size = Math.min(100, game.mouthState.openWidth); bubble.verticalVelocity = -8; bubble.driftX = (Math.random() * 2 - 1) * 2; game.addChild(bubble); game.bubbles.push(bubble); } // Update cooldown if (game.mouthState.blowCooldown > 0) { game.mouthState.blowCooldown--; } } ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
update as needed with: // Add near other game initialization variables game.mouthTracking = { lastY: 0, centerY: 0, blowThreshold: 5, cooldown: 0, COOLDOWN_FRAMES: 10 }; // Replace the mouth detection section in game.update if (facekit.mouthCenter) { // Track mouth center movement game.mouthTracking.lastY = game.mouthTracking.centerY; game.mouthTracking.centerY = facekit.mouthCenter.y; // Calculate mouth movement (positive = mouth closing) var mouthMovement = game.mouthTracking.centerY - game.mouthTracking.lastY; // Detect quick closing motion (blowing gesture) if (mouthMovement > game.mouthTracking.blowThreshold && game.mouthTracking.cooldown === 0) { // Create bubble var bubble = new Bubble(); bubble.x = facekit.mouthCenter.x; bubble.y = facekit.mouthCenter.y; bubble.size = 50 + Math.min(50, Math.abs(mouthMovement * 2)); bubble.verticalVelocity = -Math.max(5, Math.abs(mouthMovement)); bubble.driftX = (Math.random() * 2 - 1) * 2; game.addChild(bubble); game.bubbles.push(bubble); // Set cooldown game.mouthTracking.cooldown = game.mouthTracking.COOLDOWN_FRAMES; } // Update cooldown if (game.mouthTracking.cooldown > 0) { game.mouthTracking.cooldown--; } } ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
Code edit (1 edits merged)
Please save this source code
User prompt
add: // Upgrade Menu Configuration var UPGRADE_CONFIG = { player: { lungCapacity: { name: "Lung Capacity", baseCost: 50, costScale: 2, maxLevel: 10, currentLevel: 0 }, quickBreath: { name: "Quick Breath", baseCost: 75, costScale: 2, maxLevel: 10, currentLevel: 0 } }, machine: { bubbleDurability: { name: "Bubble Durability", baseCost: 200, costScale: 3, maxLevel: 5, currentLevel: 0 }, machineSpeed: { name: "Machine Speed", baseCost: 150, costScale: 2.5, maxLevel: 10, currentLevel: 0 }, autoPop: { name: "Auto-Pop", baseCost: 500, costScale: 2, maxLevel: 5, currentLevel: 0 } } }; // Create upgrade menu elements var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.9 }); var menuText = new Text2("Upgrades", { size: 48, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); menuText.anchorX = 0.5; menuText.anchorY = 0.5; menuText.x = menuTab.width / 2; menuText.y = menuTab.height / 2; menuTab.addChild(menuText); var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.5, scaleX: game.width / 67, // Adjust to game width scaleY: game.height * 0.25 / 100.3 // 25% of game height }); // Add upgrade text elements var upgradeTexts = []; var startY = 50; var columnWidth = game.width / 2; Object.entries(UPGRADE_CONFIG).forEach(([category, upgrades], categoryIndex) => { Object.entries(upgrades).forEach(([key, upgrade], index) => { var x = categoryIndex * columnWidth + 20; var y = startY + index * 60; var text = new Text2(upgrade.name + " - " + upgrade.baseCost + " BP", { size: 36, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); text.x = x; text.y = y; text.upgrade = key; upgradeTexts.push(text); menuPanel.addChild(text); }); }); // Menu state and animation var menuOpen = false; var menuTargetY = game.height; game.addChild(menuPanel); game.addChild(menuTab); // Extend the existing down handler var originalDown = game.down; game.down = function(x, y, obj) { // Check if clicked on menu tab var tabBounds = { x: menuTab.x - menuTab.width / 2, y: menuTab.y - menuTab.height, width: menuTab.width, height: menuTab.height }; if (x >= tabBounds.x && x <= tabBounds.x + tabBounds.width && y >= tabBounds.y && y <= tabBounds.y + tabBounds.height) { menuOpen = !menuOpen; var targetY = menuOpen ? game.height - menuPanel.height : game.height; tween(menuPanel, { y: targetY }, { duration: 300 }); tween(menuTab, { y: targetY }, { duration: 300 }); return true; } // Close menu if clicking outside when open if (menuOpen) { var menuBounds = { x: menuPanel.x - menuPanel.width / 2, y: menuPanel.y - menuPanel.height, width: menuPanel.width, height: menuPanel.height }; if (!(x >= menuBounds.x && x <= menuBounds.x + menuBounds.width && y >= menuBounds.y && y <= menuBounds.y + menuBounds.height)) { menuOpen = false; tween(menuPanel, { y: game.height }, { duration: 300 }); tween(menuTab, { y: game.height }, { duration: 300 }); return true; } } // Call original down handler for bubble popping return originalDown(x, y, obj); };
User prompt
update with: // Upgrade Menu Configuration var UPGRADE_CONFIG = { player: { lungCapacity: { name: "Lung Capacity", baseCost: 50, costScale: 2, maxLevel: 10, currentLevel: 0 }, quickBreath: { name: "Quick Breath", baseCost: 75, costScale: 2, maxLevel: 10, currentLevel: 0 } }, machine: { bubbleDurability: { name: "Bubble Durability", baseCost: 200, costScale: 3, maxLevel: 5, currentLevel: 0 }, machineSpeed: { name: "Machine Speed", baseCost: 150, costScale: 2.5, maxLevel: 10, currentLevel: 0 }, autoPop: { name: "Auto-Pop", baseCost: 500, costScale: 2, maxLevel: 5, currentLevel: 0 } } }; // Create upgrade menu elements var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.9 }); var menuText = new Text2("Upgrades", { size: 48, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); menuText.anchorX = 0.5; menuText.anchorY = 0.5; menuText.x = menuTab.width / 2; menuText.y = menuTab.height / 2; menuTab.addChild(menuText); var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.5, scaleX: game.width / 67, // Adjust to game width scaleY: game.height * 0.25 / 100.3 // 25% of game height }); // Add upgrade text elements var upgradeTexts = []; var startY = 50; var columnWidth = game.width / 2; Object.entries(UPGRADE_CONFIG).forEach(([category, upgrades], categoryIndex) => { Object.entries(upgrades).forEach(([key, upgrade], index) => { var x = categoryIndex * columnWidth + 20; var y = startY + index * 60; var text = new Text2(upgrade.name + " - " + upgrade.baseCost + " BP", { size: 36, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); text.x = x; text.y = y; text.upgrade = key; upgradeTexts.push(text); menuPanel.addChild(text); }); }); // Menu state and animation var menuOpen = false; var menuTargetY = game.height; game.addChild(menuPanel); game.addChild(menuTab);
User prompt
initialize the background before the menu
User prompt
update as needed with: // Upgrade Menu Configuration var UPGRADE_CONFIG = { player: { lungCapacity: { name: "Lung Capacity", baseCost: 50, costScale: 2, maxLevel: 10, currentLevel: 0 }, quickBreath: { name: "Quick Breath", baseCost: 75, costScale: 2, maxLevel: 10, currentLevel: 0 } }, machine: { bubbleDurability: { name: "Bubble Durability", baseCost: 200, costScale: 3, maxLevel: 5, currentLevel: 0 }, machineSpeed: { name: "Machine Speed", baseCost: 150, costScale: 2.5, maxLevel: 10, currentLevel: 0 }, autoPop: { name: "Auto-Pop", baseCost: 500, costScale: 2, maxLevel: 5, currentLevel: 0 } } }; // Create upgrade menu elements var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.9 }); var menuText = new Text2("Upgrades", { size: 48, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); menuText.anchorX = 0.5; menuText.anchorY = 0.5; menuText.x = menuTab.width / 2; menuText.y = menuTab.height / 2; menuTab.addChild(menuText); var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.5, scaleX: game.width / 67, // Adjust to game width scaleY: game.height * 0.25 / 100.3 // 25% of game height }); // Add upgrade text elements var upgradeTexts = []; var startY = 50; var columnWidth = game.width / 2; Object.entries(UPGRADE_CONFIG).forEach(([category, upgrades], categoryIndex) => { Object.entries(upgrades).forEach(([key, upgrade], index) => { var x = categoryIndex * columnWidth + 20; var y = startY + index * 60; var text = new Text2(upgrade.name + " - " + upgrade.baseCost + " BP", { size: 36, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); text.x = x; text.y = y; text.upgrade = key; upgradeTexts.push(text); menuPanel.addChild(text); }); }); // Menu state and animation var menuOpen = false; var menuTargetY = game.height; game.addChild(menuPanel); game.addChild(menuTab); // Extend the existing down handler var originalDown = game.down; game.down = function(x, y, obj) { // Check if clicked on menu tab var tabBounds = { x: menuTab.x - menuTab.width / 2, y: menuTab.y - menuTab.height, width: menuTab.width, height: menuTab.height }; if (x >= tabBounds.x && x <= tabBounds.x + tabBounds.width && y >= tabBounds.y && y <= tabBounds.y + tabBounds.height) { menuOpen = !menuOpen; var targetY = menuOpen ? game.height - menuPanel.height : game.height; tween(menuPanel, { y: targetY }, { duration: 300 }); tween(menuTab, { y: targetY }, { duration: 300 }); return true; } // Close menu if clicking outside when open if (menuOpen) { var menuBounds = { x: menuPanel.x - menuPanel.width / 2, y: menuPanel.y - menuPanel.height, width: menuPanel.width, height: menuPanel.height }; if (!(x >= menuBounds.x && x <= menuBounds.x + menuBounds.width && y >= menuBounds.y && y <= menuBounds.y + menuBounds.height)) { menuOpen = false; tween(menuPanel, { y: game.height }, { duration: 300 }); tween(menuTab, { y: game.height }, { duration: 300 }); return true; } } // Call original down handler for bubble popping return originalDown(x, y, obj); };
User prompt
update as needed with: // Modify the menuTab initialization var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.9, rotation: 90, // Rotate to horizontal orientation scaleX: 2, // Make it wider scaleY: 1.5 // Make it taller }); var menuText = new Text2("Upgrades", { size: 72, // Bigger text fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); menuText.anchorX = 0.5; menuText.anchorY = 0.5; menuText.x = menuTab.width / 2; menuText.y = menuTab.height / 2; menuText.rotation = -90; // Counter-rotate text to keep it readable menuTab.addChild(menuText); // Modify the menuPanel initialization var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height + game.height * 0.25, // Start offscreen alpha: 0.5, scaleX: game.width / 67, // Adjust to game width scaleY: game.height * 0.25 / 100.3 // 25% of game height }); // Modify the menu animation in the down handler game.down = function(x, y, obj) { var tabBounds = { x: menuTab.x - menuTab.width / 2, y: menuTab.y - menuTab.height, width: menuTab.width, height: menuTab.height }; if (x >= tabBounds.x && x <= tabBounds.x + tabBounds.width && y >= tabBounds.y && y <= tabBounds.y + tabBounds.height) { menuOpen = !menuOpen; // Panel moves up from offscreen var panelTargetY = menuOpen ? game.height - menuPanel.height : game.height + menuPanel.height; // Tab stays attached to panel var tabTargetY = menuOpen ? game.height - menuPanel.height : game.height; tween(menuPanel, { y: panelTargetY }, { duration: 300 }); tween(menuTab, { y: tabTargetY }, { duration: 300 }); return true; } // Close menu if clicking outside when open if (menuOpen) { var menuBounds = { x: menuPanel.x - menuPanel.width / 2, y: menuPanel.y - menuPanel.height, width: menuPanel.width, height: menuPanel.height }; if (!(x >= menuBounds.x && x <= menuBounds.x + menuBounds.width && y >= menuBounds.y && y <= menuBounds.y + menuBounds.height)) { menuOpen = false; tween(menuPanel, { y: game.height + menuPanel.height }, { duration: 300 }); tween(menuTab, { y: game.height }, { duration: 300 }); return true; } } // Call original down handler for bubble popping return originalDown(x, y, obj); };
User prompt
update with: // Create upgrade menu elements - adjust the tab to be wider than tall var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, x: game.width / 2, y: game.height, alpha: 0.9, scaleX: 3, // Make it wider scaleY: 0.8 // Make it shorter }); // Make the text bigger and position it on the tab var menuText = new Text2("Upgrades", { size: 72, // Bigger text fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); menuText.anchorX = 0.5; menuText.anchorY = 0.5; menuText.x = menuTab.width / 2; menuText.y = menuTab.height / 2; menuTab.addChild(menuText);
User prompt
update with: // Create a container for our menu elements var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Create the tab with default scale var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Position text relative to tab var menuText = new Text2("Upgrades", { size: 72, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); // Center the text menuText.anchor = {x: 0.5, y: 0.5}; menuText.x = 0; // Center in container menuText.y = -menuTab.height / 2; // Half way up the tab // Add elements to container menuContainer.addChild(menuTab); menuContainer.addChild(menuText); // Add container to game game.addChild(menuContainer);
Code edit (1 edits merged)
Please save this source code
User prompt
center the menutab behind the menutext
Code edit (2 edits merged)
Please save this source code
User prompt
move the menupanel to the menucontainer
User prompt
update with: var tabBounds = { x: menuContainer.x + menuTab.x - menuTab.width / 2, y: menuContainer.y + menuTab.y - menuTab.height, width: menuTab.width, height: menuTab.height };
User prompt
update game.down with: // Calculate how far to move up (panel height + small gap) var panelOffset = menuOpen ? -menuPanel.height - 20 : 0; // Animate the container instead of individual elements tween(menuContainer, { y: game.height + panelOffset }, { duration: 300, easing: 'easeOutBack' // Optional: adds a nice bounce effect }); ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
update as needed with: // Initialize menu structure var menuContainer = new Container(); menuContainer.x = game.width / 2; menuContainer.y = game.height; // Menu tab (handle) var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Menu panel (the full upgrade menu) var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Changed to align from top y: menuTab.height, // Position below tab alpha: 0.5, scaleX: game.width / 67, scaleY: game.height * 0.25 / 100.3 }); // Add panel first (so it's behind tab) menuContainer.addChild(menuPanel); menuContainer.addChild(menuTab); // Menu text setup var menuText = new Text2("Upgrades", { size: 90, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 3, font: "Impact" }); menuText.anchor = {x: 0.5, y: 0.5}; menuText.x = 0; // Relative to container menuText.y = -menuTab.height / 2; menuContainer.addChild(menuText); // Add upgrade texts to panel var upgradeTexts = []; var startY = 50; var columnWidth = game.width / 2; Object.entries(UPGRADE_CONFIG).forEach(function(category, categoryIndex) { Object.entries(category[1]).forEach(function(upgrade, index) { var text = new Text2(upgrade[1].name + " - " + upgrade[1].baseCost + " BP", { size: 36, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 2, font: "Impact" }); text.x = (categoryIndex * columnWidth + 20) - menuPanel.width/2; text.y = startY + index * 60; text.upgrade = upgrade[0]; upgradeTexts.push(text); menuPanel.addChild(text); }); }); // Add to game game.addChild(menuContainer); // Modified click handler game.down = function(x, y, obj) { // Convert to local coordinates for container var localX = x - menuContainer.x; var localY = y - menuContainer.y; // Check if clicked on menu tab var tabBounds = { x: -menuTab.width/2, y: -menuTab.height, width: menuTab.width, height: menuTab.height }; if (localX >= tabBounds.x && localX <= tabBounds.x + tabBounds.width && localY >= tabBounds.y && localY <= tabBounds.y + tabBounds.height) { menuOpen = !menuOpen; var targetY = menuOpen ? game.height - menuPanel.height - menuTab.height : game.height; tween(menuContainer, { y: targetY }, { duration: 300, easing: 'easeOutBack' }); return true; } // Check for clicks outside when menu is open if (menuOpen) { var menuBounds = { x: -menuPanel.width/2, y: -menuPanel.height, width: menuPanel.width, height: menuPanel.height + menuTab.height }; if (!(localX >= menuBounds.x && localX <= menuBounds.x + menuBounds.width && localY >= menuBounds.y && localY <= menuBounds.y + menuBounds.height)) { menuOpen = false; tween(menuContainer, { y: game.height }, { duration: 300, easing: 'easeInBack' }); return true; } } // Rest of click handling for bubbles... }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: easing is not a function' in or related to this line: 'return true;' Line Number: 593 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (3 edits merged)
Please save this source code
User prompt
update with: // First position the panel relative to container at y=0 var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Anchor from top y: 0, // Start at container's top alpha: 0.9, scaleX: game.width / 67, scaleY: game.height * 0.25 / 100.3 }); // Place tab at top of container, extending upward var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 1, // Anchor from bottom so it sticks up y: 0, // Align with top of container/panel scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Position container so only tab is visible menuContainer.x = game.width / 2; menuContainer.y = game.height - menuTab.height; // Show just the tab // When animating: var targetY = menuOpen ? game.height - menuPanel.height - menuTab.height : // Raise up to show panel game.height - menuTab.height; // Back down to just show tab
User prompt
Please fix the bug: 'menuTab is undefined' in or related to this line: 'menuContainer.y = game.height - menuTab.height; // Show just the tab' Line Number: 341
User prompt
update with: // Menu panel should be below the tab in the container var menuPanel = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Anchor from top y: menuTab.height, // Position it below the tab alpha: 0.9, scaleX: game.width / 67, scaleY: game.height * 0.25 / 100.3 }); // Tab at the top of the container var menuTab = LK.getAsset('upgradetab', { anchorX: 0.5, anchorY: 0, // Anchor from top y: 0, // At the very top of container scaleX: 3, scaleY: 0.8, alpha: 0.9 }); // Position container at bottom initially menuContainer.x = game.width / 2; menuContainer.y = game.height; // Start at bottom // When clicking, animate between: var targetY = menuOpen ? game.height - menuPanel.height : // Raise to show panel game.height; // Back to bottom
User prompt
update with: menuContainer.x = game.width / 2; menuContainer.y = game.height - menuTab.height; // Show just the tab initially
/****
* 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,
alpha: 0.9
});
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) {
// Add cooldown check (100ms)
var currentTime = Date.now();
if (currentTime - self.lastPopTime < 100) {
return true; // Ignore clicks too close together
}
self.lastPopTime = currentTime;
var index = game.bubbles.indexOf(self);
if (index > -1) {
game.bubbles.splice(index, 1);
}
var points = self.getBP();
game.addBP(points, self.x, self.y, false); // Pass position and flag for manual pop
// Only split if manually popped and large enough
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; // Stop event propagation
};
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 () {
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;
};
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 background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: game.width / 2,
y: game.height / 2
});
game.addChild(background);
var playerMask = new pufferMask();
game.addChild(playerMask);
// Initialize game variables
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
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.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: 120,
fill: 0xFFFFFF,
stroke: 0x33caf8,
strokeThickness: 4,
font: "Impact",
fontWeight: "bold"
});
bpText.anchor.set(1, 0);
bpText.x = game.width - 20;
bpText.y = 20;
game.addChild(bpText);
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: 96,
fill: 0xFFFF00,
font: "Impact",
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: 1200,
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();
}
});
}
};
game.update = function () {
// Add logic to grow and release bubbles based on facekit mouth state
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;
}
// 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