/**** * 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);
}
};