/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
easyScores: [],
mediumScores: [],
hardScores: []
});
/****
* Classes
****/
var Note = Container.expand(function () {
var self = Container.call(this);
var noteGraphics = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
self.isVisible = false;
self.isCollected = false;
noteGraphics.alpha = 0.3;
self.pulse = function () {
noteGraphics.alpha = 1.0;
self.isVisible = true;
tween(noteGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(noteGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
};
self.fadeOut = function () {
self.isVisible = false;
tween(noteGraphics, {
alpha: 0.3
}, {
duration: 200
});
};
return self;
});
var Orb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
// Create wave effect circles
var wave1 = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
});
var wave2 = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2,
scaleX: 2.0,
scaleY: 2.0
});
var wave3 = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.1,
scaleX: 2.5,
scaleY: 2.5
});
self.addChild(wave3);
self.addChild(wave2);
self.addChild(wave1);
self.addChild(orbGraphics);
// Start continuous wave animation
self.startWaves = function () {
// Wave 1 animation
tween(wave1, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
wave1.scaleX = 1.5;
wave1.scaleY = 1.5;
wave1.alpha = 0.3;
self.startWaves();
}
});
// Wave 2 animation (delayed)
LK.setTimeout(function () {
tween(wave2, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
wave2.scaleX = 2.0;
wave2.scaleY = 2.0;
wave2.alpha = 0.2;
}
});
}, 300);
// Wave 3 animation (more delayed)
LK.setTimeout(function () {
tween(wave3, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
wave3.scaleX = 2.5;
wave3.scaleY = 2.5;
wave3.alpha = 0.1;
}
});
}, 600);
};
// Start the wave effect immediately
self.startWaves();
self.isGlowing = false;
self.startGlow = function () {
if (!self.isGlowing) {
self.isGlowing = true;
tween(orbGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200
});
}
};
self.stopGlow = function () {
if (self.isGlowing) {
self.isGlowing = false;
tween(orbGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game state management
var gameState = 'start'; // 'start', 'playing', 'gameOver'
var difficulty = 'medium'; // 'easy', 'medium', 'hard'
var difficultySelected = false;
var startScreen = null;
var startBackground = null;
var gameBackground = null;
var orb = null;
var notes = [];
var beatWindow = false;
var beatWindowCounter = 0;
var beatCounter = 0;
var dragNode = null;
var gameStartTick = 0;
var gameTimer = 0;
var gameTimerMax = 1320; // 22 seconds at 60 FPS
// Background colors for different expressions
var neutralColor = 0x1a1a2e;
var smileColor = 0x4a90e2;
var frownColor = 0x8b0000;
var mouthOpenColor = 0x9b59b6;
var currentBgColor = neutralColor;
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 100;
var timerTxt = null;
function showScoreboard() {
gameState = 'scoreboard';
// Remove start screen
if (startScreen) {
startScreen.destroy();
startScreen = null;
}
// Create scoreboard container
var scoreboardScreen = new Container();
game.addChild(scoreboardScreen);
// Background
var scoreboardBg = scoreboardScreen.attachAsset('startBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.33,
alpha: 0.8
});
// Title
var titleText = new Text2('HIGH SCORES', {
size: 100,
fill: 0xff00ff
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
scoreboardScreen.addChild(titleText);
// Difficulty sections
var difficulties = ['EASY', 'MEDIUM', 'HARD'];
var storageKeys = ['easyScores', 'mediumScores', 'hardScores'];
var colors = [0x00ff00, 0xffff00, 0xff0000];
for (var d = 0; d < 3; d++) {
var difficultyTitle = new Text2(difficulties[d], {
size: 60,
fill: colors[d]
});
difficultyTitle.anchor.set(0.5, 0.5);
difficultyTitle.x = 350 + d * 350;
difficultyTitle.y = 600;
scoreboardScreen.addChild(difficultyTitle);
// Get scores for this difficulty
var scores = storage[storageKeys[d]] || [];
// Display top 5 scores
for (var s = 0; s < 5; s++) {
var scoreText = scores[s] ? scores[s].toString() : '-';
var scoreDisplay = new Text2(s + 1 + '. ' + scoreText, {
size: 40,
fill: 0xffffff
});
scoreDisplay.anchor.set(0.5, 0.5);
scoreDisplay.x = 350 + d * 350;
scoreDisplay.y = 700 + s * 60;
scoreboardScreen.addChild(scoreDisplay);
}
}
// Back button
var backText = new Text2('TAP TO CONTINUE', {
size: 60,
fill: 0x00ff88
});
backText.anchor.set(0.5, 0.5);
backText.x = 1024;
backText.y = 2200;
scoreboardScreen.addChild(backText);
// Store reference for cleanup
game.scoreboardScreen = scoreboardScreen;
}
function getHighestScore() {
var difficulties = ['easy', 'medium', 'hard'];
var highestScore = 0;
var highestDifficulty = 'none';
for (var d = 0; d < difficulties.length; d++) {
var storageKey = difficulties[d] + 'Scores';
var scores = storage[storageKey] || [];
if (scores.length > 0 && scores[0] > highestScore) {
highestScore = scores[0];
highestDifficulty = difficulties[d].toUpperCase();
}
}
return {
score: highestScore,
difficulty: highestDifficulty
};
}
function saveScore(score, difficulty) {
var storageKey = difficulty + 'Scores';
var scores = storage[storageKey] || [];
// Add new score
scores.push(score);
// Sort in descending order
scores.sort(function (a, b) {
return b - a;
});
// Keep only top 5
if (scores.length > 5) {
scores = scores.slice(0, 5);
}
// Save back to storage
storage[storageKey] = scores;
}
function createStartScreen() {
startScreen = new Container();
game.addChild(startScreen);
// Create animated start background
startBackground = startScreen.attachAsset('startBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.33
});
// Create multiple layers for depth effect
var bgLayer1 = LK.getAsset('startBg', {
anchorX: 0,
anchorY: 0,
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.3
});
bgLayer1.x = -100;
bgLayer1.y = -100;
startScreen.addChild(bgLayer1);
var bgLayer2 = LK.getAsset('startBg', {
anchorX: 0,
anchorY: 0,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.5
});
bgLayer2.x = 200;
bgLayer2.y = 200;
startScreen.addChild(bgLayer2);
// Animate background layers
tween(bgLayer1, {
rotation: Math.PI * 2
}, {
duration: 20000,
repeat: -1
});
tween(bgLayer2, {
rotation: -Math.PI * 2
}, {
duration: 15000,
repeat: -1
});
// Title
var titleText = new Text2('RHYTHM SYNC', {
size: 120,
fill: 0xff00ff
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
startScreen.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Beat Catcher', {
size: 60,
fill: 0x00ff88
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 920;
startScreen.addChild(subtitleText);
// Play button background
var playButtonBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
playButtonBg.x = 1024;
playButtonBg.y = 1200;
startScreen.addChild(playButtonBg);
// Play button text
var playText = new Text2('TAP TO PLAY', {
size: 80,
fill: 0xffff00
});
playText.anchor.set(0.5, 0.5);
playText.x = 1024;
playText.y = 1300;
startScreen.addChild(playText);
// Difficulty selector background
var selectorBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 1.5
});
selectorBg.x = 1024;
selectorBg.y = 1520;
selectorBg.alpha = 0.3;
startScreen.addChild(selectorBg);
// Difficulty button backgrounds
var easyBtnBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
easyBtnBg.x = 724;
easyBtnBg.y = 1520;
easyBtnBg.alpha = difficulty === 'easy' ? 0.8 : 0.3;
startScreen.addChild(easyBtnBg);
var mediumBtnBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2,
tint: 0xffffff
});
mediumBtnBg.x = 1024;
mediumBtnBg.y = 1520;
mediumBtnBg.alpha = difficulty === 'medium' ? 0.8 : 0.3;
startScreen.addChild(mediumBtnBg);
var hardBtnBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
hardBtnBg.x = 1324;
hardBtnBg.y = 1520;
hardBtnBg.alpha = difficulty === 'hard' ? 0.8 : 0.3;
startScreen.addChild(hardBtnBg);
// Difficulty buttons
var easyBtn = new Text2('EASY', {
size: 80,
fill: difficulty === 'easy' ? 0x000000 : 0xffffff,
fontWeight: 'bold'
});
easyBtn.anchor.set(0.5, 0.5);
easyBtn.x = 724;
easyBtn.y = 1520;
startScreen.addChild(easyBtn);
var mediumBtn = new Text2('MEDIUM', {
size: 80,
fill: difficulty === 'medium' ? 0x000000 : 0xffffff,
fontWeight: 'bold'
});
mediumBtn.anchor.set(0.5, 0.5);
mediumBtn.x = 1024;
mediumBtn.y = 1520;
startScreen.addChild(mediumBtn);
var hardBtn = new Text2('HARD', {
size: 80,
fill: difficulty === 'hard' ? 0x000000 : 0xffffff,
fontWeight: 'bold'
});
hardBtn.anchor.set(0.5, 0.5);
hardBtn.x = 1324;
hardBtn.y = 1520;
startScreen.addChild(hardBtn);
// Selection indicator
var selectionIndicator = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 1.0
});
selectionIndicator.alpha = 0.5;
selectionIndicator.x = difficulty === 'easy' ? 724 : difficulty === 'medium' ? 1024 : 1324;
selectionIndicator.y = 1520;
startScreen.addChild(selectionIndicator);
// Store buttons for easy access
startScreen.easyBtn = easyBtn;
startScreen.mediumBtn = mediumBtn;
startScreen.hardBtn = hardBtn;
startScreen.easyBtnBg = easyBtnBg;
startScreen.mediumBtnBg = mediumBtnBg;
startScreen.hardBtnBg = hardBtnBg;
startScreen.selectionIndicator = selectionIndicator;
startScreen.difficultySelected = true;
// Instructions
var instructText = new Text2('Drag the orb to collect notes on the beat!', {
size: 50,
fill: 0xff6600
});
instructText.anchor.set(0.5, 0.5);
instructText.x = 1024;
instructText.y = 1600;
startScreen.addChild(instructText);
// Scoreboard button
var scoreboardBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 1.0
});
scoreboardBg.x = 1024;
scoreboardBg.y = 1750;
scoreboardBg.alpha = 0.4;
startScreen.addChild(scoreboardBg);
var scoreboardText = new Text2('HIGH SCORES', {
size: 60,
fill: 0x00ffff
});
scoreboardText.anchor.set(0.5, 0.5);
scoreboardText.x = 1024;
scoreboardText.y = 1750;
startScreen.addChild(scoreboardText);
// Add highest score display under scoreboard button
var highestData = getHighestScore();
var highestScoreText = '';
if (highestData.score > 0) {
highestScoreText = 'Best: ' + highestData.score + ' (' + highestData.difficulty + ')';
} else {
highestScoreText = 'No scores yet';
}
var bestScoreDisplay = new Text2(highestScoreText, {
size: 40,
fill: 0xffaa00
});
bestScoreDisplay.anchor.set(0.5, 0.5);
bestScoreDisplay.x = 1024;
bestScoreDisplay.y = 1800;
startScreen.addChild(bestScoreDisplay);
// Animate title
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
yoyo: true,
repeat: -1
});
// Play background music for start screen
LK.playMusic('bgmusic');
}
function startGame() {
gameState = 'playing';
// Remove start screen
if (startScreen) {
startScreen.destroy();
startScreen = null;
}
// Create animated game background
gameBackground = game.attachAsset('gameBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.54
});
// Create floating elements for atmosphere
var floatingElements = [];
for (var i = 0; i < 8; i++) {
var element = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
element.alpha = 0.1;
element.x = Math.random() * 2048;
element.y = Math.random() * 2732;
game.addChild(element);
floatingElements.push(element);
// Animate floating elements
tween(element, {
y: element.y + (Math.random() * 400 - 200),
x: element.x + (Math.random() * 400 - 200),
alpha: 0.3
}, {
duration: 3000 + Math.random() * 2000,
yoyo: true,
repeat: -1
});
}
// Create orb
orb = game.addChild(new Orb());
orb.x = 1024;
orb.y = 1366;
// Reset beat timing to sync with music start
gameStartTick = LK.ticks;
beatCounter = 0;
gameTimer = 0;
// Create timer display for game
timerTxt = new Text2('22', {
size: 80,
fill: 0xFFFF00
});
timerTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(timerTxt);
timerTxt.x = -50;
timerTxt.y = 50;
// Start music based on difficulty (this will automatically stop the current bgmusic)
var musicTrack = difficulty === 'easy' ? 'easyMusic' : difficulty === 'medium' ? 'mediumMusic' : 'hardMusic';
LK.playMusic(musicTrack);
}
function spawnNote() {
var note = new Note();
note.x = Math.random() * 1848 + 100; // Keep notes within screen bounds
note.y = Math.random() * 2532 + 100;
notes.push(note);
game.addChild(note);
}
function updateBackground() {
var targetColor = neutralColor;
if (facekitAvailable) {
if (facekit.mouthOpen) {
targetColor = mouthOpenColor;
} else if (facekit.smile) {
targetColor = smileColor;
} else if (facekit.frown) {
targetColor = frownColor;
}
}
if (targetColor !== currentBgColor) {
currentBgColor = targetColor;
game.setBackgroundColor(targetColor);
}
}
function handleMove(x, y, obj) {
if (dragNode) {
dragNode.x = x;
dragNode.y = y;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (gameState === 'start') {
// Check if clicked on difficulty buttons
if (startScreen && y >= 1480 && y <= 1560) {
if (x >= 650 && x <= 800) {
// Easy button clicked
difficulty = 'easy';
difficultySelected = true;
startScreen.easyBtn.fill = 0x000000;
startScreen.mediumBtn.fill = 0xffffff;
startScreen.hardBtn.fill = 0xffffff;
startScreen.easyBtnBg.alpha = 0.8;
startScreen.mediumBtnBg.alpha = 0.3;
startScreen.hardBtnBg.alpha = 0.3;
startScreen.selectionIndicator.x = 724;
} else if (x >= 950 && x <= 1100) {
// Medium button clicked
difficulty = 'medium';
difficultySelected = true;
startScreen.easyBtn.fill = 0xffffff;
startScreen.mediumBtn.fill = 0x000000;
startScreen.hardBtn.fill = 0xffffff;
startScreen.easyBtnBg.alpha = 0.3;
startScreen.mediumBtnBg.alpha = 0.8;
startScreen.hardBtnBg.alpha = 0.3;
startScreen.selectionIndicator.x = 1024;
} else if (x >= 1250 && x <= 1400) {
// Hard button clicked
difficulty = 'hard';
difficultySelected = true;
startScreen.easyBtn.fill = 0xffffff;
startScreen.mediumBtn.fill = 0xffffff;
startScreen.hardBtn.fill = 0x000000;
startScreen.easyBtnBg.alpha = 0.3;
startScreen.mediumBtnBg.alpha = 0.3;
startScreen.hardBtnBg.alpha = 0.8;
startScreen.selectionIndicator.x = 1324;
}
} else if (y >= 1700 && y <= 1800 && x >= 800 && x <= 1250) {
// Scoreboard button clicked
showScoreboard();
} else if (difficultySelected) {
// Start game only if difficulty is selected
startGame();
}
} else if (gameState === 'scoreboard') {
// Return to start screen from scoreboard
if (game.scoreboardScreen) {
game.scoreboardScreen.destroy();
game.scoreboardScreen = null;
}
gameState = 'start';
createStartScreen();
} else if (gameState === 'scoreboard') {
// Return to start screen from scoreboard
if (game.scoreboardScreen) {
game.scoreboardScreen.destroy();
game.scoreboardScreen = null;
}
gameState = 'start';
createStartScreen();
} else if (gameState === 'playing' && orb) {
dragNode = orb;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (gameState === 'playing') {
dragNode = null;
}
};
game.update = function () {
if (gameState === 'playing') {
// Beat timing based on ticks for stable synchronization
var beatInterval = difficulty === 'easy' ? 45 : difficulty === 'medium' ? 30 : 20;
var beatWindowDuration = difficulty === 'easy' ? 12 : difficulty === 'medium' ? 8 : 5;
var spawnChance = difficulty === 'easy' ? 0.5 : difficulty === 'medium' ? 0.7 : 0.9;
// Check if we've hit a new beat using ticks since game started
var ticksSinceStart = LK.ticks - gameStartTick;
if (ticksSinceStart % beatInterval === 0 && ticksSinceStart > 0) {
beatCounter++;
// Start beat window
beatWindow = true;
beatWindowCounter = 0;
// Pulse all notes
for (var i = 0; i < notes.length; i++) {
notes[i].pulse();
}
// Glow orb during beat window
if (orb) {
orb.startGlow();
}
// Spawn new note based on difficulty
if (Math.random() < spawnChance) {
spawnNote();
}
}
// Manage beat window duration based on difficulty
if (beatWindow) {
beatWindowCounter++;
if (beatWindowCounter >= beatWindowDuration) {
beatWindow = false;
if (orb) {
orb.stopGlow();
}
// Fade out notes after beat window
for (var i = 0; i < notes.length; i++) {
notes[i].fadeOut();
}
}
}
// Check for note collection
if (orb) {
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
if (!note.isCollected && orb.intersects(note)) {
if (beatWindow && note.isVisible) {
// Perfect timing - full points
LK.setScore(LK.getScore() + 10);
LK.getSound('pulse').play();
} else if (note.isVisible) {
// Good timing - half points
LK.setScore(LK.getScore() + 5);
LK.getSound('pulse').play();
}
note.isCollected = true;
note.destroy();
notes.splice(i, 1);
scoreTxt.setText(LK.getScore());
}
}
}
// Update timer display
if (timerTxt) {
var timeLeft = Math.ceil((gameTimerMax - gameTimer) / 60);
timerTxt.setText(timeLeft.toString());
}
// Clean up old notes that haven't been collected
if (LK.ticks % 180 === 0) {
// Every 3 seconds
for (var i = notes.length - 1; i >= 0; i--) {
if (Math.random() < 0.3) {
// 30% chance to remove old notes
notes[i].destroy();
notes.splice(i, 1);
}
}
}
// Update background based on facial expressions (if camera available)
if (facekitAvailable) {
updateBackground();
}
// Update game timer
gameTimer++;
// End game when timer reaches maximum
if (gameTimer >= gameTimerMax) {
// Save the score before showing game over
saveScore(LK.getScore(), difficulty);
LK.showGameOver();
}
// Win condition at 500 points
if (LK.getScore() >= 500) {
// Save the score before showing win screen
saveScore(LK.getScore(), difficulty);
LK.showYouWin();
}
}
};
// Initialize facekit for facial expression detection if camera is available
var facekitAvailable = false;
// Create start screen
createStartScreen(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
easyScores: [],
mediumScores: [],
hardScores: []
});
/****
* Classes
****/
var Note = Container.expand(function () {
var self = Container.call(this);
var noteGraphics = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
self.isVisible = false;
self.isCollected = false;
noteGraphics.alpha = 0.3;
self.pulse = function () {
noteGraphics.alpha = 1.0;
self.isVisible = true;
tween(noteGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(noteGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
};
self.fadeOut = function () {
self.isVisible = false;
tween(noteGraphics, {
alpha: 0.3
}, {
duration: 200
});
};
return self;
});
var Orb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
// Create wave effect circles
var wave1 = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
});
var wave2 = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2,
scaleX: 2.0,
scaleY: 2.0
});
var wave3 = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.1,
scaleX: 2.5,
scaleY: 2.5
});
self.addChild(wave3);
self.addChild(wave2);
self.addChild(wave1);
self.addChild(orbGraphics);
// Start continuous wave animation
self.startWaves = function () {
// Wave 1 animation
tween(wave1, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
wave1.scaleX = 1.5;
wave1.scaleY = 1.5;
wave1.alpha = 0.3;
self.startWaves();
}
});
// Wave 2 animation (delayed)
LK.setTimeout(function () {
tween(wave2, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
wave2.scaleX = 2.0;
wave2.scaleY = 2.0;
wave2.alpha = 0.2;
}
});
}, 300);
// Wave 3 animation (more delayed)
LK.setTimeout(function () {
tween(wave3, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
wave3.scaleX = 2.5;
wave3.scaleY = 2.5;
wave3.alpha = 0.1;
}
});
}, 600);
};
// Start the wave effect immediately
self.startWaves();
self.isGlowing = false;
self.startGlow = function () {
if (!self.isGlowing) {
self.isGlowing = true;
tween(orbGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200
});
}
};
self.stopGlow = function () {
if (self.isGlowing) {
self.isGlowing = false;
tween(orbGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game state management
var gameState = 'start'; // 'start', 'playing', 'gameOver'
var difficulty = 'medium'; // 'easy', 'medium', 'hard'
var difficultySelected = false;
var startScreen = null;
var startBackground = null;
var gameBackground = null;
var orb = null;
var notes = [];
var beatWindow = false;
var beatWindowCounter = 0;
var beatCounter = 0;
var dragNode = null;
var gameStartTick = 0;
var gameTimer = 0;
var gameTimerMax = 1320; // 22 seconds at 60 FPS
// Background colors for different expressions
var neutralColor = 0x1a1a2e;
var smileColor = 0x4a90e2;
var frownColor = 0x8b0000;
var mouthOpenColor = 0x9b59b6;
var currentBgColor = neutralColor;
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 100;
var timerTxt = null;
function showScoreboard() {
gameState = 'scoreboard';
// Remove start screen
if (startScreen) {
startScreen.destroy();
startScreen = null;
}
// Create scoreboard container
var scoreboardScreen = new Container();
game.addChild(scoreboardScreen);
// Background
var scoreboardBg = scoreboardScreen.attachAsset('startBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.33,
alpha: 0.8
});
// Title
var titleText = new Text2('HIGH SCORES', {
size: 100,
fill: 0xff00ff
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
scoreboardScreen.addChild(titleText);
// Difficulty sections
var difficulties = ['EASY', 'MEDIUM', 'HARD'];
var storageKeys = ['easyScores', 'mediumScores', 'hardScores'];
var colors = [0x00ff00, 0xffff00, 0xff0000];
for (var d = 0; d < 3; d++) {
var difficultyTitle = new Text2(difficulties[d], {
size: 60,
fill: colors[d]
});
difficultyTitle.anchor.set(0.5, 0.5);
difficultyTitle.x = 350 + d * 350;
difficultyTitle.y = 600;
scoreboardScreen.addChild(difficultyTitle);
// Get scores for this difficulty
var scores = storage[storageKeys[d]] || [];
// Display top 5 scores
for (var s = 0; s < 5; s++) {
var scoreText = scores[s] ? scores[s].toString() : '-';
var scoreDisplay = new Text2(s + 1 + '. ' + scoreText, {
size: 40,
fill: 0xffffff
});
scoreDisplay.anchor.set(0.5, 0.5);
scoreDisplay.x = 350 + d * 350;
scoreDisplay.y = 700 + s * 60;
scoreboardScreen.addChild(scoreDisplay);
}
}
// Back button
var backText = new Text2('TAP TO CONTINUE', {
size: 60,
fill: 0x00ff88
});
backText.anchor.set(0.5, 0.5);
backText.x = 1024;
backText.y = 2200;
scoreboardScreen.addChild(backText);
// Store reference for cleanup
game.scoreboardScreen = scoreboardScreen;
}
function getHighestScore() {
var difficulties = ['easy', 'medium', 'hard'];
var highestScore = 0;
var highestDifficulty = 'none';
for (var d = 0; d < difficulties.length; d++) {
var storageKey = difficulties[d] + 'Scores';
var scores = storage[storageKey] || [];
if (scores.length > 0 && scores[0] > highestScore) {
highestScore = scores[0];
highestDifficulty = difficulties[d].toUpperCase();
}
}
return {
score: highestScore,
difficulty: highestDifficulty
};
}
function saveScore(score, difficulty) {
var storageKey = difficulty + 'Scores';
var scores = storage[storageKey] || [];
// Add new score
scores.push(score);
// Sort in descending order
scores.sort(function (a, b) {
return b - a;
});
// Keep only top 5
if (scores.length > 5) {
scores = scores.slice(0, 5);
}
// Save back to storage
storage[storageKey] = scores;
}
function createStartScreen() {
startScreen = new Container();
game.addChild(startScreen);
// Create animated start background
startBackground = startScreen.attachAsset('startBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.33
});
// Create multiple layers for depth effect
var bgLayer1 = LK.getAsset('startBg', {
anchorX: 0,
anchorY: 0,
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.3
});
bgLayer1.x = -100;
bgLayer1.y = -100;
startScreen.addChild(bgLayer1);
var bgLayer2 = LK.getAsset('startBg', {
anchorX: 0,
anchorY: 0,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.5
});
bgLayer2.x = 200;
bgLayer2.y = 200;
startScreen.addChild(bgLayer2);
// Animate background layers
tween(bgLayer1, {
rotation: Math.PI * 2
}, {
duration: 20000,
repeat: -1
});
tween(bgLayer2, {
rotation: -Math.PI * 2
}, {
duration: 15000,
repeat: -1
});
// Title
var titleText = new Text2('RHYTHM SYNC', {
size: 120,
fill: 0xff00ff
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
startScreen.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Beat Catcher', {
size: 60,
fill: 0x00ff88
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 920;
startScreen.addChild(subtitleText);
// Play button background
var playButtonBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
playButtonBg.x = 1024;
playButtonBg.y = 1200;
startScreen.addChild(playButtonBg);
// Play button text
var playText = new Text2('TAP TO PLAY', {
size: 80,
fill: 0xffff00
});
playText.anchor.set(0.5, 0.5);
playText.x = 1024;
playText.y = 1300;
startScreen.addChild(playText);
// Difficulty selector background
var selectorBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 1.5
});
selectorBg.x = 1024;
selectorBg.y = 1520;
selectorBg.alpha = 0.3;
startScreen.addChild(selectorBg);
// Difficulty button backgrounds
var easyBtnBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
easyBtnBg.x = 724;
easyBtnBg.y = 1520;
easyBtnBg.alpha = difficulty === 'easy' ? 0.8 : 0.3;
startScreen.addChild(easyBtnBg);
var mediumBtnBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2,
tint: 0xffffff
});
mediumBtnBg.x = 1024;
mediumBtnBg.y = 1520;
mediumBtnBg.alpha = difficulty === 'medium' ? 0.8 : 0.3;
startScreen.addChild(mediumBtnBg);
var hardBtnBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
hardBtnBg.x = 1324;
hardBtnBg.y = 1520;
hardBtnBg.alpha = difficulty === 'hard' ? 0.8 : 0.3;
startScreen.addChild(hardBtnBg);
// Difficulty buttons
var easyBtn = new Text2('EASY', {
size: 80,
fill: difficulty === 'easy' ? 0x000000 : 0xffffff,
fontWeight: 'bold'
});
easyBtn.anchor.set(0.5, 0.5);
easyBtn.x = 724;
easyBtn.y = 1520;
startScreen.addChild(easyBtn);
var mediumBtn = new Text2('MEDIUM', {
size: 80,
fill: difficulty === 'medium' ? 0x000000 : 0xffffff,
fontWeight: 'bold'
});
mediumBtn.anchor.set(0.5, 0.5);
mediumBtn.x = 1024;
mediumBtn.y = 1520;
startScreen.addChild(mediumBtn);
var hardBtn = new Text2('HARD', {
size: 80,
fill: difficulty === 'hard' ? 0x000000 : 0xffffff,
fontWeight: 'bold'
});
hardBtn.anchor.set(0.5, 0.5);
hardBtn.x = 1324;
hardBtn.y = 1520;
startScreen.addChild(hardBtn);
// Selection indicator
var selectionIndicator = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 1.0
});
selectionIndicator.alpha = 0.5;
selectionIndicator.x = difficulty === 'easy' ? 724 : difficulty === 'medium' ? 1024 : 1324;
selectionIndicator.y = 1520;
startScreen.addChild(selectionIndicator);
// Store buttons for easy access
startScreen.easyBtn = easyBtn;
startScreen.mediumBtn = mediumBtn;
startScreen.hardBtn = hardBtn;
startScreen.easyBtnBg = easyBtnBg;
startScreen.mediumBtnBg = mediumBtnBg;
startScreen.hardBtnBg = hardBtnBg;
startScreen.selectionIndicator = selectionIndicator;
startScreen.difficultySelected = true;
// Instructions
var instructText = new Text2('Drag the orb to collect notes on the beat!', {
size: 50,
fill: 0xff6600
});
instructText.anchor.set(0.5, 0.5);
instructText.x = 1024;
instructText.y = 1600;
startScreen.addChild(instructText);
// Scoreboard button
var scoreboardBg = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 1.0
});
scoreboardBg.x = 1024;
scoreboardBg.y = 1750;
scoreboardBg.alpha = 0.4;
startScreen.addChild(scoreboardBg);
var scoreboardText = new Text2('HIGH SCORES', {
size: 60,
fill: 0x00ffff
});
scoreboardText.anchor.set(0.5, 0.5);
scoreboardText.x = 1024;
scoreboardText.y = 1750;
startScreen.addChild(scoreboardText);
// Add highest score display under scoreboard button
var highestData = getHighestScore();
var highestScoreText = '';
if (highestData.score > 0) {
highestScoreText = 'Best: ' + highestData.score + ' (' + highestData.difficulty + ')';
} else {
highestScoreText = 'No scores yet';
}
var bestScoreDisplay = new Text2(highestScoreText, {
size: 40,
fill: 0xffaa00
});
bestScoreDisplay.anchor.set(0.5, 0.5);
bestScoreDisplay.x = 1024;
bestScoreDisplay.y = 1800;
startScreen.addChild(bestScoreDisplay);
// Animate title
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
yoyo: true,
repeat: -1
});
// Play background music for start screen
LK.playMusic('bgmusic');
}
function startGame() {
gameState = 'playing';
// Remove start screen
if (startScreen) {
startScreen.destroy();
startScreen = null;
}
// Create animated game background
gameBackground = game.attachAsset('gameBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.54
});
// Create floating elements for atmosphere
var floatingElements = [];
for (var i = 0; i < 8; i++) {
var element = LK.getAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
element.alpha = 0.1;
element.x = Math.random() * 2048;
element.y = Math.random() * 2732;
game.addChild(element);
floatingElements.push(element);
// Animate floating elements
tween(element, {
y: element.y + (Math.random() * 400 - 200),
x: element.x + (Math.random() * 400 - 200),
alpha: 0.3
}, {
duration: 3000 + Math.random() * 2000,
yoyo: true,
repeat: -1
});
}
// Create orb
orb = game.addChild(new Orb());
orb.x = 1024;
orb.y = 1366;
// Reset beat timing to sync with music start
gameStartTick = LK.ticks;
beatCounter = 0;
gameTimer = 0;
// Create timer display for game
timerTxt = new Text2('22', {
size: 80,
fill: 0xFFFF00
});
timerTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(timerTxt);
timerTxt.x = -50;
timerTxt.y = 50;
// Start music based on difficulty (this will automatically stop the current bgmusic)
var musicTrack = difficulty === 'easy' ? 'easyMusic' : difficulty === 'medium' ? 'mediumMusic' : 'hardMusic';
LK.playMusic(musicTrack);
}
function spawnNote() {
var note = new Note();
note.x = Math.random() * 1848 + 100; // Keep notes within screen bounds
note.y = Math.random() * 2532 + 100;
notes.push(note);
game.addChild(note);
}
function updateBackground() {
var targetColor = neutralColor;
if (facekitAvailable) {
if (facekit.mouthOpen) {
targetColor = mouthOpenColor;
} else if (facekit.smile) {
targetColor = smileColor;
} else if (facekit.frown) {
targetColor = frownColor;
}
}
if (targetColor !== currentBgColor) {
currentBgColor = targetColor;
game.setBackgroundColor(targetColor);
}
}
function handleMove(x, y, obj) {
if (dragNode) {
dragNode.x = x;
dragNode.y = y;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (gameState === 'start') {
// Check if clicked on difficulty buttons
if (startScreen && y >= 1480 && y <= 1560) {
if (x >= 650 && x <= 800) {
// Easy button clicked
difficulty = 'easy';
difficultySelected = true;
startScreen.easyBtn.fill = 0x000000;
startScreen.mediumBtn.fill = 0xffffff;
startScreen.hardBtn.fill = 0xffffff;
startScreen.easyBtnBg.alpha = 0.8;
startScreen.mediumBtnBg.alpha = 0.3;
startScreen.hardBtnBg.alpha = 0.3;
startScreen.selectionIndicator.x = 724;
} else if (x >= 950 && x <= 1100) {
// Medium button clicked
difficulty = 'medium';
difficultySelected = true;
startScreen.easyBtn.fill = 0xffffff;
startScreen.mediumBtn.fill = 0x000000;
startScreen.hardBtn.fill = 0xffffff;
startScreen.easyBtnBg.alpha = 0.3;
startScreen.mediumBtnBg.alpha = 0.8;
startScreen.hardBtnBg.alpha = 0.3;
startScreen.selectionIndicator.x = 1024;
} else if (x >= 1250 && x <= 1400) {
// Hard button clicked
difficulty = 'hard';
difficultySelected = true;
startScreen.easyBtn.fill = 0xffffff;
startScreen.mediumBtn.fill = 0xffffff;
startScreen.hardBtn.fill = 0x000000;
startScreen.easyBtnBg.alpha = 0.3;
startScreen.mediumBtnBg.alpha = 0.3;
startScreen.hardBtnBg.alpha = 0.8;
startScreen.selectionIndicator.x = 1324;
}
} else if (y >= 1700 && y <= 1800 && x >= 800 && x <= 1250) {
// Scoreboard button clicked
showScoreboard();
} else if (difficultySelected) {
// Start game only if difficulty is selected
startGame();
}
} else if (gameState === 'scoreboard') {
// Return to start screen from scoreboard
if (game.scoreboardScreen) {
game.scoreboardScreen.destroy();
game.scoreboardScreen = null;
}
gameState = 'start';
createStartScreen();
} else if (gameState === 'scoreboard') {
// Return to start screen from scoreboard
if (game.scoreboardScreen) {
game.scoreboardScreen.destroy();
game.scoreboardScreen = null;
}
gameState = 'start';
createStartScreen();
} else if (gameState === 'playing' && orb) {
dragNode = orb;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (gameState === 'playing') {
dragNode = null;
}
};
game.update = function () {
if (gameState === 'playing') {
// Beat timing based on ticks for stable synchronization
var beatInterval = difficulty === 'easy' ? 45 : difficulty === 'medium' ? 30 : 20;
var beatWindowDuration = difficulty === 'easy' ? 12 : difficulty === 'medium' ? 8 : 5;
var spawnChance = difficulty === 'easy' ? 0.5 : difficulty === 'medium' ? 0.7 : 0.9;
// Check if we've hit a new beat using ticks since game started
var ticksSinceStart = LK.ticks - gameStartTick;
if (ticksSinceStart % beatInterval === 0 && ticksSinceStart > 0) {
beatCounter++;
// Start beat window
beatWindow = true;
beatWindowCounter = 0;
// Pulse all notes
for (var i = 0; i < notes.length; i++) {
notes[i].pulse();
}
// Glow orb during beat window
if (orb) {
orb.startGlow();
}
// Spawn new note based on difficulty
if (Math.random() < spawnChance) {
spawnNote();
}
}
// Manage beat window duration based on difficulty
if (beatWindow) {
beatWindowCounter++;
if (beatWindowCounter >= beatWindowDuration) {
beatWindow = false;
if (orb) {
orb.stopGlow();
}
// Fade out notes after beat window
for (var i = 0; i < notes.length; i++) {
notes[i].fadeOut();
}
}
}
// Check for note collection
if (orb) {
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
if (!note.isCollected && orb.intersects(note)) {
if (beatWindow && note.isVisible) {
// Perfect timing - full points
LK.setScore(LK.getScore() + 10);
LK.getSound('pulse').play();
} else if (note.isVisible) {
// Good timing - half points
LK.setScore(LK.getScore() + 5);
LK.getSound('pulse').play();
}
note.isCollected = true;
note.destroy();
notes.splice(i, 1);
scoreTxt.setText(LK.getScore());
}
}
}
// Update timer display
if (timerTxt) {
var timeLeft = Math.ceil((gameTimerMax - gameTimer) / 60);
timerTxt.setText(timeLeft.toString());
}
// Clean up old notes that haven't been collected
if (LK.ticks % 180 === 0) {
// Every 3 seconds
for (var i = notes.length - 1; i >= 0; i--) {
if (Math.random() < 0.3) {
// 30% chance to remove old notes
notes[i].destroy();
notes.splice(i, 1);
}
}
}
// Update background based on facial expressions (if camera available)
if (facekitAvailable) {
updateBackground();
}
// Update game timer
gameTimer++;
// End game when timer reaches maximum
if (gameTimer >= gameTimerMax) {
// Save the score before showing game over
saveScore(LK.getScore(), difficulty);
LK.showGameOver();
}
// Win condition at 500 points
if (LK.getScore() >= 500) {
// Save the score before showing win screen
saveScore(LK.getScore(), difficulty);
LK.showYouWin();
}
}
};
// Initialize facekit for facial expression detection if camera is available
var facekitAvailable = false;
// Create start screen
createStartScreen();
A futuristic abstract 2D background for a rhythm game start screen. Neon color palette — cyan, magenta, and deep purple. Floating glowing spheres, waveforms, and pulse rings layered over a dark space-like gradient background. Visual elements should feel musical — like a living audio visualizer. High detail, soft glowing effects, minimal UI clutter space in center. Style: Digital art, vibrant, atmospheric.. In-Game asset. 2d. High contrast. No shadows
Design a seamless looping 2D abstract background for a rhythm-based game. Color palette: dark blue, black, soft purples — with faint neon pulses. Include soft gradients, subtle waveform lines, and floating particles or rings. The background should feel immersive and reactive, but not distract from gameplay elements. Style: futuristic, ambient, minimalistic digital art. No characters or sharp objects. Ideal for side-scrolling or freely moving 2D rhythm gameplay.. In-Game asset. 2d. High contrast. No shadows
a round shape futuristic speaker. In-Game asset. 2d. High contrast. No shadows