Code edit (3 edits merged)
Please save this source code
User prompt
use the LK score system too
Code edit (1 edits merged)
Please save this source code
User prompt
play menuSpawn when spawning a menu tile
Code edit (3 edits merged)
Please save this source code
User prompt
in pulse anim, replace tween on scale by a tween on rotation (small rotation left then small rotation right)
User prompt
pusle anim isn't stopped when startbutton is pressed; check why âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
Code edit (9 edits merged)
Please save this source code
User prompt
startPulse isn't working; check again if properties yoyo and repeat exist then fix âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
Code edit (2 edits merged)
Please save this source code
User prompt
Create a MenuButton class with menuIcon asset, visible during game and that return to the menu when tapped (x = 400; y=150)
Code edit (1 edits merged)
Please save this source code
User prompt
take into account speedMultiplier also in Note y calculation
User prompt
vertical distance between notes should increase when speedmultiplier is lower
User prompt
When speedMultiplier != 1.0 notes sound isn't played when notes are tapped on hitline; Analyze and fix
Code edit (1 edits merged)
Please save this source code
User prompt
apply the global speedMultiplier to notes time to control music speed globally. Ie: if speedMultiplier = 1.0 then a note aÌ time = 100 will be played after 100ms; if speedMultiplier = 0.5 then a note aÌ time = 100 will be played after 200ms if speedMultiplier = 2.0 then a note aÌ time = 100 will be played after 50ms
User prompt
add detailed comments to spawnNotes functoin
User prompt
prevent startbutton tap until entry animation ends
Code edit (1 edits merged)
Please save this source code
User prompt
play click sound when tapping a MenuTile
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
currently when 1st note appears it is already at the center of the screen; please analyse and fix so that 1st note first appears above the top of the sceen
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BackgroundManager class: manages the game background var BackgroundManager = Container.expand(function () { var self = Container.call(this); // Attach the background00 asset, anchored at top-left var bg = self.attachAsset('background00', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); return self; }); // ComboText class: animated combo display var ComboText = Container.expand(function () { var self = Container.call(this); // Internal Text2 for display self.textObj = new Text2('', { size: 80, fill: 0xFFFFFF, dropShadow: true }); self.textObj.anchor.set(0.5, 0); self.addChild(self.textObj); // Set text and animate self.setText = function (txt) { self.textObj.setText(txt); // Animate: pop if not empty, fade out if empty tween.stop(self.textObj, { scaleX: true, scaleY: true, alpha: true }); if (txt && txt.length > 0) { self.textObj.alpha = 1; self.textObj.scaleX = 1.0; self.textObj.scaleY = 1.0; tween(self.textObj, { scaleX: 1.25, scaleY: 1.25 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.textObj, { scaleX: 1.0, scaleY: 1.0 }, { duration: 120, easing: tween.cubicIn }); } }); } else { // Fade out if empty tween(self.textObj, { alpha: 0 }, { duration: 180 }); } }; return self; }); // MenuButton class: a button to return to the menu during gameplay var MenuButton = Container.expand(function () { var self = Container.call(this); // Attach the menuIcon asset, centered var icon = self.attachAsset('menuIcon', { anchorX: 0.5, anchorY: 0.5 }); // Handle tap self.down = function (x, y, obj) { // Animate press feedback tween(self, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.cubicOut }); }; self.up = function (x, y, obj) { // Animate release tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { // Stop music LK.stopMusic(); // Reset game state gameActive = false; // Reset note manager if (noteManager) { noteManager.reset(); } // Hide game UI elements if (scoreTxt) { scoreTxt.visible = false; } if (comboTxt) { comboTxt.setText(''); } if (laneHighlights) { for (var i = 0; i < laneHighlights.length; i++) { laneHighlights[i].visible = false; } } if (targets) { for (var i = 0; i < targets.length; i++) { targets[i].visible = false; } } if (restartBtn) { restartBtn.visible = false; } // Hide the menu button itself self.visible = false; // Show the menu if (menuManager) { menuManager.show(); } } }); }; return self; }); // MenuManager class: manages the song selection menu var MenuManager = Container.expand(function () { var self = Container.call(this); // Array to hold menu tiles self.menuTiles = []; // Create menu tiles for each song self.createMenuTiles = function (songs) { // Clear existing tiles for (var i = 0; i < self.menuTiles.length; i++) { self.menuTiles[i].destroy(); } self.menuTiles = []; // Create new tiles var tileHeight = 300; var tileSpacing = 260; // Increased spacing for more vertical space var startY = 500; for (var i = 0; i < songs.length; i++) { var tile = new MenuTile(); tile.x = 2048 / 2; tile.y = startY + i * (tileHeight + tileSpacing); tile.setText(songs[i].name); // Store song index for later use tile.songIndex = i; // Make tile interactive tile.down = function () { var index = this.songIndex; // Play click sound when tile is tapped LK.getSound('click').play(); // Animate press feedback tween(this, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100, easing: tween.cubicOut }); }; tile.up = function () { var index = this.songIndex; // Animate release and trigger song selection tween(this, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { if (self.onSongSelected) { self.onSongSelected(index); } } }); }; self.addChild(tile); self.menuTiles.push(tile); } }; // Callback for when a song is selected self.onSongSelected = null; // Show menu with animation self.show = function () { self.visible = true; self.alpha = 0; tween(self, { alpha: 1 }, { duration: 300, easing: tween.cubicOut }); // Animate tiles in sequence for (var i = 0; i < self.menuTiles.length; i++) { (function (tile, delay) { tile.scaleX = 0.8; tile.scaleY = 0.8; tile.alpha = 0; tween(tile, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 400, delay: delay, easing: tween.cubicOut }); })(self.menuTiles[i], i * 100); } }; // Hide menu with animation self.hide = function () { tween(self, { alpha: 0 }, { duration: 300, easing: tween.cubicIn, onFinish: function onFinish() { self.visible = false; } }); }; return self; }); // MenuTile class: a menu tile with an image and a text label var MenuTile = Container.expand(function () { var self = Container.call(this); // Attach the menuTile asset, centered self.tileImg = self.attachAsset('menuTile', { anchorX: 0.5, anchorY: 0.5 }); // Add a text label aligned to the left self.textObj = new Text2('', { size: 100, fill: 0x222222, dropShadow: true }); self.textObj.anchor.set(0, 0.5); self.textObj.x = -self.tileImg.width / 2 + 100; self.textObj.y = 0; self.addChild(self.textObj); // Add play button on the right self.playBtn = self.attachAsset('playButton', { anchorX: 0.5, anchorY: 0.5, x: self.tileImg.width / 2 - 150, y: 0 }); // Public method to set the text self.setText = function (txt) { self.textObj.setText(txt); }; return self; }); // Note class: a falling dot in a lane, with timing and tap state var Note = Container.expand(function () { var self = Container.call(this); // Attach the note dot asset, centered self.noteBall = self.attachAsset('noteDot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 }); self.noteSign = self.attachAsset('noteSign', { anchorX: 0.5, anchorY: 0.5 }); // Lane index (0,1,2) self.lane = 0; // Time (in ms) when this note should be hit self.hitTime = 0; // Whether this note has been tapped self.tapped = false; // Whether this note has been missed self.missed = false; // For tap feedback self.showTapFeedback = function () { var feedback = self.attachAsset('tapFeedback', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); // Spawn sparkles at note position var sparkles = new Sparkles(); sparkles.x = self.x; sparkles.y = self.y; if (self.parent) { self.parent.addChild(sparkles); } // Animate noteSign scale up to 4 and back to 1 self.noteSign.scaleX = 1; self.noteSign.scaleY = 1; tween(self.noteSign, { scaleX: 3, scaleY: 3 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { // tween(self.noteSign, { // scaleX: 1, // scaleY: 1 // }, { // duration: 180, // easing: tween.cubicIn // }); } }); tween(feedback, { alpha: 0 }, { duration: 250, onFinish: function onFinish() { feedback.destroy(); } }); }; // Called every tick self.update = function () { // Calculate songElapsed and t for this note if (!gameActive) { return; } var now = Date.now(); var songElapsed = now - songStartTime; // Calculate progress: 0 (spawn) to 1 (at hit line), allow t > 1 for movement past hit line // Apply speedMultiplier to make notes move at the correct visual speed var adjustedTravelTime = noteTravelTime * speedMultiplier; var t = (songElapsed - (self.hitTime - adjustedTravelTime)) / adjustedTravelTime; // Don't clamp t to 0, allow negative values for proper positioning before note starts moving // Remove the clamp for t > 1, so notes keep moving past hit line self.y = noteStartY + (hitLineY - noteStartY) * t + (t > 1 ? (t - 1) * (2732 - hitLineY) : 0); // Miss detection: if note passes hit line and not tapped if (!self.tapped && !self.missed && songElapsed > self.hitTime + 220) { self.missed = true; self.alpha = 0.3; combo = 0; // Show and animate 'Missed!' on comboTxt in red comboTxt.tint = 0xff2222; // set to red comboTxt.setText('Missed!'); tween.stop(comboTxt, { scaleX: true, scaleY: true, alpha: true }); comboTxt.scaleX = 1.0; comboTxt.scaleY = 1.0; comboTxt.alpha = 1.0; tween(comboTxt, { scaleX: 2.0, scaleY: 2.0, alpha: 0.0 }, { duration: 600, easing: tween.cubicOut, onFinish: function onFinish() { comboTxt.setText(''); comboTxt.tint = 0x3A8EE6; // restore to blue after comboTxt.scaleX = 1.0; comboTxt.scaleY = 1.0; comboTxt.alpha = 1.0; } }); // Animate noteSign to flash red tween(self.noteSign, { tint: 0xff0000 }, { duration: 60, onFinish: function onFinish() { tween(self.noteSign, { tint: 0xffffff }, { duration: 180 }); } }); // Play tapMiss sound when missed LK.getSound('tapMiss').play(); } }; // Play the corresponding key sound for this note self.down = function () { // Find the original song note for this Note instance // We'll use the lane and hitTime to match to songNotesRaw for (var i = 0; i < songNotesRaw.length; i++) { var sn = songNotesRaw[i]; var lane = 0; if (keyToLane.hasOwnProperty(sn.key)) { lane = keyToLane[sn.key]; } else { lane = i % 3; } // Compare adjusted time (sn.time / speedMultiplier) with note's hitTime if (lane === self.lane && sn.time / speedMultiplier === self.hitTime) { if (typeof sn.key === "string") { var keySoundName = sn.key.toLowerCase(); var keySound = LK.getSound(keySoundName); if (keySound) { keySound.play(); } } break; } } }; return self; }); // NoteManager class: handles spawning and management of notes var NoteManager = Container.expand(function () { var self = Container.call(this); // Notes in play self.notes = []; // Index of next note to spawn self.nextNoteIdx = 0; // Reset all state self.reset = function () { for (var i = 0; i < self.notes.length; i++) { self.notes[i].destroy(); } self.notes = []; self.nextNoteIdx = 0; }; // Spawn notes as their time approaches self.spawnNotes = function (songNotes, songElapsed, noteTravelTime, laneX, noteStartY) { // This function spawns notes at the right time so they reach the hit line exactly when they should be tapped // // Parameters: // - songNotes: Array of all notes in the song, each with {lane: 0|1|2, time: ms} // - songElapsed: Current time elapsed since song started (in milliseconds) // - noteTravelTime: Time it takes for a note to travel from spawn point to hit line (in milliseconds) // - laneX: Array of x-coordinates for each lane [left, center, right] // - noteStartY: Y-coordinate where notes spawn (above the screen) // The key timing logic: // A note should spawn when: songElapsed >= (note.hitTime - noteTravelTime) // This ensures the note has exactly noteTravelTime milliseconds to travel to the hit line // Example: If a note should be hit at 5000ms and travel time is 4000ms, it spawns at 1000ms // Keep spawning notes while there are unspawned notes whose spawn time has arrived // Apply speedMultiplier to the note time to control music playback speed while (self.nextNoteIdx < songNotes.length && songNotes[self.nextNoteIdx].time / speedMultiplier - noteTravelTime <= songElapsed) { // Get the data for the next note to spawn var noteData = songNotes[self.nextNoteIdx]; // Create a new Note instance var note = new Note(); // Set which lane (0=left, 1=center, 2=right) note.lane = noteData.lane; // Set when this note should be hit (in ms from song start) // Apply speedMultiplier to sync with music speed note.hitTime = noteData.time / speedMultiplier; // Position the note horizontally in its lane note.x = laneX[note.lane]; // Position the note vertically at the spawn point (above screen) note.y = noteStartY; // Add note to our tracking array self.notes.push(note); // Add note to the game display game.addChild(note); // Move to the next note in the song self.nextNoteIdx++; } }; // Remove notes that are far past the hit line self.cleanupNotes = function (songElapsed) { for (var i = self.notes.length - 1; i >= 0; i--) { var note = self.notes[i]; // Remove note if it has moved past the bottom of the screen if (note.y > 2732 + 100) { note.destroy(); self.notes.splice(i, 1); } } }; // Remove a specific note from the manager self.removeNote = function (note) { for (var i = 0; i < self.notes.length; i++) { if (self.notes[i] === note) { self.notes.splice(i, 1); break; } } }; // Get all notes in play self.getNotes = function () { return self.notes; }; // Get the index of the next note to spawn self.getNextNoteIdx = function () { return self.nextNoteIdx; }; // Set the index of the next note to spawn self.setNextNoteIdx = function (idx) { self.nextNoteIdx = idx; }; return self; }); // RestartButton class: a restart button shown during gameplay var RestartButton = Container.expand(function () { var self = Container.call(this); // Attach the restartButton asset var btn = self.attachAsset('restartButton', { anchorX: 0.5, anchorY: 0.5 }); // Handle tap self.down = function (x, y, obj) { // Animate press feedback tween(self, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.cubicOut }); }; self.up = function (x, y, obj) { // Animate release and restart game tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.cubicIn, onFinish: function onFinish() { // Stop music and restart current song LK.stopMusic(); gameActive = false; // Reset note manager if (noteManager) { noteManager.reset(); } // Restart the current song (not the entire game) startGame(); } }); }; return self; }); // ScoreText class: animated score display (mirrors ComboText) var ScoreText = Container.expand(function () { var self = Container.call(this); // Internal Text2 for display self.textObj = new Text2('', { size: 120, fill: 0xFFFFFF, dropShadow: true }); self.textObj.anchor.set(0.5, 0); self.addChild(self.textObj); // Set text and animate self.setText = function (txt) { self.textObj.setText(txt); // Animate: pop if not empty, fade out if empty tween.stop(self.textObj, { scaleX: true, scaleY: true, alpha: true }); if (txt && txt.length > 0) { self.textObj.alpha = 1; self.textObj.scaleX = 1.0; self.textObj.scaleY = 1.0; tween(self.textObj, { scaleX: 1.25, scaleY: 1.25 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(self.textObj, { scaleX: 1.0, scaleY: 1.0 }, { duration: 120, easing: tween.cubicIn }); } }); } else { // Fade out if empty tween(self.textObj, { alpha: 0 }, { duration: 180 }); } }; return self; }); // Sparkles class: creates and animates a small particle explosion var Sparkles = Container.expand(function () { var self = Container.call(this); // Configurable parameters var particleCount = 12; var minSpeed = 160; var maxSpeed = 360; var minScale = 0.5; var maxScale = 1.2; var minAlpha = 0.7; var maxAlpha = 1.0; var minDuration = 320; var maxDuration = 1520; // Particle colors: only white to blue shades var colors = [0xffffff, // white 0xe0f7fa, // very light blue 0xb3e5fc, // light blue 0x81d4fa, // sky blue 0x4fc3f7, // lighter blue 0x29b6f6, // blue 0x039be5, // vivid blue 0x0288d1, // deep blue 0x0277bd, // darker blue 0x01579b, // navy blue 0x3a8ee6 // main game blue ]; // Create particles for (var i = 0; i < particleCount; i++) { var angle = Math.PI * 2 * (i / particleCount); // + Math.random() * 0.3; var speed = minSpeed + Math.random() * (maxSpeed - minSpeed); var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; var scale = minScale + Math.random() * (maxScale - minScale); var alpha = minAlpha + Math.random() * (maxAlpha - minAlpha); var color = colors[Math.floor(Math.random() * colors.length)]; var duration = minDuration + Math.random() * (maxDuration - minDuration); var targetAngle = Math.PI * 2 * Math.random(); // Use a small circle as the sparkle var sparkle = self.attachAsset('sparkle', { anchorX: 0.5, anchorY: 0.5, scaleX: scale, scaleY: scale, alpha: alpha, tint: color }); // Animate outward movement, fade and shrink (function (sparkle, vx, vy, duration) { var startX = 0, startY = 0; // Calculate the final position based on velocity and duration (assuming 60fps, so duration/1000 seconds) var seconds = duration / 1000; var finalX = startX + vx * seconds; var finalY = startY + vy * seconds; tween(sparkle, { x: finalX, y: finalY, alpha: 0, scaleX: 0.1, scaleY: 0.1, rotation: targetAngle }, { duration: duration, easing: tween.cubicOut, onFinish: function onFinish() { sparkle.destroy(); } }); })(sparkle, vx, vy, duration); } // Destroy the container after all particles are done LK.setTimeout(function () { self.destroy(); }, maxDuration + 40); return self; }); // StartButton class: a tappable start button with image and animation var StartButton = Container.expand(function () { var self = Container.call(this); // Attach a big noteDot asset, centered, before the startText asset var dot = self.attachAsset('noteDot', { anchorX: 0.5, anchorY: 0.5, scaleX: 5, scaleY: 5, alpha: 0.85 }); // Attach the startText asset, centered, above the dot var btn = self.attachAsset('startText', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1, alpha: 1 }); // Track if animation is complete self.animationComplete = false; // Animate pop-in (longer duration) self.scaleX = 0.1; self.scaleY = 0.1; tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 1000, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 240, easing: tween.cubicIn, onFinish: function onFinish() { self.animationComplete = true; } }); } }); // Optional: pulse animation var pulseTween; function startPulse() { pulseTween = tween(self, { scaleX: 1.08, scaleY: 1.08 }, { duration: 1000, easing: tween.cubicInOut, yoyo: true, repeat: Infinity }); } startPulse(); // Public: set callback for tap self.onTap = null; // Handle tap self.down = function (x, y, obj) { // Only allow tap if animation is complete if (!self.animationComplete) { return; } // Animate tap feedback tween.stop(self, { scaleX: true, scaleY: true }); self.scaleX = 1.0; self.scaleY = 1.0; // --- Moved start logic here --- // Play start sound when start is pressed LK.getSound('startSound').play(); // Animate exit: scale up and fade out, then destroy and start game (longer duration) tween(self, { scaleX: 100, scaleY: 100, alpha: 0 }, { duration: 1200, easing: tween.cubicIn, onFinish: function onFinish() { self.destroy(); self = null; startGame(); } }); }; // Clean up pulse tween on destroy var origDestroy = self.destroy; self.destroy = function () { if (pulseTween) { tween.stop(self, { scaleX: true, scaleY: true }); } origDestroy.call(self); }; return self; }); // Target class: represents the target area for each lane var Target = Container.expand(function () { var self = Container.call(this); // Attach the target asset, centered, with blue tint var targetAsset = self.attachAsset('target', { anchorX: 0.5, anchorY: 0.5, tint: 0x3a8ee6, alpha: 0.8 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Music track (Ode to Joy, assumed loaded as 'odeToJoy') // Sound for miss // Sound for correct tap // Lane highlight (subtle gray) // Tap feedback (blue highlight) // Falling note (white dot) // Sound assets for key0 to key14 var SONGS = [{ "name": "Ode to Joy\r\nBeethoven", "bpm": 220, "pitchLevel": 0, "bitsPerPage": 16, "isComposed": false, "songNotes": [{ "time": 1432, "key": "Key6" }, { "time": 1855, "key": "Key6" }, { "time": 2305, "key": "Key7" }, { "time": 2788, "key": "Key8" }, { "time": 3216, "key": "Key8" }, { "time": 3666, "key": "Key7" }, { "time": 4122, "key": "Key6" }, { "time": 4567, "key": "Key5" }, { "time": 5027, "key": "Key4" }, { "time": 5479, "key": "Key4" }, { "time": 5937, "key": "Key5" }, { "time": 6397, "key": "Key6" }, { "time": 6864, "key": "Key6" }, { "time": 7583, "key": "Key5" }, { "time": 7820, "key": "Key5" }, { "time": 8816, "key": "Key6" }, { "time": 9289, "key": "Key6" }, { "time": 9778, "key": "Key7" }, { "time": 10205, "key": "Key8" }, { "time": 10672, "key": "Key8" }, { "time": 11108, "key": "Key7" }, { "time": 11564, "key": "Key6" }, { "time": 12000, "key": "Key5" }, { "time": 12455, "key": "Key4" }, { "time": 12911, "key": "Key4" }, { "time": 13339, "key": "Key5" }, { "time": 13785, "key": "Key6" }, { "time": 14370, "key": "Key5" }, { "time": 15131, "key": "Key4" }, { "time": 15341, "key": "Key4" }, { "time": 16318, "key": "Key5" }, { "time": 16760, "key": "Key5" }, { "time": 17243, "key": "Key6" }, { "time": 17711, "key": "Key4" }, { "time": 18164, "key": "Key5" }, { "time": 18607, "key": "Key6" }, { "time": 18840, "key": "Key7" }, { "time": 19107, "key": "Key6" }, { "time": 19556, "key": "Key4" }, { "time": 20007, "key": "Key5" }, { "time": 20428, "key": "Key6" }, { "time": 20634, "key": "Key7" }, { "time": 20915, "key": "Key6" }, { "time": 21375, "key": "Key5" }, { "time": 21859, "key": "Key4" }, { "time": 22325, "key": "Key5" }, { "time": 22818, "key": "Key1" }, { "time": 23809, "key": "Key6" }, { "time": 24259, "key": "Key6" }, { "time": 24725, "key": "Key7" }, { "time": 25156, "key": "Key8" }, { "time": 25597, "key": "Key8" }, { "time": 26039, "key": "Key7" }, { "time": 26496, "key": "Key6" }, { "time": 26950, "key": "Key5" }, { "time": 27413, "key": "Key4" }, { "time": 27882, "key": "Key4" }, { "time": 28309, "key": "Key5" }, { "time": 28830, "key": "Key6" }, { "time": 29319, "key": "Key5" }, { "time": 30092, "key": "Key4" }, { "time": 30343, "key": "Key4" }], "fromLibrary": true }, { "name": "Bella ciao", "bpm": 650, "pitchLevel": 4, "bitsPerPage": 16, "isComposed": true, "songNotes": [{ "time": 368, "key": "Key2" }, { "time": 552, "key": "Key5" }, { "time": 736, "key": "Key6" }, { "time": 1012, "key": "Key7" }, { "time": 1196, "key": "Key5" }, { "time": 1932, "key": "Key2" }, { "time": 2208, "key": "Key5" }, { "time": 2392, "key": "Key6" }, { "time": 2576, "key": "Key7" }, { "time": 2852, "key": "Key5" }, { "time": 3588, "key": "Key2" }, { "time": 3772, "key": "Key5" }, { "time": 3956, "key": "Key6" }, { "time": 4232, "key": "Key7" }, { "time": 4508, "key": "Key6" }, { "time": 4692, "key": "Key5" }, { "time": 4968, "key": "Key7" }, { "time": 5244, "key": "Key6" }, { "time": 5428, "key": "Key5" }, { "time": 5704, "key": "Key9" }, { "time": 5980, "key": "Key9" }, { "time": 6348, "key": "Key9" }, { "time": 6532, "key": "Key9" }, { "time": 6808, "key": "Key8" }, { "time": 6992, "key": "Key9" }, { "time": 7176, "key": "Key10" }, { "time": 7452, "key": "Key10" }, { "time": 7636, "key": "Key1" }, { "time": 7912, "key": "Key3" }, { "time": 8188, "key": "Key10" }, { "time": 8372, "key": "Key9" }, { "time": 8556, "key": "Key8" }, { "time": 8832, "key": "Key10" }, { "time": 9016, "key": "Key9" }, { "time": 9752, "key": "Key9" }, { "time": 9936, "key": "Key8" }, { "time": 10120, "key": "Key7" }, { "time": 10396, "key": "Key6" }, { "time": 10672, "key": "Key9" }, { "time": 11040, "key": "Key6" }, { "time": 11316, "key": "Key7" }, { "time": 11684, "key": "Key5" }, { "time": 11960, "key": "Key2" }, { "time": 12328, "key": "Key2" }, { "time": 12512, "key": "Key2" }, { "time": 12696, "key": "Key5" }, { "time": 12972, "key": "Key6" }, { "time": 13156, "key": "Key7" }, { "time": 13340, "key": "Key5" }, { "time": 14168, "key": "Key2" }, { "time": 14352, "key": "Key5" }, { "time": 14536, "key": "Key6" }, { "time": 14720, "key": "Key7" }, { "time": 14904, "key": "Key5" }, { "time": 15640, "key": "Key2" }, { "time": 15916, "key": "Key5" }, { "time": 16100, "key": "Key6" }, { "time": 16284, "key": "Key7" }, { "time": 16652, "key": "Key6" }, { "time": 16836, "key": "Key5" }, { "time": 17112, "key": "Key7" }, { "time": 17388, "key": "Key6" }, { "time": 17664, "key": "Key5" }, { "time": 17848, "key": "Key9" }, { "time": 18124, "key": "Key9" }, { "time": 18492, "key": "Key9" }, { "time": 18676, "key": "Key9" }, { "time": 18952, "key": "Key8" }, { "time": 19136, "key": "Key9" }, { "time": 19320, "key": "Key10" }, { "time": 19596, "key": "Key10" }, { "time": 19780, "key": "Key1" }, { "time": 20056, "key": "Key3" }, { "time": 20332, "key": "Key10" }, { "time": 20516, "key": "Key1" }, { "time": 20516, "key": "Key9" }, { "time": 20700, "key": "Key8" }, { "time": 20976, "key": "Key10" }, { "time": 21160, "key": "Key9" }, { "time": 21896, "key": "Key9" }, { "time": 22080, "key": "Key8" }, { "time": 22264, "key": "Key7" }, { "time": 22540, "key": "Key6" }, { "time": 22816, "key": "Key9" }, { "time": 23184, "key": "Key6" }, { "time": 23460, "key": "Key7" }, { "time": 23828, "key": "Key5" }, { "time": 24104, "key": "Key2" }, { "time": 24472, "key": "Key2" }, { "time": 24656, "key": "Key2" }, { "time": 24840, "key": "Key5" }, { "time": 25116, "key": "Key6" }, { "time": 25300, "key": "Key7" }, { "time": 25484, "key": "Key5" }, { "time": 26220, "key": "Key2" }, { "time": 26496, "key": "Key5" }, { "time": 26680, "key": "Key6" }, { "time": 26864, "key": "Key7" }, { "time": 27048, "key": "Key5" }, { "time": 27784, "key": "Key2" }, { "time": 27968, "key": "Key5" }, { "time": 28152, "key": "Key6" }, { "time": 28428, "key": "Key7" }, { "time": 28704, "key": "Key6" }, { "time": 28980, "key": "Key5" }, { "time": 29164, "key": "Key7" }, { "time": 29532, "key": "Key6" }, { "time": 29716, "key": "Key5" }, { "time": 29900, "key": "Key9" }, { "time": 30268, "key": "Key9" }, { "time": 30544, "key": "Key9" }, { "time": 30728, "key": "Key9" }, { "time": 31004, "key": "Key8" }, { "time": 31188, "key": "Key9" }, { "time": 31372, "key": "Key10" }, { "time": 31648, "key": "Key10" }, { "time": 31832, "key": "Key1" }, { "time": 32200, "key": "Key3" }, { "time": 32384, "key": "Key10" }, { "time": 32568, "key": "Key1" }, { "time": 32568, "key": "Key9" }, { "time": 32844, "key": "Key8" }, { "time": 33028, "key": "Key10" }, { "time": 33212, "key": "Key9" }, { "time": 33948, "key": "Key9" }, { "time": 34132, "key": "Key8" }, { "time": 34408, "key": "Key7" }, { "time": 34592, "key": "Key6" }, { "time": 34868, "key": "Key9" }, { "time": 35236, "key": "Key6" }, { "time": 35512, "key": "Key7" }, { "time": 35880, "key": "Key5" }, { "time": 36156, "key": "Key2" }, { "time": 36524, "key": "Key2" }, { "time": 36708, "key": "Key2" }, { "time": 36984, "key": "Key5" }, { "time": 37168, "key": "Key6" }, { "time": 37352, "key": "Key7" }, { "time": 37628, "key": "Key5" }, { "time": 38364, "key": "Key2" }, { "time": 38548, "key": "Key5" }, { "time": 38732, "key": "Key6" }, { "time": 39008, "key": "Key7" }, { "time": 39192, "key": "Key5" }, { "time": 39928, "key": "Key2" }, { "time": 40112, "key": "Key5" }, { "time": 40388, "key": "Key6" }, { "time": 40572, "key": "Key7" }, { "time": 40940, "key": "Key6" }, { "time": 41124, "key": "Key5" }, { "time": 41308, "key": "Key7" }, { "time": 41676, "key": "Key6" }, { "time": 41860, "key": "Key5" }, { "time": 42044, "key": "Key9" }, { "time": 42412, "key": "Key9" }, { "time": 42688, "key": "Key9" }, { "time": 42964, "key": "Key9" }, { "time": 43148, "key": "Key8" }, { "time": 43332, "key": "Key9" }, { "time": 43608, "key": "Key10" }, { "time": 43792, "key": "Key10" }, { "time": 43976, "key": "Key1" }, { "time": 44344, "key": "Key3" }, { "time": 44528, "key": "Key10" }, { "time": 44712, "key": "Key1" }, { "time": 44712, "key": "Key9" }, { "time": 44988, "key": "Key8" }, { "time": 45172, "key": "Key10" }, { "time": 45356, "key": "Key9" }, { "time": 46092, "key": "Key9" }, { "time": 46276, "key": "Key8" }, { "time": 46552, "key": "Key7" }, { "time": 46736, "key": "Key6" }, { "time": 47104, "key": "Key9" }, { "time": 47380, "key": "Key6" }, { "time": 47748, "key": "Key7" }, { "time": 48024, "key": "Key5" }, { "time": 48392, "key": "Key0" }, { "time": 48392, "key": "Key2" }, { "time": 48668, "key": "Key0" }, { "time": 48668, "key": "Key2" }], "fromLibrary": true }, { "name": "Believer\r\nImagine Dragons", "bpm": 200, "pitchLevel": 0, "bitsPerPage": 16, "isComposed": "false", "songNotes": [{ "time": 1266, "key": "Key1" }, { "time": 1773, "key": "Key5" }, { "time": 2237, "key": "Key4" }, { "time": 2745, "key": "Key4" }, { "time": 3091, "key": "Key3" }, { "time": 3275, "key": "Key4" }, { "time": 3707, "key": "Key4" }, { "time": 4037, "key": "Key5" }, { "time": 4212, "key": "Key4" }, { "time": 4545, "key": "Key3" }, { "time": 4787, "key": "Key1" }, { "time": 5162, "key": "Key0" }, { "time": 5399, "key": "Key1" }, { "time": 6060, "key": "Key5" }, { "time": 6529, "key": "Key4" }, { "time": 6993, "key": "Key4" }, { "time": 7338, "key": "Key3" }, { "time": 7521, "key": "Key4" }, { "time": 7971, "key": "Key4" }, { "time": 8264, "key": "Key5" }, { "time": 8432, "key": "Key4" }, { "time": 8760, "key": "Key3" }, { "time": 8940, "key": "Key1" }, { "time": 9281, "key": "Key0" }, { "time": 9458, "key": "Key1" }, { "time": 9961, "key": "Key3" }, { "time": 10433, "key": "Key8" }, { "time": 11387, "key": "Key5" }, { "time": 12115, "key": "Key5" }, { "time": 12314, "key": "Key4" }, { "time": 12650, "key": "Key3" }, { "time": 12818, "key": "Key1" }, { "time": 13129, "key": "Key0" }, { "time": 13336, "key": "Key1" }, { "time": 13833, "key": "Key3" }, { "time": 14329, "key": "Key8" }, { "time": 15243, "key": "Key7" }, { "time": 17091, "key": "Key1" }, { "time": 17394, "key": "Key1" }, { "time": 17587, "key": "Key5" }, { "time": 18033, "key": "Key4" }, { "time": 18329, "key": "Key4" }, { "time": 18521, "key": "Key4" }, { "time": 18844, "key": "Key3" }, { "time": 19026, "key": "Key4" }, { "time": 19484, "key": "Key4" }, { "time": 19745, "key": "Key5" }, { "time": 19940, "key": "Key4" }, { "time": 20249, "key": "Key3" }, { "time": 20402, "key": "Key1" }, { "time": 20714, "key": "Key0" }, { "time": 20890, "key": "Key1" }, { "time": 21338, "key": "Key4" }, { "time": 21642, "key": "Key5" }, { "time": 21813, "key": "Key4" }, { "time": 22289, "key": "Key4" }, { "time": 22586, "key": "Key3" }, { "time": 22786, "key": "Key4" }, { "time": 23225, "key": "Key4" }, { "time": 23554, "key": "Key5" }, { "time": 23722, "key": "Key4" }, { "time": 24021, "key": "Key3" }, { "time": 24153, "key": "Key1" }, { "time": 24427, "key": "Key0" }, { "time": 24642, "key": "Key1" }, { "time": 25073, "key": "Key3" }, { "time": 25541, "key": "Key8" }, { "time": 26547, "key": "Key5" }, { "time": 27282, "key": "Key5" }, { "time": 27474, "key": "Key4" }, { "time": 27787, "key": "Key3" }, { "time": 27923, "key": "Key1" }, { "time": 28221, "key": "Key0" }, { "time": 28422, "key": "Key1" }, { "time": 28906, "key": "Key3" }, { "time": 29379, "key": "Key8" }, { "time": 30386, "key": "Key7" }, { "time": 31794, "key": "Key1" }, { "time": 31930, "key": "Key0" }, { "time": 32126, "key": "Key1" }, { "time": 32419, "key": "Key1" }, { "time": 32739, "key": "Key1" }, { "time": 32907, "key": "Key1" }, { "time": 33055, "key": "Key1" }, { "time": 33490, "key": "Key1" }, { "time": 33778, "key": "Key1" }, { "time": 33897, "key": "Key0" }, { "time": 34047, "key": "Key1" }, { "time": 34396, "key": "Key1" }, { "time": 34681, "key": "Key1" }, { "time": 34836, "key": "Key1" }, { "time": 34996, "key": "Key1" }, { "time": 35396, "key": "Key1" }, { "time": 35852, "key": "Key3" }, { "time": 36187, "key": "Key3" }, { "time": 36507, "key": "Key3" }, { "time": 36658, "key": "Key2" }, { "time": 36796, "key": "Key3" }, { "time": 37212, "key": "Key3" }, { "time": 37667, "key": "Key2" }, { "time": 37988, "key": "Key2" }, { "time": 38306, "key": "Key2" }, { "time": 38437, "key": "Key1" }, { "time": 38596, "key": "Key2" }, { "time": 39020, "key": "Key2" }, { "time": 39443, "key": "Key1" }, { "time": 39794, "key": "Key1" }, { "time": 40098, "key": "Key1" }, { "time": 40234, "key": "Key0" }, { "time": 40394, "key": "Key1" }, { "time": 40860, "key": "Key1" }, { "time": 41282, "key": "Key1" }, { "time": 41626, "key": "Key1" }, { "time": 41955, "key": "Key1" }, { "time": 42114, "key": "Key0" }, { "time": 42274, "key": "Key1" }, { "time": 42674, "key": "Key1" }, { "time": 43108, "key": "Key3" }, { "time": 43436, "key": "Key3" }, { "time": 43762, "key": "Key3" }, { "time": 43925, "key": "Key2" }, { "time": 44075, "key": "Key3" }, { "time": 44539, "key": "Key3" }, { "time": 44988, "key": "Key2" }, { "time": 45325, "key": "Key2" }, { "time": 45676, "key": "Key2" }, { "time": 45821, "key": "Key1" }, { "time": 46037, "key": "Key2" }, { "time": 46523, "key": "Key2" }, { "time": 47924, "key": "Key8" }, { "time": 48636, "key": "Key7" }, { "time": 48845, "key": "Key10" }, { "time": 49155, "key": "Key9" }, { "time": 49347, "key": "Key8" }, { "time": 49669, "key": "Key7" }, { "time": 49914, "key": "Key10" }, { "time": 50239, "key": "Key9" }, { "time": 50414, "key": "Key8" }, { "time": 50741, "key": "Key7" }, { "time": 50900, "key": "Key8" }, { "time": 51411, "key": "Key10" }, { "time": 52468, "key": "Key10" }, { "time": 52659, "key": "Key9" }, { "time": 53156, "key": "Key7" }, { "time": 55582, "key": "Key8" }, { "time": 56259, "key": "Key7" }, { "time": 56459, "key": "Key10" }, { "time": 56747, "key": "Key9" }, { "time": 56923, "key": "Key8" }, { "time": 57238, "key": "Key7" }, { "time": 57423, "key": "Key10" }, { "time": 57715, "key": "Key9" }, { "time": 57898, "key": "Key8" }, { "time": 58211, "key": "Key7" }, { "time": 58411, "key": "Key8" }, { "time": 58915, "key": "Key10" }, { "time": 59989, "key": "Key10" }, { "time": 60161, "key": "Key9" }, { "time": 60659, "key": "Key7" }], "fromLibrary": true }]; ; // --- Song Data: Use SONGS[0] --- // Each note: {lane: 0|1|2, time: ms from song start} // Lane 0: left, 1: center, 2: right // We'll map keys to lanes below. var songNotesRaw = SONGS[0].songNotes; var keyToLane = { "Key0": 0, "Key1": 1, "Key2": 2, "Key3": 0, "Key4": 1, "Key5": 2, "Key6": 0, "Key7": 1, "Key8": 2, "Key9": 0, "Key10": 1, "Key11": 2, "Key12": 0, "Key13": 1, "Key14": 2 }; var songNotes = []; // Song duration (ms) var songDuration; // --- Lane positions (3 columns) --- var laneCount = 3; var laneWidth = 600; var laneOffset = 150; var laneSpacing = 2048 / laneCount; var laneX = [laneSpacing * 0.5 + laneOffset, // left laneSpacing * 1.5, // center laneSpacing * 2.5 - laneOffset // right ]; // --- Visual: show hit line (subtle, not interactive) --- var hitLine; // --- Hit line (invisible, for reference) --- var hitLineYDisplay; // --- Instantiate 3 targets, one in each column at hitLineY --- var targets = []; // --- Note fall parameters --- var speedMultiplier = 0.75; //1.0; // Global speed multiplier (1.0 = normal, >1 = faster, <1 = slower) var baseNoteTravelTime = 4000; //8000; // ms: base time from spawn (top) to hit line (bottom) var noteTravelTime = baseNoteTravelTime / speedMultiplier; // effective travel time, updated if speedMultiplier changes var hitLineY = 2000; // y position where notes should be tapped var baseNoteStartY = -900; // base spawn position var noteStartY = baseNoteStartY; // actual spawn position (will be updated based on speedMultiplier) // --- State --- // Declare all global game objects as variables (no instantiation here) var noteManager; var menuManager; var restartBtn; var songStartTime; var gameActive; var score; var combo; var maxCombo; var lastTapTime; // --- GUI Elements --- var scoreTxt; var comboTxt; var menuButton; // --- Instantiate and add background manager --- var bgManager; // --- Lane highlights (for visual feedback) --- var laneHighlights; function startGame() { // Reset state if (!noteManager) { noteManager = new NoteManager(); } noteManager.reset(); score = 0; combo = 0; maxCombo = 0; if (!scoreTxt) { scoreTxt = new ScoreText(); scoreTxt.y = 0; LK.gui.top.addChild(scoreTxt); } scoreTxt.visible = true; scoreTxt.setText('0'); if (!comboTxt) { comboTxt = new ComboText(); comboTxt.y = 130; comboTxt.tint = 0x3A8EE6; LK.gui.top.addChild(comboTxt); } comboTxt.setText(''); lastTapTime = 0; // Show lane highlights when game starts if (laneHighlights) { for (var i = 0; i < laneHighlights.length; i++) { laneHighlights[i].visible = true; } } // Show targets when game starts if (targets) { for (var i = 0; i < targets.length; i++) { targets[i].visible = true; } } // Show restart button when game starts if (restartBtn) { restartBtn.visible = true; } // Show menu button when game starts if (menuButton) { menuButton.visible = true; } LK.setTimeout(function () { gameActive = true; songStartTime = Date.now(); }, 1000); } // --- Main game update loop --- game.update = function () { // Update noteTravelTime in case speedMultiplier has changed noteTravelTime = baseNoteTravelTime / speedMultiplier; // Update noteStartY to maintain visual spacing when speed changes noteStartY = baseNoteStartY / speedMultiplier; if (!gameActive) { return; } var now = Date.now(); var songElapsed = now - songStartTime; // --- Spawn notes as their time approaches --- noteManager.spawnNotes(songNotes, songElapsed, noteTravelTime, laneX, noteStartY); // --- Update notes: remove notes that are far past the hit line --- noteManager.cleanupNotes(songElapsed); // --- Win condition: song finished and all notes handled --- if (songElapsed > songDuration + 1000 && noteManager.getNotes().length === 0 && gameActive) { gameActive = false; // Play cheers sound before YouWin LK.getSound('cheers').play(); LK.setTimeout(function () { LK.showYouWin(); }, 2000); } }; // --- Tap input handling --- // Convert x to lane index (0,1,2) function getLaneFromX(x) { // Each lane is laneSpacing wide, centered at laneX[i] for (var i = 0; i < laneCount; i++) { var left = laneX[i] - laneWidth / 2; var right = laneX[i] + laneWidth / 2; if (x >= left && x <= right) { return i; } } // Out of bounds return -1; } // On tap (down) anywhere in game game.down = function (x, y, obj) { if (!gameActive) { return; } // Only accept taps near the hit line (±220px) if (y < hitLineY - 220 || y > hitLineY + 220) { return; } var lane = getLaneFromX(x); if (lane < 0 || lane >= laneCount) { return; } // Find the earliest untapped note in this lane within hit window var now = Date.now(); var songElapsed = now - songStartTime; var bestNote = null; var bestDelta = 9999; var notesInPlay = noteManager.getNotes(); for (var i = 0; i < notesInPlay.length; i++) { var note = notesInPlay[i]; if (note.lane !== lane) { continue; } if (note.tapped || note.missed) { continue; } var delta = Math.abs(songElapsed - note.hitTime); if (delta < 320 && delta < bestDelta) { // 320ms window bestNote = note; bestDelta = delta; } } if (bestNote) { // Correct tap! bestNote.tapped = true; bestNote.showTapFeedback(); LK.getSound('tapGood').play(); // Play the corresponding key sound for this note (moved to Note.down) if (typeof bestNote.down === "function") { bestNote.down(); } // Calculate points based on the distance to the hitLineY var distance = Math.abs(bestNote.y - hitLineY); var norm = distance / (bestNote.height / 2); var points = 1; if (norm <= 0.1) { points = 10; } else if (norm <= 0.2) { points = 9; } else if (norm <= 0.3) { points = 8; } else if (norm <= 0.4) { points = 7; } else if (norm <= 0.5) { points = 6; } else if (norm <= 0.6) { points = 5; } else if (norm <= 0.7) { points = 4; } else if (norm <= 0.8) { points = 3; } else if (norm <= 0.9) { points = 2; } else { points = 1; } combo += 1; score += combo * points; if (combo > maxCombo) { maxCombo = combo; } scoreTxt.setText(score + ''); // Animate scoreTxt scale pop tween.stop(scoreTxt, { scaleX: true, scaleY: true }); scoreTxt.scaleX = 1.0; scoreTxt.scaleY = 1.0; tween(scoreTxt, { scaleX: 1.2, scaleY: 1.2 }, { duration: 160, easing: tween.cubicOut, onFinish: function onFinish() { tween(scoreTxt, { scaleX: 1.0, scaleY: 1.0 }, { duration: 120, easing: tween.cubicIn }); } }); if (combo > 1) { comboTxt.setText('Combo x' + combo + '!'); // Animate comboTxt scale pop tween.stop(comboTxt, { scaleX: true, scaleY: true }); comboTxt.scaleX = 1.0; comboTxt.scaleY = 1.0; tween(comboTxt, { scaleX: 1.6, scaleY: 1.6 }, { duration: 160, easing: tween.cubicOut, onFinish: function onFinish() { tween(comboTxt, { scaleX: 1.0, scaleY: 1.0 }, { duration: 120, easing: tween.cubicIn }); } }); } else { comboTxt.setText(''); } // Flash lane blue LK.effects.flashObject(laneHighlights[lane], 0x3a8ee6, 180); // Animate corresponding target scale var tappedTarget = targets[lane]; if (tappedTarget) { tappedTarget.scaleX = 1; tappedTarget.scaleY = 1; tween(tappedTarget, { scaleX: 1.4, scaleY: 1.4 }, { duration: 90, easing: tween.cubicOut, onFinish: function onFinish() { tween(tappedTarget, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.cubicIn }); } }); } // Remove note visually tween(bestNote, { alpha: 0 }, { duration: 180, onFinish: function onFinish() { bestNote.destroy(); } }); // Remove from notes array after fade noteManager.removeNote(bestNote); } else { // Miss! (Tapped with no note in window) combo = 0; comboTxt.setText(''); LK.getSound('tapMiss').play(); LK.effects.flashObject(laneHighlights[lane], 0xff0000, 300); //LK.effects.flashScreen(0xff0000, 100); /* gameActive = false; LK.setTimeout(function () { LK.showGameOver(); }, 600); */ } }; // --- Reset game on game over or win --- LK.on('gameover', function () { LK.stopMusic(); startGame(); }); LK.on('youwin', function () { LK.stopMusic(); startGame(); }); // Game initialization function initializeGame() { // Instantiate and initialize all global objects here // --- Instantiate and add background manager --- bgManager = new BackgroundManager(); game.addChild(bgManager); // Create menu manager menuManager = new MenuManager(); menuManager.createMenuTiles(SONGS); menuManager.onSongSelected = function (songIndex) { // Hide menu menuManager.hide(); // Update current song data songNotesRaw = SONGS[songIndex].songNotes; songNotes = []; // Process notes for the selected song for (var i = 0; i < songNotesRaw.length; i++) { var note = songNotesRaw[i]; var lane = 0; if (keyToLane.hasOwnProperty(note.key)) { lane = keyToLane[note.key]; } else { lane = i % 3; } songNotes.push({ lane: lane, time: note.time }); } // Update song duration with speedMultiplier applied songDuration = songNotes.length > 0 ? songNotes[songNotes.length - 1].time / speedMultiplier + 1000 : 9000; // Start the game startGame(); }; game.addChild(menuManager); menuManager.visible = false; // Initially hidden // If the song uses only Key6/Key7/Key8, map them to 0/1/2, else fallback to a round-robin for (var i = 0; i < songNotesRaw.length; i++) { var note = songNotesRaw[i]; var lane = 0; if (keyToLane.hasOwnProperty(note.key)) { lane = keyToLane[note.key]; } else { lane = i % 3; } songNotes.push({ lane: lane, time: note.time }); } // Song duration (ms) with speedMultiplier applied songDuration = songNotes.length > 0 ? songNotes[songNotes.length - 1].time / speedMultiplier + 1000 : 9000; hitLine = LK.getAsset('laneHighlight', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 8, color: 0x3a8ee6, alpha: 0.18, x: 2048 / 2, y: hitLineY }); game.addChild(hitLine); for (var i = 0; i < laneCount; i++) { var target = new Target(); target.x = laneX[i]; target.y = hitLineY; target.visible = false; // hide initially game.addChild(target); targets.push(target); } // --- State --- noteManager = new NoteManager(); // Handles all notes and spawning songStartTime = 0; // Date.now() when song started gameActive = false; // True if game is running score = 0; combo = 0; maxCombo = 0; lastTapTime = 0; // --- GUI Elements --- scoreTxt = new ScoreText(); scoreTxt.setText('0'); scoreTxt.y = 0; scoreTxt.visible = false; // hide initially LK.gui.top.addChild(scoreTxt); comboTxt = new ComboText(); comboTxt.y = 130; comboTxt.tint = 0x3A8EE6; LK.gui.top.addChild(comboTxt); // Add restart button to bottom right if (!restartBtn) { restartBtn = new RestartButton(); restartBtn.visible = false; // Initially hidden restartBtn.x = 1900; // Move left to ensure it's fully on screen restartBtn.y = 150; // Move left to ensure it's fully on screen game.addChild(restartBtn); } // Add menu button if (!menuButton) { menuButton = new MenuButton(); menuButton.visible = false; // Initially hidden menuButton.x = 50; menuButton.y = 450; game.addChild(menuButton); } // --- Lane highlights (for visual feedback) --- laneHighlights = []; for (var i = 0; i < laneCount; i++) { var laneHL = LK.getAsset('laneHighlight', { anchorX: 0.5, anchorY: 0, alpha: 0.07, x: laneX[i], y: 0, height: 2732, visible: false // hide initially }); game.addChild(laneHL); laneHighlights.push(laneHL); } // --- Hit line (invisible, for reference) --- hitLineYDisplay = hitLineY; // Show StartButton and call startGame only when tapped if (typeof startBtn !== "undefined" && startBtn) { startBtn.destroy(); startBtn = null; } startBtn = new StartButton(); startBtn.x = 2048 / 2; startBtn.y = 1200; // Modify start button to show menu instead startBtn.down = function (x, y, obj) { // Only allow tap if animation is complete if (!startBtn.animationComplete) { return; } // Animate tap feedback tween.stop(startBtn, { scaleX: true, scaleY: true }); startBtn.scaleX = 1.0; startBtn.scaleY = 1.0; // Play start sound LK.getSound('startSound').play(); // Animate exit and show menu tween(startBtn, { scaleX: 100, scaleY: 100, alpha: 0 }, { duration: 1200, easing: tween.cubicIn, onFinish: function onFinish() { startBtn.destroy(); startBtn = null; // Hide restart button when showing menu if (restartBtn) { restartBtn.visible = false; } // Hide menu button when showing menu if (menuButton) { menuButton.visible = false; } // Show menu manager menuManager.show(); } }); }; game.addChild(startBtn); } initializeGame();
===================================================================
--- original.js
+++ change.js
@@ -2492,10 +2492,10 @@
// Add menu button
if (!menuButton) {
menuButton = new MenuButton();
menuButton.visible = false; // Initially hidden
- menuButton.x = 400;
- menuButton.y = 150;
+ menuButton.x = 50;
+ menuButton.y = 450;
game.addChild(menuButton);
}
// --- Lane highlights (for visual feedback) ---
laneHighlights = [];
key0
Sound effect
key1
Sound effect
key2
Sound effect
key3
Sound effect
key4
Sound effect
key5
Sound effect
key6
Sound effect
key7
Sound effect
key8
Sound effect
key10
Sound effect
key11
Sound effect
key12
Sound effect
key13
Sound effect
key14
Sound effect
key9
Sound effect
tapMiss
Sound effect
cheers
Sound effect
startSound
Sound effect
click
Sound effect
menuSpawn
Sound effect
jeers
Sound effect