User prompt
Add a more bolder font
User prompt
Make an asset for the respawn button
User prompt
When the player reaches 1000 points imploment a you win screen that has a button in the middle that respawns you back to the start
User prompt
Make a level 2 with more balls and then at the end imploment a you win screen
User prompt
Make the duplicated cannons shoot
User prompt
Everytime when the player gets a xp the player duplicates for help
User prompt
Make a background asset
User prompt
Add all bubble variabts in one area
User prompt
When game loads play music asset
User prompt
Make more bubbles
User prompt
Make it possible to crush 4 in one try with swipe
User prompt
We you tap the bubble disappears and you earn xp
Code edit (1 edits merged)
Please save this source code
User prompt
Bubble XP Shooter
Initial prompt
Bubble shooter where you shoot bubbles to earn XP
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bubble class for grid bubbles var Bubble = Container.expand(function () { var self = Container.call(this); // color: string, e.g. 'red' self.init = function (color) { self.color = color; self.assetId = 'bubble_' + color; self.asset = self.attachAsset(self.assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.asset.width / 2; self.gridRow = 0; self.gridCol = 0; self.popped = false; }; // Pop animation self.pop = function (_onFinish) { if (self.popped) return; self.popped = true; tween(self.asset, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); if (_onFinish) _onFinish(); } }); LK.getSound('pop').play(); }; return self; }); // Cannon class var Cannon = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('cannon', { anchorX: 0.5, anchorY: 0.5 }); self.angle = -Math.PI / 2; // Upwards // Set angle (radians) self.setAngle = function (a) { // Clamp angle between -80deg and -100deg (left/right) var minA = -Math.PI * 5 / 6; // -150deg var maxA = -Math.PI / 6; // -30deg if (a < minA) a = minA; if (a > maxA) a = maxA; self.angle = a; self.asset.rotation = a + Math.PI / 2; }; return self; }); // Shot bubble class var ShotBubble = Container.expand(function () { var self = Container.call(this); // color: string, angle: radians self.init = function (color, angle) { self.color = color; self.assetId = 'bubble_shot_' + color; self.asset = self.attachAsset(self.assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.asset.width / 2; self.angle = angle; self.speed = 38; // px per tick self.stuck = false; }; // Move bubble self.update = function () { if (self.stuck) return; self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; }; // Stick bubble (stop movement) self.stick = function () { self.stuck = true; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222a38 }); /**** * Game Code ****/ // Background image asset (full screen) // --- Other assets --- // --- Shot bubble asset variants --- // --- Bubble asset variants grouped for easy management --- // --- Game constants --- // Bubble colors // Cannon // Bubble shot // Sound // Music (optional, not played here) // LK.init.music('bgmusic', {volume: 1}); var GAME_W = 2048; var GAME_H = 2732; var GRID_COLS = 10; var GRID_ROWS = 12; var BUBBLE_SIZE = 120; // px var GRID_TOP = 200; var GRID_LEFT = (GAME_W - GRID_COLS * BUBBLE_SIZE) / 2; var GRID_BOTTOM = GRID_TOP + GRID_ROWS * BUBBLE_SIZE; var COLORS = ['red', 'blue', 'green', 'yellow', 'purple', 'orange']; var START_COLORS = 3; // Start with 3 colors, increase as XP increases var XP_PER_BUBBLE = 10; // --- Game state --- var grid = []; // 2D array [row][col] of Bubble or null var bubbles = []; // All Bubble instances var shotBubbles = []; // All ShotBubble instances var cannon; var nextBubbleColor = null; var canShoot = true; var score = 0; var xp = 0; var level = 1; var lastTouchX = 0, lastTouchY = 0; var dragAiming = false; var gameOver = false; // --- UI --- var scoreTxt = new Text2('XP: 0', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var levelTxt = new Text2('Level 1', { size: 60, fill: "#fff" }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); levelTxt.y = 110; // --- Helper functions --- // Get color for new bubble (based on current grid) function getRandomColor() { // Only use colors present in grid, or up to current level var available = {}; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { var b = grid[r][c]; if (b && !available[b.color]) available[b.color] = true; } } var colorList = []; for (var i = 0; i < START_COLORS + level - 1 && i < COLORS.length; i++) colorList.push(COLORS[i]); var gridColors = []; for (var k in available) gridColors.push(k); var useColors = gridColors.length ? gridColors : colorList; return useColors[Math.floor(Math.random() * useColors.length)]; } // Convert grid row,col to x,y function gridToXY(row, col) { var x = GRID_LEFT + col * BUBBLE_SIZE + BUBBLE_SIZE / 2; var y = GRID_TOP + row * BUBBLE_SIZE + BUBBLE_SIZE / 2; return { x: x, y: y }; } // Convert x,y to grid row,col function xyToGrid(x, y) { var col = Math.floor((x - GRID_LEFT) / BUBBLE_SIZE); var row = Math.floor((y - GRID_TOP) / BUBBLE_SIZE); return { row: row, col: col }; } // Check if row,col is in grid function inGrid(row, col) { return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS; } // Find all connected bubbles of same color (flood fill) function findConnected(row, col, color, visited) { if (!inGrid(row, col)) return []; if (!grid[row][col]) return []; if (grid[row][col].color !== color) return []; var key = row + ',' + col; if (visited[key]) return []; visited[key] = true; var found = [[row, col]]; // 4-way neighbors var dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]; for (var i = 0; i < dirs.length; i++) { var nr = row + dirs[i][0], nc = col + dirs[i][1]; if (inGrid(nr, nc)) { var more = findConnected(nr, nc, color, visited); for (var j = 0; j < more.length; j++) found.push(more[j]); } } return found; } // Remove bubbles at positions function popBubbles(positions) { for (var i = 0; i < positions.length; i++) { var rc = positions[i]; var b = grid[rc[0]][rc[1]]; if (b) { b.pop(); grid[rc[0]][rc[1]] = null; } } // Check for win condition: all bubbles cleared in level 2 if (level === 2) { var anyBubble = false; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c]) { anyBubble = true; break; } } if (anyBubble) break; } if (!anyBubble) { // All bubbles cleared, show you win LK.effects.flashScreen(0x00ff00, 1000); LK.showYouWin(); gameOver = true; } } } // Drop floating bubbles (not connected to top) function dropFloatingBubbles() { var connected = {}; // Mark all bubbles connected to top row function markConnected(row, col) { var key = row + ',' + col; if (!inGrid(row, col)) return; if (!grid[row][col]) return; if (connected[key]) return; connected[key] = true; var dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]; for (var i = 0; i < dirs.length; i++) { var nr = row + dirs[i][0], nc = col + dirs[i][1]; markConnected(nr, nc); } } for (var c = 0; c < GRID_COLS; c++) { if (grid[0][c]) markConnected(0, c); } // Any bubble not in connected is floating for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { var key = r + ',' + c; if (grid[r][c] && !connected[key]) { grid[r][c].pop(); grid[r][c] = null; } } } } // Check for game over (bubbles at bottom row) function checkGameOver() { for (var c = 0; c < GRID_COLS; c++) { if (grid[GRID_ROWS - 1][c]) { gameOver = true; LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return true; } } return false; } // Add XP and update UI function addXP(amount) { xp += amount; scoreTxt.setText('XP: ' + xp); // Duplicate the player (cannon) for help every time XP is gained if (typeof cannons === "undefined") { cannons = [cannon]; } var newCannon = new Cannon(); newCannon.x = GAME_W / 2 + cannons.length * 120 - 60 * (cannons.length % 2 === 0 ? 1 : -1); newCannon.y = GAME_H - 180; newCannon.setAngle(-Math.PI / 2); game.addChild(newCannon); cannons.push(newCannon); // Level up every 200 XP var newLevel = 1 + Math.floor(xp / 200); if (newLevel > level) { level = newLevel; levelTxt.setText('Level ' + level); if (level === 2) { // Start level 2: reset grid with more balls // Remove all bubbles from game for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r][c]) { grid[r][c].destroy(); grid[r][c] = null; } } } // Remove all shot bubbles for (var i = 0; i < shotBubbles.length; i++) { if (shotBubbles[i]) shotBubbles[i].destroy(); } shotBubbles = []; // Reset cannons to just one for (var i = 1; i < cannons.length; i++) { if (cannons[i]) cannons[i].destroy(); } cannons = [cannon]; // Re-initialize grid for level 2 initGrid(); pickNextBubbleColor(); canShoot = true; gameOver = false; } else { // Add new color if possible if (START_COLORS + level - 1 <= COLORS.length) { // Add a row of new color bubbles at top addNewRow(); } } } } // Add a new row of bubbles at the top (for level up) function addNewRow() { // Shift all rows down for (var r = GRID_ROWS - 1; r > 0; r--) { for (var c = 0; c < GRID_COLS; c++) { if (grid[r - 1][c]) { grid[r][c] = grid[r - 1][c]; grid[r][c].gridRow = r; var pos = gridToXY(r, c); tween(grid[r][c], { x: pos.x, y: pos.y }, { duration: 200, easing: tween.easeOut }); } else { grid[r][c] = null; } } } // Add new row var colorIdx = Math.min(START_COLORS + level - 2, COLORS.length - 1); var color = COLORS[colorIdx]; for (var c = 0; c < GRID_COLS; c++) { var b = new Bubble(); b.init(color); var pos = gridToXY(0, c); b.x = pos.x; b.y = pos.y; b.gridRow = 0; b.gridCol = c; grid[0][c] = b; game.addChild(b); } } // --- Game setup --- // Initialize grid function initGrid() { grid = []; var fillRows = 8; if (level === 2) { fillRows = 12; // Fill all rows for level 2 } for (var r = 0; r < GRID_ROWS; r++) { grid[r] = []; for (var c = 0; c < GRID_COLS; c++) { if (r < fillRows) { // Fill top rows with random color var colorIdx = Math.floor(Math.random() * (START_COLORS + level - 1)); var color = COLORS[colorIdx]; var b = new Bubble(); b.init(color); var pos = gridToXY(r, c); b.x = pos.x; b.y = pos.y; b.gridRow = r; b.gridCol = c; grid[r][c] = b; game.addChild(b); } else { grid[r][c] = null; } } } } // Initialize cannon function initCannon() { cannon = new Cannon(); cannon.x = GAME_W / 2; cannon.y = GAME_H - 180; cannon.setAngle(-Math.PI / 2); game.addChild(cannon); } // Prepare next bubble color function pickNextBubbleColor() { nextBubbleColor = getRandomColor(); } // Shoot a bubble function shootBubble(angle) { if (!canShoot || gameOver) return; canShoot = false; // If cannons array is not defined, fallback to single cannon if (typeof cannons === "undefined" || !Array.isArray(cannons)) { cannons = [cannon]; } // Each cannon shoots a bubble for (var i = 0; i < cannons.length; i++) { var c = cannons[i]; var b = new ShotBubble(); b.init(nextBubbleColor, c.angle); b.x = c.x; b.y = c.y - 60; shotBubbles.push(b); game.addChild(b); LK.getSound('shoot').play(); } pickNextBubbleColor(); // Allow next shot after short delay LK.setTimeout(function () { canShoot = true; }, 300); } // --- Input handling --- // Aim cannon with drag/tap function aimAt(x, y) { // Clamp to avoid aiming below horizontal var dx = x - cannon.x; var dy = y - cannon.y; var angle = Math.atan2(dy, dx); cannon.setAngle(angle); lastTouchX = x; lastTouchY = y; } // Shoot on release function tryShoot() { if (!canShoot || gameOver) return; shootBubble(cannon.angle); } // Touch/drag events game.down = function (x, y, obj) { if (gameOver) return; if (y > GAME_H - 500) { dragAiming = true; aimAt(x, y); } }; game.move = function (x, y, obj) { if (dragAiming && !gameOver) { aimAt(x, y); // --- Swipe to crush 4+ adjacent bubbles --- // Convert to grid coordinates var pos = xyToGrid(x, y); var row = pos.row, col = pos.col; if (inGrid(row, col)) { var gb = grid[row][col]; if (gb && !gb.popped && gb.asset && gb.asset._wasSwiped !== true) { // Find all connected bubbles of same color var connected = findConnected(row, col, gb.color, {}); if (connected.length >= 4) { // Mark all as swiped to prevent double pop in one swipe for (var i = 0; i < connected.length; i++) { var rc = connected[i]; if (grid[rc[0]][rc[1]] && grid[rc[0]][rc[1]].asset) { grid[rc[0]][rc[1]].asset._wasSwiped = true; } } popBubbles(connected); addXP(connected.length * XP_PER_BUBBLE); dropFloatingBubbles(); } } } } }; game.up = function (x, y, obj) { if (dragAiming && !gameOver) { tryShoot(); dragAiming = false; } }; // --- Main update loop --- game.update = function () { // Update shot bubbles for (var i = shotBubbles.length - 1; i >= 0; i--) { var b = shotBubbles[i]; b.update(); // Out of bounds if (b.x < 0 || b.x > GAME_W || b.y < 0) { b.destroy(); shotBubbles.splice(i, 1); continue; } // Collision with grid var hit = false; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { var gb = grid[r][c]; if (gb) { var dx = b.x - gb.x; var dy = b.y - gb.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < BUBBLE_SIZE - 10) { // Stick bubble to grid var pos = xyToGrid(b.x, b.y); var row = pos.row, col = pos.col; if (!inGrid(row, col)) { // Out of grid, game over b.destroy(); shotBubbles.splice(i, 1); checkGameOver(); return; } // Find nearest empty cell if (grid[row][col]) { // Try above if (row > 0 && !grid[row - 1][col]) row--;else if (col > 0 && !grid[row][col - 1]) col--;else if (col < GRID_COLS - 1 && !grid[row][col + 1]) col++; } // Place bubble b.stick(); var pos2 = gridToXY(row, col); tween(b, { x: pos2.x, y: pos2.y }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() {} }); b.gridRow = row; b.gridCol = col; // Convert to grid bubble var newB = new Bubble(); newB.init(b.color); newB.x = pos2.x; newB.y = pos2.y; newB.gridRow = row; newB.gridCol = col; grid[row][col] = newB; game.addChild(newB); // Remove shot bubble b.destroy(); shotBubbles.splice(i, 1); // Check for matches var connected = findConnected(row, col, newB.color, {}); if (connected.length >= 3) { popBubbles(connected); addXP(connected.length * XP_PER_BUBBLE); dropFloatingBubbles(); } checkGameOver(); hit = true; break; } } } if (hit) break; } } // --- Tap to pop grid bubbles and earn XP --- for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { var gb = grid[r][c]; if (gb && !gb.popped && gb.asset && gb.asset._wasDown !== true && gb.asset._downListenerSet !== true) { // Set a one-time down listener for this bubble gb.asset._downListenerSet = true; (function (gb, r, c) { gb.asset.down = function (x, y, obj) { if (gb.popped) return; gb.pop(); grid[r][c] = null; addXP(XP_PER_BUBBLE); }; })(gb, r, c); } } } }; // --- Game start --- function startGame() { xp = 0; level = 1; scoreTxt.setText('XP: 0'); levelTxt.setText('Level 1'); gameOver = false; shotBubbles = []; canShoot = true; // Add background image to the game scene var bg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChildAt(bg, 0); initGrid(); initCannon(); pickNextBubbleColor(); } startGame(); ; LK.playMusic('Space');
===================================================================
--- original.js
+++ change.js
@@ -221,8 +221,27 @@
b.pop();
grid[rc[0]][rc[1]] = null;
}
}
+ // Check for win condition: all bubbles cleared in level 2
+ if (level === 2) {
+ var anyBubble = false;
+ for (var r = 0; r < GRID_ROWS; r++) {
+ for (var c = 0; c < GRID_COLS; c++) {
+ if (grid[r][c]) {
+ anyBubble = true;
+ break;
+ }
+ }
+ if (anyBubble) break;
+ }
+ if (!anyBubble) {
+ // All bubbles cleared, show you win
+ LK.effects.flashScreen(0x00ff00, 1000);
+ LK.showYouWin();
+ gameOver = true;
+ }
+ }
}
// Drop floating bubbles (not connected to top)
function dropFloatingBubbles() {
var connected = {};
@@ -284,12 +303,40 @@
var newLevel = 1 + Math.floor(xp / 200);
if (newLevel > level) {
level = newLevel;
levelTxt.setText('Level ' + level);
- // Add new color if possible
- if (START_COLORS + level - 1 <= COLORS.length) {
- // Add a row of new color bubbles at top
- addNewRow();
+ if (level === 2) {
+ // Start level 2: reset grid with more balls
+ // Remove all bubbles from game
+ for (var r = 0; r < GRID_ROWS; r++) {
+ for (var c = 0; c < GRID_COLS; c++) {
+ if (grid[r][c]) {
+ grid[r][c].destroy();
+ grid[r][c] = null;
+ }
+ }
+ }
+ // Remove all shot bubbles
+ for (var i = 0; i < shotBubbles.length; i++) {
+ if (shotBubbles[i]) shotBubbles[i].destroy();
+ }
+ shotBubbles = [];
+ // Reset cannons to just one
+ for (var i = 1; i < cannons.length; i++) {
+ if (cannons[i]) cannons[i].destroy();
+ }
+ cannons = [cannon];
+ // Re-initialize grid for level 2
+ initGrid();
+ pickNextBubbleColor();
+ canShoot = true;
+ gameOver = false;
+ } else {
+ // Add new color if possible
+ if (START_COLORS + level - 1 <= COLORS.length) {
+ // Add a row of new color bubbles at top
+ addNewRow();
+ }
}
}
}
// Add a new row of bubbles at the top (for level up)
@@ -331,14 +378,18 @@
// --- Game setup ---
// Initialize grid
function initGrid() {
grid = [];
+ var fillRows = 8;
+ if (level === 2) {
+ fillRows = 12; // Fill all rows for level 2
+ }
for (var r = 0; r < GRID_ROWS; r++) {
grid[r] = [];
for (var c = 0; c < GRID_COLS; c++) {
- if (r < 8) {
- // Fill top 8 rows with random color (was 5)
- var colorIdx = Math.floor(Math.random() * START_COLORS);
+ if (r < fillRows) {
+ // Fill top rows with random color
+ var colorIdx = Math.floor(Math.random() * (START_COLORS + level - 1));
var color = COLORS[colorIdx];
var b = new Bubble();
b.init(color);
var pos = gridToXY(r, c);
Blue bubble. In-Game asset. 2d. High contrast. No shadows
Red bubble. In-Game asset. 2d. High contrast. No shadows
Orange bubble with question mark. In-Game asset. 2d. High contrast. No shadows
Purple bubble with smiley face. In-Game asset. 2d. High contrast. No shadows
Cannon. In-Game asset. 2d. High contrast. No shadows
Space. In-Game asset. 2d. High contrast. No shadows
Wood texture. In-Game asset. 2d. High contrast. No shadows