/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Beat Particle
var BeatParticle = Container.expand(function () {
var self = Container.call(this);
var p = self.attachAsset('beatParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.alpha = 1;
self.scale = 1;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.scale += 0.02;
self.alpha -= 0.018;
p.scaleX = p.scaleY = self.scale;
if (self.alpha <= 0) {
self.destroyed = true;
}
};
return self;
});
// BreathBar: Visual breath meter with fill, danger, and update logic
var BreathBar = Container.expand(function () {
var self = Container.call(this);
// Background
var bg = self.attachAsset('breathBarBg', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 0
});
// Danger zone (red, bottom)
var danger = self.attachAsset('breathBarDanger', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 20
});
// Fill (cyan, dynamic)
var fill = self.attachAsset('breathBarFill', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 20
});
// Bar state
self.maxValue = 100;
self.value = 100;
self.drainPerTick = 0.18; // ~10s to empty
self.minValue = 0;
// Set fill height based on value
self.setValue = function (v) {
self.value = Math.max(self.minValue, Math.min(self.maxValue, v));
// Fill height: 0-660px, y=20
var fillHeight = Math.round(660 * (self.value / self.maxValue));
fill.height = fillHeight;
fill.y = 20 + (660 - fillHeight);
// Danger always at y=20, height=660
// Optionally, fade fill if low
if (self.value < 20) {
fill.alpha = 0.5 + 0.5 * (self.value / 20);
} else {
fill.alpha = 1;
}
};
// Drain bar per tick
self.update = function () {
self.setValue(self.value - self.drainPerTick);
};
// Refill by 5% (e.g. on tap)
self.refill = function () {
var increase = self.maxValue * 0.05;
self.setValue(self.value + increase);
};
// Is bar empty?
self.isEmpty = function () {
return self.value <= self.minValue + 0.01;
};
// Init
self.setValue(self.value);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a1a
});
/****
* Game Code
****/
// Music
// Sound
// Soundwave
// Slogan Neon Bar
// Beat Particle
// Music Note
// --- Breath Meter
// Red Tap Button
// Character: Big, cartoon beatboxer with headphones, hoodie, sneakers
// --- Neon Slogan at Top ---
function _typeof5(o) {
"@babel/helpers - typeof";
return _typeof5 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof5(o);
}
function _typeof4(o) {
"@babel/helpers - typeof";
return _typeof4 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof4(o);
}
function _typeof3(o) {
"@babel/helpers - typeof";
return _typeof3 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof3(o);
}
function _typeof2(o) {
"@babel/helpers - typeof";
return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof2(o);
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
// --- Static Background (behind all elements) ---
// Make sure the background fully covers the screen (2048x2732)
var beatboxBg = LK.getAsset('beatbox_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 2048,
height: 2732
});
game.addChild(beatboxBg);
var sloganText = new Text2('BEATBOX TAP STAR', {
size: 140,
fill: 0x00FFFF,
font: "Impact, 'Arial Black', Tahoma"
});
sloganText.anchor.set(0.5, 0.5);
sloganText.x = 2048 / 2;
sloganText.y = 130;
game.addChild(sloganText);
// --- Score Text (top right) ---
var scoreText = new Text2('0', {
size: 110,
fill: 0xffffff,
font: "Impact, 'Arial Black', Tahoma"
});
scoreText.anchor.set(1, 0); // right-top
scoreText.x = -10; // 10px from right edge
scoreText.y = 30; // 30px from top (no breathbar)
// Do not add to LK.gui.right here; will be added to game after breathBar is created
// --- Best Score Text (bottom left corner) ---
var bestScore = storage.bestScore || 0;
var bestScoreText = new Text2('BEST: ' + bestScore, {
size: 80,
fill: 0xffe066,
font: "Impact, 'Arial Black', Tahoma"
});
bestScoreText.anchor.set(0, 1); // left-bottom
bestScoreText.x = 40; // 40px from left edge
bestScoreText.y = 2732 - 40; // 40px from bottom edge
game.addChild(bestScoreText);
// --- Beatboxer Character (center stage) ---
// Character switching logic
// Character style 0: Blue square placeholder
var characterPlaceholder0 = LK.getAsset('characterPlaceholder', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350
});
// Character style 1: Pink ellipse placeholder
var characterPlaceholder1 = LK.getAsset('beatboxerHead', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350,
color: 0xff66cc
});
characterPlaceholder1.visible = false;
// Character style 2: Green box placeholder
var characterPlaceholder2 = LK.getAsset('beatParticle', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350,
color: 0x00ffcc
});
characterPlaceholder2.visible = false;
// Character style 3: Blue square placeholder (new)
var characterPlaceholder3 = LK.getAsset('characterPlaceholderSquare', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350
});
characterPlaceholder3.visible = false;
game.addChild(characterPlaceholder0);
game.addChild(characterPlaceholder1);
game.addChild(characterPlaceholder2);
game.addChild(characterPlaceholder3);
// Track which character is currently shown (0, 1, 2, 3)
var characterStyle = 0;
var characterList = [characterPlaceholder0, characterPlaceholder1, characterPlaceholder2, characterPlaceholder3];
// --- Beatboxer character instance removed as per instructions ---
// --- Tap Button (bottom center) ---
var tapButton = LK.getAsset('tapButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2350 //{1U} // moved up by 150px
});
game.addChild(tapButton);
// --- Animated Background Particles ---
var beatParticles = [];
// --- Game State ---
var score = 0;
var isGameOver = false;
var canTap = true;
var lastTapTick = 0;
// Remove tap count and speedup logic; breathBar is always fast
// --- Nickname and Leaderboard ---
var nickname = storage.nickname || null;
var leaderboard = storage.leaderboard || [];
var leaderboardText = null;
// --- Nickname Display (under sloganText, small font) ---
var nicknameDisplayText = null;
function updateNicknameDisplay() {
if (!nicknameDisplayText) {
nicknameDisplayText = new Text2('', {
size: 90,
fill: 0xcccccc,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
nicknameDisplayText.anchor.set(0.5, 0);
nicknameDisplayText.x = 2048 / 2;
nicknameDisplayText.y = sloganText.y + sloganText.height / 2 + 10;
game.addChild(nicknameDisplayText);
}
nicknameDisplayText.setText(nickname ? nickname : '');
nicknameDisplayText.visible = !!nickname;
}
// --- Nickname Overlay (semi-transparent black) ---
var nicknameOverlay = null;
// --- Nickname Animation ---
var nicknameAnimText = null;
var nicknameAnimTimer = null;
var nicknameAnimTicks = 0;
var nicknameAnimBase = "BeatBoxer";
var nicknameAnimCurrent = "";
var nicknameAnimDone = false;
// Assign a random nickname and animate it
function assignRandomNicknameAndShow() {
// If already set, skip
if (nickname) return;
nicknameAnimTicks = 0;
nicknameAnimDone = false;
// Create overlay (if not present)
if (!nicknameOverlay) {
nicknameOverlay = LK.getAsset('breathBarBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
color: 0x000000
});
nicknameOverlay.alpha = 0.7;
nicknameOverlay.interactive = false;
game.addChild(nicknameOverlay);
} else {
nicknameOverlay.visible = true;
}
// Create animated nickname text
if (nicknameAnimText) {
nicknameAnimText.destroy();
nicknameAnimText = null;
}
nicknameAnimText = new Text2("YOUR NICKNAME IS ...", {
size: 160,
fill: 0xffffff,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
nicknameAnimText.anchor.set(0.5, 0.5);
nicknameAnimText.x = 2048 / 2;
nicknameAnimText.y = 700;
game.addChild(nicknameAnimText);
// Animate the number rolling for 1.2 seconds, then pick a random number
nicknameAnimTimer = LK.setInterval(function () {
nicknameAnimTicks++;
// Animate number from 01 to 99
var num = Math.floor(Math.random() * 99) + 1;
var numStr = (num < 10 ? "0" : "") + num;
nicknameAnimCurrent = nicknameAnimBase + numStr;
nicknameAnimText.setText("YOUR NICKNAME IS\n" + nicknameAnimCurrent);
// After 1.2 seconds (about 18 frames), finalize nickname
if (nicknameAnimTicks > 18) {
LK.clearInterval(nicknameAnimTimer);
nicknameAnimTimer = null;
// Final random number
var finalNum = Math.floor(Math.random() * 99) + 1;
var finalNumStr = (finalNum < 10 ? "0" : "") + finalNum;
nickname = nicknameAnimBase + finalNumStr;
storage.nickname = nickname;
nicknameAnimText.setText("YOUR NICKNAME IS\n" + nickname);
nicknameAnimDone = true;
// Remove after 1.2s
LK.setTimeout(function () {
if (nicknameAnimText) {
nicknameAnimText.destroy();
nicknameAnimText = null;
}
updateNicknameDisplay();
gamePausedForNickname = false;
}, 1200);
}
}, 65);
// Pause game logic until nickname is set
gamePausedForNickname = true;
}
// On first load, always clear storage and assign new nickname, then show Ready button
function resetAndAssignNickname() {
// Clear storage for nickname and leaderboard
storage.nickname = null;
storage.leaderboard = [];
nickname = null;
leaderboard = [];
// Remove any previous nickname text
if (nicknameAnimText) {
nicknameAnimText.destroy();
nicknameAnimText = null;
}
// Remove nickname display if present
if (nicknameDisplayText) {
nicknameDisplayText.destroy();
nicknameDisplayText = null;
}
// Assign new nickname with animation
assignRandomNicknameAndShow();
// After nickname animation is done, show Ready button
function showReadyButtonWhenDone() {
if (nicknameAnimDone) {
// Show Ready button
if (window.readyBtn) {
window.readyBtn.destroy();
window.readyBtn = null;
}
var readyBtn = new Text2("READY", {
size: 140,
fill: 0x00ffcc,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
readyBtn.anchor.set(0.5, 0.5);
readyBtn.x = 2048 / 2;
readyBtn.y = 1100;
game.addChild(readyBtn);
window.readyBtn = readyBtn;
readyBtn.interactive = true;
readyBtn.buttonMode = true;
readyBtn.down = function (x, y, obj) {
// Remove button, unpause game
if (window.readyBtn) {
window.readyBtn.destroy();
window.readyBtn = null;
}
// Hide overlay when Ready is pressed
if (nicknameOverlay) {
nicknameOverlay.visible = false;
}
gamePausedForNickname = false;
// Start background music when Ready is pressed
// (Moved to first tap on red button)
// Create and show breathBar (if not already)
if (!breathBar) {
breathBar = new BreathBar();
// Set fast drain from the start
breathBar.drainPerTick = 0.36; // double the original speed
// Place at right edge, below top margin (avoid top right 100x100)
// breathBar.width is 80, so anchorX:0.5 means center is at x
// Place 80px from right, 160px from top
breathBar.x = 2048 - 80;
breathBar.y = 160;
game.addChild(breathBar);
// Move scoreText below breathBar
// scoreText is already created above, just move it
scoreText.anchor.set(0.5, 0); // center-top
scoreText.x = 2048 - 80; // align with breathBar.x
scoreText.y = breathBar.y + 700 + 30; // breathBar height (700) + 30px margin
// Remove from LK.gui.right if present, add to game
if (scoreText.parent && scoreText.parent.removeChild) {
scoreText.parent.removeChild(scoreText);
}
game.addChild(scoreText);
}
};
} else {
LK.setTimeout(showReadyButtonWhenDone, 100);
}
}
showReadyButtonWhenDone();
// Pause game logic until nickname is set and Ready pressed
gamePausedForNickname = true;
}
resetAndAssignNickname();
function showLeaderboard() {
if (leaderboardText) {
leaderboardText.destroy();
leaderboardText = null;
}
var sorted = leaderboard.slice().sort(function (a, b) {
return b.score - a.score;
}).slice(0, 10);
var lines = ['🏆 TOP SCORES 🏆'];
for (var i = 0; i < sorted.length; i++) {
lines.push(i + 1 + '. ' + sorted[i].nickname + ' - ' + sorted[i].score);
}
leaderboardText = new Text2(lines.join('\n'), {
size: 80,
fill: 0xffff00,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
leaderboardText.anchor.set(0.5, 0.5);
leaderboardText.x = 2048 / 2;
leaderboardText.y = 2732 / 2;
game.addChild(leaderboardText);
// Remove leaderboard after 3 seconds
LK.setTimeout(function () {
if (leaderboardText) {
leaderboardText.destroy();
leaderboardText = null;
}
}, 3000);
}
// On first load, ask for nickname if not set
var gamePausedForNickname = false;
// --- BreathBar instance (created after Ready) ---
var breathBar = null;
// --- Helper: Animate Tap Button Glow ---
function animateTapButtonGlow() {
// Button press animation: scale down then up
tween(tapButton, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(tapButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
// --- Helper: Add Beat Particle ---
function spawnBeatParticle() {
var p = new BeatParticle();
p.x = 2048 / 2;
p.y = 1200 - 220;
p.vx = (Math.random() - 0.5) * 1.5;
p.vy = -1.5 - Math.random() * 1.5;
p.alpha = 0.8 + Math.random() * 0.2;
beatParticles.push(p);
game.addChild(p);
}
// --- Tap Button Handler ---
function handleTap(x, y, obj) {
if (isGameOver || !canTap) {
return;
}
// Only register tap if inside button
var dx = x - tapButton.x;
var dy = y - tapButton.y;
var r = tapButton.width / 2;
if (dx * dx + dy * dy > r * r) {
return;
}
// Tap registered!
canTap = false;
lastTapTick = LK.ticks;
// Animate
animateTapButtonGlow();
spawnBeatParticle();
// Sound effects removed; only background music remains
// Refill breathBar on tap
if (breathBar) {
breathBar.refill();
}
// Update score and UI
score++;
scoreText.setText(score);
// --- Motivational Text Animation ---
if (score === 20 || score === 50 || score === 80) {
var msg = "";
if (score === 20) msg = "SUPER!";
if (score === 50) msg = "EXCELLENT!";
if (score === 80) msg = "FANTASTIC!";
var motivText = new Text2(msg, {
size: 200,
fill: 0xffff00,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
motivText.anchor.set(0.5, 0.5);
motivText.x = 2048 / 2;
motivText.y = 900;
motivText.alpha = 0.0;
game.addChild(motivText);
// Animate: fade in, scale up, then fade out
motivText.scaleX = motivText.scaleY = 0.7;
tween(motivText, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 220,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(motivText, {
alpha: 0,
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 600,
easing: tween.cubicIn,
onFinish: function onFinish() {
motivText.destroy();
}
});
}
});
}
// No game over at 20, 50, or 80 taps; game continues
// Switch character: loop through 3 styles
// Hide all, show next
characterList[characterStyle].visible = false;
characterStyle = (characterStyle + 1) % characterList.length;
characterList[characterStyle].visible = true;
// Animate if Beatboxer
// (No Beatboxer instance present, so no animation triggered here)
// Win condition removed for endless play
}
// --- Tap Button Down/Up/Move ---
var musicStarted = false; // Track if music has started
game.down = function (x, y, obj) {
if (gamePausedForNickname) return;
// Block all background taps if Ready button is visible
if (window.readyBtn) return;
// Start background music on first tap of red button
if (!musicStarted) {
LK.playMusic('neonbeat', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
musicStarted = true;
}
handleTap(x, y, obj);
};
game.move = function (x, y, obj) {
if (gamePausedForNickname) return;
// Block all background move events if Ready button is visible
if (window.readyBtn) return;
// No drag needed, but allow repeated tap
if (!canTap && LK.ticks - lastTapTick > 7) {
canTap = true;
}
};
game.up = function (x, y, obj) {
if (gamePausedForNickname) return;
// Block all background up events if Ready button is visible
if (window.readyBtn) return;
// Allow next tap
canTap = true;
};
// --- Main Game Update ---
game.update = function () {
if (isGameOver || gamePausedForNickname) {
// Still allow sloganText and background particles to animate
// Animate sloganText neon pulse
sloganText.alpha = 0.85 + 0.15 * Math.sin(LK.ticks * 0.08);
sloganText.scaleX = sloganText.scaleY = 1 + 0.03 * Math.sin(LK.ticks * 0.07);
// Animate background: spawn beat particles
if (LK.ticks % 30 === 0) {
spawnBeatParticle();
}
// Update and cleanup beat particles
for (var i = beatParticles.length - 1; i >= 0; i--) {
var p = beatParticles[i];
p.update();
if (p.destroyed) {
p.destroy();
beatParticles.splice(i, 1);
}
}
// Prevent breathbar from draining or updating while waiting for Ready
return;
}
// --- BreathBar logic (drain, refill, game over) ---
if (breathBar) {
breathBar.update();
if (breathBar.isEmpty() && !isGameOver) {
isGameOver = true;
// Stop background music when breathBar is empty
LK.stopMusic();
// Update best score if needed
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
if (bestScoreText) bestScoreText.setText('BEST: ' + bestScore);
}
// Show overlay background behind score
var scoreOverlay = LK.getAsset('breathBarBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
color: 0x000000
});
scoreOverlay.alpha = 0.7;
scoreOverlay.interactive = false;
game.addChild(scoreOverlay);
// Show "Your Score Is" and the score in the center, but a bit higher
var bigScoreText = new Text2("Your Score Is\n" + score, {
size: 220,
fill: 0xffff00,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
bigScoreText.anchor.set(0.5, 0.5);
bigScoreText.x = 2048 / 2;
// Move further up (higher on the screen)
bigScoreText.y = 800;
game.addChild(bigScoreText);
// Animate: fade in, scale up, then stay visible (do not fade out or destroy)
bigScoreText.alpha = 0.0;
bigScoreText.scaleX = bigScoreText.scaleY = 0.7;
tween(bigScoreText, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 420,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Do not fade out or destroy, keep on screen
// Remove overlay and show game over after a short delay, but keep bigScoreText
LK.setTimeout(function () {
if (scoreOverlay) scoreOverlay.destroy();
LK.showGameOver();
}, 1500);
}
});
// Optionally, flash bar red
tween(breathBar, {
alpha: 0.2
}, {
duration: 200,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
breathBar.alpha = 1;
}
});
}
}
// Animate sloganText neon pulse
sloganText.alpha = 0.85 + 0.15 * Math.sin(LK.ticks * 0.08);
sloganText.scaleX = sloganText.scaleY = 1 + 0.03 * Math.sin(LK.ticks * 0.07);
// Animate background: spawn beat particles
if (LK.ticks % 30 === 0) {
spawnBeatParticle();
}
// Update and cleanup beat particles
for (var i = beatParticles.length - 1; i >= 0; i--) {
var p = beatParticles[i];
p.update();
if (p.destroyed) {
p.destroy();
beatParticles.splice(i, 1);
}
}
};
// --- Play Music ---
// (Music now starts after Ready is pressed, see readyBtn.down handler) /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Beat Particle
var BeatParticle = Container.expand(function () {
var self = Container.call(this);
var p = self.attachAsset('beatParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.alpha = 1;
self.scale = 1;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.scale += 0.02;
self.alpha -= 0.018;
p.scaleX = p.scaleY = self.scale;
if (self.alpha <= 0) {
self.destroyed = true;
}
};
return self;
});
// BreathBar: Visual breath meter with fill, danger, and update logic
var BreathBar = Container.expand(function () {
var self = Container.call(this);
// Background
var bg = self.attachAsset('breathBarBg', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 0
});
// Danger zone (red, bottom)
var danger = self.attachAsset('breathBarDanger', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 20
});
// Fill (cyan, dynamic)
var fill = self.attachAsset('breathBarFill', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 20
});
// Bar state
self.maxValue = 100;
self.value = 100;
self.drainPerTick = 0.18; // ~10s to empty
self.minValue = 0;
// Set fill height based on value
self.setValue = function (v) {
self.value = Math.max(self.minValue, Math.min(self.maxValue, v));
// Fill height: 0-660px, y=20
var fillHeight = Math.round(660 * (self.value / self.maxValue));
fill.height = fillHeight;
fill.y = 20 + (660 - fillHeight);
// Danger always at y=20, height=660
// Optionally, fade fill if low
if (self.value < 20) {
fill.alpha = 0.5 + 0.5 * (self.value / 20);
} else {
fill.alpha = 1;
}
};
// Drain bar per tick
self.update = function () {
self.setValue(self.value - self.drainPerTick);
};
// Refill by 5% (e.g. on tap)
self.refill = function () {
var increase = self.maxValue * 0.05;
self.setValue(self.value + increase);
};
// Is bar empty?
self.isEmpty = function () {
return self.value <= self.minValue + 0.01;
};
// Init
self.setValue(self.value);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a1a
});
/****
* Game Code
****/
// Music
// Sound
// Soundwave
// Slogan Neon Bar
// Beat Particle
// Music Note
// --- Breath Meter
// Red Tap Button
// Character: Big, cartoon beatboxer with headphones, hoodie, sneakers
// --- Neon Slogan at Top ---
function _typeof5(o) {
"@babel/helpers - typeof";
return _typeof5 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof5(o);
}
function _typeof4(o) {
"@babel/helpers - typeof";
return _typeof4 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof4(o);
}
function _typeof3(o) {
"@babel/helpers - typeof";
return _typeof3 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof3(o);
}
function _typeof2(o) {
"@babel/helpers - typeof";
return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof2(o);
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
// --- Static Background (behind all elements) ---
// Make sure the background fully covers the screen (2048x2732)
var beatboxBg = LK.getAsset('beatbox_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 2048,
height: 2732
});
game.addChild(beatboxBg);
var sloganText = new Text2('BEATBOX TAP STAR', {
size: 140,
fill: 0x00FFFF,
font: "Impact, 'Arial Black', Tahoma"
});
sloganText.anchor.set(0.5, 0.5);
sloganText.x = 2048 / 2;
sloganText.y = 130;
game.addChild(sloganText);
// --- Score Text (top right) ---
var scoreText = new Text2('0', {
size: 110,
fill: 0xffffff,
font: "Impact, 'Arial Black', Tahoma"
});
scoreText.anchor.set(1, 0); // right-top
scoreText.x = -10; // 10px from right edge
scoreText.y = 30; // 30px from top (no breathbar)
// Do not add to LK.gui.right here; will be added to game after breathBar is created
// --- Best Score Text (bottom left corner) ---
var bestScore = storage.bestScore || 0;
var bestScoreText = new Text2('BEST: ' + bestScore, {
size: 80,
fill: 0xffe066,
font: "Impact, 'Arial Black', Tahoma"
});
bestScoreText.anchor.set(0, 1); // left-bottom
bestScoreText.x = 40; // 40px from left edge
bestScoreText.y = 2732 - 40; // 40px from bottom edge
game.addChild(bestScoreText);
// --- Beatboxer Character (center stage) ---
// Character switching logic
// Character style 0: Blue square placeholder
var characterPlaceholder0 = LK.getAsset('characterPlaceholder', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350
});
// Character style 1: Pink ellipse placeholder
var characterPlaceholder1 = LK.getAsset('beatboxerHead', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350,
color: 0xff66cc
});
characterPlaceholder1.visible = false;
// Character style 2: Green box placeholder
var characterPlaceholder2 = LK.getAsset('beatParticle', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350,
color: 0x00ffcc
});
characterPlaceholder2.visible = false;
// Character style 3: Blue square placeholder (new)
var characterPlaceholder3 = LK.getAsset('characterPlaceholderSquare', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2350
});
characterPlaceholder3.visible = false;
game.addChild(characterPlaceholder0);
game.addChild(characterPlaceholder1);
game.addChild(characterPlaceholder2);
game.addChild(characterPlaceholder3);
// Track which character is currently shown (0, 1, 2, 3)
var characterStyle = 0;
var characterList = [characterPlaceholder0, characterPlaceholder1, characterPlaceholder2, characterPlaceholder3];
// --- Beatboxer character instance removed as per instructions ---
// --- Tap Button (bottom center) ---
var tapButton = LK.getAsset('tapButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2350 //{1U} // moved up by 150px
});
game.addChild(tapButton);
// --- Animated Background Particles ---
var beatParticles = [];
// --- Game State ---
var score = 0;
var isGameOver = false;
var canTap = true;
var lastTapTick = 0;
// Remove tap count and speedup logic; breathBar is always fast
// --- Nickname and Leaderboard ---
var nickname = storage.nickname || null;
var leaderboard = storage.leaderboard || [];
var leaderboardText = null;
// --- Nickname Display (under sloganText, small font) ---
var nicknameDisplayText = null;
function updateNicknameDisplay() {
if (!nicknameDisplayText) {
nicknameDisplayText = new Text2('', {
size: 90,
fill: 0xcccccc,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
nicknameDisplayText.anchor.set(0.5, 0);
nicknameDisplayText.x = 2048 / 2;
nicknameDisplayText.y = sloganText.y + sloganText.height / 2 + 10;
game.addChild(nicknameDisplayText);
}
nicknameDisplayText.setText(nickname ? nickname : '');
nicknameDisplayText.visible = !!nickname;
}
// --- Nickname Overlay (semi-transparent black) ---
var nicknameOverlay = null;
// --- Nickname Animation ---
var nicknameAnimText = null;
var nicknameAnimTimer = null;
var nicknameAnimTicks = 0;
var nicknameAnimBase = "BeatBoxer";
var nicknameAnimCurrent = "";
var nicknameAnimDone = false;
// Assign a random nickname and animate it
function assignRandomNicknameAndShow() {
// If already set, skip
if (nickname) return;
nicknameAnimTicks = 0;
nicknameAnimDone = false;
// Create overlay (if not present)
if (!nicknameOverlay) {
nicknameOverlay = LK.getAsset('breathBarBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
color: 0x000000
});
nicknameOverlay.alpha = 0.7;
nicknameOverlay.interactive = false;
game.addChild(nicknameOverlay);
} else {
nicknameOverlay.visible = true;
}
// Create animated nickname text
if (nicknameAnimText) {
nicknameAnimText.destroy();
nicknameAnimText = null;
}
nicknameAnimText = new Text2("YOUR NICKNAME IS ...", {
size: 160,
fill: 0xffffff,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
nicknameAnimText.anchor.set(0.5, 0.5);
nicknameAnimText.x = 2048 / 2;
nicknameAnimText.y = 700;
game.addChild(nicknameAnimText);
// Animate the number rolling for 1.2 seconds, then pick a random number
nicknameAnimTimer = LK.setInterval(function () {
nicknameAnimTicks++;
// Animate number from 01 to 99
var num = Math.floor(Math.random() * 99) + 1;
var numStr = (num < 10 ? "0" : "") + num;
nicknameAnimCurrent = nicknameAnimBase + numStr;
nicknameAnimText.setText("YOUR NICKNAME IS\n" + nicknameAnimCurrent);
// After 1.2 seconds (about 18 frames), finalize nickname
if (nicknameAnimTicks > 18) {
LK.clearInterval(nicknameAnimTimer);
nicknameAnimTimer = null;
// Final random number
var finalNum = Math.floor(Math.random() * 99) + 1;
var finalNumStr = (finalNum < 10 ? "0" : "") + finalNum;
nickname = nicknameAnimBase + finalNumStr;
storage.nickname = nickname;
nicknameAnimText.setText("YOUR NICKNAME IS\n" + nickname);
nicknameAnimDone = true;
// Remove after 1.2s
LK.setTimeout(function () {
if (nicknameAnimText) {
nicknameAnimText.destroy();
nicknameAnimText = null;
}
updateNicknameDisplay();
gamePausedForNickname = false;
}, 1200);
}
}, 65);
// Pause game logic until nickname is set
gamePausedForNickname = true;
}
// On first load, always clear storage and assign new nickname, then show Ready button
function resetAndAssignNickname() {
// Clear storage for nickname and leaderboard
storage.nickname = null;
storage.leaderboard = [];
nickname = null;
leaderboard = [];
// Remove any previous nickname text
if (nicknameAnimText) {
nicknameAnimText.destroy();
nicknameAnimText = null;
}
// Remove nickname display if present
if (nicknameDisplayText) {
nicknameDisplayText.destroy();
nicknameDisplayText = null;
}
// Assign new nickname with animation
assignRandomNicknameAndShow();
// After nickname animation is done, show Ready button
function showReadyButtonWhenDone() {
if (nicknameAnimDone) {
// Show Ready button
if (window.readyBtn) {
window.readyBtn.destroy();
window.readyBtn = null;
}
var readyBtn = new Text2("READY", {
size: 140,
fill: 0x00ffcc,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
readyBtn.anchor.set(0.5, 0.5);
readyBtn.x = 2048 / 2;
readyBtn.y = 1100;
game.addChild(readyBtn);
window.readyBtn = readyBtn;
readyBtn.interactive = true;
readyBtn.buttonMode = true;
readyBtn.down = function (x, y, obj) {
// Remove button, unpause game
if (window.readyBtn) {
window.readyBtn.destroy();
window.readyBtn = null;
}
// Hide overlay when Ready is pressed
if (nicknameOverlay) {
nicknameOverlay.visible = false;
}
gamePausedForNickname = false;
// Start background music when Ready is pressed
// (Moved to first tap on red button)
// Create and show breathBar (if not already)
if (!breathBar) {
breathBar = new BreathBar();
// Set fast drain from the start
breathBar.drainPerTick = 0.36; // double the original speed
// Place at right edge, below top margin (avoid top right 100x100)
// breathBar.width is 80, so anchorX:0.5 means center is at x
// Place 80px from right, 160px from top
breathBar.x = 2048 - 80;
breathBar.y = 160;
game.addChild(breathBar);
// Move scoreText below breathBar
// scoreText is already created above, just move it
scoreText.anchor.set(0.5, 0); // center-top
scoreText.x = 2048 - 80; // align with breathBar.x
scoreText.y = breathBar.y + 700 + 30; // breathBar height (700) + 30px margin
// Remove from LK.gui.right if present, add to game
if (scoreText.parent && scoreText.parent.removeChild) {
scoreText.parent.removeChild(scoreText);
}
game.addChild(scoreText);
}
};
} else {
LK.setTimeout(showReadyButtonWhenDone, 100);
}
}
showReadyButtonWhenDone();
// Pause game logic until nickname is set and Ready pressed
gamePausedForNickname = true;
}
resetAndAssignNickname();
function showLeaderboard() {
if (leaderboardText) {
leaderboardText.destroy();
leaderboardText = null;
}
var sorted = leaderboard.slice().sort(function (a, b) {
return b.score - a.score;
}).slice(0, 10);
var lines = ['🏆 TOP SCORES 🏆'];
for (var i = 0; i < sorted.length; i++) {
lines.push(i + 1 + '. ' + sorted[i].nickname + ' - ' + sorted[i].score);
}
leaderboardText = new Text2(lines.join('\n'), {
size: 80,
fill: 0xffff00,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
leaderboardText.anchor.set(0.5, 0.5);
leaderboardText.x = 2048 / 2;
leaderboardText.y = 2732 / 2;
game.addChild(leaderboardText);
// Remove leaderboard after 3 seconds
LK.setTimeout(function () {
if (leaderboardText) {
leaderboardText.destroy();
leaderboardText = null;
}
}, 3000);
}
// On first load, ask for nickname if not set
var gamePausedForNickname = false;
// --- BreathBar instance (created after Ready) ---
var breathBar = null;
// --- Helper: Animate Tap Button Glow ---
function animateTapButtonGlow() {
// Button press animation: scale down then up
tween(tapButton, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(tapButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
// --- Helper: Add Beat Particle ---
function spawnBeatParticle() {
var p = new BeatParticle();
p.x = 2048 / 2;
p.y = 1200 - 220;
p.vx = (Math.random() - 0.5) * 1.5;
p.vy = -1.5 - Math.random() * 1.5;
p.alpha = 0.8 + Math.random() * 0.2;
beatParticles.push(p);
game.addChild(p);
}
// --- Tap Button Handler ---
function handleTap(x, y, obj) {
if (isGameOver || !canTap) {
return;
}
// Only register tap if inside button
var dx = x - tapButton.x;
var dy = y - tapButton.y;
var r = tapButton.width / 2;
if (dx * dx + dy * dy > r * r) {
return;
}
// Tap registered!
canTap = false;
lastTapTick = LK.ticks;
// Animate
animateTapButtonGlow();
spawnBeatParticle();
// Sound effects removed; only background music remains
// Refill breathBar on tap
if (breathBar) {
breathBar.refill();
}
// Update score and UI
score++;
scoreText.setText(score);
// --- Motivational Text Animation ---
if (score === 20 || score === 50 || score === 80) {
var msg = "";
if (score === 20) msg = "SUPER!";
if (score === 50) msg = "EXCELLENT!";
if (score === 80) msg = "FANTASTIC!";
var motivText = new Text2(msg, {
size: 200,
fill: 0xffff00,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
motivText.anchor.set(0.5, 0.5);
motivText.x = 2048 / 2;
motivText.y = 900;
motivText.alpha = 0.0;
game.addChild(motivText);
// Animate: fade in, scale up, then fade out
motivText.scaleX = motivText.scaleY = 0.7;
tween(motivText, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 220,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(motivText, {
alpha: 0,
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 600,
easing: tween.cubicIn,
onFinish: function onFinish() {
motivText.destroy();
}
});
}
});
}
// No game over at 20, 50, or 80 taps; game continues
// Switch character: loop through 3 styles
// Hide all, show next
characterList[characterStyle].visible = false;
characterStyle = (characterStyle + 1) % characterList.length;
characterList[characterStyle].visible = true;
// Animate if Beatboxer
// (No Beatboxer instance present, so no animation triggered here)
// Win condition removed for endless play
}
// --- Tap Button Down/Up/Move ---
var musicStarted = false; // Track if music has started
game.down = function (x, y, obj) {
if (gamePausedForNickname) return;
// Block all background taps if Ready button is visible
if (window.readyBtn) return;
// Start background music on first tap of red button
if (!musicStarted) {
LK.playMusic('neonbeat', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
musicStarted = true;
}
handleTap(x, y, obj);
};
game.move = function (x, y, obj) {
if (gamePausedForNickname) return;
// Block all background move events if Ready button is visible
if (window.readyBtn) return;
// No drag needed, but allow repeated tap
if (!canTap && LK.ticks - lastTapTick > 7) {
canTap = true;
}
};
game.up = function (x, y, obj) {
if (gamePausedForNickname) return;
// Block all background up events if Ready button is visible
if (window.readyBtn) return;
// Allow next tap
canTap = true;
};
// --- Main Game Update ---
game.update = function () {
if (isGameOver || gamePausedForNickname) {
// Still allow sloganText and background particles to animate
// Animate sloganText neon pulse
sloganText.alpha = 0.85 + 0.15 * Math.sin(LK.ticks * 0.08);
sloganText.scaleX = sloganText.scaleY = 1 + 0.03 * Math.sin(LK.ticks * 0.07);
// Animate background: spawn beat particles
if (LK.ticks % 30 === 0) {
spawnBeatParticle();
}
// Update and cleanup beat particles
for (var i = beatParticles.length - 1; i >= 0; i--) {
var p = beatParticles[i];
p.update();
if (p.destroyed) {
p.destroy();
beatParticles.splice(i, 1);
}
}
// Prevent breathbar from draining or updating while waiting for Ready
return;
}
// --- BreathBar logic (drain, refill, game over) ---
if (breathBar) {
breathBar.update();
if (breathBar.isEmpty() && !isGameOver) {
isGameOver = true;
// Stop background music when breathBar is empty
LK.stopMusic();
// Update best score if needed
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
if (bestScoreText) bestScoreText.setText('BEST: ' + bestScore);
}
// Show overlay background behind score
var scoreOverlay = LK.getAsset('breathBarBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
color: 0x000000
});
scoreOverlay.alpha = 0.7;
scoreOverlay.interactive = false;
game.addChild(scoreOverlay);
// Show "Your Score Is" and the score in the center, but a bit higher
var bigScoreText = new Text2("Your Score Is\n" + score, {
size: 220,
fill: 0xffff00,
font: "Impact, 'Arial Black', Tahoma",
align: 'center'
});
bigScoreText.anchor.set(0.5, 0.5);
bigScoreText.x = 2048 / 2;
// Move further up (higher on the screen)
bigScoreText.y = 800;
game.addChild(bigScoreText);
// Animate: fade in, scale up, then stay visible (do not fade out or destroy)
bigScoreText.alpha = 0.0;
bigScoreText.scaleX = bigScoreText.scaleY = 0.7;
tween(bigScoreText, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 420,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Do not fade out or destroy, keep on screen
// Remove overlay and show game over after a short delay, but keep bigScoreText
LK.setTimeout(function () {
if (scoreOverlay) scoreOverlay.destroy();
LK.showGameOver();
}, 1500);
}
});
// Optionally, flash bar red
tween(breathBar, {
alpha: 0.2
}, {
duration: 200,
yoyo: true,
repeat: 2,
onFinish: function onFinish() {
breathBar.alpha = 1;
}
});
}
}
// Animate sloganText neon pulse
sloganText.alpha = 0.85 + 0.15 * Math.sin(LK.ticks * 0.08);
sloganText.scaleX = sloganText.scaleY = 1 + 0.03 * Math.sin(LK.ticks * 0.07);
// Animate background: spawn beat particles
if (LK.ticks % 30 === 0) {
spawnBeatParticle();
}
// Update and cleanup beat particles
for (var i = beatParticles.length - 1; i >= 0; i--) {
var p = beatParticles[i];
p.update();
if (p.destroyed) {
p.destroy();
beatParticles.splice(i, 1);
}
}
};
// --- Play Music ---
// (Music now starts after Ready is pressed, see readyBtn.down handler)