/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Balloon class var Balloon = Container.expand(function () { var self = Container.call(this); // Balloon color selection var colorIds = ['balloon_red', 'balloon_blue', 'balloon_green', 'balloon_yellow', 'balloon_purple']; var colorIdx = Math.floor(Math.random() * colorIds.length); var balloonAsset = self.attachAsset(colorIds[colorIdx], { anchorX: 0.5, anchorY: 0.8 // anchor near bottom for more natural float }); // Pop effect (hidden by default) var popEffect = self.attachAsset('pop_effect', { anchorX: 0.5, anchorY: 0.8, alpha: 0 }); // Balloon speed (randomized a bit) self.speed = 2.2 + Math.random() * 1.2; // px per frame // Slight horizontal drift self.drift = (Math.random() - 0.5) * 1.2; // px per frame // Used to prevent double pop self.popped = false; // Pop animation self.pop = function (_onFinish) { if (self.popped) return; self.popped = true; // Hide balloon, show pop effect balloonAsset.alpha = 0; popEffect.alpha = 1; // Animate pop effect: scale up and fade out popEffect.scaleX = 1; popEffect.scaleY = 1; tween(popEffect, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); // Play pop sound LK.getSound('pop').play(); }; // Update method self.update = function () { // Balloons do not move if (self.popped) return; }; return self; }); // Shot class var Shot = Container.expand(function () { var self = Container.call(this); var shotAsset = self.attachAsset('shot', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 32; // px per frame, fast self.update = function () { self.y -= self.speed; }; return self; }); // Sight class (movable aim reticle) var Sight = Container.expand(function () { var self = Container.call(this); var sightAsset = self.attachAsset('sight', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); // Animate a slight scale pulse for feedback self.pulse = function () { tween.stop(self, { scaleX: true, scaleY: true }); self.scaleX = 1; self.scaleY = 1; tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 80, easing: tween.easeIn }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ // Sound: pop // Shot: small yellow box, 32x32 px // Sight: white ring (ellipse, 120x120, with alpha) // Pop effect: white ellipse, 180x220 px (for burst animation) // Balloons: 5 colors, all ellipses, 160x200 px // --- Global variables --- var balloons = []; var shots = []; var sight = null; var scoreTxt = null; var escapedTxt = null; var score = 0; var escaped = 0; var maxEscaped = 5; var canShoot = true; // Prevent rapid fire var shootCooldown = 7; // frames between shots var shootTimer = 0; // --- Setup --- // Add sight to game, center initially sight = new Sight(); game.addChild(sight); sight.x = 2048 / 2; sight.y = 2732 * 0.7; // Add score text to gui.top (centered) scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Add escaped text to gui.topRight (shows "Missed: X/5" for missed shots) escapedTxt = new Text2('Missed: 0/5', { size: 70, fill: 0xFFF8E1 }); escapedTxt.anchor.set(1, 0); LK.gui.topRight.addChild(escapedTxt); // --- Timer for 20 seconds --- var timeLimit = 20; // seconds var timeLeft = timeLimit; var timerTxt = new Text2('20', { size: 90, fill: 0xFFD700 }); timerTxt.anchor.set(0.5, 0); LK.gui.top.addChild(timerTxt); // Place timer to the left of score, but not in top left (menu) timerTxt.x = 2048 * 0.25; timerTxt.y = 0; // Timer interval (every 1000ms) var timerInterval = LK.setInterval(function () { timeLeft--; timerTxt.setText(timeLeft); if (timeLeft <= 0) { LK.clearInterval(timerInterval); LK.showGameOver(); } }, 1000); // --- Spawn Balloons --- function spawnBalloons() { // 10 balloons, random x, y anywhere in the play area (not overlapping menu/edges) // Ensure balloons are fully visible by accounting for balloon size and anchor for (var i = 0; i < 10; i++) { var b = new Balloon(); // Get balloon asset size and anchor var balloonW = 160; var balloonH = 200; var anchorX = 0.5; var anchorY = 0.8; // x: avoid leftmost 100px (menu), and rightmost 100px, and keep balloon fully visible var minX = 120 + balloonW * anchorX; var maxX = 2048 - 120 - balloonW * (1 - anchorX); b.x = minX + Math.random() * (maxX - minX); // y: avoid top 120px (score), and bottom 120px, and keep balloon fully visible var minY = 120 + balloonH * anchorY; var maxY = 2732 - 120 - balloonH * (1 - anchorY); b.y = minY + Math.random() * (maxY - minY); balloons.push(b); game.addChild(b); } } spawnBalloons(); // --- Input Handling --- // Move sight with touch/mouse move function handleMove(x, y, obj) { // Clamp sight inside game area (avoid top 100px for score) var minX = 60, maxX = 2048 - 60; var minY = 120, maxY = 2732 - 60; sight.x = Math.max(minX, Math.min(maxX, x)); sight.y = Math.max(minY, Math.min(maxY, y)); } game.move = handleMove; // Shoot on down (tap/press) game.down = function (x, y, obj) { // Only allow shooting if not in cooldown if (!canShoot) return; canShoot = false; shootTimer = shootCooldown; // Move sight to tap position for instant feedback handleMove(x, y, obj); // Pulse sight sight.pulse(); // Create shot at sight position var s = new Shot(); s.x = sight.x; s.y = sight.y; shots.push(s); game.addChild(s); }; // Allow holding to shoot repeatedly (optional, but here: only tap to shoot) game.up = function (x, y, obj) { // No-op }; // --- Game Update Loop --- game.update = function () { // --- Shooting cooldown --- if (!canShoot) { shootTimer--; if (shootTimer <= 0) { canShoot = true; } } // --- Update shots --- for (var i = shots.length - 1; i >= 0; i--) { var s = shots[i]; s.update(); // Remove if off top if (s.y < -40) { s.destroy(); shots.splice(i, 1); // Count as missed shot escaped++; escapedTxt.setText('Missed: ' + escaped + '/5'); // End game if 5 missed if (escaped >= maxEscaped) { LK.showGameOver(); return; } continue; } // Check collision with balloons var hit = false; for (var j = balloons.length - 1; j >= 0; j--) { var b = balloons[j]; if (b.popped) continue; if (s.intersects(b)) { // Pop balloon b.pop(function () { // Remove balloon after pop anim b.destroy(); }); balloons.splice(j, 1); // Remove shot s.destroy(); shots.splice(i, 1); // Update score score++; scoreTxt.setText(score); hit = true; break; } } if (hit) continue; } // --- Update balloons --- for (var i = balloons.length - 1; i >= 0; i--) { var b = balloons[i]; b.update(); // Balloons never escape now; do not remove or count as missed } // --- Win condition: all balloons popped --- if (balloons.length === 0 && escaped < maxEscaped) { if (typeof timerInterval !== "undefined") LK.clearInterval(timerInterval); LK.showYouWin(); return; } // --- Lose condition: already handled in timer interval, but also clear timer if lost by missed shots --- if (escaped >= maxEscaped) { if (typeof timerInterval !== "undefined") LK.clearInterval(timerInterval); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Balloon class
var Balloon = Container.expand(function () {
var self = Container.call(this);
// Balloon color selection
var colorIds = ['balloon_red', 'balloon_blue', 'balloon_green', 'balloon_yellow', 'balloon_purple'];
var colorIdx = Math.floor(Math.random() * colorIds.length);
var balloonAsset = self.attachAsset(colorIds[colorIdx], {
anchorX: 0.5,
anchorY: 0.8 // anchor near bottom for more natural float
});
// Pop effect (hidden by default)
var popEffect = self.attachAsset('pop_effect', {
anchorX: 0.5,
anchorY: 0.8,
alpha: 0
});
// Balloon speed (randomized a bit)
self.speed = 2.2 + Math.random() * 1.2; // px per frame
// Slight horizontal drift
self.drift = (Math.random() - 0.5) * 1.2; // px per frame
// Used to prevent double pop
self.popped = false;
// Pop animation
self.pop = function (_onFinish) {
if (self.popped) return;
self.popped = true;
// Hide balloon, show pop effect
balloonAsset.alpha = 0;
popEffect.alpha = 1;
// Animate pop effect: scale up and fade out
popEffect.scaleX = 1;
popEffect.scaleY = 1;
tween(popEffect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (_onFinish) _onFinish();
}
});
// Play pop sound
LK.getSound('pop').play();
};
// Update method
self.update = function () {
// Balloons do not move
if (self.popped) return;
};
return self;
});
// Shot class
var Shot = Container.expand(function () {
var self = Container.call(this);
var shotAsset = self.attachAsset('shot', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 32; // px per frame, fast
self.update = function () {
self.y -= self.speed;
};
return self;
});
// Sight class (movable aim reticle)
var Sight = Container.expand(function () {
var self = Container.call(this);
var sightAsset = self.attachAsset('sight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
// Animate a slight scale pulse for feedback
self.pulse = function () {
tween.stop(self, {
scaleX: true,
scaleY: true
});
self.scaleX = 1;
self.scaleY = 1;
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.easeIn
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Sound: pop
// Shot: small yellow box, 32x32 px
// Sight: white ring (ellipse, 120x120, with alpha)
// Pop effect: white ellipse, 180x220 px (for burst animation)
// Balloons: 5 colors, all ellipses, 160x200 px
// --- Global variables ---
var balloons = [];
var shots = [];
var sight = null;
var scoreTxt = null;
var escapedTxt = null;
var score = 0;
var escaped = 0;
var maxEscaped = 5;
var canShoot = true; // Prevent rapid fire
var shootCooldown = 7; // frames between shots
var shootTimer = 0;
// --- Setup ---
// Add sight to game, center initially
sight = new Sight();
game.addChild(sight);
sight.x = 2048 / 2;
sight.y = 2732 * 0.7;
// Add score text to gui.top (centered)
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Add escaped text to gui.topRight (shows "Missed: X/5" for missed shots)
escapedTxt = new Text2('Missed: 0/5', {
size: 70,
fill: 0xFFF8E1
});
escapedTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(escapedTxt);
// --- Timer for 20 seconds ---
var timeLimit = 20; // seconds
var timeLeft = timeLimit;
var timerTxt = new Text2('20', {
size: 90,
fill: 0xFFD700
});
timerTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(timerTxt);
// Place timer to the left of score, but not in top left (menu)
timerTxt.x = 2048 * 0.25;
timerTxt.y = 0;
// Timer interval (every 1000ms)
var timerInterval = LK.setInterval(function () {
timeLeft--;
timerTxt.setText(timeLeft);
if (timeLeft <= 0) {
LK.clearInterval(timerInterval);
LK.showGameOver();
}
}, 1000);
// --- Spawn Balloons ---
function spawnBalloons() {
// 10 balloons, random x, y anywhere in the play area (not overlapping menu/edges)
// Ensure balloons are fully visible by accounting for balloon size and anchor
for (var i = 0; i < 10; i++) {
var b = new Balloon();
// Get balloon asset size and anchor
var balloonW = 160;
var balloonH = 200;
var anchorX = 0.5;
var anchorY = 0.8;
// x: avoid leftmost 100px (menu), and rightmost 100px, and keep balloon fully visible
var minX = 120 + balloonW * anchorX;
var maxX = 2048 - 120 - balloonW * (1 - anchorX);
b.x = minX + Math.random() * (maxX - minX);
// y: avoid top 120px (score), and bottom 120px, and keep balloon fully visible
var minY = 120 + balloonH * anchorY;
var maxY = 2732 - 120 - balloonH * (1 - anchorY);
b.y = minY + Math.random() * (maxY - minY);
balloons.push(b);
game.addChild(b);
}
}
spawnBalloons();
// --- Input Handling ---
// Move sight with touch/mouse move
function handleMove(x, y, obj) {
// Clamp sight inside game area (avoid top 100px for score)
var minX = 60,
maxX = 2048 - 60;
var minY = 120,
maxY = 2732 - 60;
sight.x = Math.max(minX, Math.min(maxX, x));
sight.y = Math.max(minY, Math.min(maxY, y));
}
game.move = handleMove;
// Shoot on down (tap/press)
game.down = function (x, y, obj) {
// Only allow shooting if not in cooldown
if (!canShoot) return;
canShoot = false;
shootTimer = shootCooldown;
// Move sight to tap position for instant feedback
handleMove(x, y, obj);
// Pulse sight
sight.pulse();
// Create shot at sight position
var s = new Shot();
s.x = sight.x;
s.y = sight.y;
shots.push(s);
game.addChild(s);
};
// Allow holding to shoot repeatedly (optional, but here: only tap to shoot)
game.up = function (x, y, obj) {
// No-op
};
// --- Game Update Loop ---
game.update = function () {
// --- Shooting cooldown ---
if (!canShoot) {
shootTimer--;
if (shootTimer <= 0) {
canShoot = true;
}
}
// --- Update shots ---
for (var i = shots.length - 1; i >= 0; i--) {
var s = shots[i];
s.update();
// Remove if off top
if (s.y < -40) {
s.destroy();
shots.splice(i, 1);
// Count as missed shot
escaped++;
escapedTxt.setText('Missed: ' + escaped + '/5');
// End game if 5 missed
if (escaped >= maxEscaped) {
LK.showGameOver();
return;
}
continue;
}
// Check collision with balloons
var hit = false;
for (var j = balloons.length - 1; j >= 0; j--) {
var b = balloons[j];
if (b.popped) continue;
if (s.intersects(b)) {
// Pop balloon
b.pop(function () {
// Remove balloon after pop anim
b.destroy();
});
balloons.splice(j, 1);
// Remove shot
s.destroy();
shots.splice(i, 1);
// Update score
score++;
scoreTxt.setText(score);
hit = true;
break;
}
}
if (hit) continue;
}
// --- Update balloons ---
for (var i = balloons.length - 1; i >= 0; i--) {
var b = balloons[i];
b.update();
// Balloons never escape now; do not remove or count as missed
}
// --- Win condition: all balloons popped ---
if (balloons.length === 0 && escaped < maxEscaped) {
if (typeof timerInterval !== "undefined") LK.clearInterval(timerInterval);
LK.showYouWin();
return;
}
// --- Lose condition: already handled in timer interval, but also clear timer if lost by missed shots ---
if (escaped >= maxEscaped) {
if (typeof timerInterval !== "undefined") LK.clearInterval(timerInterval);
}
};