/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 0;
self.maxLifetime = 2000; // Default 2 seconds
self.isAlive = true;
// Spawn animation
bubbleGraphics.alpha = 0;
bubbleGraphics.scaleX = 0.5;
bubbleGraphics.scaleY = 0.5;
tween(bubbleGraphics, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
self.setLifetime = function (duration) {
self.maxLifetime = duration;
};
self.update = function () {
if (!self.isAlive) return;
self.lifetime += 16.67; // Approximate frame time
if (self.lifetime >= self.maxLifetime) {
self.destroy();
}
};
self.destroy = function () {
if (!self.isAlive) return;
self.isAlive = false;
// Death animation
tween(bubbleGraphics, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Remove from bubbles array and container
for (var i = bubbles.length - 1; i >= 0; i--) {
if (bubbles[i] === self) {
bubbles.splice(i, 1);
break;
}
}
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.pop = function () {
if (!self.isAlive) return false;
self.isAlive = false;
// Visual feedback - flash white briefly before pop animation
tween(bubbleGraphics, {
tint: 0xFFFFFF
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
// Pop animation
tween(bubbleGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0,
tint: 0x4a90e2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from bubbles array and container
for (var i = bubbles.length - 1; i >= 0; i--) {
if (bubbles[i] === self) {
bubbles.splice(i, 1);
break;
}
}
if (self.parent) {
self.parent.removeChild(self);
}
}
});
}
});
return true;
};
self.down = function (x, y, obj) {
if (self.pop()) {
LK.getSound('bubblePop').play();
LK.setScore(LK.getScore() + 10);
scoreText.setText(LK.getScore());
}
};
return self;
});
var Crosshair = Container.expand(function () {
var self = Container.call(this);
var vertical = self.attachAsset('crosshairVertical', {
anchorX: 0.5,
anchorY: 0.5
});
var horizontal = self.attachAsset('crosshairHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Game state variables
var gameState = 'menu'; // 'menu', 'playing', 'gameover'
var difficulty = 'easy';
var gameTime = 60000; // 60 seconds in milliseconds
var currentTime = 0;
var bubbles = [];
var crosshair;
var lastBubbleSpawn = 0;
var spawnInterval = 2000; // Default spawn interval
var bubbleLifetime = 2000; // Default bubble lifetime
// UI Elements
var titleText = new Text2('Bubble Aim Trainer', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 300;
game.addChild(titleText);
var difficultyText = new Text2('Select Difficulty', {
size: 60,
fill: 0xFFFFFF
});
difficultyText.anchor.set(0.5, 0.5);
difficultyText.x = 1024;
difficultyText.y = 500;
game.addChild(difficultyText);
// Difficulty buttons
var easyButton = new Text2('EASY', {
size: 70,
fill: 0x00FF00
});
easyButton.anchor.set(0.5, 0.5);
easyButton.x = 1024;
easyButton.y = 700;
game.addChild(easyButton);
var mediumButton = new Text2('MEDIUM', {
size: 70,
fill: 0xFFFF00
});
mediumButton.anchor.set(0.5, 0.5);
mediumButton.x = 1024;
mediumButton.y = 850;
game.addChild(mediumButton);
var hardButton = new Text2('HARD', {
size: 70,
fill: 0xFF0000
});
hardButton.anchor.set(0.5, 0.5);
hardButton.x = 1024;
hardButton.y = 1000;
game.addChild(hardButton);
// Game UI
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 120;
scoreText.y = 60;
scoreText.visible = false;
LK.gui.topLeft.addChild(scoreText);
var timeText = new Text2('Time: 60', {
size: 60,
fill: 0xFFFFFF
});
timeText.anchor.set(1, 0);
timeText.y = 60;
timeText.visible = false;
LK.gui.topRight.addChild(timeText);
// Best score displays
var bestScoreText = new Text2('Best Scores', {
size: 50,
fill: 0xFFD700
});
bestScoreText.anchor.set(0.5, 0.5);
bestScoreText.x = 1024;
bestScoreText.y = 1200;
game.addChild(bestScoreText);
var easyBestText = new Text2('Easy: ' + (storage.bestEasy || 0), {
size: 40,
fill: 0x00FF00
});
easyBestText.anchor.set(0.5, 0.5);
easyBestText.x = 1024;
easyBestText.y = 1300;
game.addChild(easyBestText);
var mediumBestText = new Text2('Medium: ' + (storage.bestMedium || 0), {
size: 40,
fill: 0xFFFF00
});
mediumBestText.anchor.set(0.5, 0.5);
mediumBestText.x = 1024;
mediumBestText.y = 1400;
game.addChild(mediumBestText);
var hardBestText = new Text2('Hard: ' + (storage.bestHard || 0), {
size: 40,
fill: 0xFF0000
});
hardBestText.anchor.set(0.5, 0.5);
hardBestText.x = 1024;
hardBestText.y = 1500;
game.addChild(hardBestText);
// Create crosshair
crosshair = new Crosshair();
crosshair.visible = false;
game.addChild(crosshair);
// Button event handlers
easyButton.down = function () {
startGame('easy');
};
mediumButton.down = function () {
startGame('medium');
};
hardButton.down = function () {
startGame('hard');
};
function startGame(selectedDifficulty) {
difficulty = selectedDifficulty;
gameState = 'playing';
currentTime = 0;
LK.setScore(0);
// Set difficulty parameters
switch (difficulty) {
case 'easy':
spawnInterval = 2000;
bubbleLifetime = 4000; // Increased from 2000 to 4000 (+2 seconds)
break;
case 'medium':
spawnInterval = 1000;
bubbleLifetime = 2000; // Increased from 1000 to 2000 (+1 second)
break;
case 'hard':
spawnInterval = 500;
bubbleLifetime = 1000; // Increased from 500 to 1000 (+0.5 second)
break;
}
// Hide menu elements
titleText.visible = false;
difficultyText.visible = false;
easyButton.visible = false;
mediumButton.visible = false;
hardButton.visible = false;
bestScoreText.visible = false;
easyBestText.visible = false;
mediumBestText.visible = false;
hardBestText.visible = false;
// Show game UI
scoreText.visible = true;
timeText.visible = true;
crosshair.visible = true;
// Clear any existing bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
bubbles[i].destroy();
}
bubbles = [];
lastBubbleSpawn = 0;
scoreText.setText('Score: 0');
timeText.setText('Time: 60');
}
function endGame() {
gameState = 'gameover';
// Check and update best scores
var finalScore = LK.getScore();
var bestKey = 'best' + difficulty.charAt(0).toUpperCase() + difficulty.slice(1);
var currentBest = storage[bestKey] || 0;
if (finalScore > currentBest) {
storage[bestKey] = finalScore;
// Update display
switch (difficulty) {
case 'easy':
easyBestText.setText('Easy: ' + finalScore);
break;
case 'medium':
mediumBestText.setText('Medium: ' + finalScore);
break;
case 'hard':
hardBestText.setText('Hard: ' + finalScore);
break;
}
}
// Hide game UI
scoreText.visible = false;
timeText.visible = false;
crosshair.visible = false;
// Show menu elements
titleText.visible = true;
difficultyText.visible = true;
easyButton.visible = true;
mediumButton.visible = true;
hardButton.visible = true;
bestScoreText.visible = true;
easyBestText.visible = true;
mediumBestText.visible = true;
hardBestText.visible = true;
// Clear bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
bubbles[i].destroy();
}
bubbles = [];
LK.showGameOver();
}
function spawnBubble() {
var bubble = new Bubble();
bubble.setLifetime(bubbleLifetime);
// Random position with better mobile margins and avoid top-left menu area
var margin = 200; // Increased margin for mobile
var topMargin = 250; // Extra margin from top for UI
var bottomMargin = 200; // Margin from bottom for mobile navigation
bubble.x = margin + Math.random() * (2048 - 2 * margin);
bubble.y = topMargin + Math.random() * (2732 - topMargin - bottomMargin);
bubbles.push(bubble);
game.addChild(bubble);
}
// Game move handler for crosshair
game.move = function (x, y, obj) {
if (gameState === 'playing') {
crosshair.x = x;
crosshair.y = y;
}
};
// Game touch start handler for immediate crosshair positioning
game.down = function (x, y, obj) {
if (gameState === 'playing') {
crosshair.x = x;
crosshair.y = y;
}
};
// Main game update loop
game.update = function () {
if (gameState === 'playing') {
currentTime += 16.67; // Approximate frame time
// Update timer display
var remainingTime = Math.max(0, Math.ceil((gameTime - currentTime) / 1000));
timeText.setText('Time: ' + remainingTime);
// Check if game time is up
if (currentTime >= gameTime) {
endGame();
return;
}
// Spawn bubbles at intervals
if (currentTime - lastBubbleSpawn >= spawnInterval) {
spawnBubble();
lastBubbleSpawn = currentTime;
}
// Update all bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
if (bubbles[i].isAlive) {
bubbles[i].update();
}
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifetime = 0;
self.maxLifetime = 2000; // Default 2 seconds
self.isAlive = true;
// Spawn animation
bubbleGraphics.alpha = 0;
bubbleGraphics.scaleX = 0.5;
bubbleGraphics.scaleY = 0.5;
tween(bubbleGraphics, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
self.setLifetime = function (duration) {
self.maxLifetime = duration;
};
self.update = function () {
if (!self.isAlive) return;
self.lifetime += 16.67; // Approximate frame time
if (self.lifetime >= self.maxLifetime) {
self.destroy();
}
};
self.destroy = function () {
if (!self.isAlive) return;
self.isAlive = false;
// Death animation
tween(bubbleGraphics, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Remove from bubbles array and container
for (var i = bubbles.length - 1; i >= 0; i--) {
if (bubbles[i] === self) {
bubbles.splice(i, 1);
break;
}
}
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.pop = function () {
if (!self.isAlive) return false;
self.isAlive = false;
// Visual feedback - flash white briefly before pop animation
tween(bubbleGraphics, {
tint: 0xFFFFFF
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
// Pop animation
tween(bubbleGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0,
tint: 0x4a90e2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from bubbles array and container
for (var i = bubbles.length - 1; i >= 0; i--) {
if (bubbles[i] === self) {
bubbles.splice(i, 1);
break;
}
}
if (self.parent) {
self.parent.removeChild(self);
}
}
});
}
});
return true;
};
self.down = function (x, y, obj) {
if (self.pop()) {
LK.getSound('bubblePop').play();
LK.setScore(LK.getScore() + 10);
scoreText.setText(LK.getScore());
}
};
return self;
});
var Crosshair = Container.expand(function () {
var self = Container.call(this);
var vertical = self.attachAsset('crosshairVertical', {
anchorX: 0.5,
anchorY: 0.5
});
var horizontal = self.attachAsset('crosshairHorizontal', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Game state variables
var gameState = 'menu'; // 'menu', 'playing', 'gameover'
var difficulty = 'easy';
var gameTime = 60000; // 60 seconds in milliseconds
var currentTime = 0;
var bubbles = [];
var crosshair;
var lastBubbleSpawn = 0;
var spawnInterval = 2000; // Default spawn interval
var bubbleLifetime = 2000; // Default bubble lifetime
// UI Elements
var titleText = new Text2('Bubble Aim Trainer', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 300;
game.addChild(titleText);
var difficultyText = new Text2('Select Difficulty', {
size: 60,
fill: 0xFFFFFF
});
difficultyText.anchor.set(0.5, 0.5);
difficultyText.x = 1024;
difficultyText.y = 500;
game.addChild(difficultyText);
// Difficulty buttons
var easyButton = new Text2('EASY', {
size: 70,
fill: 0x00FF00
});
easyButton.anchor.set(0.5, 0.5);
easyButton.x = 1024;
easyButton.y = 700;
game.addChild(easyButton);
var mediumButton = new Text2('MEDIUM', {
size: 70,
fill: 0xFFFF00
});
mediumButton.anchor.set(0.5, 0.5);
mediumButton.x = 1024;
mediumButton.y = 850;
game.addChild(mediumButton);
var hardButton = new Text2('HARD', {
size: 70,
fill: 0xFF0000
});
hardButton.anchor.set(0.5, 0.5);
hardButton.x = 1024;
hardButton.y = 1000;
game.addChild(hardButton);
// Game UI
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 120;
scoreText.y = 60;
scoreText.visible = false;
LK.gui.topLeft.addChild(scoreText);
var timeText = new Text2('Time: 60', {
size: 60,
fill: 0xFFFFFF
});
timeText.anchor.set(1, 0);
timeText.y = 60;
timeText.visible = false;
LK.gui.topRight.addChild(timeText);
// Best score displays
var bestScoreText = new Text2('Best Scores', {
size: 50,
fill: 0xFFD700
});
bestScoreText.anchor.set(0.5, 0.5);
bestScoreText.x = 1024;
bestScoreText.y = 1200;
game.addChild(bestScoreText);
var easyBestText = new Text2('Easy: ' + (storage.bestEasy || 0), {
size: 40,
fill: 0x00FF00
});
easyBestText.anchor.set(0.5, 0.5);
easyBestText.x = 1024;
easyBestText.y = 1300;
game.addChild(easyBestText);
var mediumBestText = new Text2('Medium: ' + (storage.bestMedium || 0), {
size: 40,
fill: 0xFFFF00
});
mediumBestText.anchor.set(0.5, 0.5);
mediumBestText.x = 1024;
mediumBestText.y = 1400;
game.addChild(mediumBestText);
var hardBestText = new Text2('Hard: ' + (storage.bestHard || 0), {
size: 40,
fill: 0xFF0000
});
hardBestText.anchor.set(0.5, 0.5);
hardBestText.x = 1024;
hardBestText.y = 1500;
game.addChild(hardBestText);
// Create crosshair
crosshair = new Crosshair();
crosshair.visible = false;
game.addChild(crosshair);
// Button event handlers
easyButton.down = function () {
startGame('easy');
};
mediumButton.down = function () {
startGame('medium');
};
hardButton.down = function () {
startGame('hard');
};
function startGame(selectedDifficulty) {
difficulty = selectedDifficulty;
gameState = 'playing';
currentTime = 0;
LK.setScore(0);
// Set difficulty parameters
switch (difficulty) {
case 'easy':
spawnInterval = 2000;
bubbleLifetime = 4000; // Increased from 2000 to 4000 (+2 seconds)
break;
case 'medium':
spawnInterval = 1000;
bubbleLifetime = 2000; // Increased from 1000 to 2000 (+1 second)
break;
case 'hard':
spawnInterval = 500;
bubbleLifetime = 1000; // Increased from 500 to 1000 (+0.5 second)
break;
}
// Hide menu elements
titleText.visible = false;
difficultyText.visible = false;
easyButton.visible = false;
mediumButton.visible = false;
hardButton.visible = false;
bestScoreText.visible = false;
easyBestText.visible = false;
mediumBestText.visible = false;
hardBestText.visible = false;
// Show game UI
scoreText.visible = true;
timeText.visible = true;
crosshair.visible = true;
// Clear any existing bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
bubbles[i].destroy();
}
bubbles = [];
lastBubbleSpawn = 0;
scoreText.setText('Score: 0');
timeText.setText('Time: 60');
}
function endGame() {
gameState = 'gameover';
// Check and update best scores
var finalScore = LK.getScore();
var bestKey = 'best' + difficulty.charAt(0).toUpperCase() + difficulty.slice(1);
var currentBest = storage[bestKey] || 0;
if (finalScore > currentBest) {
storage[bestKey] = finalScore;
// Update display
switch (difficulty) {
case 'easy':
easyBestText.setText('Easy: ' + finalScore);
break;
case 'medium':
mediumBestText.setText('Medium: ' + finalScore);
break;
case 'hard':
hardBestText.setText('Hard: ' + finalScore);
break;
}
}
// Hide game UI
scoreText.visible = false;
timeText.visible = false;
crosshair.visible = false;
// Show menu elements
titleText.visible = true;
difficultyText.visible = true;
easyButton.visible = true;
mediumButton.visible = true;
hardButton.visible = true;
bestScoreText.visible = true;
easyBestText.visible = true;
mediumBestText.visible = true;
hardBestText.visible = true;
// Clear bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
bubbles[i].destroy();
}
bubbles = [];
LK.showGameOver();
}
function spawnBubble() {
var bubble = new Bubble();
bubble.setLifetime(bubbleLifetime);
// Random position with better mobile margins and avoid top-left menu area
var margin = 200; // Increased margin for mobile
var topMargin = 250; // Extra margin from top for UI
var bottomMargin = 200; // Margin from bottom for mobile navigation
bubble.x = margin + Math.random() * (2048 - 2 * margin);
bubble.y = topMargin + Math.random() * (2732 - topMargin - bottomMargin);
bubbles.push(bubble);
game.addChild(bubble);
}
// Game move handler for crosshair
game.move = function (x, y, obj) {
if (gameState === 'playing') {
crosshair.x = x;
crosshair.y = y;
}
};
// Game touch start handler for immediate crosshair positioning
game.down = function (x, y, obj) {
if (gameState === 'playing') {
crosshair.x = x;
crosshair.y = y;
}
};
// Main game update loop
game.update = function () {
if (gameState === 'playing') {
currentTime += 16.67; // Approximate frame time
// Update timer display
var remainingTime = Math.max(0, Math.ceil((gameTime - currentTime) / 1000));
timeText.setText('Time: ' + remainingTime);
// Check if game time is up
if (currentTime >= gameTime) {
endGame();
return;
}
// Spawn bubbles at intervals
if (currentTime - lastBubbleSpawn >= spawnInterval) {
spawnBubble();
lastBubbleSpawn = currentTime;
}
// Update all bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
if (bubbles[i].isAlive) {
bubbles[i].update();
}
}
}
};