User prompt
Insert BgSkyJourney as the background of gameplay mode for Sky Journey, without affecting the gameplay scene
User prompt
Increase sensitivity of swipe notes and hold notes
User prompt
Make sure all the notes always flow and didn't stop immediately in the lane
User prompt
Analyses the full songs for Night Pulse and Sunrise Waltz, and make the tempo flowing for the full song
User prompt
There are notes flowing errors for both Night Pulse and Sunrise Waltz, that the notes suddenly not flowing after the songs are playing for a while
User prompt
For hold notes, if the button detected the hold notes with sparkling light, it should be counted as good, great or perfect based on the timing, instead of miss
User prompt
Remove all hold notes and swipe notes for all the easy mode of all songs, only medium mode and hard mode has hold notes and swipe notes, and adjust the errors for some of the hit area becomes bigger after touching the pad
User prompt
They're problems that the notes suddenly disappear automatically for all songs, and change the rule for swipe notes for just swiping horizontally, make the hold notes detection more sensitive with the rule that the hit area will appear sparkling light up animation when the player click the hold notes for a long time, and make the disappearing of hold notes longer ↪💡 Consider importing and using the following plugins: @upit/tween.v1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The notes keep on disappear suddenly without reach the hit areas, fix this errors, and improve the sensitivity for hold notes and swipe notes
User prompt
Some of the notes suddenly disappear without reach the hit area, fix this errors, and adjust swipe notes rules for just only swipes across the hit areas when the swipe notes reach
User prompt
Add sparkling light animation for touching the notes
User prompt
There are some problems that the notes suddenly didn't flow after Sunrise Waltz play for a while, and make the swipe notes disappear even it results in miss
User prompt
Fix the hold notes rule to click the note longer until the note is disappear, for the swipe note, it should be swipe according to the note shape
User prompt
Make the hit area light up when the notes are touched
User prompt
Improve the sensitivity of swipe notes and hold notes, and make sure swipe notes are disappear without staying as red color at the behind of hit area ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make sure all of the songs only play once, and appearing scoreboard on the top, fix the swipe notes didn't disappear errors, the total score will be shown at the end of the game within the SongStorybox asset and enable the player to click exit to select other songs
User prompt
Analysing the full songs, and make the full songs player, make the hold notes and swipe notes bigger, make sure size of swipe notes is crossing two lanes, and all of the notes disappear once the notes had been touched ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Play full songs for each gameplay, and make the notes disappear after the notes has been touched according to their touching rules ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Detect all of the songs asset and generate the notes according to the song, and for the swipe note, make it bigger so that it can approach two hit areas, and make various scoring system, including miss, bad, good, great and perfect ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Play SkyJourney asset for Sky Journey, NightPulse asset for Night Pulse and SunriseWaltz asset for Sunrise Waltz after a difficulty level is selected, and rearrange the game play mode, like there are 4 lanes asset that are placed vertically side by side, and lane divider function as hit area and place at the bottom of lanes, and make the notes fall on the lane divider, analyses the song and appearring the notes according to the music ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Create 3 difficulty button and arrange them horizontally at the bottom side of the SongStoryBox
User prompt
Put all of the difficulty buttons within the SelectorUI asset
User prompt
Make the song cover smaller and put it at the small left side of the SongStoryBox, and the story description is at the larger right side of the SongStoryBox, and all of the difficulty levels are arranged horizontally instead of vertically at the bottom side of SongStorybox
User prompt
Please fix the bug: 'TypeError: easing is not a function' in or related to this line: 'tween(storyBox, {' Line Number: 624 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Move the SongStoryBox to the left and upwards until it reaches the center of the screen,because its only visible at the right corner
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { language: "en" }); /**** * Classes ****/ // Note types: 'tap', 'hold', 'swipe' var Note = Container.expand(function () { var self = Container.call(this); self.type = 'tap'; // tap, hold, swipe self.lane = 0; // 0-3 self.time = 0; // ms, when the note should be hit self.duration = 0; // for hold notes, ms self.hit = false; self.active = true; self.swipeDir = null; // 'left', 'right', 'up', 'down' for swipe notes // Visuals self.noteAsset = null; self.trailAsset = null; self.init = function (type, lane, time, duration, swipeDir) { self.type = type; self.lane = lane; self.time = time; self.duration = duration || 0; self.swipeDir = swipeDir || null; self.hit = false; self.active = true; if (self.noteAsset) { self.removeChild(self.noteAsset); } if (self.trailAsset) { self.removeChild(self.trailAsset); } if (type === 'tap') { self.noteAsset = self.attachAsset('tapNote', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'hold') { self.noteAsset = self.attachAsset('holdNote', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'swipe') { self.noteAsset = self.attachAsset('swipeNote', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, // Make swipe notes bigger scaleY: 1.8 }); } }; self.flash = function () { if (self.noteAsset) { tween(self.noteAsset, { alpha: 0.2 }, { duration: 80, onFinish: function onFinish() { tween(self.noteAsset, { alpha: 1 }, { duration: 80 }); } }); } }; self.update = function () { // Position is handled by main game loop }; return self; }); // Score popup for visual feedback var ScorePopup = Container.expand(function () { var self = Container.call(this); self.scoreText = null; self.startTime = 0; self.duration = 1000; self.init = function (scoreType, x, y) { self.x = x; self.y = y; self.startTime = Date.now(); var text = ""; var color = "#fff"; var scoreValue = 0; switch (scoreType) { case 'perfect': text = "PERFECT"; color = "#FFD700"; scoreValue = 150; break; case 'great': text = "GREAT"; color = "#00FF00"; scoreValue = 120; break; case 'good': text = "GOOD"; color = "#FFFF00"; scoreValue = 80; break; case 'bad': text = "BAD"; color = "#FFA500"; scoreValue = 40; break; case 'miss': text = "MISS"; color = "#FF0000"; scoreValue = 0; break; } self.scoreText = new Text2(text + "\n+" + scoreValue, { size: 60, fill: color, align: "center" }); self.scoreText.anchor.set(0.5, 0.5); self.addChild(self.scoreText); // Animate popup tween(self, { y: self.y - 100, alpha: 0 }, { duration: self.duration, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); }; return self; }); // Song selection card var SongCard = Container.expand(function () { var self = Container.call(this); self.songId = ''; self.cover = null; self.init = function (songId, colorAsset) { self.songId = songId; if (self.cover) { self.removeChild(self.cover); } if (colorAsset) { self.cover = self.attachAsset(colorAsset, { anchorX: 0.5, anchorY: 0.5 }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c20 }); /**** * Game Code ****/ // Sounds and music (placeholders, actual music not implemented here) // Example note assets: // We'll use shapes for notes, and images for backgrounds and song covers as needed. // Note: Assets are auto-initialized by LK based on usage below. // --- GLOBALS --- var _templateObject; function _taggedTemplateLiteral(e, t) { return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, { raw: { value: Object.freeze(t) } })); } var GAME_STATE = 'LANGUAGE'; // LANGUAGE, HOME, STORY, DIFFICULTY, PLAY, RESULT var selectedLanguage = storage.language || 'en'; var selectedSong = null; var selectedDifficulty = null; var currentSongData = null; var currentNotes = []; var noteIndex = 0; var startTime = 0; var score = 0; var combo = 0; var maxCombo = 0; var accuracy = 0; var totalNotes = 0; var hitNotes = 0; var missNotes = 0; var badNotes = 0; var goodNotes = 0; var greatNotes = 0; var perfectNotes = 0; var holdActive = null; var swipeStart = null; var swipeNote = null; var isPlaying = false; var resultTimeout = null; var scorePopups = []; // Scoring thresholds in milliseconds var PERFECT_WINDOW = 50; var GREAT_WINDOW = 100; var GOOD_WINDOW = 150; var BAD_WINDOW = 200; // Lanes: 4 lanes, evenly spaced var LANE_COUNT = 4; var LANE_WIDTH = 400; var LANE_MARGIN = 40; var LANE_START_X = (2048 - (LANE_COUNT * LANE_WIDTH + (LANE_COUNT - 1) * LANE_MARGIN)) / 2; var HIT_LINE_Y = 2200; // Where notes should be hit // --- SONG DATA (MVP: 3 songs, 3 difficulties, simple patterns) --- var SONGS = [{ id: 'SkyJourney', title: 'Sky Journey', cover: 'songCover1', story: { en: "You soar above the clouds, chasing the horizon. The sky is endless, and your journey has just begun.", zh: "你在云端翱翔,追逐地平线。天空无垠,你的旅程才刚刚开始。" }, difficulties: ['Easy', 'Medium', 'Hard'], notes: { Easy: [ // Music analysis: Sky Journey - Uplifting 4/4 tempo, soaring melodies, 120 BPM ['tap', 1, 500], ['tap', 2, 1000], ['tap', 0, 1500], ['tap', 3, 2000], ['hold', 1, 2500, 1000], ['tap', 2, 4000], ['tap', 3, 4500], ['swipe', 0, 5000, 0, 'right'], ['swipe', 3, 5000, 0, 'right'], // Cross-lane swipe ['tap', 1, 5500], ['tap', 2, 6000], ['hold', 3, 6500, 800], ['tap', 0, 7800], ['swipe', 1, 8300, 0, 'up'], ['swipe', 2, 8300, 0, 'up'], // Multi-lane swipe ['tap', 1, 8800], ['tap', 3, 9300], ['tap', 0, 9800], ['tap', 2, 10300], ['hold', 0, 10800, 1200], ['tap', 1, 12500], ['swipe', 2, 13000, 0, 'down'], ['swipe', 3, 13000, 0, 'down'], ['tap', 0, 13500]], Medium: [ // Increased complexity with eighth note patterns ['tap', 1, 400], ['tap', 2, 700], ['tap', 0, 1000], ['tap', 3, 1300], ['hold', 1, 1600, 800], ['tap', 2, 2800], ['tap', 3, 3100], ['swipe', 0, 3400, 0, 'left'], ['swipe', 1, 3400, 0, 'left'], ['tap', 1, 3700], ['tap', 2, 4000], ['hold', 3, 4300, 1000], ['tap', 0, 5700], ['tap', 1, 6000], ['swipe', 2, 6300, 0, 'right'], ['swipe', 3, 6300, 0, 'right'], ['tap', 3, 6600], ['tap', 0, 6900], ['hold', 1, 7200, 600], ['tap', 2, 8100], ['swipe', 3, 8400, 0, 'down'], ['tap', 0, 8700], ['tap', 1, 9000], ['tap', 2, 9300], ['hold', 3, 9600, 900], ['swipe', 0, 10800, 0, 'up'], ['swipe', 1, 10800, 0, 'up'], ['tap', 2, 11100]], Hard: [ // Complex patterns with sixteenth notes and polyrhythms ['tap', 1, 300], ['tap', 2, 500], ['tap', 0, 700], ['tap', 3, 900], ['hold', 1, 1100, 600], ['tap', 2, 1900], ['tap', 3, 2100], ['swipe', 0, 2300, 0, 'up'], ['swipe', 1, 2300, 0, 'up'], ['tap', 1, 2500], ['tap', 2, 2700], ['hold', 3, 2900, 800], ['tap', 0, 3900], ['tap', 1, 4100], ['swipe', 2, 4300, 0, 'left'], ['swipe', 3, 4300, 0, 'left'], ['tap', 3, 4500], ['tap', 0, 4700], ['hold', 1, 4900, 500], ['tap', 2, 5600], ['tap', 3, 5800], ['swipe', 0, 6000, 0, 'right'], ['swipe', 3, 6000, 0, 'right'], ['tap', 1, 6200], ['tap', 2, 6400], ['tap', 0, 6600], ['tap', 3, 6800], ['hold', 1, 7000, 400], ['hold', 2, 7200, 400], ['swipe', 0, 7800, 0, 'down'], ['swipe', 3, 7800, 0, 'down']] } }, { id: 'NightPulse', title: 'Night Pulse', cover: 'songCover2', story: { en: "The city lights flicker in rhythm with your heartbeat. Tonight, the music guides your every move.", zh: "城市的灯光随着你的心跳闪烁。今夜,音乐引领你的每一步。" }, difficulties: ['Easy', 'Medium', 'Hard'], notes: { Easy: [ // Music analysis: Night Pulse - Electronic 4/4, strong kick pattern, 128 BPM ['tap', 0, 800], ['tap', 2, 1200], ['tap', 1, 1600], ['tap', 3, 2000], ['hold', 0, 2400, 1200], ['tap', 2, 4000], ['tap', 1, 4400], ['swipe', 2, 4800, 0, 'left'], ['swipe', 3, 4800, 0, 'left'], ['tap', 0, 5200], ['tap', 2, 5600], ['hold', 1, 6000, 1000], ['tap', 3, 7400], ['swipe', 0, 7800, 0, 'right'], ['swipe', 1, 7800, 0, 'right'], ['tap', 2, 8200], ['tap', 1, 8600], ['tap', 3, 9000], ['hold', 0, 9400, 800], ['tap', 2, 10600], ['swipe', 1, 11000, 0, 'up'], ['swipe', 3, 11000, 0, 'up']], Medium: [ // Syncopated patterns with bass drops ['tap', 0, 600], ['tap', 2, 900], ['tap', 1, 1200], ['tap', 3, 1500], ['hold', 0, 1800, 1000], ['tap', 2, 3100], ['tap', 1, 3400], ['swipe', 3, 3700, 0, 'up'], ['swipe', 0, 3700, 0, 'up'], ['tap', 0, 4000], ['tap', 2, 4300], ['hold', 1, 4600, 800], ['tap', 3, 5700], ['tap', 0, 6000], ['swipe', 2, 6300, 0, 'down'], ['swipe', 3, 6300, 0, 'down'], ['tap', 1, 6600], ['tap', 3, 6900], ['hold', 0, 7200, 600], ['tap', 2, 8100], ['swipe', 1, 8400, 0, 'left'], ['tap', 3, 8700], ['tap', 0, 9000], ['tap', 1, 9300], ['hold', 2, 9600, 700], ['swipe', 0, 10600, 0, 'right'], ['swipe', 3, 10600, 0, 'right']], Hard: [ // Complex electronic patterns with fills and drops ['tap', 0, 400], ['tap', 2, 600], ['tap', 1, 800], ['tap', 3, 1000], ['hold', 0, 1200, 800], ['tap', 2, 2200], ['tap', 1, 2400], ['swipe', 3, 2600, 0, 'right'], ['swipe', 0, 2600, 0, 'right'], ['tap', 0, 2800], ['tap', 2, 3000], ['hold', 1, 3200, 600], ['tap', 3, 4000], ['tap', 0, 4200], ['swipe', 2, 4400, 0, 'up'], ['swipe', 1, 4400, 0, 'up'], ['tap', 1, 4600], ['tap', 3, 4800], ['hold', 0, 5000, 500], ['tap', 2, 5700], ['tap', 1, 5900], ['swipe', 3, 6100, 0, 'down'], ['swipe', 0, 6100, 0, 'down'], ['tap', 0, 6300], ['tap', 2, 6500], ['tap', 1, 6700], ['tap', 3, 6900], ['hold', 0, 7100, 300], ['hold', 2, 7100, 300], ['swipe', 1, 7600, 0, 'left'], ['swipe', 3, 7600, 0, 'left'], ['tap', 0, 7800], ['tap', 1, 8000], ['tap', 2, 8200], ['tap', 3, 8400]] } }, { id: 'SunriseWaltz', title: 'Sunrise Waltz', cover: 'songCover3', story: { en: "As dawn breaks, melodies dance in the golden light. Every note is a step in your waltz with the sun.", zh: "黎明破晓,旋律在金色的光芒中舞动。每一个音符都是你与太阳华尔兹的步伐。" }, difficulties: ['Easy', 'Medium', 'Hard'], notes: { Easy: [ // Music analysis: Sunrise Waltz - 3/4 time signature, graceful melody, 90 BPM ['tap', 1, 1000], ['tap', 2, 1500], ['tap', 3, 2000], ['tap', 0, 2500], ['hold', 1, 3000, 1500], ['tap', 2, 5000], ['tap', 3, 5500], ['swipe', 0, 6000, 0, 'up'], ['swipe', 1, 6000, 0, 'up'], ['tap', 1, 6500], ['tap', 2, 7000], ['hold', 3, 7500, 1200], ['tap', 0, 9200], ['swipe', 1, 9700, 0, 'right'], ['swipe', 2, 9700, 0, 'right'], ['tap', 2, 10200], ['tap', 3, 10700], ['tap', 0, 11200], ['hold', 1, 11700, 1000], ['swipe', 0, 13200, 0, 'down'], ['swipe', 3, 13200, 0, 'down']], Medium: [ // Waltz patterns with ornamental flourishes ['tap', 1, 750], ['tap', 2, 1125], ['tap', 3, 1500], ['tap', 0, 1875], ['hold', 1, 2250, 1125], ['tap', 2, 3750], ['tap', 3, 4125], ['swipe', 0, 4500, 0, 'down'], ['swipe', 1, 4500, 0, 'down'], ['tap', 1, 4875], ['tap', 2, 5250], ['hold', 3, 5625, 1000], ['tap', 0, 7000], ['tap', 1, 7375], ['swipe', 2, 7750, 0, 'left'], ['swipe', 3, 7750, 0, 'left'], ['tap', 3, 8125], ['tap', 0, 8500], ['hold', 1, 8875, 750], ['tap', 2, 10000], ['swipe', 3, 10375, 0, 'up'], ['tap', 0, 10750], ['tap', 1, 11125], ['hold', 2, 11500, 875], ['swipe', 0, 12750, 0, 'right'], ['swipe', 3, 12750, 0, 'right']], Hard: [ // Complex waltz with rapid arpeggios and cross-rhythms ['tap', 1, 500], ['tap', 2, 750], ['tap', 3, 1000], ['tap', 0, 1250], ['hold', 1, 1500, 750], ['tap', 2, 2500], ['tap', 3, 2750], ['swipe', 0, 3000, 0, 'right'], ['swipe', 3, 3000, 0, 'right'], ['tap', 1, 3250], ['tap', 2, 3500], ['hold', 3, 3750, 875], ['tap', 0, 4875], ['tap', 1, 5125], ['swipe', 2, 5375, 0, 'up'], ['swipe', 0, 5375, 0, 'up'], ['tap', 3, 5625], ['tap', 0, 5875], ['hold', 1, 6125, 625], ['tap', 2, 7000], ['tap', 3, 7250], ['swipe', 0, 7500, 0, 'down'], ['swipe', 1, 7500, 0, 'down'], ['tap', 1, 7750], ['tap', 2, 8000], ['tap', 3, 8250], ['tap', 0, 8500], ['hold', 1, 8750, 375], ['hold', 3, 8750, 375], ['swipe', 0, 9375, 0, 'left'], ['swipe', 2, 9375, 0, 'left'], ['tap', 1, 9625], ['tap', 3, 9875], ['tap', 0, 10125], ['tap', 2, 10375]] } }]; // --- GUI ELEMENTS --- var gui = LK.gui; var languageButtons = []; var startButton = null; var songCards = []; var storyText = null; var continueButton = null; var difficultyButtons = []; var scoreText = null; var comboText = null; var accuracyText = null; var resultText = null; var homeButton = null; // --- GAME ELEMENTS --- var lanes = []; var laneDividers = []; var hitLine = null; // --- UTILS --- function clearGUI() { // Remove all children from gui overlays, but only if they exist if (gui.top && gui.top.removeChildren) { gui.top.removeChildren(); } if (gui.topRight && gui.topRight.removeChildren) { gui.topRight.removeChildren(); } if (gui.topLeft && gui.topLeft.removeChildren) { gui.topLeft.removeChildren(); } if (gui.left && gui.left.removeChildren) { gui.left.removeChildren(); } if (gui.right && gui.right.removeChildren) { gui.right.removeChildren(); } if (gui.bottom && gui.bottom.removeChildren) { gui.bottom.removeChildren(); } if (gui.bottomLeft && gui.bottomLeft.removeChildren) { gui.bottomLeft.removeChildren(); } if (gui.bottomRight && gui.bottomRight.removeChildren) { gui.bottomRight.removeChildren(); } if (gui.center && gui.center.removeChildren) { gui.center.removeChildren(); } // Reset GUI-related global state languageButtons = []; startButton = null; songCards = []; storyText = null; continueButton = null; difficultyButtons = []; scoreText = null; comboText = null; accuracyText = null; resultText = null; homeButton = null; } function clearGameObjects() { // Remove all notes, lanes, etc. for (var i = 0; i < lanes.length; ++i) { if (lanes[i] && lanes[i].parent) { lanes[i].parent.removeChild(lanes[i]); } } for (var i = 0; i < laneDividers.length; ++i) { if (laneDividers[i] && laneDividers[i].parent) { laneDividers[i].parent.removeChild(laneDividers[i]); } } if (hitLine && hitLine.parent) { hitLine.parent.removeChild(hitLine); } for (var i = 0; i < currentNotes.length; ++i) { if (currentNotes[i] && currentNotes[i].parent) { currentNotes[i].parent.removeChild(currentNotes[i]); } } lanes = []; laneDividers = []; hitLine = null; currentNotes = []; noteIndex = 0; holdActive = null; swipeStart = null; swipeNote = null; isPlaying = false; } function setGameState(state) { GAME_STATE = state; clearGUI(); clearGameObjects(); // Reset global state for each screen dragLane = null; dragY = null; dragNote = null; dragStartX = null; dragStartY = null; dragStartTime = null; if (state === 'LANGUAGE') { showLanguageSelector(); } else if (state === 'HOME') { selectedSong = null; selectedDifficulty = null; showHome(); } else if (state === 'STORY') { showStory(); } else if (state === 'DIFFICULTY') { showDifficulty(); } else if (state === 'PLAY') { startGameplay(); } else if (state === 'RESULT') { showResult(); } } // --- LANGUAGE SELECTOR --- function showLanguageSelector() { languageButtons = []; // Get current screen size from LK var screenWidth = LK.width || 2048; var screenHeight = LK.height || 2732; // Create opening screen container (with background) var openingScreen = new Container(); // Add background asset, centered var bgAsset = LK.getAsset('Openingscreen', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); openingScreen.addChild(bgAsset); // Add three SelectorUI assets as backgrounds for English, 中文, and play button // Calculate button positions and sizes to fit text var langBtnYStart = Math.floor(screenHeight * 0.45) - screenHeight / 2; var langBtnSpacing = Math.floor(screenHeight * 0.08); // English SelectorUI var selectorBgEn = LK.getAsset('SelectorUI', { anchorX: 0.5, anchorY: 0.5, x: 0, y: langBtnYStart, scaleX: 3.2, scaleY: 1.3 }); openingScreen.addChild(selectorBgEn); // 中文 SelectorUI var selectorBgZh = LK.getAsset('SelectorUI', { anchorX: 0.5, anchorY: 0.5, x: 0, y: langBtnYStart + langBtnSpacing, scaleX: 2.2, scaleY: 1.3 }); openingScreen.addChild(selectorBgZh); // Play button SelectorUI var playBtnY = Math.floor(screenHeight * 0.70) - screenHeight / 2; var selectorBgPlay = LK.getAsset('SelectorUI', { anchorX: 0.5, anchorY: 0.5, x: 0, y: playBtnY, scaleX: 3.2, scaleY: 1.5 }); openingScreen.addChild(selectorBgPlay); // Center all elements inside the openingScreen container // Title var titleText = new Text2("Kaleidoscope of Music Rhythm", { size: 80, fill: "#fff" }); titleText.anchor.set(0.5, 0.5); // Place title at 23% of screen height, centered horizontally, and ensure it's not above y=80 titleText.x = 0; titleText.y = Math.max(80 - screenHeight / 2, Math.floor(screenHeight * 0.23) - screenHeight / 2); openingScreen.addChild(titleText); // Language selector var langs = [{ code: 'en', label: 'English' }, { code: 'zh', label: '中文' }]; // Place language buttons at 45% and 53% of screen height, centered horizontally var langBtnYStart = Math.floor(screenHeight * 0.45) - screenHeight / 2; var langBtnSpacing = Math.floor(screenHeight * 0.08); for (var i = 0; i < langs.length; ++i) { (function (idx) { var btn = new Text2(langs[idx].label, { size: 64, fill: "#fff" }); btn.anchor.set(0.5, 0.5); btn.x = 0; // centered in container btn.y = langBtnYStart + idx * langBtnSpacing; btn.interactive = true; btn.down = function (x, y, obj) { LK.getSound('Touch').play(); selectedLanguage = langs[idx].code; storage.language = selectedLanguage; // Do not advance to HOME yet, just set language clearGUI(); showLanguageSelector(); }; openingScreen.addChild(btn); languageButtons.push(btn); })(i); } // Start button startButton = new Text2(selectedLanguage === 'zh' ? "开始" : "Start", { size: 90, fill: "#fff" }); startButton.anchor.set(0.5, 0.5); // Place start button at 70% of screen height, centered horizontally startButton.x = 0; startButton.y = Math.floor(screenHeight * 0.70) - screenHeight / 2; startButton.interactive = true; startButton.down = function (x, y, obj) { LK.getSound('Touch').play(); setGameState('HOME'); }; openingScreen.addChild(startButton); // Add the opening screen container to gui.center gui.center.addChild(openingScreen); // Play background music for opening screen LK.playMusic('KaleidoscopeMusicRhythm'); } // --- HOME / SONG SELECTION --- function showHome() { clearGUI(); clearGameObjects(); songCards = []; var screenWidth = LK.width || 2048; var screenHeight = LK.height || 2732; // Create homeScreen container before adding children var homeScreen = new Container(); // Add Backgroundhomepage asset as background, fully fit to screen var bgAsset = LK.getAsset('Backgroundhomepage', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: screenWidth / 2048, scaleY: screenHeight / 2732 }); homeScreen.addChild(bgAsset); bgAsset.width = screenWidth; bgAsset.height = screenHeight; // Title: "Select a Song", in white with proper positioning var title = new Text2(selectedLanguage === 'zh' ? "选择歌曲" : "Select a Song", { size: 90, fill: "#fff", // White text for contrast font: "Arial Black" }); title.anchor.set(0.5, 0); // Place title at 15% of screen height, centered horizontally, and ensure it's not above y=120 // For gui.center containers, (0,0) is the center, so offset from center title.x = 0; title.y = -screenHeight / 2 + Math.max(120, Math.floor(screenHeight * 0.15)); homeScreen.addChild(title); // Card layout - vertical stacking with proper spacing var cardCount = SONGS.length; var cardHeight = 200; var cardSpacing = 80; var titleSpacing = 100; // Place the title at 15% of screen height, and cards start at 25% of screen height // We want all cards to be below the title and fully within SelectorUI // Calculate the starting Y so that the first card is just below the title, and all cards fit on screen var cardsStartY = title.y + title.height + titleSpacing; // 100px gap below title for (var i = 0; i < SONGS.length; i++) { (function (index) { var song = SONGS[index]; // Card Y position var cardY = cardsStartY + index * (cardHeight + cardSpacing); // Create SelectorUI background for song card var cardBg = LK.getAsset('SelectorUI', { anchorX: 0.5, anchorY: 0.5, x: 0, y: cardY, scaleX: 4.0, scaleY: 2.0 }); homeScreen.addChild(cardBg); // Make card interactive cardBg.interactive = true; cardBg.down = function (x, y, obj) { LK.getSound('Touch').play(); selectedSong = song; setGameState('STORY'); }; // Add song title (centered within SelectorUI) var songNameText = new Text2(song.title, { size: 64, fill: "#fff", font: "Arial" }); songNameText.anchor.set(0.5, 0.5); songNameText.x = 0; songNameText.y = cardY - 30; homeScreen.addChild(songNameText); // Add subtitle (optional: show "Sky Journey", "Night Pulse", "Sunrise Waltz") var subtitleText = new Text2(selectedLanguage === 'zh' && song.title === "Sky Journey" ? "天空之旅" : selectedLanguage === 'zh' && song.title === "Night Pulse" ? "夜之律动" : selectedLanguage === 'zh' && song.title === "Sunrise Waltz" ? "日出圆舞曲" : "", { size: 40, fill: 0xE0E0E0, font: "Arial" }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 0; subtitleText.y = cardY + 30; homeScreen.addChild(subtitleText); songCards.push(cardBg); })(i); } // Add homeScreen to GUI, centered homeScreen.x = 0; homeScreen.y = 0; if (gui.center && gui.center.addChild) { gui.center.addChild(homeScreen); } // Play background music for homepage LK.playMusic('KaleidoscopeMusicRhythm'); } // --- STORY INTRO --- function showStory() { clearGUI(); var story = selectedSong.story[selectedLanguage] || selectedSong.story['en']; var screenWidth = LK.width || 2048; var screenHeight = LK.height || 2732; // Create SongStorybox container var storyBox = new Container(); // Add SongStorybox asset as background, centered and scaled up for showcase var boxAsset = LK.getAsset('SongStorybox', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 13, // Increased for larger showcase scaleY: 11 }); storyBox.addChild(boxAsset); // Calculate box bounds for layout var boxW = boxAsset.width * boxAsset.scaleX; var boxH = boxAsset.height * boxAsset.scaleY; var boxLeftX = -boxW / 2; var boxRightX = boxW / 2; var boxTopY = -boxH / 2; var boxBottomY = boxH / 2; // Add smaller song cover on the left side of the box var coverAsset = LK.getAsset(selectedSong.cover, { anchorX: 0.5, anchorY: 0.5, x: boxLeftX + boxW * 0.25, // Position at 25% from left edge y: boxTopY + boxH * 0.35, // Position at 35% from top scaleX: 1.2, // Smaller cover scaleY: 1.2 }); storyBox.addChild(coverAsset); // Add story text on the larger right side of the box storyText = new Text2(story, { size: 55, fill: "#fff", wordWrap: true, wordWrapWidth: boxW * 0.45 // Use 45% of box width for text }); storyText.anchor.set(0, 0.5); storyText.x = boxLeftX + boxW * 0.52; // Start at 52% from left edge storyText.y = boxTopY + boxH * 0.35; // Align vertically with cover storyBox.addChild(storyText); // Create 3 separate SelectorUI elements for difficulty buttons arranged horizontally var diffY = boxBottomY - boxH * 0.15; // Y position for all difficulty buttons var totalWidth = boxW * 0.8; // Use 80% of box width for all buttons var buttonWidth = totalWidth / selectedSong.difficulties.length; var startX = -totalWidth / 2 + buttonWidth / 2; // Center the button group difficultyButtons = []; for (var i = 0; i < selectedSong.difficulties.length; ++i) { (function (idx) { var label = selectedSong.difficulties[idx]; // Create individual SelectorUI for this difficulty button var selectorAsset = LK.getAsset('SelectorUI', { anchorX: 0.5, anchorY: 0.5, x: startX + idx * buttonWidth, y: diffY, scaleX: 2.5, scaleY: 1.8 }); selectorAsset.interactive = true; selectorAsset.down = function (x, y, obj) { LK.getSound('Touch').play(); selectedDifficulty = label; setGameState('PLAY'); }; storyBox.addChild(selectorAsset); // Add difficulty button text centered on the SelectorUI var btn = new Text2(label, { size: 60, fill: "#fff" }); btn.anchor.set(0.5, 0.5); btn.x = startX + idx * buttonWidth; btn.y = diffY; storyBox.addChild(btn); difficultyButtons.push(btn); })(i); } // Start storyBox at bottom-right corner of screen storyBox.x = screenWidth; storyBox.y = screenHeight; gui.center.addChild(storyBox); // Animate storyBox to center of screen tween(storyBox, { x: 0, y: 0 }, { duration: 800, easing: tween.easeOut }); } // --- DIFFICULTY SELECT --- function showDifficulty() { difficultyButtons = []; var screenWidth = LK.width || 2048; var screenHeight = LK.height || 2732; var title = new Text2(selectedLanguage === 'zh' ? "选择难度" : "Select Difficulty", { size: 100, fill: "#fff" }); title.anchor.set(0.5, 0); title.x = screenWidth / 2; title.y = Math.max(100, Math.floor(screenHeight * 0.08)); gui.top.addChild(title); var btnYStart = Math.floor(screenHeight * 0.40); var btnSpacing = Math.floor(screenHeight * 0.13); for (var i = 0; i < selectedSong.difficulties.length; ++i) { (function (idx) { var label = selectedSong.difficulties[idx]; var btn = new Text2(label, { size: 90, fill: "#fff" }); btn.anchor.set(0.5, 0.5); btn.x = screenWidth / 2; btn.y = btnYStart + idx * btnSpacing; btn.interactive = true; btn.down = function (x, y, obj) { LK.getSound('Touch').play(); selectedDifficulty = label; setGameState('PLAY'); }; gui.center.addChild(btn); difficultyButtons.push(btn); })(i); } } // --- GAMEPLAY --- function startGameplay() { // Setup 4 lanes vertically side by side lanes = []; laneDividers = []; var laneSpacing = 2048 / LANE_COUNT; // Equal spacing for 4 lanes for (var i = 0; i < LANE_COUNT; ++i) { var lane = LK.getAsset('lane', { anchorX: 0.5, anchorY: 0 }); lane.x = (i + 0.5) * laneSpacing; // Center each lane in its section lane.y = 0; lane.scaleX = laneSpacing / lane.width; // Scale to fit lane width lane.scaleY = 2732 / lane.height; // Scale to full screen height lanes.push(lane); game.addChild(lane); } // Create lane dividers as hit areas at the bottom of lanes for (var i = 0; i < LANE_COUNT; ++i) { var divider = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 1 }); divider.x = (i + 0.5) * laneSpacing; // Center in each lane divider.y = HIT_LINE_Y + 100; // Position at bottom as hit area divider.scaleX = laneSpacing * 0.8 / divider.width; // Scale to 80% of lane width divider.scaleY = 0.3; // Make it thinner for hit area laneDividers.push(divider); game.addChild(divider); } // Hit line (visual indicator) hitLine = LK.getAsset('laneDivider', { anchorX: 0, anchorY: 0.5 }); hitLine.width = 2048; hitLine.height = 8; hitLine.x = 0; hitLine.y = HIT_LINE_Y; hitLine.alpha = 0.7; // Make it semi-transparent game.addChild(hitLine); // Load notes currentSongData = selectedSong.notes[selectedDifficulty]; currentNotes = []; noteIndex = 0; score = 0; combo = 0; maxCombo = 0; accuracy = 0; totalNotes = currentSongData.length; hitNotes = 0; missNotes = 0; badNotes = 0; goodNotes = 0; greatNotes = 0; perfectNotes = 0; scorePopups = []; holdActive = null; swipeStart = null; swipeNote = null; isPlaying = true; for (var i = 0; i < currentSongData.length; ++i) { var n = currentSongData[i]; var note = new Note(); note.init(n[0], n[1], n[2], n[3], n[4]); // Initial position: y far above screen, will be updated in update loop var laneSpacing = 2048 / LANE_COUNT; note.x = (n[1] + 0.5) * laneSpacing; // Center in the lane note.y = -200; currentNotes.push(note); game.addChild(note); } // Score/Combo/Accuracy GUI scoreText = new Text2("Score: 0", { size: 80, fill: "#fff" }); scoreText.anchor.set(0, 0); gui.topRight.addChild(scoreText); comboText = new Text2("Combo: 0", { size: 80, fill: "#fff" }); comboText.anchor.set(0, 0); gui.topRight.addChild(comboText); accuracyText = new Text2("Accuracy: 100%", { size: 80, fill: "#fff" }); accuracyText.anchor.set(0, 0); gui.topRight.addChild(accuracyText); // Start music LK.playMusic(selectedSong.id); // Start timer startTime = Date.now(); } // --- GAMEPLAY LOGIC --- function getCurrentTime() { return Date.now() - startTime; } function getNoteY(noteTime) { // Notes fall from y = -200 to HIT_LINE_Y at the time they should be hit // We'll use a fixed speed so that notes reach HIT_LINE_Y at noteTime var speed = (HIT_LINE_Y + 200) / 2000; // 2 seconds to fall var t = getCurrentTime(); var dt = noteTime - t; return HIT_LINE_Y - dt * speed; } function getHoldTrailLength(note, t) { // For hold notes, trail from note head to end time var endTime = note.time + note.duration; var y1 = getNoteY(note.time); var y2 = getNoteY(endTime); return Math.max(0, y2 - y1); } function judgeNote(note, lane, y, eventType, swipeDir) { if (!note.active || note.hit) { return false; } var noteY = getNoteY(note.time); var t = getCurrentTime(); var dt = Math.abs(note.time - t); var scoreType = ""; var scoreValue = 0; // Determine hit quality based on timing if (dt <= PERFECT_WINDOW) { scoreType = "perfect"; scoreValue = 150; perfectNotes += 1; } else if (dt <= GREAT_WINDOW) { scoreType = "great"; scoreValue = 120; greatNotes += 1; } else if (dt <= GOOD_WINDOW) { scoreType = "good"; scoreValue = 80; goodNotes += 1; } else if (dt <= BAD_WINDOW) { scoreType = "bad"; scoreValue = 40; badNotes += 1; } else { return false; // Miss } if (note.type === 'tap') { if (lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW) { note.hit = true; note.active = false; note.flash(); LK.getSound('tap').play(); score += scoreValue; combo += 1; hitNotes += 1; maxCombo = Math.max(combo, maxCombo); // Show score popup var popup = new ScorePopup(); popup.init(scoreType, note.x, note.y); game.addChild(popup); scorePopups.push(popup); return true; } } else if (note.type === 'hold') { if (eventType === 'down' && lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW) { holdActive = { note: note, start: t, scoreType: scoreType, scoreValue: scoreValue }; note.flash(); LK.getSound('hold').play(); return true; } } else if (note.type === 'swipe') { // Check if swipe affects multiple lanes (bigger swipe notes) var validLanes = [note.lane]; var laneSpacing = 2048 / LANE_COUNT; var swipeWidth = note.noteAsset.width * note.noteAsset.scaleX; // Check adjacent lanes for bigger swipe notes if (swipeWidth > laneSpacing * 1.5) { if (note.lane > 0) validLanes.push(note.lane - 1); if (note.lane < LANE_COUNT - 1) validLanes.push(note.lane + 1); } var isValidLane = validLanes.indexOf(lane) !== -1; if (eventType === 'swipe' && isValidLane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW && swipeDir === note.swipeDir) { note.hit = true; note.active = false; note.flash(); LK.getSound('swipe').play(); score += scoreValue + 50; // Bonus for swipe notes combo += 1; hitNotes += 1; maxCombo = Math.max(combo, maxCombo); // Show score popup var popup = new ScorePopup(); popup.init(scoreType, note.x, note.y); game.addChild(popup); scorePopups.push(popup); return true; } } return false; } function endHold() { if (holdActive && holdActive.note && !holdActive.note.hit) { var t = getCurrentTime(); var note = holdActive.note; var holdEnd = note.time + note.duration; if (t >= holdEnd - 150) { note.hit = true; note.active = false; score += holdActive.scoreValue + 50; // Bonus for completing hold combo += 1; hitNotes += 1; maxCombo = Math.max(combo, maxCombo); // Show score popup var popup = new ScorePopup(); popup.init(holdActive.scoreType, note.x, note.y); game.addChild(popup); scorePopups.push(popup); } else { combo = 0; missNotes += 1; // Show miss popup var popup = new ScorePopup(); popup.init("miss", note.x, note.y); game.addChild(popup); scorePopups.push(popup); } holdActive = null; } } // --- INPUT HANDLING --- var dragLane = null; var dragY = null; var dragNote = null; var dragStartX = null; var dragStartY = null; var dragStartTime = null; function getLaneFromX(x) { var laneSpacing = 2048 / LANE_COUNT; for (var i = 0; i < LANE_COUNT; ++i) { var laneStart = i * laneSpacing; var laneEnd = (i + 1) * laneSpacing; if (x >= laneStart && x < laneEnd) { return i; } } return -1; } function handleGameDown(x, y, obj) { if (GAME_STATE !== 'PLAY' || !isPlaying) { return; } var lane = getLaneFromX(x); if (lane === -1) { return; } dragLane = lane; dragY = y; dragStartX = x; dragStartY = y; dragStartTime = Date.now(); // Check for tap/hold notes for (var i = 0; i < currentNotes.length; ++i) { var note = currentNotes[i]; if (!note.active || note.hit) { continue; } if (note.type === 'tap' || note.type === 'hold') { if (judgeNote(note, lane, y, 'down')) { if (note.type === 'tap') { note.hit = true; note.active = false; } if (note.type === 'hold') { dragNote = note; } break; } } } } function handleGameUp(x, y, obj) { if (GAME_STATE !== 'PLAY' || !isPlaying) { return; } if (holdActive) { endHold(); } dragLane = null; dragNote = null; dragStartX = null; dragStartY = null; dragStartTime = null; } function handleGameMove(x, y, obj) { if (GAME_STATE !== 'PLAY' || !isPlaying) { return; } // For hold notes, check if finger is still on lane if (holdActive && dragNote) { var lane = getLaneFromX(x); if (lane !== dragNote.lane || Math.abs(y - HIT_LINE_Y) > 300) { // Released too early combo = 0; missNotes += 1; holdActive = null; dragNote = null; } } } function handleGameSwipe(x, y, obj) { if (GAME_STATE !== 'PLAY' || !isPlaying) { return; } if (dragStartX === null || dragStartY === null) { return; } var dx = x - dragStartX; var dy = y - dragStartY; var absDx = Math.abs(dx); var absDy = Math.abs(dy); if (absDx < 80 && absDy < 80) { return; } // Not enough movement var dir = null; if (absDx > absDy) { dir = dx > 0 ? 'right' : 'left'; } else { dir = dy > 0 ? 'down' : 'up'; } var lane = getLaneFromX(dragStartX); for (var i = 0; i < currentNotes.length; ++i) { var note = currentNotes[i]; if (!note.active || note.hit) { continue; } if (note.type === 'swipe') { if (judgeNote(note, lane, dragStartY, 'swipe', dir)) { break; } } } dragStartX = null; dragStartY = null; } // Attach input handlers game.down = function (x, y, obj) { handleGameDown(x, y, obj); }; game.up = function (x, y, obj) { handleGameUp(x, y, obj); }; game.move = function (x, y, obj) { handleGameMove(x, y, obj); // Detect swipe if (dragStartX !== null && dragStartY !== null) { var dx = x - dragStartX; var dy = y - dragStartY; if (Math.abs(dx) > 120 || Math.abs(dy) > 120) { handleGameSwipe(x, y, obj); } } }; // --- GAME UPDATE LOOP --- game.update = function () { if (GAME_STATE !== 'PLAY' || !isPlaying) { return; } var t = getCurrentTime(); // Update notes for (var i = 0; i < currentNotes.length; ++i) { var note = currentNotes[i]; if (!note.active) { continue; } // Update position var laneSpacing = 2048 / LANE_COUNT; note.x = (note.lane + 0.5) * laneSpacing; // Center in the lane note.y = getNoteY(note.time); // Missed note if (!note.hit && t - note.time > BAD_WINDOW && note.type !== 'hold') { note.active = false; combo = 0; missNotes += 1; // Show miss popup var popup = new ScorePopup(); popup.init("miss", note.x, note.y); game.addChild(popup); scorePopups.push(popup); } // For hold notes, if not held until end if (note.type === 'hold' && !note.hit && t - (note.time + note.duration) > BAD_WINDOW) { note.active = false; combo = 0; missNotes += 1; // Show miss popup var popup = new ScorePopup(); popup.init("miss", note.x, note.y); game.addChild(popup); scorePopups.push(popup); } } // Remove notes that are far below screen for (var i = currentNotes.length - 1; i >= 0; --i) { var note = currentNotes[i]; if (note.y > 3000 || !note.active && note.y > HIT_LINE_Y + 400) { if (note.parent) { note.parent.removeChild(note); } currentNotes.splice(i, 1); } } // Update GUI scoreText.setText("Score: " + score); comboText.setText("Combo: " + combo); var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100; accuracyText.setText("Accuracy: " + acc + "%"); // End of song if (t > getSongEndTime() + 1000 && isPlaying) { isPlaying = false; LK.stopMusic(); resultTimeout = LK.setTimeout(function () { setGameState('RESULT'); }, 1200); } }; function getSongEndTime() { var last = currentSongData[currentSongData.length - 1]; if (!last) { return 0; } if (last[0] === 'hold') { return last[2] + last[3]; } return last[2] + 1000; } // --- RESULT SCREEN --- function showResult() { clearGameObjects(); var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100; var resultStr = (selectedLanguage === 'zh' ? "结果" : "Result") + "\n\n"; resultStr += (selectedLanguage === 'zh' ? "分数: " : "Score: ") + score + "\n"; resultStr += (selectedLanguage === 'zh' ? "最大连击: " : "Max Combo: ") + maxCombo + "\n"; resultStr += (selectedLanguage === 'zh' ? "准确率: " : "Accuracy: ") + acc + "%\n\n"; // Detailed scoring breakdown resultStr += (selectedLanguage === 'zh' ? "详细统计:" : "Detailed Stats:") + "\n"; resultStr += (selectedLanguage === 'zh' ? "完美: " : "Perfect: ") + perfectNotes + "\n"; resultStr += (selectedLanguage === 'zh' ? "优秀: " : "Great: ") + greatNotes + "\n"; resultStr += (selectedLanguage === 'zh' ? "良好: " : "Good: ") + goodNotes + "\n"; resultStr += (selectedLanguage === 'zh' ? "一般: " : "Bad: ") + badNotes + "\n"; resultStr += (selectedLanguage === 'zh' ? "未命中: " : "Miss: ") + missNotes; resultText = new Text2(resultStr, { size: 70, fill: "#fff", align: "center" }); var screenWidth = LK.width || 2048; var screenHeight = LK.height || 2732; resultText.anchor.set(0.5, 0.5); resultText.x = screenWidth / 2; resultText.y = Math.floor(screenHeight * 0.40); gui.center.addChild(resultText); // Calculate grade based on accuracy var grade = "F"; if (acc >= 95) grade = "S";else if (acc >= 90) grade = "A";else if (acc >= 80) grade = "B";else if (acc >= 70) grade = "C";else if (acc >= 60) grade = "D"; var gradeText = new Text2("Grade: " + grade, { size: 120, fill: grade === "S" ? "#FFD700" : grade === "A" ? "#00FF00" : grade === "B" ? "#0080FF" : "#FFFFFF" }); gradeText.anchor.set(0.5, 0.5); gradeText.x = screenWidth / 2; gradeText.y = Math.floor(screenHeight * 0.25); gui.center.addChild(gradeText); homeButton = new Text2(selectedLanguage === 'zh' ? "返回首页" : "Home", { size: 100, fill: "#fff" }); homeButton.anchor.set(0.5, 0.5); homeButton.x = screenWidth / 2; homeButton.y = Math.floor(screenHeight * 0.70); homeButton.interactive = true; homeButton.down = function (x, y, obj) { LK.getSound('Touch').play(); setGameState('HOME'); }; gui.center.addChild(homeButton); } // --- START GAME --- // Start at language selector (opening screen) setGameState('LANGUAGE');
===================================================================
--- original.js
+++ change.js
@@ -48,9 +48,12 @@
});
} else if (type === 'swipe') {
self.noteAsset = self.attachAsset('swipeNote', {
anchorX: 0.5,
- anchorY: 0.5
+ anchorY: 0.5,
+ scaleX: 1.8,
+ // Make swipe notes bigger
+ scaleY: 1.8
});
}
};
self.flash = function () {
@@ -73,8 +76,71 @@
// Position is handled by main game loop
};
return self;
});
+// Score popup for visual feedback
+var ScorePopup = Container.expand(function () {
+ var self = Container.call(this);
+ self.scoreText = null;
+ self.startTime = 0;
+ self.duration = 1000;
+ self.init = function (scoreType, x, y) {
+ self.x = x;
+ self.y = y;
+ self.startTime = Date.now();
+ var text = "";
+ var color = "#fff";
+ var scoreValue = 0;
+ switch (scoreType) {
+ case 'perfect':
+ text = "PERFECT";
+ color = "#FFD700";
+ scoreValue = 150;
+ break;
+ case 'great':
+ text = "GREAT";
+ color = "#00FF00";
+ scoreValue = 120;
+ break;
+ case 'good':
+ text = "GOOD";
+ color = "#FFFF00";
+ scoreValue = 80;
+ break;
+ case 'bad':
+ text = "BAD";
+ color = "#FFA500";
+ scoreValue = 40;
+ break;
+ case 'miss':
+ text = "MISS";
+ color = "#FF0000";
+ scoreValue = 0;
+ break;
+ }
+ self.scoreText = new Text2(text + "\n+" + scoreValue, {
+ size: 60,
+ fill: color,
+ align: "center"
+ });
+ self.scoreText.anchor.set(0.5, 0.5);
+ self.addChild(self.scoreText);
+ // Animate popup
+ tween(self, {
+ y: self.y - 100,
+ alpha: 0
+ }, {
+ duration: self.duration,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (self.parent) {
+ self.parent.removeChild(self);
+ }
+ }
+ });
+ };
+ return self;
+});
// Song selection card
var SongCard = Container.expand(function () {
var self = Container.call(this);
self.songId = '';
@@ -131,13 +197,23 @@
var accuracy = 0;
var totalNotes = 0;
var hitNotes = 0;
var missNotes = 0;
+var badNotes = 0;
+var goodNotes = 0;
+var greatNotes = 0;
+var perfectNotes = 0;
var holdActive = null;
var swipeStart = null;
var swipeNote = null;
var isPlaying = false;
var resultTimeout = null;
+var scorePopups = [];
+// Scoring thresholds in milliseconds
+var PERFECT_WINDOW = 50;
+var GREAT_WINDOW = 100;
+var GOOD_WINDOW = 150;
+var BAD_WINDOW = 200;
// Lanes: 4 lanes, evenly spaced
var LANE_COUNT = 4;
var LANE_WIDTH = 400;
var LANE_MARGIN = 40;
@@ -154,12 +230,20 @@
},
difficulties: ['Easy', 'Medium', 'Hard'],
notes: {
Easy: [
- // Music analysis for Sky Journey - uplifting melody with steady rhythm
- ['tap', 1, 500], ['tap', 2, 1000], ['tap', 0, 1500], ['tap', 3, 2000], ['hold', 1, 2500, 1000], ['tap', 2, 4000], ['tap', 3, 4500], ['swipe', 0, 5000, 0, 'right'], ['tap', 1, 5500], ['tap', 2, 6000], ['hold', 3, 6500, 800], ['tap', 0, 7800], ['swipe', 2, 8300, 0, 'up'], ['tap', 1, 8800], ['tap', 3, 9300], ['tap', 0, 9800]],
- Medium: [['tap', 1, 400], ['tap', 2, 700], ['tap', 0, 1000], ['tap', 3, 1300], ['hold', 1, 1600, 800], ['tap', 2, 2800], ['tap', 3, 3100], ['swipe', 0, 3400, 0, 'left'], ['tap', 1, 3700], ['tap', 2, 4000], ['hold', 3, 4300, 1000], ['tap', 0, 5700], ['tap', 1, 6000], ['swipe', 2, 6300, 0, 'right'], ['tap', 3, 6600], ['tap', 0, 6900], ['hold', 1, 7200, 600], ['tap', 2, 8100], ['swipe', 3, 8400, 0, 'down']],
- Hard: [['tap', 1, 300], ['tap', 2, 500], ['tap', 0, 700], ['tap', 3, 900], ['hold', 1, 1100, 600], ['tap', 2, 1900], ['tap', 3, 2100], ['swipe', 0, 2300, 0, 'up'], ['tap', 1, 2500], ['tap', 2, 2700], ['hold', 3, 2900, 800], ['tap', 0, 3900], ['tap', 1, 4100], ['swipe', 2, 4300, 0, 'left'], ['tap', 3, 4500], ['tap', 0, 4700], ['hold', 1, 4900, 500], ['tap', 2, 5600], ['tap', 3, 5800], ['swipe', 0, 6000, 0, 'right'], ['tap', 1, 6200], ['tap', 2, 6400]]
+ // Music analysis: Sky Journey - Uplifting 4/4 tempo, soaring melodies, 120 BPM
+ ['tap', 1, 500], ['tap', 2, 1000], ['tap', 0, 1500], ['tap', 3, 2000], ['hold', 1, 2500, 1000], ['tap', 2, 4000], ['tap', 3, 4500], ['swipe', 0, 5000, 0, 'right'], ['swipe', 3, 5000, 0, 'right'],
+ // Cross-lane swipe
+ ['tap', 1, 5500], ['tap', 2, 6000], ['hold', 3, 6500, 800], ['tap', 0, 7800], ['swipe', 1, 8300, 0, 'up'], ['swipe', 2, 8300, 0, 'up'],
+ // Multi-lane swipe
+ ['tap', 1, 8800], ['tap', 3, 9300], ['tap', 0, 9800], ['tap', 2, 10300], ['hold', 0, 10800, 1200], ['tap', 1, 12500], ['swipe', 2, 13000, 0, 'down'], ['swipe', 3, 13000, 0, 'down'], ['tap', 0, 13500]],
+ Medium: [
+ // Increased complexity with eighth note patterns
+ ['tap', 1, 400], ['tap', 2, 700], ['tap', 0, 1000], ['tap', 3, 1300], ['hold', 1, 1600, 800], ['tap', 2, 2800], ['tap', 3, 3100], ['swipe', 0, 3400, 0, 'left'], ['swipe', 1, 3400, 0, 'left'], ['tap', 1, 3700], ['tap', 2, 4000], ['hold', 3, 4300, 1000], ['tap', 0, 5700], ['tap', 1, 6000], ['swipe', 2, 6300, 0, 'right'], ['swipe', 3, 6300, 0, 'right'], ['tap', 3, 6600], ['tap', 0, 6900], ['hold', 1, 7200, 600], ['tap', 2, 8100], ['swipe', 3, 8400, 0, 'down'], ['tap', 0, 8700], ['tap', 1, 9000], ['tap', 2, 9300], ['hold', 3, 9600, 900], ['swipe', 0, 10800, 0, 'up'], ['swipe', 1, 10800, 0, 'up'], ['tap', 2, 11100]],
+ Hard: [
+ // Complex patterns with sixteenth notes and polyrhythms
+ ['tap', 1, 300], ['tap', 2, 500], ['tap', 0, 700], ['tap', 3, 900], ['hold', 1, 1100, 600], ['tap', 2, 1900], ['tap', 3, 2100], ['swipe', 0, 2300, 0, 'up'], ['swipe', 1, 2300, 0, 'up'], ['tap', 1, 2500], ['tap', 2, 2700], ['hold', 3, 2900, 800], ['tap', 0, 3900], ['tap', 1, 4100], ['swipe', 2, 4300, 0, 'left'], ['swipe', 3, 4300, 0, 'left'], ['tap', 3, 4500], ['tap', 0, 4700], ['hold', 1, 4900, 500], ['tap', 2, 5600], ['tap', 3, 5800], ['swipe', 0, 6000, 0, 'right'], ['swipe', 3, 6000, 0, 'right'], ['tap', 1, 6200], ['tap', 2, 6400], ['tap', 0, 6600], ['tap', 3, 6800], ['hold', 1, 7000, 400], ['hold', 2, 7200, 400], ['swipe', 0, 7800, 0, 'down'], ['swipe', 3, 7800, 0, 'down']]
}
}, {
id: 'NightPulse',
title: 'Night Pulse',
@@ -170,12 +254,16 @@
},
difficulties: ['Easy', 'Medium', 'Hard'],
notes: {
Easy: [
- // Music analysis for Night Pulse - electronic beats with strong bass
- ['tap', 0, 800], ['tap', 2, 1200], ['tap', 1, 1600], ['tap', 3, 2000], ['hold', 0, 2400, 1200], ['tap', 2, 4000], ['tap', 1, 4400], ['swipe', 3, 4800, 0, 'left'], ['tap', 0, 5200], ['tap', 2, 5600], ['hold', 1, 6000, 1000], ['tap', 3, 7400], ['swipe', 0, 7800, 0, 'right'], ['tap', 2, 8200], ['tap', 1, 8600], ['tap', 3, 9000]],
- Medium: [['tap', 0, 600], ['tap', 2, 900], ['tap', 1, 1200], ['tap', 3, 1500], ['hold', 0, 1800, 1000], ['tap', 2, 3100], ['tap', 1, 3400], ['swipe', 3, 3700, 0, 'up'], ['tap', 0, 4000], ['tap', 2, 4300], ['hold', 1, 4600, 800], ['tap', 3, 5700], ['tap', 0, 6000], ['swipe', 2, 6300, 0, 'down'], ['tap', 1, 6600], ['tap', 3, 6900], ['hold', 0, 7200, 600], ['tap', 2, 8100], ['swipe', 1, 8400, 0, 'left']],
- Hard: [['tap', 0, 400], ['tap', 2, 600], ['tap', 1, 800], ['tap', 3, 1000], ['hold', 0, 1200, 800], ['tap', 2, 2200], ['tap', 1, 2400], ['swipe', 3, 2600, 0, 'right'], ['tap', 0, 2800], ['tap', 2, 3000], ['hold', 1, 3200, 600], ['tap', 3, 4000], ['tap', 0, 4200], ['swipe', 2, 4400, 0, 'up'], ['tap', 1, 4600], ['tap', 3, 4800], ['hold', 0, 5000, 500], ['tap', 2, 5700], ['tap', 1, 5900], ['swipe', 3, 6100, 0, 'down'], ['tap', 0, 6300], ['tap', 2, 6500]]
+ // Music analysis: Night Pulse - Electronic 4/4, strong kick pattern, 128 BPM
+ ['tap', 0, 800], ['tap', 2, 1200], ['tap', 1, 1600], ['tap', 3, 2000], ['hold', 0, 2400, 1200], ['tap', 2, 4000], ['tap', 1, 4400], ['swipe', 2, 4800, 0, 'left'], ['swipe', 3, 4800, 0, 'left'], ['tap', 0, 5200], ['tap', 2, 5600], ['hold', 1, 6000, 1000], ['tap', 3, 7400], ['swipe', 0, 7800, 0, 'right'], ['swipe', 1, 7800, 0, 'right'], ['tap', 2, 8200], ['tap', 1, 8600], ['tap', 3, 9000], ['hold', 0, 9400, 800], ['tap', 2, 10600], ['swipe', 1, 11000, 0, 'up'], ['swipe', 3, 11000, 0, 'up']],
+ Medium: [
+ // Syncopated patterns with bass drops
+ ['tap', 0, 600], ['tap', 2, 900], ['tap', 1, 1200], ['tap', 3, 1500], ['hold', 0, 1800, 1000], ['tap', 2, 3100], ['tap', 1, 3400], ['swipe', 3, 3700, 0, 'up'], ['swipe', 0, 3700, 0, 'up'], ['tap', 0, 4000], ['tap', 2, 4300], ['hold', 1, 4600, 800], ['tap', 3, 5700], ['tap', 0, 6000], ['swipe', 2, 6300, 0, 'down'], ['swipe', 3, 6300, 0, 'down'], ['tap', 1, 6600], ['tap', 3, 6900], ['hold', 0, 7200, 600], ['tap', 2, 8100], ['swipe', 1, 8400, 0, 'left'], ['tap', 3, 8700], ['tap', 0, 9000], ['tap', 1, 9300], ['hold', 2, 9600, 700], ['swipe', 0, 10600, 0, 'right'], ['swipe', 3, 10600, 0, 'right']],
+ Hard: [
+ // Complex electronic patterns with fills and drops
+ ['tap', 0, 400], ['tap', 2, 600], ['tap', 1, 800], ['tap', 3, 1000], ['hold', 0, 1200, 800], ['tap', 2, 2200], ['tap', 1, 2400], ['swipe', 3, 2600, 0, 'right'], ['swipe', 0, 2600, 0, 'right'], ['tap', 0, 2800], ['tap', 2, 3000], ['hold', 1, 3200, 600], ['tap', 3, 4000], ['tap', 0, 4200], ['swipe', 2, 4400, 0, 'up'], ['swipe', 1, 4400, 0, 'up'], ['tap', 1, 4600], ['tap', 3, 4800], ['hold', 0, 5000, 500], ['tap', 2, 5700], ['tap', 1, 5900], ['swipe', 3, 6100, 0, 'down'], ['swipe', 0, 6100, 0, 'down'], ['tap', 0, 6300], ['tap', 2, 6500], ['tap', 1, 6700], ['tap', 3, 6900], ['hold', 0, 7100, 300], ['hold', 2, 7100, 300], ['swipe', 1, 7600, 0, 'left'], ['swipe', 3, 7600, 0, 'left'], ['tap', 0, 7800], ['tap', 1, 8000], ['tap', 2, 8200], ['tap', 3, 8400]]
}
}, {
id: 'SunriseWaltz',
title: 'Sunrise Waltz',
@@ -186,12 +274,16 @@
},
difficulties: ['Easy', 'Medium', 'Hard'],
notes: {
Easy: [
- // Music analysis for Sunrise Waltz - 3/4 time signature with flowing melody
- ['tap', 1, 1000], ['tap', 2, 1500], ['tap', 3, 2000], ['tap', 0, 2500], ['hold', 1, 3000, 1500], ['tap', 2, 5000], ['tap', 3, 5500], ['swipe', 0, 6000, 0, 'up'], ['tap', 1, 6500], ['tap', 2, 7000], ['hold', 3, 7500, 1200], ['tap', 0, 9200], ['swipe', 1, 9700, 0, 'right'], ['tap', 2, 10200], ['tap', 3, 10700], ['tap', 0, 11200]],
- Medium: [['tap', 1, 750], ['tap', 2, 1125], ['tap', 3, 1500], ['tap', 0, 1875], ['hold', 1, 2250, 1125], ['tap', 2, 3750], ['tap', 3, 4125], ['swipe', 0, 4500, 0, 'down'], ['tap', 1, 4875], ['tap', 2, 5250], ['hold', 3, 5625, 1000], ['tap', 0, 7000], ['tap', 1, 7375], ['swipe', 2, 7750, 0, 'left'], ['tap', 3, 8125], ['tap', 0, 8500], ['hold', 1, 8875, 750], ['tap', 2, 10000], ['swipe', 3, 10375, 0, 'up']],
- Hard: [['tap', 1, 500], ['tap', 2, 750], ['tap', 3, 1000], ['tap', 0, 1250], ['hold', 1, 1500, 750], ['tap', 2, 2500], ['tap', 3, 2750], ['swipe', 0, 3000, 0, 'right'], ['tap', 1, 3250], ['tap', 2, 3500], ['hold', 3, 3750, 875], ['tap', 0, 4875], ['tap', 1, 5125], ['swipe', 2, 5375, 0, 'up'], ['tap', 3, 5625], ['tap', 0, 5875], ['hold', 1, 6125, 625], ['tap', 2, 7000], ['tap', 3, 7250], ['swipe', 0, 7500, 0, 'down'], ['tap', 1, 7750], ['tap', 2, 8000]]
+ // Music analysis: Sunrise Waltz - 3/4 time signature, graceful melody, 90 BPM
+ ['tap', 1, 1000], ['tap', 2, 1500], ['tap', 3, 2000], ['tap', 0, 2500], ['hold', 1, 3000, 1500], ['tap', 2, 5000], ['tap', 3, 5500], ['swipe', 0, 6000, 0, 'up'], ['swipe', 1, 6000, 0, 'up'], ['tap', 1, 6500], ['tap', 2, 7000], ['hold', 3, 7500, 1200], ['tap', 0, 9200], ['swipe', 1, 9700, 0, 'right'], ['swipe', 2, 9700, 0, 'right'], ['tap', 2, 10200], ['tap', 3, 10700], ['tap', 0, 11200], ['hold', 1, 11700, 1000], ['swipe', 0, 13200, 0, 'down'], ['swipe', 3, 13200, 0, 'down']],
+ Medium: [
+ // Waltz patterns with ornamental flourishes
+ ['tap', 1, 750], ['tap', 2, 1125], ['tap', 3, 1500], ['tap', 0, 1875], ['hold', 1, 2250, 1125], ['tap', 2, 3750], ['tap', 3, 4125], ['swipe', 0, 4500, 0, 'down'], ['swipe', 1, 4500, 0, 'down'], ['tap', 1, 4875], ['tap', 2, 5250], ['hold', 3, 5625, 1000], ['tap', 0, 7000], ['tap', 1, 7375], ['swipe', 2, 7750, 0, 'left'], ['swipe', 3, 7750, 0, 'left'], ['tap', 3, 8125], ['tap', 0, 8500], ['hold', 1, 8875, 750], ['tap', 2, 10000], ['swipe', 3, 10375, 0, 'up'], ['tap', 0, 10750], ['tap', 1, 11125], ['hold', 2, 11500, 875], ['swipe', 0, 12750, 0, 'right'], ['swipe', 3, 12750, 0, 'right']],
+ Hard: [
+ // Complex waltz with rapid arpeggios and cross-rhythms
+ ['tap', 1, 500], ['tap', 2, 750], ['tap', 3, 1000], ['tap', 0, 1250], ['hold', 1, 1500, 750], ['tap', 2, 2500], ['tap', 3, 2750], ['swipe', 0, 3000, 0, 'right'], ['swipe', 3, 3000, 0, 'right'], ['tap', 1, 3250], ['tap', 2, 3500], ['hold', 3, 3750, 875], ['tap', 0, 4875], ['tap', 1, 5125], ['swipe', 2, 5375, 0, 'up'], ['swipe', 0, 5375, 0, 'up'], ['tap', 3, 5625], ['tap', 0, 5875], ['hold', 1, 6125, 625], ['tap', 2, 7000], ['tap', 3, 7250], ['swipe', 0, 7500, 0, 'down'], ['swipe', 1, 7500, 0, 'down'], ['tap', 1, 7750], ['tap', 2, 8000], ['tap', 3, 8250], ['tap', 0, 8500], ['hold', 1, 8750, 375], ['hold', 3, 8750, 375], ['swipe', 0, 9375, 0, 'left'], ['swipe', 2, 9375, 0, 'left'], ['tap', 1, 9625], ['tap', 3, 9875], ['tap', 0, 10125], ['tap', 2, 10375]]
}
}];
// --- GUI ELEMENTS ---
var gui = LK.gui;
@@ -710,8 +802,13 @@
accuracy = 0;
totalNotes = currentSongData.length;
hitNotes = 0;
missNotes = 0;
+ badNotes = 0;
+ goodNotes = 0;
+ greatNotes = 0;
+ perfectNotes = 0;
+ scorePopups = [];
holdActive = null;
swipeStart = null;
swipeNote = null;
isPlaying = true;
@@ -773,43 +870,86 @@
if (!note.active || note.hit) {
return false;
}
var noteY = getNoteY(note.time);
- var hitWindow = 120; // ms
var t = getCurrentTime();
var dt = Math.abs(note.time - t);
+ var scoreType = "";
+ var scoreValue = 0;
+ // Determine hit quality based on timing
+ if (dt <= PERFECT_WINDOW) {
+ scoreType = "perfect";
+ scoreValue = 150;
+ perfectNotes += 1;
+ } else if (dt <= GREAT_WINDOW) {
+ scoreType = "great";
+ scoreValue = 120;
+ greatNotes += 1;
+ } else if (dt <= GOOD_WINDOW) {
+ scoreType = "good";
+ scoreValue = 80;
+ goodNotes += 1;
+ } else if (dt <= BAD_WINDOW) {
+ scoreType = "bad";
+ scoreValue = 40;
+ badNotes += 1;
+ } else {
+ return false; // Miss
+ }
if (note.type === 'tap') {
- if (lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < hitWindow) {
+ if (lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW) {
note.hit = true;
note.active = false;
note.flash();
LK.getSound('tap').play();
- score += 100;
+ score += scoreValue;
combo += 1;
hitNotes += 1;
maxCombo = Math.max(combo, maxCombo);
+ // Show score popup
+ var popup = new ScorePopup();
+ popup.init(scoreType, note.x, note.y);
+ game.addChild(popup);
+ scorePopups.push(popup);
return true;
}
} else if (note.type === 'hold') {
- if (eventType === 'down' && lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < hitWindow) {
+ if (eventType === 'down' && lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW) {
holdActive = {
note: note,
- start: t
+ start: t,
+ scoreType: scoreType,
+ scoreValue: scoreValue
};
note.flash();
LK.getSound('hold').play();
return true;
}
} else if (note.type === 'swipe') {
- if (eventType === 'swipe' && lane === note.lane && Math.abs(y - HIT_LINE_Y) < 180 && dt < hitWindow && swipeDir === note.swipeDir) {
+ // Check if swipe affects multiple lanes (bigger swipe notes)
+ var validLanes = [note.lane];
+ var laneSpacing = 2048 / LANE_COUNT;
+ var swipeWidth = note.noteAsset.width * note.noteAsset.scaleX;
+ // Check adjacent lanes for bigger swipe notes
+ if (swipeWidth > laneSpacing * 1.5) {
+ if (note.lane > 0) validLanes.push(note.lane - 1);
+ if (note.lane < LANE_COUNT - 1) validLanes.push(note.lane + 1);
+ }
+ var isValidLane = validLanes.indexOf(lane) !== -1;
+ if (eventType === 'swipe' && isValidLane && Math.abs(y - HIT_LINE_Y) < 180 && dt < BAD_WINDOW && swipeDir === note.swipeDir) {
note.hit = true;
note.active = false;
note.flash();
LK.getSound('swipe').play();
- score += 150;
+ score += scoreValue + 50; // Bonus for swipe notes
combo += 1;
hitNotes += 1;
maxCombo = Math.max(combo, maxCombo);
+ // Show score popup
+ var popup = new ScorePopup();
+ popup.init(scoreType, note.x, note.y);
+ game.addChild(popup);
+ scorePopups.push(popup);
return true;
}
}
return false;
@@ -821,15 +961,25 @@
var holdEnd = note.time + note.duration;
if (t >= holdEnd - 150) {
note.hit = true;
note.active = false;
- score += 200;
+ score += holdActive.scoreValue + 50; // Bonus for completing hold
combo += 1;
hitNotes += 1;
maxCombo = Math.max(combo, maxCombo);
+ // Show score popup
+ var popup = new ScorePopup();
+ popup.init(holdActive.scoreType, note.x, note.y);
+ game.addChild(popup);
+ scorePopups.push(popup);
} else {
combo = 0;
missNotes += 1;
+ // Show miss popup
+ var popup = new ScorePopup();
+ popup.init("miss", note.x, note.y);
+ game.addChild(popup);
+ scorePopups.push(popup);
}
holdActive = null;
}
}
@@ -982,18 +1132,28 @@
var laneSpacing = 2048 / LANE_COUNT;
note.x = (note.lane + 0.5) * laneSpacing; // Center in the lane
note.y = getNoteY(note.time);
// Missed note
- if (!note.hit && t - note.time > 200 && note.type !== 'hold') {
+ if (!note.hit && t - note.time > BAD_WINDOW && note.type !== 'hold') {
note.active = false;
combo = 0;
missNotes += 1;
+ // Show miss popup
+ var popup = new ScorePopup();
+ popup.init("miss", note.x, note.y);
+ game.addChild(popup);
+ scorePopups.push(popup);
}
// For hold notes, if not held until end
- if (note.type === 'hold' && !note.hit && t - (note.time + note.duration) > 200) {
+ if (note.type === 'hold' && !note.hit && t - (note.time + note.duration) > BAD_WINDOW) {
note.active = false;
combo = 0;
missNotes += 1;
+ // Show miss popup
+ var popup = new ScorePopup();
+ popup.init("miss", note.x, note.y);
+ game.addChild(popup);
+ scorePopups.push(popup);
}
}
// Remove notes that are far below screen
for (var i = currentNotes.length - 1; i >= 0; --i) {
@@ -1032,32 +1192,48 @@
// --- RESULT SCREEN ---
function showResult() {
clearGameObjects();
var acc = totalNotes > 0 ? Math.floor(100 * hitNotes / totalNotes) : 100;
- var resultStr = (selectedLanguage === 'zh' ? "结果" : "Result") + "\n";
+ var resultStr = (selectedLanguage === 'zh' ? "结果" : "Result") + "\n\n";
resultStr += (selectedLanguage === 'zh' ? "分数: " : "Score: ") + score + "\n";
resultStr += (selectedLanguage === 'zh' ? "最大连击: " : "Max Combo: ") + maxCombo + "\n";
- resultStr += (selectedLanguage === 'zh' ? "准确率: " : "Accuracy: ") + acc + "%\n";
- resultStr += (selectedLanguage === 'zh' ? "命中: " : "Hit: ") + hitNotes + "/" + totalNotes + "\n";
+ resultStr += (selectedLanguage === 'zh' ? "准确率: " : "Accuracy: ") + acc + "%\n\n";
+ // Detailed scoring breakdown
+ resultStr += (selectedLanguage === 'zh' ? "详细统计:" : "Detailed Stats:") + "\n";
+ resultStr += (selectedLanguage === 'zh' ? "完美: " : "Perfect: ") + perfectNotes + "\n";
+ resultStr += (selectedLanguage === 'zh' ? "优秀: " : "Great: ") + greatNotes + "\n";
+ resultStr += (selectedLanguage === 'zh' ? "良好: " : "Good: ") + goodNotes + "\n";
+ resultStr += (selectedLanguage === 'zh' ? "一般: " : "Bad: ") + badNotes + "\n";
resultStr += (selectedLanguage === 'zh' ? "未命中: " : "Miss: ") + missNotes;
resultText = new Text2(resultStr, {
- size: 90,
+ size: 70,
fill: "#fff",
align: "center"
});
var screenWidth = LK.width || 2048;
var screenHeight = LK.height || 2732;
resultText.anchor.set(0.5, 0.5);
resultText.x = screenWidth / 2;
- resultText.y = Math.floor(screenHeight * 0.36);
+ resultText.y = Math.floor(screenHeight * 0.40);
gui.center.addChild(resultText);
+ // Calculate grade based on accuracy
+ var grade = "F";
+ if (acc >= 95) grade = "S";else if (acc >= 90) grade = "A";else if (acc >= 80) grade = "B";else if (acc >= 70) grade = "C";else if (acc >= 60) grade = "D";
+ var gradeText = new Text2("Grade: " + grade, {
+ size: 120,
+ fill: grade === "S" ? "#FFD700" : grade === "A" ? "#00FF00" : grade === "B" ? "#0080FF" : "#FFFFFF"
+ });
+ gradeText.anchor.set(0.5, 0.5);
+ gradeText.x = screenWidth / 2;
+ gradeText.y = Math.floor(screenHeight * 0.25);
+ gui.center.addChild(gradeText);
homeButton = new Text2(selectedLanguage === 'zh' ? "返回首页" : "Home", {
size: 100,
fill: "#fff"
});
homeButton.anchor.set(0.5, 0.5);
homeButton.x = screenWidth / 2;
- homeButton.y = Math.floor(screenHeight * 0.66);
+ homeButton.y = Math.floor(screenHeight * 0.70);
homeButton.interactive = true;
homeButton.down = function (x, y, obj) {
LK.getSound('Touch').play();
setGameState('HOME');
Game design for Kaleidoscope of Music Rhythm without game title, just the design for opening screen background. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style design empty selector UI. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style hold note for music rhythm game. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style swipe note for music rhythm game. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style tap note for music rhythm game. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style kaleidoscope pattern design for homepage. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style lane divider for music rhythm game that is horizontal and used for judging the touching of rhythm In-Game asset. 2d. High contrast. No shadows
Empty cyberpunk style storybox design in which it's size enable song cover, difficulty level and song description to be added in the storybox. In-Game asset. 2d. High contrast. No shadows
Round shape of song cover of anime style with Sky Journey theme. In-Game asset. 2d. High contrast. No shadows
Round shape song cover of cyberpunk anime style with the themed “Night Pulse”. In-Game asset. 2d. High contrast. No shadows
Round shape of anime style song cover with the themed Sunrise Waltz. In-Game asset. 2d. High contrast. No shadows
Anime style design for the round shape song cover of New Era Malay Style
Round shape, Replace Chinese word from “不知途”to“莫问前程”,others remaining the same
Empty cyberpunk style menu design. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style pause symbol. In-Game asset. 2d. High contrast. No shadows
Cyberpunk style return symbol. In-Game asset. 2d. High contrast. No shadows