/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Arrow = Container.expand(function () {
var self = Container.call(this);
// Create arrow shaft
var arrowShaft = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
arrowShaft.scale.set(0.4, 1.8); // Longer, slimmer shaft
arrowShaft.tint = 0xFFAA00; // Brighter orange color
self.addChild(arrowShaft);
// Add arrow tip (triangle shape approximation)
var arrowTip = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
arrowTip.scale.set(1.0, 1.0);
arrowTip.tint = 0xFFAA00;
arrowTip.y = -60;
self.addChild(arrowTip);
// Add feathers at the back of the arrow
var leftFeather = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
leftFeather.scale.set(0.5, 0.5);
leftFeather.tint = 0xFFFFFF;
leftFeather.x = -15;
leftFeather.y = 50;
self.addChild(leftFeather);
var rightFeather = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
rightFeather.scale.set(0.5, 0.5);
rightFeather.tint = 0xFFFFFF;
rightFeather.x = 15;
rightFeather.y = 50;
self.addChild(rightFeather);
// Add glow effect
var glow = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
glow.scale.set(2.0, 2.0);
glow.tint = 0xFFAA00;
glow.alpha = 0.3;
self.addChild(glow);
// Animate the glow
tween(glow, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0.2
}, {
duration: 1000,
easing: tween.easeInOut,
yoyo: true,
repeat: -1
});
// Timer for popping bubbles
self.timer = null;
self.isActive = true;
// Method to pop a random bubble every 2 seconds
self.startPopping = function () {
self.timer = LK.setInterval(function () {
self.popRandomBubble();
}, 2000);
};
// Stop popping bubbles
self.stopPopping = function () {
if (self.timer) {
LK.clearInterval(self.timer);
self.timer = null;
}
};
// Function to pop a random bubble
self.popRandomBubble = function () {
// Find valid bubbles (not bombs, and active)
var validBubbles = bubbles.filter(function (bubble) {
return bubble.isActive && bubble.type !== 'bomb';
});
if (validBubbles.length > 0) {
// Get a random bubble from valid ones
var randomIndex = Math.floor(Math.random() * validBubbles.length);
var targetBubble = validBubbles[randomIndex];
// Pop it
targetBubble.pop();
// Create a more visually interesting arrow projectile
var arrowProjectile = new Container();
// Arrow projectile body
var projectileBody = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
projectileBody.scale.set(0.3, 0.7);
projectileBody.tint = 0xFFAA00;
arrowProjectile.addChild(projectileBody);
// Arrow projectile tip
var projectileTip = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
projectileTip.scale.set(0.6, 0.5);
projectileTip.tint = 0xFFAA00;
projectileTip.y = -20;
arrowProjectile.addChild(projectileTip);
// Trail effect
var trail = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
trail.scale.set(0.2, 1.0);
trail.tint = 0xFFDD00;
trail.alpha = 0.7;
trail.y = 25;
arrowProjectile.addChild(trail);
// Set position
arrowProjectile.x = self.x;
arrowProjectile.y = self.y;
game.addChild(arrowProjectile);
// Calculate angle to target
var angleToTarget = Math.atan2(targetBubble.y - self.y, targetBubble.x - self.x);
arrowProjectile.rotation = angleToTarget + Math.PI / 2;
// Animate projectile to target with a slight arc
var midX = (self.x + targetBubble.x) / 2;
var midY = (self.y + targetBubble.y) / 2 - 50; // Arc up a bit
// First part of animation - to midpoint with slight arc
tween(arrowProjectile, {
x: midX,
y: midY,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second part - to target
tween(arrowProjectile, {
x: targetBubble.x,
y: targetBubble.y,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.8
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Flash effect at impact
var impact = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
impact.scale.set(1.0, 1.0);
impact.tint = 0xFFFF00;
impact.alpha = 0.8;
impact.x = targetBubble.x;
impact.y = targetBubble.y;
game.addChild(impact);
// Animate impact flash
tween(impact, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(impact);
}
});
game.removeChild(arrowProjectile);
}
});
}
});
}
};
return self;
});
var AutoShooter = Container.expand(function () {
var self = Container.call(this);
self.isActive = false;
self.timer = null;
// Visual representation of auto-shooter as a gun
var shooterGraphics = self.attachAsset('gunImage', {
anchorX: 0.5,
anchorY: 0.5
});
// Customize appearance
shooterGraphics.scale.set(1.2);
// Add visual indicator
var shooterText = new Text2('AUTO', {
size: 50,
fill: 0xFFFFFF
});
shooterText.anchor.set(0.5, 0.5);
self.addChild(shooterText);
// Start the auto-shooter
self.activate = function () {
if (self.isActive) {
return;
}
self.isActive = true;
// Pulse animation to show it's active
tween(shooterGraphics, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 1000,
easing: tween.easeInOut
});
// Set up timer to pop a bubble every second
self.timer = LK.setInterval(function () {
self.popRandomBubble();
}, 1000);
};
self.deactivate = function () {
if (!self.isActive) {
return;
}
self.isActive = false;
if (self.timer) {
LK.clearInterval(self.timer);
self.timer = null;
}
// Return to normal size
tween(shooterGraphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut
});
};
// Function to pop a random bubble
self.popRandomBubble = function () {
// Find valid bubbles (not bombs, and active)
var validBubbles = bubbles.filter(function (bubble) {
return bubble.isActive && bubble.type !== 'bomb';
});
if (validBubbles.length > 0) {
// Get a random bubble from valid ones
var randomIndex = Math.floor(Math.random() * validBubbles.length);
var targetBubble = validBubbles[randomIndex];
// Pop it
targetBubble.pop();
// Visual effect from auto-shooter to bubble - resembles a bullet
var beam = LK.getAsset('gunImage', {
anchorX: 0.5,
anchorY: 0.5
});
beam.scale.set(0.3, 0.15); // Larger beam size
beam.tint = 0xFF0000;
beam.x = self.x;
beam.y = self.y;
game.addChild(beam);
// Add glow effect
beam.alpha = 0.9;
// Animate beam to target bubble with more dramatic effect
tween(beam, {
x: targetBubble.x,
y: targetBubble.y,
scaleX: 0.5,
// Grow while traveling
scaleY: 0.25,
alpha: 0
}, {
duration: 400,
// Slightly longer duration for better visibility
easing: tween.linear,
onFinish: function onFinish() {
game.removeChild(beam);
}
});
}
};
return self;
});
var Bubble = Container.expand(function (type, speed) {
var self = Container.call(this);
self.type = type || 'standard';
self.speed = speed || 2;
self.points = 1;
self.isActive = true;
var assetId = 'standardBubble';
if (self.type === 'bonus') {
assetId = 'bonusBubble';
self.points = 5;
} else if (self.type === 'bomb') {
assetId = 'bombBubble';
self.points = -10;
} else if (self.type === 'powerup') {
assetId = 'powerupBubble';
self.points = 2;
}
var bubbleGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.type === 'bonus') {
bubbleGraphics.scale.set(1.2);
} else if (self.type === 'powerup') {
bubbleGraphics.scale.set(0.9);
}
// Add some interactivity by scaling bubble on press
self.down = function (x, y, obj) {
if (!self.isActive) {
return;
}
// Scale down bubble when pressed
tween(bubbleGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut
});
self.pop();
};
self.pop = function () {
if (!self.isActive) {
return;
}
self.isActive = false;
// Play sound based on bubble type
if (self.type === 'bomb') {
LK.getSound('explosion').play();
} else if (self.type === 'bonus') {
LK.getSound('bonus').play();
} else if (self.type === 'powerup') {
LK.getSound('powerup').play();
} else {
LK.getSound('pop').play();
}
// Animation for popping
tween(bubbleGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
// Update score
LK.setScore(LK.getScore() + self.points);
// Handle special effects
if (self.type === 'bomb') {
// Flash screen red for bomb
LK.effects.flashScreen(0xe74c3c, 300);
} else if (self.type === 'powerup') {
// Slow down all bubbles
if (slowDownTimer) {
LK.clearTimeout(slowDownTimer);
}
bubbleSpeed = baseSpeed * 0.5;
slowDownTimer = LK.setTimeout(function () {
bubbleSpeed = baseSpeed;
}, 5000);
}
};
self.update = function () {
if (!self.isActive) {
return;
}
// Move bubble upward
self.y -= self.speed;
// Slight side-to-side movement
self.x += Math.sin(LK.ticks / 20 + self.id * 0.3) * 0.8;
// Remove if bubble goes off-screen
if (self.y < -150) {
// If bomb escapes screen, that's good
if (self.type === 'bomb') {
LK.setScore(LK.getScore() + 2);
}
// If it's a bonus and escapes, that's bad
else if (self.type === 'bonus') {
LK.setScore(LK.getScore() - 2);
}
if (self.parent) {
self.parent.removeChild(self);
}
}
};
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
// Create shop panel background
var panelBg = self.attachAsset('standardBubble', {
anchorX: 0.5,
anchorY: 0.5
});
// Stretch to make panel
panelBg.scale.set(10, 6);
panelBg.alpha = 0.9;
// Add title
var titleText = new Text2('Bubble Shop', {
size: 100,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.y = -300;
self.addChild(titleText);
// Add close button
var closeButton = new Text2('Close', {
size: 60,
fill: 0xFFFFFF
});
closeButton.anchor.set(0.5, 0.5);
closeButton.y = 300;
closeButton.interactive = true;
closeButton.down = function () {
closeShop();
};
self.addChild(closeButton);
// Add shop items
// Arrow item
var item1 = new Text2('Arrow: 100 pts', {
size: 60,
fill: 0xFFFFFF
});
item1.anchor.set(0.5, 0.5);
item1.y = -100; // Position above auto shooter
item1.interactive = true;
item1.down = function () {
// Check if player has enough points
if (LK.getScore() >= 100) {
// Deduct points
LK.setScore(LK.getScore() - 100);
// Create and position arrow at left middle of screen
var arrow = new Arrow();
arrow.x = 100; // Position at left edge
arrow.y = 2732 / 2; // Middle of screen height
game.addChild(arrow);
// Start the arrow's bubble popping behavior
arrow.startPopping();
// Visual feedback
tween(item1, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(item1, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Close shop after purchase
closeShop();
}
};
self.addChild(item1);
// Auto shooter item
var item3 = new Text2('Auto Shooter: 150 pts', {
size: 60,
fill: 0xFFFFFF
});
item3.anchor.set(0.5, 0.5);
item3.y = 0; // Centered position
item3.interactive = true;
item3.down = function () {
// Check if player has enough points
if (LK.getScore() >= 150) {
// Deduct points
LK.setScore(LK.getScore() - 150);
// Increment auto-shooter count
autoShooterCount++;
// Activate auto-shooter
activateAutoShooter();
// Visual feedback
tween(item3, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(item3, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Close shop after purchase
closeShop();
}
};
self.addChild(item3);
// Initialize with hidden state
self.visible = false;
return self;
});
var ShopButton = Container.expand(function () {
var self = Container.call(this);
// Create shop button visual
var buttonGraphics = self.attachAsset('standardBubble', {
anchorX: 0.5,
anchorY: 0.5
});
// Customize appearance
buttonGraphics.scale.set(1.2);
// Add text label
var buttonText = new Text2('Shop', {
size: 70,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
// Handle click
self.down = function (x, y, obj) {
tween(buttonGraphics, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 100,
easing: tween.easeOut
});
// Open shop
openShop();
};
self.up = function (x, y, obj) {
tween(buttonGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bubbles = [];
var level = 1;
var baseSpeed = 2;
var bubbleSpeed = baseSpeed;
var spawnRate = 60; // Ticks between bubble spawns
var maxBubbles = 10;
var timer = 0;
var scoreDisplay;
var levelDisplay;
var slowDownTimer = null;
var bubbleIdCounter = 0;
var shopButton;
var shopPanel;
var isShopOpen = false;
var autoShooter;
var secondGun;
var autoShooterCount = 0;
var autoShooterActive = false;
var arrows = []; // Track arrows for management
// Auto-shooter functions
function activateAutoShooter() {
if (!autoShooterActive) {
autoShooterActive = true;
// Create auto-shooter if it doesn't exist
if (!autoShooter) {
// Create first gun
autoShooter = new AutoShooter();
autoShooter.x = 2048 - 100; // Position at right side of screen
autoShooter.y = 2732 / 2; // Position at vertical center
game.addChild(autoShooter);
// Check if we already have a second gun
if (!secondGun && autoShooterCount > 1) {
// Create second gun symmetrically on left side
secondGun = new AutoShooter();
secondGun.x = 100; // Position at left side of screen (symmetrical to first gun)
secondGun.y = 2732 / 2; // Position at vertical center
game.addChild(secondGun);
secondGun.activate();
}
}
// Activate it
autoShooter.activate();
}
}
function deactivateAutoShooter() {
if (autoShooterActive) {
autoShooterActive = false;
if (autoShooter) {
autoShooter.deactivate();
}
if (secondGun) {
secondGun.deactivate();
}
}
}
// Shop functions
function openShop() {
if (!isShopOpen) {
isShopOpen = true;
shopPanel.visible = true;
// Animate shop opening
shopPanel.scale.set(0.1);
tween(shopPanel, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
}
function closeShop() {
if (isShopOpen) {
// Animate shop closing
tween(shopPanel, {
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
shopPanel.visible = false;
isShopOpen = false;
}
});
}
}
// Set up UI
function setupUI() {
// Score display
scoreDisplay = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreDisplay);
// Level display
levelDisplay = new Text2('Level: ' + level, {
size: 80,
fill: 0xFFFFFF
});
levelDisplay.anchor.set(1, 0);
LK.gui.topLeft.addChild(levelDisplay);
// Position them properly
levelDisplay.x = -120;
levelDisplay.y = 120; // Keep away from top left corner
scoreDisplay.y = 20;
// Add shop button to top right
shopButton = new ShopButton();
shopButton.x = -100;
shopButton.y = 100;
LK.gui.topRight.addChild(shopButton);
// Create shop panel
shopPanel = new Shop();
shopPanel.visible = false;
LK.gui.center.addChild(shopPanel);
}
// Spawn a new bubble
function spawnBubble() {
if (bubbles.length >= maxBubbles) {
return;
}
var type = 'standard';
var rand = Math.random();
// Determine bubble type based on probability
if (rand < 0.05) {
type = 'bomb';
} else if (rand < 0.15) {
type = 'bonus';
} else if (rand < 0.20) {
type = 'powerup';
}
// Create bubble
var bubble = new Bubble(type, bubbleSpeed);
bubble.id = bubbleIdCounter++;
// Position at random X at bottom of screen
bubble.x = Math.random() * (2048 - 200) + 100;
bubble.y = 2732 + 100;
// Add bubble to game and array
game.addChild(bubble);
bubbles.push(bubble);
}
// Remove bubble from array
function removeBubble(bubble) {
var index = bubbles.indexOf(bubble);
if (index > -1) {
bubbles.splice(index, 1);
}
}
// Update game difficulty based on level
function updateDifficulty() {
baseSpeed = 2 + level * 0.5;
bubbleSpeed = baseSpeed;
spawnRate = Math.max(20, 60 - level * 5);
maxBubbles = 10 + level;
}
// Update level based on score
function checkLevelUp() {
var newLevel = Math.floor(LK.getScore() / 50) + 1;
if (newLevel > level) {
level = newLevel;
levelDisplay.setText('Level: ' + level);
updateDifficulty();
// Flash screen to indicate level up
LK.effects.flashScreen(0x2ecc71, 500);
}
}
// Initialize game
function initGame() {
LK.setScore(0);
level = 1;
timer = 0;
// No gameTime limit - infinite gameplay
bubbleSpeed = baseSpeed;
bubbleIdCounter = 0;
// Reset auto-shooter
if (autoShooter && autoShooter.parent) {
autoShooter.deactivate();
game.removeChild(autoShooter);
}
if (secondGun && secondGun.parent) {
secondGun.deactivate();
game.removeChild(secondGun);
}
autoShooter = null;
secondGun = null;
autoShooterCount = 0;
autoShooterActive = false;
// Clear any arrows
var allChildren = game.children.slice();
for (var i = 0; i < allChildren.length; i++) {
if (allChildren[i] instanceof Arrow) {
if (allChildren[i].timer) {
allChildren[i].stopPopping();
}
game.removeChild(allChildren[i]);
}
}
// Clear any existing bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
game.removeChild(bubbles[i]);
}
bubbles = [];
// Set up UI
setupUI();
// Update difficulty
updateDifficulty();
// Start background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
// No update timer function needed for infinite gameplay
// End game - will only be called manually when reaching score milestones
function endGame() {
if (LK.getScore() >= 100) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
// Initialize the game
initGame();
// Main game update function
game.update = function () {
timer++;
// Update score display
scoreDisplay.setText('Score: ' + LK.getScore());
// Check for level up
checkLevelUp();
// Spawn bubbles at regular intervals
if (timer % spawnRate === 0) {
spawnBubble();
}
// Clean up bubbles that are no longer in the scene
for (var i = bubbles.length - 1; i >= 0; i--) {
if (!bubbles[i].parent) {
removeBubble(bubbles[i]);
}
}
};
// Handle game clicks/taps
game.down = function (x, y, obj) {
// Just handle click/tap on the game background
// Bubbles have their own click handlers
};
// Handle mouse/touch movement
game.move = function (x, y, obj) {
// No implementation needed for this game
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Arrow = Container.expand(function () {
var self = Container.call(this);
// Create arrow shaft
var arrowShaft = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
arrowShaft.scale.set(0.4, 1.8); // Longer, slimmer shaft
arrowShaft.tint = 0xFFAA00; // Brighter orange color
self.addChild(arrowShaft);
// Add arrow tip (triangle shape approximation)
var arrowTip = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
arrowTip.scale.set(1.0, 1.0);
arrowTip.tint = 0xFFAA00;
arrowTip.y = -60;
self.addChild(arrowTip);
// Add feathers at the back of the arrow
var leftFeather = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
leftFeather.scale.set(0.5, 0.5);
leftFeather.tint = 0xFFFFFF;
leftFeather.x = -15;
leftFeather.y = 50;
self.addChild(leftFeather);
var rightFeather = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
rightFeather.scale.set(0.5, 0.5);
rightFeather.tint = 0xFFFFFF;
rightFeather.x = 15;
rightFeather.y = 50;
self.addChild(rightFeather);
// Add glow effect
var glow = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
glow.scale.set(2.0, 2.0);
glow.tint = 0xFFAA00;
glow.alpha = 0.3;
self.addChild(glow);
// Animate the glow
tween(glow, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0.2
}, {
duration: 1000,
easing: tween.easeInOut,
yoyo: true,
repeat: -1
});
// Timer for popping bubbles
self.timer = null;
self.isActive = true;
// Method to pop a random bubble every 2 seconds
self.startPopping = function () {
self.timer = LK.setInterval(function () {
self.popRandomBubble();
}, 2000);
};
// Stop popping bubbles
self.stopPopping = function () {
if (self.timer) {
LK.clearInterval(self.timer);
self.timer = null;
}
};
// Function to pop a random bubble
self.popRandomBubble = function () {
// Find valid bubbles (not bombs, and active)
var validBubbles = bubbles.filter(function (bubble) {
return bubble.isActive && bubble.type !== 'bomb';
});
if (validBubbles.length > 0) {
// Get a random bubble from valid ones
var randomIndex = Math.floor(Math.random() * validBubbles.length);
var targetBubble = validBubbles[randomIndex];
// Pop it
targetBubble.pop();
// Create a more visually interesting arrow projectile
var arrowProjectile = new Container();
// Arrow projectile body
var projectileBody = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
projectileBody.scale.set(0.3, 0.7);
projectileBody.tint = 0xFFAA00;
arrowProjectile.addChild(projectileBody);
// Arrow projectile tip
var projectileTip = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
projectileTip.scale.set(0.6, 0.5);
projectileTip.tint = 0xFFAA00;
projectileTip.y = -20;
arrowProjectile.addChild(projectileTip);
// Trail effect
var trail = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
trail.scale.set(0.2, 1.0);
trail.tint = 0xFFDD00;
trail.alpha = 0.7;
trail.y = 25;
arrowProjectile.addChild(trail);
// Set position
arrowProjectile.x = self.x;
arrowProjectile.y = self.y;
game.addChild(arrowProjectile);
// Calculate angle to target
var angleToTarget = Math.atan2(targetBubble.y - self.y, targetBubble.x - self.x);
arrowProjectile.rotation = angleToTarget + Math.PI / 2;
// Animate projectile to target with a slight arc
var midX = (self.x + targetBubble.x) / 2;
var midY = (self.y + targetBubble.y) / 2 - 50; // Arc up a bit
// First part of animation - to midpoint with slight arc
tween(arrowProjectile, {
x: midX,
y: midY,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second part - to target
tween(arrowProjectile, {
x: targetBubble.x,
y: targetBubble.y,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.8
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Flash effect at impact
var impact = LK.getAsset('powerupBubble', {
anchorX: 0.5,
anchorY: 0.5
});
impact.scale.set(1.0, 1.0);
impact.tint = 0xFFFF00;
impact.alpha = 0.8;
impact.x = targetBubble.x;
impact.y = targetBubble.y;
game.addChild(impact);
// Animate impact flash
tween(impact, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(impact);
}
});
game.removeChild(arrowProjectile);
}
});
}
});
}
};
return self;
});
var AutoShooter = Container.expand(function () {
var self = Container.call(this);
self.isActive = false;
self.timer = null;
// Visual representation of auto-shooter as a gun
var shooterGraphics = self.attachAsset('gunImage', {
anchorX: 0.5,
anchorY: 0.5
});
// Customize appearance
shooterGraphics.scale.set(1.2);
// Add visual indicator
var shooterText = new Text2('AUTO', {
size: 50,
fill: 0xFFFFFF
});
shooterText.anchor.set(0.5, 0.5);
self.addChild(shooterText);
// Start the auto-shooter
self.activate = function () {
if (self.isActive) {
return;
}
self.isActive = true;
// Pulse animation to show it's active
tween(shooterGraphics, {
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 1000,
easing: tween.easeInOut
});
// Set up timer to pop a bubble every second
self.timer = LK.setInterval(function () {
self.popRandomBubble();
}, 1000);
};
self.deactivate = function () {
if (!self.isActive) {
return;
}
self.isActive = false;
if (self.timer) {
LK.clearInterval(self.timer);
self.timer = null;
}
// Return to normal size
tween(shooterGraphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut
});
};
// Function to pop a random bubble
self.popRandomBubble = function () {
// Find valid bubbles (not bombs, and active)
var validBubbles = bubbles.filter(function (bubble) {
return bubble.isActive && bubble.type !== 'bomb';
});
if (validBubbles.length > 0) {
// Get a random bubble from valid ones
var randomIndex = Math.floor(Math.random() * validBubbles.length);
var targetBubble = validBubbles[randomIndex];
// Pop it
targetBubble.pop();
// Visual effect from auto-shooter to bubble - resembles a bullet
var beam = LK.getAsset('gunImage', {
anchorX: 0.5,
anchorY: 0.5
});
beam.scale.set(0.3, 0.15); // Larger beam size
beam.tint = 0xFF0000;
beam.x = self.x;
beam.y = self.y;
game.addChild(beam);
// Add glow effect
beam.alpha = 0.9;
// Animate beam to target bubble with more dramatic effect
tween(beam, {
x: targetBubble.x,
y: targetBubble.y,
scaleX: 0.5,
// Grow while traveling
scaleY: 0.25,
alpha: 0
}, {
duration: 400,
// Slightly longer duration for better visibility
easing: tween.linear,
onFinish: function onFinish() {
game.removeChild(beam);
}
});
}
};
return self;
});
var Bubble = Container.expand(function (type, speed) {
var self = Container.call(this);
self.type = type || 'standard';
self.speed = speed || 2;
self.points = 1;
self.isActive = true;
var assetId = 'standardBubble';
if (self.type === 'bonus') {
assetId = 'bonusBubble';
self.points = 5;
} else if (self.type === 'bomb') {
assetId = 'bombBubble';
self.points = -10;
} else if (self.type === 'powerup') {
assetId = 'powerupBubble';
self.points = 2;
}
var bubbleGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.type === 'bonus') {
bubbleGraphics.scale.set(1.2);
} else if (self.type === 'powerup') {
bubbleGraphics.scale.set(0.9);
}
// Add some interactivity by scaling bubble on press
self.down = function (x, y, obj) {
if (!self.isActive) {
return;
}
// Scale down bubble when pressed
tween(bubbleGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut
});
self.pop();
};
self.pop = function () {
if (!self.isActive) {
return;
}
self.isActive = false;
// Play sound based on bubble type
if (self.type === 'bomb') {
LK.getSound('explosion').play();
} else if (self.type === 'bonus') {
LK.getSound('bonus').play();
} else if (self.type === 'powerup') {
LK.getSound('powerup').play();
} else {
LK.getSound('pop').play();
}
// Animation for popping
tween(bubbleGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
// Update score
LK.setScore(LK.getScore() + self.points);
// Handle special effects
if (self.type === 'bomb') {
// Flash screen red for bomb
LK.effects.flashScreen(0xe74c3c, 300);
} else if (self.type === 'powerup') {
// Slow down all bubbles
if (slowDownTimer) {
LK.clearTimeout(slowDownTimer);
}
bubbleSpeed = baseSpeed * 0.5;
slowDownTimer = LK.setTimeout(function () {
bubbleSpeed = baseSpeed;
}, 5000);
}
};
self.update = function () {
if (!self.isActive) {
return;
}
// Move bubble upward
self.y -= self.speed;
// Slight side-to-side movement
self.x += Math.sin(LK.ticks / 20 + self.id * 0.3) * 0.8;
// Remove if bubble goes off-screen
if (self.y < -150) {
// If bomb escapes screen, that's good
if (self.type === 'bomb') {
LK.setScore(LK.getScore() + 2);
}
// If it's a bonus and escapes, that's bad
else if (self.type === 'bonus') {
LK.setScore(LK.getScore() - 2);
}
if (self.parent) {
self.parent.removeChild(self);
}
}
};
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
// Create shop panel background
var panelBg = self.attachAsset('standardBubble', {
anchorX: 0.5,
anchorY: 0.5
});
// Stretch to make panel
panelBg.scale.set(10, 6);
panelBg.alpha = 0.9;
// Add title
var titleText = new Text2('Bubble Shop', {
size: 100,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.y = -300;
self.addChild(titleText);
// Add close button
var closeButton = new Text2('Close', {
size: 60,
fill: 0xFFFFFF
});
closeButton.anchor.set(0.5, 0.5);
closeButton.y = 300;
closeButton.interactive = true;
closeButton.down = function () {
closeShop();
};
self.addChild(closeButton);
// Add shop items
// Arrow item
var item1 = new Text2('Arrow: 100 pts', {
size: 60,
fill: 0xFFFFFF
});
item1.anchor.set(0.5, 0.5);
item1.y = -100; // Position above auto shooter
item1.interactive = true;
item1.down = function () {
// Check if player has enough points
if (LK.getScore() >= 100) {
// Deduct points
LK.setScore(LK.getScore() - 100);
// Create and position arrow at left middle of screen
var arrow = new Arrow();
arrow.x = 100; // Position at left edge
arrow.y = 2732 / 2; // Middle of screen height
game.addChild(arrow);
// Start the arrow's bubble popping behavior
arrow.startPopping();
// Visual feedback
tween(item1, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(item1, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Close shop after purchase
closeShop();
}
};
self.addChild(item1);
// Auto shooter item
var item3 = new Text2('Auto Shooter: 150 pts', {
size: 60,
fill: 0xFFFFFF
});
item3.anchor.set(0.5, 0.5);
item3.y = 0; // Centered position
item3.interactive = true;
item3.down = function () {
// Check if player has enough points
if (LK.getScore() >= 150) {
// Deduct points
LK.setScore(LK.getScore() - 150);
// Increment auto-shooter count
autoShooterCount++;
// Activate auto-shooter
activateAutoShooter();
// Visual feedback
tween(item3, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(item3, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
// Close shop after purchase
closeShop();
}
};
self.addChild(item3);
// Initialize with hidden state
self.visible = false;
return self;
});
var ShopButton = Container.expand(function () {
var self = Container.call(this);
// Create shop button visual
var buttonGraphics = self.attachAsset('standardBubble', {
anchorX: 0.5,
anchorY: 0.5
});
// Customize appearance
buttonGraphics.scale.set(1.2);
// Add text label
var buttonText = new Text2('Shop', {
size: 70,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
// Handle click
self.down = function (x, y, obj) {
tween(buttonGraphics, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 100,
easing: tween.easeOut
});
// Open shop
openShop();
};
self.up = function (x, y, obj) {
tween(buttonGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bubbles = [];
var level = 1;
var baseSpeed = 2;
var bubbleSpeed = baseSpeed;
var spawnRate = 60; // Ticks between bubble spawns
var maxBubbles = 10;
var timer = 0;
var scoreDisplay;
var levelDisplay;
var slowDownTimer = null;
var bubbleIdCounter = 0;
var shopButton;
var shopPanel;
var isShopOpen = false;
var autoShooter;
var secondGun;
var autoShooterCount = 0;
var autoShooterActive = false;
var arrows = []; // Track arrows for management
// Auto-shooter functions
function activateAutoShooter() {
if (!autoShooterActive) {
autoShooterActive = true;
// Create auto-shooter if it doesn't exist
if (!autoShooter) {
// Create first gun
autoShooter = new AutoShooter();
autoShooter.x = 2048 - 100; // Position at right side of screen
autoShooter.y = 2732 / 2; // Position at vertical center
game.addChild(autoShooter);
// Check if we already have a second gun
if (!secondGun && autoShooterCount > 1) {
// Create second gun symmetrically on left side
secondGun = new AutoShooter();
secondGun.x = 100; // Position at left side of screen (symmetrical to first gun)
secondGun.y = 2732 / 2; // Position at vertical center
game.addChild(secondGun);
secondGun.activate();
}
}
// Activate it
autoShooter.activate();
}
}
function deactivateAutoShooter() {
if (autoShooterActive) {
autoShooterActive = false;
if (autoShooter) {
autoShooter.deactivate();
}
if (secondGun) {
secondGun.deactivate();
}
}
}
// Shop functions
function openShop() {
if (!isShopOpen) {
isShopOpen = true;
shopPanel.visible = true;
// Animate shop opening
shopPanel.scale.set(0.1);
tween(shopPanel, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
}
function closeShop() {
if (isShopOpen) {
// Animate shop closing
tween(shopPanel, {
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
shopPanel.visible = false;
isShopOpen = false;
}
});
}
}
// Set up UI
function setupUI() {
// Score display
scoreDisplay = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreDisplay);
// Level display
levelDisplay = new Text2('Level: ' + level, {
size: 80,
fill: 0xFFFFFF
});
levelDisplay.anchor.set(1, 0);
LK.gui.topLeft.addChild(levelDisplay);
// Position them properly
levelDisplay.x = -120;
levelDisplay.y = 120; // Keep away from top left corner
scoreDisplay.y = 20;
// Add shop button to top right
shopButton = new ShopButton();
shopButton.x = -100;
shopButton.y = 100;
LK.gui.topRight.addChild(shopButton);
// Create shop panel
shopPanel = new Shop();
shopPanel.visible = false;
LK.gui.center.addChild(shopPanel);
}
// Spawn a new bubble
function spawnBubble() {
if (bubbles.length >= maxBubbles) {
return;
}
var type = 'standard';
var rand = Math.random();
// Determine bubble type based on probability
if (rand < 0.05) {
type = 'bomb';
} else if (rand < 0.15) {
type = 'bonus';
} else if (rand < 0.20) {
type = 'powerup';
}
// Create bubble
var bubble = new Bubble(type, bubbleSpeed);
bubble.id = bubbleIdCounter++;
// Position at random X at bottom of screen
bubble.x = Math.random() * (2048 - 200) + 100;
bubble.y = 2732 + 100;
// Add bubble to game and array
game.addChild(bubble);
bubbles.push(bubble);
}
// Remove bubble from array
function removeBubble(bubble) {
var index = bubbles.indexOf(bubble);
if (index > -1) {
bubbles.splice(index, 1);
}
}
// Update game difficulty based on level
function updateDifficulty() {
baseSpeed = 2 + level * 0.5;
bubbleSpeed = baseSpeed;
spawnRate = Math.max(20, 60 - level * 5);
maxBubbles = 10 + level;
}
// Update level based on score
function checkLevelUp() {
var newLevel = Math.floor(LK.getScore() / 50) + 1;
if (newLevel > level) {
level = newLevel;
levelDisplay.setText('Level: ' + level);
updateDifficulty();
// Flash screen to indicate level up
LK.effects.flashScreen(0x2ecc71, 500);
}
}
// Initialize game
function initGame() {
LK.setScore(0);
level = 1;
timer = 0;
// No gameTime limit - infinite gameplay
bubbleSpeed = baseSpeed;
bubbleIdCounter = 0;
// Reset auto-shooter
if (autoShooter && autoShooter.parent) {
autoShooter.deactivate();
game.removeChild(autoShooter);
}
if (secondGun && secondGun.parent) {
secondGun.deactivate();
game.removeChild(secondGun);
}
autoShooter = null;
secondGun = null;
autoShooterCount = 0;
autoShooterActive = false;
// Clear any arrows
var allChildren = game.children.slice();
for (var i = 0; i < allChildren.length; i++) {
if (allChildren[i] instanceof Arrow) {
if (allChildren[i].timer) {
allChildren[i].stopPopping();
}
game.removeChild(allChildren[i]);
}
}
// Clear any existing bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
game.removeChild(bubbles[i]);
}
bubbles = [];
// Set up UI
setupUI();
// Update difficulty
updateDifficulty();
// Start background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
// No update timer function needed for infinite gameplay
// End game - will only be called manually when reaching score milestones
function endGame() {
if (LK.getScore() >= 100) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
// Initialize the game
initGame();
// Main game update function
game.update = function () {
timer++;
// Update score display
scoreDisplay.setText('Score: ' + LK.getScore());
// Check for level up
checkLevelUp();
// Spawn bubbles at regular intervals
if (timer % spawnRate === 0) {
spawnBubble();
}
// Clean up bubbles that are no longer in the scene
for (var i = bubbles.length - 1; i >= 0; i--) {
if (!bubbles[i].parent) {
removeBubble(bubbles[i]);
}
}
};
// Handle game clicks/taps
game.down = function (x, y, obj) {
// Just handle click/tap on the game background
// Bubbles have their own click handlers
};
// Handle mouse/touch movement
game.move = function (x, y, obj) {
// No implementation needed for this game
};