User prompt
update spawnsplitbubble with: var bubble = new Bubble(); bubble.x = parentX; bubble.y = parentY; bubble.size = size; bubble.initLifetime(); // Recalculate after size is set
User prompt
update with: var self = Container.call(this); self.lifetime = 0; self.size = 100; // ... other init code ... self.initLifetime = function() { self.maxLifetime = Math.floor(Math.random() * 120 + 180); self.maxLifetime *= Math.min(1, self.size / 100); }; self.initLifetime();
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: split is undefined' in or related to this line: 'split.maxLifetime *= 0.7; // Shorter lifetime for split bubbles' Line Number: 81
Code edit (1 edits merged)
Please save this source code
User prompt
update with: self.update = function() { self.lifetime++; // 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; } // Rest of update code... }
User prompt
update with: self.maxLifetime = Math.floor(Math.random() * 120 + 180); // 3-5 seconds base lifetime self.maxLifetime *= Math.min(1, self.size / 100); // Keep size scaling
User prompt
update with: if (self.lifetime > self.maxLifetime) { // Check for splitting first if (self.size > 60 && !self.hasSplit && !self.justSplit) { 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; } self.destroy(); return; } // Auto-pop as fallback self.autoPop(); return; }
Code edit (5 edits merged)
Please save this source code
User prompt
update with: // At Bubble initialization, add random lifetime max self.lifetime = 0; self.maxLifetime = Math.floor(Math.random() * 300 + 300); // 5-10 seconds base lifetime // Scale down lifetime for smaller bubbles self.maxLifetime *= Math.min(1, self.size / 100); // In the update method, replace the height-based checks with lifetime check self.update = function() { self.lifetime++; // Only allow splits after bubble has existed for some frames if (self.lifetime < 10) { return; } // Auto-pop when lifetime exceeded if (self.lifetime > self.maxLifetime) { // Keep guaranteed BP for small bubbles near top if (self.size <= 70 && self.y < game.height / 4) { self.autoPop(); return; } // For larger bubbles, split if moving naturally if (self.size > 60 && !self.justSplit && !self.hasSplit && self !== game.growingBubble && self.verticalVelocity >= 0 && self.verticalVelocity <= self.floatSpeed * 1.2) { 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); // Give split bubbles shorter lifetimes split.maxLifetime *= 0.7; } self.destroy(); return; } } // Rest of update code... };
Code edit (1 edits merged)
Please save this source code
User prompt
update with: if (isAutoPop) { // Pure upward/sideways motion for natural splits bubble.verticalVelocity = bubble.floatSpeed; // Start at float speed bubble.driftX = direction * (Math.random() * 2 + 1.5); } else { // Manual pop physics bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 1.5 + 2); }
User prompt
update with: // In Bubble's update method if (self.size > 60 && self.y < self.splitHeight && !self.justSplit && !self.hasSplit && self !== game.growingBubble) { self.hasSplit = true; // Check if bubble is naturally floating (using floatSpeed) if (self.verticalVelocity >= 0 && self.verticalVelocity <= self.floatSpeed * 1.2) { // Added tolerance 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, true); } } self.destroy(); return; }
User prompt
update with: // In Bubble's update method, replace the splitting logic with: if (self.size > 60 && self.y < self.splitHeight && !self.justSplit && !self.hasSplit && self !== game.growingBubble) { self.hasSplit = true; // Only split if bubble is moving upward to prevent infinite loops if (self.verticalVelocity < 0) { 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, true); // true for isAutoPop } } self.destroy(); return; }
User prompt
update with: if (isAutoPop) { // Pure upward/sideways motion for auto-splits bubble.verticalVelocity = -(Math.random() * 2 + 2); // Only upward velocity bubble.driftX = direction * (Math.random() * 2 + 1.5); // Gentle horizontal spread bubble.floatSpeed = (60 + Math.random() * 15) / 60 * speedMultiplier; // Slightly faster float } else { // Keep manual pop physics as is bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 1.5 + 2); bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier; }
User prompt
update with: if (isAutoPop) { // Gentle upward/sideways motion for auto-splits bubble.verticalVelocity = -(Math.random() * 1.5 + 2); // Gentler upward bubble.driftX = direction * (Math.random() * 2 + 1); // Gentler spread bubble.floatSpeed = (55 + Math.random() * 15) / 60 * speedMultiplier; } else { // Manual pop physics - back to original values bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 1.5 + 2); // Reduced from previous adjustment bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier; }
User prompt
update with: if (self.size > 60 && self.y < self.splitHeight && !self.justSplit && !self.hasSplit && self !== game.growingBubble) { self.hasSplit = true; 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, true); // Added true for isAutoPop } self.destroy(); return; }
User prompt
update with: // Inside self.update function // 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 // More gradual vertical speed transition if (self.verticalVelocity < self.floatSpeed) { self.verticalVelocity += 0.08; // More gentle transition }
User prompt
update with: // Adjust bubble physics if (isAutoPop) { // More natural upward/sideways motion for auto-splits bubble.verticalVelocity = -(Math.random() * 2 + 3); // More upward drift bubble.driftX = direction * (Math.random() * 2.5 + 1.5); // More horizontal spread // Add slight random vertical variance bubble.floatSpeed = (55 + Math.random() * 15) / 60 * speedMultiplier; } else { // Manual pop physics - more dramatic split bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 2 + 4); bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier; }
Code edit (3 edits merged)
Please save this source code
User prompt
update with: if (self.size > 60 && self.y < game.height * 0.4 && // Top 40% of screen !self.justSplit && !self.hasSplit && self !== game.growingBubble) { // Correct comparison syntax
User prompt
update with: // Auto-split large bubbles when they float high enough (but not while growing) if (self.size > 60 && self.y < game.height * 0.4 && // This means 60% from the bottom or higher !self.justSplit && !self.hasSplit && !game.growingBubble === self) { // Don't split if this is the currently growing bubble self.hasSplit = true; 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, true); } self.destroy(); return; }
User prompt
update with: if (self.size > 60 && self.y < game.height * (Math.random() * 0.2 + 0.5) && !self.justSplit && !self.hasSplit) { self.hasSplit = true; 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, true); // true for autoPop } self.destroy(); return; }
User prompt
update with: function spawnSplitBubble(parentX, parentY, size, direction, isAutoPop = false) { var bubble = new Bubble(); bubble.x = parentX; bubble.y = parentY; bubble.size = size; bubble.justSplit = true; var speedMultiplier = 120 / size * (0.9 + Math.random() * 0.2); if (isAutoPop) { // Gentler split for auto-pops - mostly upward/sideways motion bubble.verticalVelocity = -(Math.random() * 1 + 2); // Reduced downward velocity bubble.driftX = direction * (Math.random() * 1.5 + 2); // Gentler spread } else { // Keep existing dramatic split for manual pops bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 2 + 4); } bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier; game.addChild(bubble); game.bubbles.push(bubble); }
User prompt
update with: // Replace the fixed height check with a randomized range if (self.size > 60 && self.y < game.height * (Math.random() * 0.2 + 0.5) && !self.justSplit && !self.hasSplit) { self.hasSplit = true; // Prevent future splits 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; }
/**** * 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.lifetime = 0; 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; // 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); // 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++; // Only allow splits after bubble has existed for some frames if (self.lifetime < 10) { return; } // Replace the fixed height check with a randomized range // Calculate random split height once when bubble is created if (!self.splitHeight) { self.splitHeight = game.height * (Math.random() * 0.3 + 0.1); // 10% to 40% from top } if (self.size > 60 && self.y < self.splitHeight && !self.justSplit && !self.hasSplit && self !== game.growingBubble) { // Correct comparison syntax // Don't split if this is the currently growing bubble self.hasSplit = true; 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, true); } self.destroy(); return; } // Auto-pop smaller bubbles near top if (self.size <= 70 && self.y < game.height / 4) { self.autoPop(); return; } // Clear off-screen bubbles if (self.y < -self.size) { self.destroy(); return; } self.justSplit = false; // Clear the flag after first update // Add gravity effect if (self.verticalVelocity < self.floatSpeed) { self.verticalVelocity += 0.1; // Gradually transition to floating } 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 game.addBP(points); self.destroy(); // Just destroy, no splitting 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.justSplit = true; var speedMultiplier = 120 / size * (0.9 + Math.random() * 0.2); if (isAutoPop) { // More natural upward/sideways motion for auto-splits bubble.verticalVelocity = -(Math.random() * 2 + 3); // More upward drift bubble.driftX = direction * (Math.random() * 2.5 + 1.5); // More horizontal spread // Add slight random vertical variance bubble.floatSpeed = (55 + Math.random() * 15) / 60 * speedMultiplier; } else { // Manual pop physics - more dramatic split bubble.verticalVelocity = -(Math.random() * 2 + 4); bubble.driftX = direction * (Math.random() * 2 + 4); bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier; } game.addChild(bubble); game.bubbles.push(bubble); } game.bubbles = []; // Track bubble array // Initialize game variables //<Assets used in the game will automatically appear here> game.bp = 0; // Track total BP 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("BP: 0", { size: 100, fill: 0xFFFFFF }); bpText.anchor.set(0.5, 0); bpText.x = game.width - 200; // Position in top right bpText.y = 100; game.addChild(bpText); game.addBP = function (points) { game.bp += points; // Update display bpText.text = formatBP(game.bp) + " BP"; }; 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 } } }; ;
===================================================================
--- original.js
+++ change.js
@@ -12,8 +12,9 @@
var self = Container.call(this);
self.lifetime = 0;
self.hasSplit = false;
self.lifetime = 0;
+ self.splitHeight = null;
self.AUTO_POP_SIZE = 40;
self.MIN_SPLIT_SIZE = 30;
self.lastPopTime = 0; // Add timestamp tracking
var sprite = self.attachAsset('bubble', {
@@ -58,11 +59,13 @@
if (self.lifetime < 10) {
return;
}
// Replace the fixed height check with a randomized range
- if (self.size > 60 && self.y < game.height * (Math.random() * 0.4 + 0.2) &&
- // Top 40% of screen
- !self.justSplit && !self.hasSplit && self !== game.growingBubble) {
+ // Calculate random split height once when bubble is created
+ if (!self.splitHeight) {
+ self.splitHeight = game.height * (Math.random() * 0.3 + 0.1); // 10% to 40% from top
+ }
+ if (self.size > 60 && self.y < self.splitHeight && !self.justSplit && !self.hasSplit && self !== game.growingBubble) {
// Correct comparison syntax
// Don't split if this is the currently growing bubble
self.hasSplit = true;
var newSize = Math.max(self.MIN_SPLIT_SIZE, self.size * 0.6);
@@ -219,17 +222,19 @@
bubble.size = size;
bubble.justSplit = true;
var speedMultiplier = 120 / size * (0.9 + Math.random() * 0.2);
if (isAutoPop) {
- // Gentler split for auto-pops - mostly upward/sideways motion
- bubble.verticalVelocity = -(Math.random() * 1 + 2); // Reduced downward velocity
- bubble.driftX = direction * (Math.random() * 1.5 + 2); // Gentler spread
+ // More natural upward/sideways motion for auto-splits
+ bubble.verticalVelocity = -(Math.random() * 2 + 3); // More upward drift
+ bubble.driftX = direction * (Math.random() * 2.5 + 1.5); // More horizontal spread
+ // Add slight random vertical variance
+ bubble.floatSpeed = (55 + Math.random() * 15) / 60 * speedMultiplier;
} else {
- // Keep existing dramatic split for manual pops
+ // Manual pop physics - more dramatic split
bubble.verticalVelocity = -(Math.random() * 2 + 4);
bubble.driftX = direction * (Math.random() * 2 + 4);
+ bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier;
}
- bubble.floatSpeed = (45 + Math.random() * 10) / 60 * speedMultiplier;
game.addChild(bubble);
game.bubbles.push(bubble);
}
game.bubbles = []; // Track bubble array
A white bubble with a black outline 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
A outstretched straight octopus tentacle. Green with purple suckers. Cartoon.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A colorful underwater coral reef background. Cartoon Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A white bubble with a black outline. Pixel art.. 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