/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Lane = Container.expand(function (laneIndex) {
var self = Container.call(this);
var laneGraphics = self.attachAsset('lane', {
anchorX: 0.5,
anchorY: 0
});
var hitZone = self.attachAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5
});
var laneButton = self.attachAsset('laneButton', {
anchorX: 0.5,
anchorY: 0.5
});
self.laneIndex = laneIndex;
self.x = 2048 / 6 * (laneIndex + 1);
hitZone.y = 2500;
hitZone.alpha = 0.3;
laneButton.y = 2550;
laneButton.alpha = 0.7;
return self;
});
var Note = Container.expand(function () {
var self = Container.call(this);
// Randomly select note type (1 or 3 only)
var noteTypes = [1, 3];
self.noteType = noteTypes[Math.floor(Math.random() * 2)];
var noteAssetId = 'note' + self.noteType;
var noteGraphics = self.attachAsset(noteAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.lane = 0;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.y += self.speed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game states
var gameState = 'menu'; // 'menu' or 'playing'
var menuBackground;
var menuTitle;
var playButton;
var instructionsText;
// Game variables
var lanes = [];
var notes = [];
var score = 0;
var combo = 0;
var gameSpeed = 1;
var spawnTimer = 0;
var lives = 3;
var scoreTxt;
var comboTxt;
var feedbackTxt;
var livesTxt;
var feedbackTimer = 0;
// Create main menu
function createMainMenu() {
// Menu background - sky
menuBackground = LK.getAsset('skyBackground', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.x = 2048 / 2;
menuBackground.y = 2732 / 2;
game.addChild(menuBackground);
// Menu title
menuTitle = new Text2('RHYTHM\nARENA', {
size: 120,
fill: 0xFFFFFF
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = 2048 / 2;
menuTitle.y = 800;
game.addChild(menuTitle);
// Play button
playButton = LK.getAsset('laneButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.5,
tint: 0x00ff00
});
playButton.x = 2048 / 2;
playButton.y = 1400;
game.addChild(playButton);
// Play button text
var playButtonText = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 2048 / 2;
playButtonText.y = 1400;
game.addChild(playButtonText);
// Instructions
instructionsText = new Text2('Tap the lanes to hit notes\nwith perfect timing!\n\nDefend your tower!', {
size: 60,
fill: 0xFFFFFF
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 2048 / 2;
instructionsText.y = 1800;
game.addChild(instructionsText);
}
// Create game elements
function createGameElements() {
// Create lanes
for (var i = 0; i < 5; i++) {
var lane = new Lane(i);
lanes.push(lane);
game.addChild(lane);
}
// Create UI text
scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.x = 0;
scoreTxt.y = 30;
comboTxt = new Text2('Combo: 0', {
size: 40,
fill: 0xFFFF00
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.x = 0;
comboTxt.y = 80;
livesTxt = new Text2('Lives: 3', {
size: 50,
fill: 0xFF0000
});
livesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(livesTxt);
livesTxt.x = 0;
livesTxt.y = 140;
feedbackTxt = new Text2('', {
size: 80,
fill: 0x00FF00
});
feedbackTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(feedbackTxt);
}
// Initialize menu
createMainMenu();
function spawnNote() {
var note = new Note();
var laneIndex = Math.floor(Math.random() * 5);
note.lane = laneIndex;
note.x = lanes[laneIndex].x;
note.y = -50;
// Start with slower initial speed, but increase more dramatically over time
note.speed = 4 + gameSpeed * 1.2;
notes.push(note);
game.addChild(note);
}
function checkNoteHit(note, timing) {
var distance = Math.abs(note.y - 2500);
var perfectRange = 30;
var goodRange = 60;
if (distance <= perfectRange) {
// Perfect hit
score += 100 + combo * 10;
combo++;
showFeedback('PERFECT!', 0x00ff00);
LK.getSound('lane' + note.lane).play();
// Add hit effect - scale up and fade out
tween(note, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Add light effect at bottom
var lightEffect = LK.getAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.5,
alpha: 0.8,
tint: 0xFFFFFF
});
lightEffect.x = note.x;
lightEffect.y = 2500;
game.addChild(lightEffect);
// Animate light effect
tween(lightEffect, {
scaleX: 5,
scaleY: 1,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
lightEffect.destroy();
}
});
return true;
} else if (distance <= goodRange) {
// Good hit
score += 50 + combo * 5;
combo++;
showFeedback('GOOD', 0xffff00);
LK.getSound('lane' + note.lane).play();
// Add hit effect - scale up and fade out
tween(note, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 250,
easing: tween.easeOut
});
// Add light effect at bottom
var lightEffect = LK.getAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.4,
alpha: 0.6,
tint: 0xFFFF00
});
lightEffect.x = note.x;
lightEffect.y = 2500;
game.addChild(lightEffect);
// Animate light effect
tween(lightEffect, {
scaleX: 4,
scaleY: 0.8,
alpha: 0
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
lightEffect.destroy();
}
});
return true;
} else {
// Miss
combo = 0;
showFeedback('MISS', 0xff0000);
LK.getSound('miss').play();
return false;
}
}
function showFeedback(text, color) {
feedbackTxt.setText(text);
feedbackTxt.tint = color;
feedbackTxt.alpha = 1;
feedbackTimer = 60; // Show for 1 second
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
comboTxt.setText('Combo: ' + combo);
livesTxt.setText('Lives: ' + lives);
if (feedbackTimer > 0) {
feedbackTimer--;
feedbackTxt.alpha = feedbackTimer / 60;
}
}
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if play button was clicked
if (playButton && Math.abs(x - playButton.x) < 300 && Math.abs(y - playButton.y) < 90) {
// Start game
gameState = 'playing';
// Remove menu elements
if (menuBackground) menuBackground.destroy();
if (menuTitle) menuTitle.destroy();
if (playButton) playButton.destroy();
if (instructionsText) instructionsText.destroy();
// Create game elements
createGameElements();
// Start background music
LK.playMusic('bgmusic', {
loop: true
});
// Add button press effect
tween(playButton, {
scaleX: 1.8,
scaleY: 1.3
}, {
duration: 100,
easing: tween.easeOut
});
}
return;
}
// Game playing state
if (gameState === 'playing') {
// Check which lane was tapped
var tappedLane = -1;
for (var i = 0; i < lanes.length; i++) {
var laneX = lanes[i].x;
if (Math.abs(x - laneX) < 150) {
tappedLane = i;
// Add visual feedback when button is pressed
LK.effects.flashObject(lanes[i], 0xffffff, 200);
// Add button press effect - scale down and back up
var laneButton = lanes[i].children[2]; // Lane button is the 3rd child
tween(laneButton, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(laneButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
break;
}
}
if (tappedLane === -1) return;
// Find the closest note in that lane
var closestNote = null;
var closestDistance = Infinity;
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.lane === tappedLane && note.isActive) {
var distance = Math.abs(note.y - 2500);
if (distance < closestDistance && distance < 100) {
closestDistance = distance;
closestNote = note;
}
}
}
if (closestNote) {
var hit = checkNoteHit(closestNote, 0);
if (hit) {
closestNote.isActive = false;
closestNote.destroy();
var index = notes.indexOf(closestNote);
if (index > -1) {
notes.splice(index, 1);
}
}
} else {
// No note found in tapped lane - wrong tap
lives--;
combo = 0;
showFeedback('WRONG LANE!', 0xff0000);
LK.getSound('miss').play();
// Check if game over
if (lives <= 0) {
LK.showGameOver();
}
}
}
};
game.update = function () {
if (gameState === 'playing') {
// Spawn notes with musical rhythm
spawnTimer++;
// Reduced note frequency - spawn every 60 ticks (1 second at 60fps) initially
var baseSpawnRate = 60;
// Keep spawn rate more stable, reduce frequency less aggressively
var spawnRate = Math.max(baseSpawnRate - Math.floor(LK.ticks / 7200), 45); // Slower tempo increase
if (spawnTimer >= spawnRate) {
spawnNote();
spawnTimer = 0;
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
if (!note.isActive) continue;
// Check if note reached the bottom without being hit
if (note.y > 2550) {
combo = 0;
lives--;
showFeedback('MISSED!', 0xff0000);
LK.getSound('miss').play();
note.destroy();
notes.splice(i, 1);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
}
continue;
}
// Remove notes that are way off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
}
// Increase difficulty over time
if (LK.ticks % 1200 === 0) {
// Every 20 seconds
gameSpeed += 0.2; // Faster speed increase
}
// Update UI
updateUI();
// Update score in LK system
LK.setScore(score);
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Lane = Container.expand(function (laneIndex) {
var self = Container.call(this);
var laneGraphics = self.attachAsset('lane', {
anchorX: 0.5,
anchorY: 0
});
var hitZone = self.attachAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5
});
var laneButton = self.attachAsset('laneButton', {
anchorX: 0.5,
anchorY: 0.5
});
self.laneIndex = laneIndex;
self.x = 2048 / 6 * (laneIndex + 1);
hitZone.y = 2500;
hitZone.alpha = 0.3;
laneButton.y = 2550;
laneButton.alpha = 0.7;
return self;
});
var Note = Container.expand(function () {
var self = Container.call(this);
// Randomly select note type (1 or 3 only)
var noteTypes = [1, 3];
self.noteType = noteTypes[Math.floor(Math.random() * 2)];
var noteAssetId = 'note' + self.noteType;
var noteGraphics = self.attachAsset(noteAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.lane = 0;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.y += self.speed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game states
var gameState = 'menu'; // 'menu' or 'playing'
var menuBackground;
var menuTitle;
var playButton;
var instructionsText;
// Game variables
var lanes = [];
var notes = [];
var score = 0;
var combo = 0;
var gameSpeed = 1;
var spawnTimer = 0;
var lives = 3;
var scoreTxt;
var comboTxt;
var feedbackTxt;
var livesTxt;
var feedbackTimer = 0;
// Create main menu
function createMainMenu() {
// Menu background - sky
menuBackground = LK.getAsset('skyBackground', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.x = 2048 / 2;
menuBackground.y = 2732 / 2;
game.addChild(menuBackground);
// Menu title
menuTitle = new Text2('RHYTHM\nARENA', {
size: 120,
fill: 0xFFFFFF
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = 2048 / 2;
menuTitle.y = 800;
game.addChild(menuTitle);
// Play button
playButton = LK.getAsset('laneButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.5,
tint: 0x00ff00
});
playButton.x = 2048 / 2;
playButton.y = 1400;
game.addChild(playButton);
// Play button text
var playButtonText = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 2048 / 2;
playButtonText.y = 1400;
game.addChild(playButtonText);
// Instructions
instructionsText = new Text2('Tap the lanes to hit notes\nwith perfect timing!\n\nDefend your tower!', {
size: 60,
fill: 0xFFFFFF
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 2048 / 2;
instructionsText.y = 1800;
game.addChild(instructionsText);
}
// Create game elements
function createGameElements() {
// Create lanes
for (var i = 0; i < 5; i++) {
var lane = new Lane(i);
lanes.push(lane);
game.addChild(lane);
}
// Create UI text
scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.x = 0;
scoreTxt.y = 30;
comboTxt = new Text2('Combo: 0', {
size: 40,
fill: 0xFFFF00
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.x = 0;
comboTxt.y = 80;
livesTxt = new Text2('Lives: 3', {
size: 50,
fill: 0xFF0000
});
livesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(livesTxt);
livesTxt.x = 0;
livesTxt.y = 140;
feedbackTxt = new Text2('', {
size: 80,
fill: 0x00FF00
});
feedbackTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(feedbackTxt);
}
// Initialize menu
createMainMenu();
function spawnNote() {
var note = new Note();
var laneIndex = Math.floor(Math.random() * 5);
note.lane = laneIndex;
note.x = lanes[laneIndex].x;
note.y = -50;
// Start with slower initial speed, but increase more dramatically over time
note.speed = 4 + gameSpeed * 1.2;
notes.push(note);
game.addChild(note);
}
function checkNoteHit(note, timing) {
var distance = Math.abs(note.y - 2500);
var perfectRange = 30;
var goodRange = 60;
if (distance <= perfectRange) {
// Perfect hit
score += 100 + combo * 10;
combo++;
showFeedback('PERFECT!', 0x00ff00);
LK.getSound('lane' + note.lane).play();
// Add hit effect - scale up and fade out
tween(note, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Add light effect at bottom
var lightEffect = LK.getAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.5,
alpha: 0.8,
tint: 0xFFFFFF
});
lightEffect.x = note.x;
lightEffect.y = 2500;
game.addChild(lightEffect);
// Animate light effect
tween(lightEffect, {
scaleX: 5,
scaleY: 1,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
lightEffect.destroy();
}
});
return true;
} else if (distance <= goodRange) {
// Good hit
score += 50 + combo * 5;
combo++;
showFeedback('GOOD', 0xffff00);
LK.getSound('lane' + note.lane).play();
// Add hit effect - scale up and fade out
tween(note, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 250,
easing: tween.easeOut
});
// Add light effect at bottom
var lightEffect = LK.getAsset('hitZone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.4,
alpha: 0.6,
tint: 0xFFFF00
});
lightEffect.x = note.x;
lightEffect.y = 2500;
game.addChild(lightEffect);
// Animate light effect
tween(lightEffect, {
scaleX: 4,
scaleY: 0.8,
alpha: 0
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
lightEffect.destroy();
}
});
return true;
} else {
// Miss
combo = 0;
showFeedback('MISS', 0xff0000);
LK.getSound('miss').play();
return false;
}
}
function showFeedback(text, color) {
feedbackTxt.setText(text);
feedbackTxt.tint = color;
feedbackTxt.alpha = 1;
feedbackTimer = 60; // Show for 1 second
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
comboTxt.setText('Combo: ' + combo);
livesTxt.setText('Lives: ' + lives);
if (feedbackTimer > 0) {
feedbackTimer--;
feedbackTxt.alpha = feedbackTimer / 60;
}
}
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if play button was clicked
if (playButton && Math.abs(x - playButton.x) < 300 && Math.abs(y - playButton.y) < 90) {
// Start game
gameState = 'playing';
// Remove menu elements
if (menuBackground) menuBackground.destroy();
if (menuTitle) menuTitle.destroy();
if (playButton) playButton.destroy();
if (instructionsText) instructionsText.destroy();
// Create game elements
createGameElements();
// Start background music
LK.playMusic('bgmusic', {
loop: true
});
// Add button press effect
tween(playButton, {
scaleX: 1.8,
scaleY: 1.3
}, {
duration: 100,
easing: tween.easeOut
});
}
return;
}
// Game playing state
if (gameState === 'playing') {
// Check which lane was tapped
var tappedLane = -1;
for (var i = 0; i < lanes.length; i++) {
var laneX = lanes[i].x;
if (Math.abs(x - laneX) < 150) {
tappedLane = i;
// Add visual feedback when button is pressed
LK.effects.flashObject(lanes[i], 0xffffff, 200);
// Add button press effect - scale down and back up
var laneButton = lanes[i].children[2]; // Lane button is the 3rd child
tween(laneButton, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(laneButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
break;
}
}
if (tappedLane === -1) return;
// Find the closest note in that lane
var closestNote = null;
var closestDistance = Infinity;
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.lane === tappedLane && note.isActive) {
var distance = Math.abs(note.y - 2500);
if (distance < closestDistance && distance < 100) {
closestDistance = distance;
closestNote = note;
}
}
}
if (closestNote) {
var hit = checkNoteHit(closestNote, 0);
if (hit) {
closestNote.isActive = false;
closestNote.destroy();
var index = notes.indexOf(closestNote);
if (index > -1) {
notes.splice(index, 1);
}
}
} else {
// No note found in tapped lane - wrong tap
lives--;
combo = 0;
showFeedback('WRONG LANE!', 0xff0000);
LK.getSound('miss').play();
// Check if game over
if (lives <= 0) {
LK.showGameOver();
}
}
}
};
game.update = function () {
if (gameState === 'playing') {
// Spawn notes with musical rhythm
spawnTimer++;
// Reduced note frequency - spawn every 60 ticks (1 second at 60fps) initially
var baseSpawnRate = 60;
// Keep spawn rate more stable, reduce frequency less aggressively
var spawnRate = Math.max(baseSpawnRate - Math.floor(LK.ticks / 7200), 45); // Slower tempo increase
if (spawnTimer >= spawnRate) {
spawnNote();
spawnTimer = 0;
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
if (!note.isActive) continue;
// Check if note reached the bottom without being hit
if (note.y > 2550) {
combo = 0;
lives--;
showFeedback('MISSED!', 0xff0000);
LK.getSound('miss').play();
note.destroy();
notes.splice(i, 1);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
}
continue;
}
// Remove notes that are way off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
}
// Increase difficulty over time
if (LK.ticks % 1200 === 0) {
// Every 20 seconds
gameSpeed += 0.2; // Faster speed increase
}
// Update UI
updateUI();
// Update score in LK system
LK.setScore(score);
}
};