User prompt
ekle
Code edit (11 edits merged)
Please save this source code
User prompt
endless ile settings buttonunun yerini değiştirir misin
User prompt
endless textini kaldır ve endless modu için endlessbutton assetini kullan
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'getWaveParams is not defined' in or related to this line: 'var params = getWaveParams(bpm);' Line Number: 4503
Code edit (7 edits merged)
Please save this source code
User prompt
Please fix the bug: 'beat is not defined' in or related to this line: 'beat.y = getWaveformY(beat.x);' Line Number: 5015
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'newBpm is not defined' in or related to this line: 'if (bpm !== newBpm) {' Line Number: 4975
User prompt
Please fix the bug: 'newBpm is not defined' in or related to this line: 'if (bpm !== newBpm) {' Line Number: 4975
User prompt
Please fix the bug: 'newBpm is not defined' in or related to this line: 'if (bpm !== newBpm) {' Line Number: 4975
User prompt
Please fix the bug: 'newBpm is not defined' in or related to this line: 'if (bpm !== newBpm) {' Line Number: 4975
User prompt
Please fix the bug: 'newBpm is not defined' in or related to this line: 'if (bpm !== newBpm) {' Line Number: 4975
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'newBpm is not defined' in or related to this line: 'if (bpm !== newBpm) {' Line Number: 4977
Code edit (2 edits merged)
Please save this source code
User prompt
endless game modunda 5k puana gelince story modundan devam etmesin ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
BPM Change Only Effective at the Beginning of the Game: After changing the BPM in Settings, if you do not enter endless mode again, the new BPM will not be active. (This is expected behavior.) Let's also make improvements to this issue so that when you start the game in endless mode and get 5k points, it does not switch to story mode. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
BPM Change Only Effective at the Beginning of the Game: After changing the BPM in Settings, if you do not enter endless mode again, the new BPM will not be active. (This is expected behavior.) Let's also make improvements to this issue so that when you start the game in endless mode and get 5k points, it does not switch to story mode. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'call')' in or related to this line: 'originalGameUpdate.call(this);' Line Number: 2608
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'score')' in or related to this line: 'gameState.score = 0;' Line Number: 255
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bpm: 100, soundVolume: 100, selectedKickSound: "kick", selectedHihatSound: "hihat", selectedSnareClapSound: "snareclap", selectedPercussionSound: "percussion", tutorialCompleted: false, currentChapter: 1 }); /**** * Classes ****/ // default gameState.bpm 100 // Beat marker class (moving dot) var BeatMarker = Container.expand(function () { var self = Container.call(this); var marker = self.attachAsset('beatMarker', { anchorX: 0.5, anchorY: 0.5 }); self.active = true; // Whether this beat is still hittable self.hit = false; // Whether this beat was hit self.assetType = 'circle'; // Varsayılan asset tipi // Trail effect properties self.lastTrailTime = 0; self.trailInterval = 15; // Her 15ms'de parçacık oluştur self.updateTrail = function () { var now = Date.now(); if (now - this.lastTrailTime > this.trailInterval) { var colors = BEAT_COLORS[this.assetType] || BEAT_COLORS.circle; // Beat'in hareket yönünü belirle (sağ tarafta mı?) var isRightSide = this.x > HIT_ZONE_X; var directionMultiplier = isRightSide ? 1 : -1; for (var i = 0; i < 10; i++) { // Ana parçacık var mainParticle = createTrailParticle(this.x + (Math.random() - 0.5) * 20, this.y + (Math.random() - 0.5) * 20, colors.mainTrail); mainParticle.velocityX = (Math.random() * 2 + 1) * directionMultiplier; mainParticle.velocityY = (Math.random() - 0.5) * 3; // Yan parçacık 1 var subParticle1 = createTrailParticle(this.x + (Math.random() - 0.5) * 30, this.y + (Math.random() - 0.5) * 30, colors.subTrail1); subParticle1.width = 4; subParticle1.height = 4; subParticle1.velocityX = (Math.random() * 1.5 + 0.5) * directionMultiplier; subParticle1.velocityY = (Math.random() - 0.5) * 2; // Yan parçacık 2 var subParticle2 = createTrailParticle(this.x + (Math.random() - 0.5) * 25, this.y + (Math.random() - 0.5) * 25, colors.subTrail2); subParticle2.width = 3; subParticle2.height = 3; subParticle2.velocityX = (Math.random() * 1 + 0.5) * directionMultiplier; subParticle2.velocityY = (Math.random() - 0.5) * 1.5; // Parçacıkları oyun ekranına ekle [mainParticle, subParticle1, subParticle2].forEach(function (p) { p.fadeSpeed = 0.03; p.gravity = 0.1; p.drag = 0.98; gamescreen.addChild(p); trailParticles.push(p); }); } this.lastTrailTime = now; } }; // For feedback animation self.showFeedback = function (type) { var feedbackId = type === 'good' ? 'goodCircle' : 'missCircle'; var feedback = LK.getAsset(feedbackId, { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.7, scaleX: 1, scaleY: 1 }); game.addChild(feedback); tween(feedback, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { feedback.destroy(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // black background }); /**** * Game Code ****/ // --- Story Chapters Configuration --- // Bass drum (kick) hit circle // Bass drum (kick) hit circle when active // Timeline bar // Beat marker // Miss feedback // Good feedback // Sound for kick // Music (looping, short demo) // --- BPM-specific music assets (kickless versions) --- var STORY_CHAPTERS = [{ bpm: 60, musicId: '60BPMLowKey', songName: 'LowKey' }, { bpm: 90, musicId: '90BPMDigitalPulse', songName: 'DigitalPulse' }]; // --- Game constants --- var TIMELINE_WIDTH = 2048; var TIMELINE_HEIGHT = 25; var TIMELINE_Y = 2000; // Y position of timeline bar var TIMELINE_X = 0; var HIT_ZONE_X = 2048 / 2; // Center of screen var HIT_ZONE_Y = TIMELINE_Y + TIMELINE_HEIGHT / 2; var HIT_WINDOW = 120; // px distance for perfect hit // Beat speed remains constant - only spacing between beats changes based on BPM function getBeatSpeed() { return 400; // Constant speed of 400 px/s regardless of BPM } var INITIAL_BPM = 100; // Starting BPM var BEATS_PER_MEASURE = 4; var BEAT_INTERVAL = 60 / INITIAL_BPM; // seconds per beat var DIFFICULTY_STEP = 0.15; // How much to increase BPM per level var MAX_MISSES = 5; // --- Game state --- var gameState = { score: 0, combo: 0, currentChapter: 1, lives: 3, heartshields: 3, bpm: 100, gameActive: false, gamePaused: false, maxCombo: 0, perfectCount: 0, maxPerfectCombo: 0, lastBpmUpgradeScore: 0 }; var beats = []; // Active BeatMarker objects var beatPattern = []; // Array of {time, type} var nextBeatIdx = 0; // Next beat to spawn var songTime = 0; // Elapsed time in seconds var lastTickTime = Date.now(); var misses = 0; var ignoredMisses = 0; // Track first 3 misses to ignore var tutorialCompleted = false; // Reset storage values to ensure game always starts from beginning on reload storage.tutorialCompleted = false; storage.currentChapter = 1; var isTutorial = false; gameState.bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM; var soundVolume = typeof storage.soundVolume === "number" ? storage.soundVolume : 100; var selectedKickSound = typeof storage.selectedKickSound === "string" ? storage.selectedKickSound : 'kick'; var selectedHihatSound = typeof storage.selectedHihatSound === "string" ? storage.selectedHihatSound : 'hihat'; var selectedSnareClapSound = typeof storage.selectedSnareClapSound === "string" ? storage.selectedSnareClapSound : 'snareclap'; var selectedPercussionSound = typeof storage.selectedPercussionSound === "string" ? storage.selectedPercussionSound : 'percussion'; // Function to update all sound volumes based on soundVolume setting function updateAllSoundVolumes() { var volumeMultiplier = soundVolume / 100; // Update kick sound volume var kickSound = LK.getSound(selectedKickSound); if (kickSound && kickSound.setVolume) { kickSound.setVolume(1 * volumeMultiplier); } // Update hihat sound volume var hihatSound = LK.getSound(selectedHihatSound); if (hihatSound && hihatSound.setVolume) { hihatSound.setVolume(0.5 * volumeMultiplier); } // Update snare/clap sound volume var snareClapSound = LK.getSound(selectedSnareClapSound); if (snareClapSound && snareClapSound.setVolume) { snareClapSound.setVolume(0.6 * volumeMultiplier); } // Update percussion sound volume var percussionSound = LK.getSound(selectedPercussionSound); if (percussionSound && percussionSound.setVolume) { percussionSound.setVolume(0.6 * volumeMultiplier); } // Update miss sound volume var missSound = LK.getSound('miss'); if (missSound && missSound.setVolume) { missSound.setVolume(0.6 * volumeMultiplier); } } var beatInterval = 60 / gameState.bpm; gameState.gameActive = true; var feedbackTimeout = null; // --- UI elements --- // --- Heart/Life UI --- // (Initialization moved after gameState.scoreTxt is defined) var MAX_LIVES = 3; gameState.lives = MAX_LIVES; var heartIcons = []; var heartSpacing = 40; gameState.heartshields = 3; // Number of gameState.heartshields remaining // Heart/life UI will be initialized after gameState.scoreTxt is defined // --- Gameplay Screen Container --- var gamescreen = new Container(); gamescreen.visible = false; game.addChild(gamescreen); // Add isyeri background image, anchored top-left, covering the whole game area var isyeriBg = LK.getAsset('isyeri', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); gamescreen.addChild(isyeriBg); var timelineBar = LK.getAsset('timelineBar', { anchorX: 0, anchorY: 0.5, x: TIMELINE_X, y: TIMELINE_Y + TIMELINE_HEIGHT / 2 }); gamescreen.addChild(timelineBar); // Hit zone (kick target) var kickTarget = LK.getAsset('kickTarget', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X, y: HIT_ZONE_Y }); gamescreen.addChild(kickTarget); // Score text - pixelated effect using small size and scale gameState.scoreTxt = new Text2('0', { size: 60, fill: 0xFFFFFF }); gameState.scoreTxt.scale.set(2, 2); // Scale up 2x to create more pixelated effect gameState.scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(gameState.scoreTxt); // Combo text gameState.comboTxt = new Text2('', { size: 36, fill: 0x00FF00 }); gameState.comboTxt.scale.set(2, 2); gameState.comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(gameState.comboTxt); gameState.comboTxt.y = 120; // BPM display text - 135px below gameState.combo text (moved 30px down) gameState.bpmDisplayTxt = new Text2(gameState.bpm + ' ' + 'BPM', { size: 36, fill: 0xef69b9 }); gameState.bpmDisplayTxt.scale.set(2, 2); gameState.bpmDisplayTxt.anchor.set(0.5, 0); LK.gui.top.addChild(gameState.bpmDisplayTxt); gameState.bpmDisplayTxt.y = gameState.comboTxt.y + 135; // Song name display text - below BPM display var songNameTxt = new Text2('', { size: 36, fill: 0xffffff }); songNameTxt.scale.set(2, 2); songNameTxt.anchor.set(0.5, 0); LK.gui.top.addChild(songNameTxt); songNameTxt.y = gameState.bpmDisplayTxt.y + 82; // Pause button in top right corner (avoiding top-left platform menu) var pauseBtn = LK.getAsset('pause', { anchorX: 1, anchorY: 0, x: 2048 - 50, y: 220, scaleX: 1.6, scaleY: 1.6 }); gamescreen.addChild(pauseBtn); // No miss text, only hearts for gameState.lives display // --- Heart/Life UI --- (moved here to ensure gameState.scoreTxt is defined) // Calculate heartY based on gameState.scoreTxt position and height var heartY = gameState.scoreTxt.y + 50; // Score text'in 50px altında var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; // Sağdan 100px boşluk bırak // Remove any existing heart icons from their parents before recreating for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; for (var i = 0; i < MAX_LIVES; i++) { var heartshield = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartshield); heartIcons.push(heartshield); } // --- Beat pattern generation --- function generateBeatPattern(bpm, measures) { // Simple: 4/4, kick on every beat, can add more complex patterns later var pattern = []; var interval = 60 / bpm; // seconds per beat based on current BPM var totalBeats = 100000; // Target 100,000 total beats for (var beatIndex = 0; beatIndex < totalBeats; beatIndex++) { pattern.push({ time: beatIndex * interval, // Each beat spaced by BPM interval type: 'kick' }); } return pattern; } // --- Start game --- function startGame(options) { options = options || {}; var mode = options.mode || "story"; // Reset state for (var i = 0; i < beats.length; i++) { beats[i].destroy(); } beats = []; if (mode === "story") { var chapterIdx = typeof gameState.currentChapter === 'number' ? gameState.currentChapter - 1 : 0; var chapter = STORY_CHAPTERS[chapterIdx] || STORY_CHAPTERS[0]; isTutorial = chapterIdx === 0 && !tutorialCompleted; gameState.bpm = chapter.bpm; var storyMusicId = chapter.musicId; var storySongName = chapter.songName; beatInterval = 60 / gameState.bpm; beatPattern = generateBeatPattern(gameState.bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); gameState.score = 0; misses = 0; ignoredMisses = 0; gameState.combo = 0; perfectCombo = 0; gameState.lives = MAX_LIVES; gameState.heartshields = 3; gameState.lastBpmUpgradeScore = 0; for (var i = beats.length - 1; i >= 0; i--) { if (typeof beats[i].x !== "undefined" && Math.abs(beats[i].x - HIT_ZONE_X) < 2 || typeof beats[i].x !== "undefined" && beats[i].x >= HIT_ZONE_X - HIT_WINDOW) { beats[i].destroy(); beats.splice(i, 1); } } if (beats.length > 0 && typeof beats[0].x !== "undefined" && Math.abs(beats[0].x - HIT_ZONE_X) < 2) { beats[0].destroy(); beats.splice(0, 1); } updateHeartsUI(gameState.lives, gameState.heartshields); var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = gameState.scoreTxt.y + 50; for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; gameState.heartshields = MAX_LIVES; gameState.lives = MAX_LIVES; for (var i = 0; i < MAX_LIVES; i++) { var heartAsset = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } gameState.gameActive = true; gameState.gamePaused = false; gameState.scoreTxt.setText('0'); gameState.comboTxt.setText(''); kickTarget.visible = true; LK.setScore(0); applyGlowToVioletElements(); updateAllSoundVolumes(); tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameState.gameActive && gameState.heartshields > 0) { gameState.heartshields = 0; updateHeartsUI(gameState.lives, gameState.heartshields); } } }); var musicId = storyMusicId; var volumeMultiplier = soundVolume / 100; var musicVolume = isTutorial ? 0.5 : 1; LK.playMusic(musicId, { volume: musicVolume * volumeMultiplier, loop: true }); if (gameState.bpmDisplayTxt) { gameState.bpmDisplayTxt.setText(gameState.bpm + ' BPM'); } if (songNameTxt) { songNameTxt.setText(storySongName); } gameState.scoreTxt.visible = true; gameState.comboTxt.visible = true; gameState.bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; return; } else if (mode === "endless") { isTutorial = false; gameState.bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM; beatInterval = 60 / gameState.bpm; beatPattern = generateBeatPattern(gameState.bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); gameState.score = 0; misses = 0; ignoredMisses = 0; gameState.combo = 0; perfectCombo = 0; gameState.lives = MAX_LIVES; gameState.heartshields = 3; gameState.lastBpmUpgradeScore = 0; for (var i = beats.length - 1; i >= 0; i--) { if (typeof beats[i].x !== "undefined" && Math.abs(beats[i].x - HIT_ZONE_X) < 2 || typeof beats[i].x !== "undefined" && beats[i].x >= HIT_ZONE_X - HIT_WINDOW) { beats[i].destroy(); beats.splice(i, 1); } } if (beats.length > 0 && typeof beats[0].x !== "undefined" && Math.abs(beats[0].x - HIT_ZONE_X) < 2) { beats[0].destroy(); beats.splice(0, 1); } updateHeartsUI(gameState.lives, gameState.heartshields); var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = gameState.scoreTxt.y + 50; for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; gameState.heartshields = MAX_LIVES; gameState.lives = MAX_LIVES; for (var i = 0; i < MAX_LIVES; i++) { var heartAsset = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } gameState.gameActive = true; gameState.gamePaused = false; gameState.scoreTxt.setText('0'); gameState.comboTxt.setText(''); kickTarget.visible = true; LK.setScore(0); applyGlowToVioletElements(); updateAllSoundVolumes(); tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameState.gameActive && gameState.heartshields > 0) { gameState.heartshields = 0; updateHeartsUI(gameState.lives, gameState.heartshields); } } }); updateEndlessBpmAndMusic(); gameState.scoreTxt.visible = true; gameState.comboTxt.visible = true; gameState.bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; } } // Endless modunda BPM ve müzik güncelleme fonksiyonu function updateEndlessBpmAndMusic() { // BPM'e göre uygun müzik ID'sini seç var musicId = '100BPMSynthWave'; if (gameState.bpm === 60) { musicId = '60BPMLowKey'; } else if (gameState.bpm === 40) { musicId = '40BPMDeepVibes'; } else if (gameState.bpm === 45) { musicId = '45BPMChillWave'; } else if (gameState.bpm === 50) { musicId = '50BPMSlowMotion'; } else if (gameState.bpm === 55) { musicId = '55BPMDreamscape'; } else if (gameState.bpm === 65) { musicId = '65BPMRelaxed'; } else if (gameState.bpm === 70) { musicId = '70BPMCalmVibes'; } else if (gameState.bpm === 75) { musicId = '75BPMElectricDreams'; } else if (gameState.bpm === 80) { musicId = '80BPMNeonNights'; } else if (gameState.bpm === 85) { musicId = '85BPMCyberFlow'; } else if (gameState.bpm === 90) { musicId = '90BPMDigitalPulse'; } else if (gameState.bpm === 95) { musicId = '95BPMFutureBass'; } else if (gameState.bpm === 100) { musicId = '100BPMSynthWave'; } else if (gameState.bpm === 105) { musicId = '105BPMHyperDrive'; } else if (gameState.bpm === 110) { musicId = '110BPMTechnoRush'; } else if (gameState.bpm === 115) { musicId = '115BPMHighEnergy'; } else if (gameState.bpm === 120) { musicId = '120BPMMaximum'; } var volumeMultiplier = soundVolume / 100; LK.playMusic(musicId, { volume: 1 * volumeMultiplier, loop: true }); if (gameState.bpmDisplayTxt) { gameState.bpmDisplayTxt.setText(gameState.bpm + ' BPM'); } if (songNameTxt) { songNameTxt.setText(musicId.replace('BPM', ' BPM ')); } } // --- Main Menu Implementation --- // Add main menu background image, anchored top-left, covering the whole game area var mainMenuContainer = new Container(); mainMenuContainer.visible = true; var mainMenuBg = LK.getAsset('mainmanubg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); mainMenuContainer.addChild(mainMenuBg); // Title - DJ Asset var djAsset = LK.getAsset('DJ', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 315, scaleX: 0.2, scaleY: 0.2 }); mainMenuContainer.addChild(djAsset); // Calculate button positions with 5px spacing var buttonStartY = 1000; var buttonHeight = 289; // Approximate height with scale 5 var buttonSpacing = 5; // Start Button var startBtn = LK.getAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY, scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(startBtn); // Settings Button var settingsBtn = LK.getAsset('settingsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + buttonHeight + buttonSpacing, scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(settingsBtn); // Credits Button var creditsBtn = LK.getAsset('creditsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + 2 * (buttonHeight + buttonSpacing), scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(creditsBtn); // Support Us Button var supportBtn = LK.getAsset('supportbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + 3 * (buttonHeight + buttonSpacing), scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(supportBtn); // Overlay for credits/settings/support var settingsmenu = new Container(); settingsmenu.visible = false; game.addChild(settingsmenu); var creditsmenu = new Container(); creditsmenu.visible = false; game.addChild(creditsmenu); var supportmenu = new Container(); supportmenu.visible = false; game.addChild(supportmenu); // Helper to hide all overlays function hideAllMenus() { settingsmenu.visible = false; creditsmenu.visible = false; supportmenu.visible = false; } // Show credits menu function showCreditsMenu() { hideAllMenus(); creditsmenu.removeChildren(); var bg = LK.getAsset('creditsbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); creditsmenu.addChild(bg); var creditsTab = LK.getAsset('creditstab', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 0.5, scaleY: 0.5 }); creditsmenu.addChild(creditsTab); // Close button (bottom right) var closeBtn = LK.getAsset('closebutton', { anchorX: 1, anchorY: 1, x: 2048 - 60, y: 2732 - 60, scaleX: 3, scaleY: 3 }); creditsmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(closeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(closeBtn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }); creditsmenu.visible = false; mainMenuContainer.visible = true; }; creditsmenu.visible = true; // Remove tap-to-close on background creditsmenu.down = undefined; } // Show support menu function showSupportMenu() { hideAllMenus(); supportmenu.removeChildren(); var bg = LK.getAsset('supportbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); supportmenu.addChild(bg); var supportTab = LK.getAsset('supporttab', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 0.5, scaleY: 0.5 }); supportmenu.addChild(supportTab); // Close button (bottom right) var closeBtn = LK.getAsset('closebutton', { anchorX: 1, anchorY: 1, x: 2048 - 60, y: 2732 - 60, scaleX: 3, scaleY: 3 }); supportmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(closeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(closeBtn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }); supportmenu.visible = false; mainMenuContainer.visible = true; }; supportmenu.visible = true; // Remove tap-to-close on background supportmenu.down = undefined; // Play thanks sound after 2 seconds LK.setTimeout(function () { LK.getSound('thanks').play(); }, 2000); } // Show settings menu function showSettingsMenu() { hideAllMenus(); settingsmenu.removeChildren(); var bg = LK.getAsset('settingsbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); settingsmenu.addChild(bg); var settingsAsset = LK.getAsset('settingsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 700, scaleX: 0.3, scaleY: 0.3 }); settingsmenu.addChild(settingsAsset); // --- BPM CIRCLE UI --- // Container for the whole BPM control gameState.bpmCircleContainer = new Container(); gameState.bpmCircleContainer.x = 2048 / 2 - 300; gameState.bpmCircleContainer.y = 1100; settingsmenu.addChild(gameState.bpmCircleContainer); // --- KICK SOUND SELECTION UI --- // Container for kick sound selection var kickSoundContainer = new Container(); kickSoundContainer.x = 2048 / 2; kickSoundContainer.y = 1400; settingsmenu.addChild(kickSoundContainer); // Kick sound label var kickSoundLabelTxt = new Text2("KICK SOUND", { size: 24, fill: 0xffffff }); kickSoundLabelTxt.scale.set(2, 2); kickSoundLabelTxt.anchor.set(0.5, 0.5); kickSoundLabelTxt.x = 0; kickSoundLabelTxt.y = -120; kickSoundContainer.addChild(kickSoundLabelTxt); // Add settingsbar background behind kick buttons var kickSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); kickSoundContainer.addChild(kickSettingsBar); // Create kick sound buttons var kickSounds = ['kick', 'kick2', 'kick3', 'kick4']; var kickButtons = []; var buttonSpacing = 220; var startX = -330; for (var i = 0; i < kickSounds.length; i++) { var kickButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: startX + i * buttonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedKickSound === kickSounds[i] ? 1 : 0.5 }); kickSoundContainer.addChild(kickButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = startX + i * buttonSpacing; numberTxt.y = 0; kickSoundContainer.addChild(numberTxt); kickButton.kickSoundId = kickSounds[i]; kickButton.buttonIndex = i; kickButtons.push(kickButton); // Button click handler kickButton.down = function (x, y, obj) { // If this is already selected kick sound and any other sound is selected, deselect all kick sounds if (selectedKickSound === this.kickSoundId && (selectedHihatSound !== '' || selectedSnareClapSound !== '' || selectedPercussionSound !== '')) { // Play the selected kick sound LK.getSound(this.kickSoundId).play(); // Deselect all kick sounds selectedKickSound = ''; storage.selectedKickSound = selectedKickSound; // Update button appearances - all kick buttons become inactive for (var j = 0; j < kickButtons.length; j++) { kickButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedKickSound === this.kickSoundId && selectedHihatSound === '' && selectedSnareClapSound === '' && selectedPercussionSound === '') { // Play the selected kick sound LK.getSound(this.kickSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected kick sound LK.getSound(this.kickSoundId).play(); // Update selected kick sound selectedKickSound = this.kickSoundId; storage.selectedKickSound = selectedKickSound; // Update button appearances for (var j = 0; j < kickButtons.length; j++) { kickButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(kickButton); } // --- HIHAT SOUND SELECTION UI --- // Container for hihat sound selection var hihatSoundContainer = new Container(); hihatSoundContainer.x = 2048 / 2; hihatSoundContainer.y = 1700; settingsmenu.addChild(hihatSoundContainer); // Hihat sound label var hihatSoundLabelTxt = new Text2("HI HATS", { size: 24, fill: 0xffffff }); hihatSoundLabelTxt.scale.set(2, 2); hihatSoundLabelTxt.anchor.set(0.5, 0.5); hihatSoundLabelTxt.x = 0; hihatSoundLabelTxt.y = -120; hihatSoundContainer.addChild(hihatSoundLabelTxt); // Add settingsbar background behind hihat buttons var hihatSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); hihatSoundContainer.addChild(hihatSettingsBar); // Create hihat sound buttons var hihatSounds = ['hihat', 'hihat2', 'hihat3', 'hihat4']; var hihatButtons = []; var hihatButtonSpacing = 220; var hihatStartX = -330; for (var i = 0; i < hihatSounds.length; i++) { var hihatButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: hihatStartX + i * hihatButtonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedHihatSound === hihatSounds[i] ? 1 : 0.5 }); hihatSoundContainer.addChild(hihatButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = hihatStartX + i * hihatButtonSpacing; numberTxt.y = 0; hihatSoundContainer.addChild(numberTxt); hihatButton.hihatSoundId = hihatSounds[i]; hihatButton.buttonIndex = i; hihatButtons.push(hihatButton); // Button click handler hihatButton.down = function (x, y, obj) { // If this is already selected hihat sound and any other sound is selected, deselect all hihat sounds if (selectedHihatSound === this.hihatSoundId && (selectedKickSound !== '' || selectedSnareClapSound !== '' || selectedPercussionSound !== '')) { // Play the selected hihat sound LK.getSound(this.hihatSoundId).play(); // Deselect all hihat sounds selectedHihatSound = ''; storage.selectedHihatSound = selectedHihatSound; // Update button appearances - all hihat buttons become inactive for (var j = 0; j < hihatButtons.length; j++) { hihatButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedHihatSound === this.hihatSoundId && selectedKickSound === '' && selectedSnareClapSound === '' && selectedPercussionSound === '') { // Play the selected hihat sound LK.getSound(this.hihatSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected hihat sound LK.getSound(this.hihatSoundId).play(); // Update selected hihat sound selectedHihatSound = this.hihatSoundId; storage.selectedHihatSound = selectedHihatSound; // Update button appearances for (var j = 0; j < hihatButtons.length; j++) { hihatButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(hihatButton); } // --- SNARE/CLAP SOUND SELECTION UI --- // Container for snare/clap sound selection var snareClapSoundContainer = new Container(); snareClapSoundContainer.x = 2048 / 2; snareClapSoundContainer.y = 2000; settingsmenu.addChild(snareClapSoundContainer); // Snare/clap sound label var snareClapSoundLabelTxt = new Text2("SNARE/CLAP", { size: 24, fill: 0xffffff }); snareClapSoundLabelTxt.scale.set(2, 2); snareClapSoundLabelTxt.anchor.set(0.5, 0.5); snareClapSoundLabelTxt.x = 0; snareClapSoundLabelTxt.y = -120; snareClapSoundContainer.addChild(snareClapSoundLabelTxt); // Add settingsbar background behind snare/clap buttons var snareClapSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); snareClapSoundContainer.addChild(snareClapSettingsBar); // Create snare/clap sound buttons var snareClapSounds = ['snareclap', 'snareclap2', 'snareclap3', 'snareclap4']; var snareClapButtons = []; var snareClapButtonSpacing = 220; var snareClapStartX = -330; for (var i = 0; i < snareClapSounds.length; i++) { var snareClapButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: snareClapStartX + i * snareClapButtonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedSnareClapSound === snareClapSounds[i] ? 1 : 0.5 }); snareClapSoundContainer.addChild(snareClapButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = snareClapStartX + i * snareClapButtonSpacing; numberTxt.y = 0; snareClapSoundContainer.addChild(numberTxt); snareClapButton.snareClapSoundId = snareClapSounds[i]; snareClapButton.buttonIndex = i; snareClapButtons.push(snareClapButton); // Button click handler snareClapButton.down = function (x, y, obj) { // If this is already selected snare/clap sound and any other sound is selected, deselect all snare/clap sounds if (selectedSnareClapSound === this.snareClapSoundId && (selectedKickSound !== '' || selectedHihatSound !== '' || selectedPercussionSound !== '')) { // Play the selected snare/clap sound LK.getSound(this.snareClapSoundId).play(); // Deselect all snare/clap sounds selectedSnareClapSound = ''; storage.selectedSnareClapSound = selectedSnareClapSound; // Update button appearances - all snare/clap buttons become inactive for (var j = 0; j < snareClapButtons.length; j++) { snareClapButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedSnareClapSound === this.snareClapSoundId && selectedKickSound === '' && selectedHihatSound === '' && selectedPercussionSound === '') { // Play the selected snare/clap sound LK.getSound(this.snareClapSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected snare/clap sound LK.getSound(this.snareClapSoundId).play(); // Update selected snare/clap sound selectedSnareClapSound = this.snareClapSoundId; storage.selectedSnareClapSound = selectedSnareClapSound; // Update button appearances for (var j = 0; j < snareClapButtons.length; j++) { snareClapButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(snareClapButton); } // --- PERCUSSION SOUND SELECTION UI --- // Container for percussion sound selection var percussionSoundContainer = new Container(); percussionSoundContainer.x = 2048 / 2; percussionSoundContainer.y = 2300; settingsmenu.addChild(percussionSoundContainer); // Percussion sound label var percussionSoundLabelTxt = new Text2("PERCUSSION", { size: 24, fill: 0xffffff }); percussionSoundLabelTxt.scale.set(2, 2); percussionSoundLabelTxt.anchor.set(0.5, 0.5); percussionSoundLabelTxt.x = 0; percussionSoundLabelTxt.y = -120; percussionSoundContainer.addChild(percussionSoundLabelTxt); // Add settingsbar background behind percussion buttons var percussionSettingsBar = LK.getAsset('settingsbar', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.2, scaleY: 1.2, alpha: 0.8 }); percussionSoundContainer.addChild(percussionSettingsBar); // Create percussion sound buttons var percussionSounds = ['percussion', 'percussion2', 'percussion3', 'percussion4']; var percussionButtons = []; var percussionButtonSpacing = 220; var percussionStartX = -330; for (var i = 0; i < percussionSounds.length; i++) { var percussionButton = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: percussionStartX + i * percussionButtonSpacing, y: 0, scaleX: 0.8, scaleY: 0.8, alpha: selectedPercussionSound === percussionSounds[i] ? 1 : 0.5 }); percussionSoundContainer.addChild(percussionButton); // Add number text to button var numberTxt = new Text2(i + 1 + "", { size: 36, fill: 0xffffff }); numberTxt.scale.set(2, 2); numberTxt.anchor.set(0.5, 0.5); numberTxt.x = percussionStartX + i * percussionButtonSpacing; numberTxt.y = 0; percussionSoundContainer.addChild(numberTxt); percussionButton.percussionSoundId = percussionSounds[i]; percussionButton.buttonIndex = i; percussionButtons.push(percussionButton); // Button click handler percussionButton.down = function (x, y, obj) { // If this is already selected percussion sound and any other sound is selected, deselect all percussion sounds if (selectedPercussionSound === this.percussionSoundId && (selectedKickSound !== '' || selectedHihatSound !== '' || selectedSnareClapSound !== '')) { // Play the selected percussion sound LK.getSound(this.percussionSoundId).play(); // Deselect all percussion sounds selectedPercussionSound = ''; storage.selectedPercussionSound = selectedPercussionSound; // Update button appearances - all percussion buttons become inactive for (var j = 0; j < percussionButtons.length; j++) { percussionButtons[j].alpha = 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); return; } // If this is already selected and it's the only one selected, don't allow deselection if (selectedPercussionSound === this.percussionSoundId && selectedKickSound === '' && selectedHihatSound === '' && selectedSnareClapSound === '') { // Play the selected percussion sound LK.getSound(this.percussionSoundId).play(); return; // Don't deselect if it would leave no sound selected } // Play the selected percussion sound LK.getSound(this.percussionSoundId).play(); // Update selected percussion sound selectedPercussionSound = this.percussionSoundId; storage.selectedPercussionSound = selectedPercussionSound; // Update button appearances for (var j = 0; j < percussionButtons.length; j++) { percussionButtons[j].alpha = j === this.buttonIndex ? 1 : 0.5; } // Animate button tween(this, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function () { tween(this, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); }.bind(this) }); }.bind(percussionButton); } // --- SOUND VOLUME CIRCLE UI --- // Container for the whole sound volume control var volumeCircleContainer = new Container(); volumeCircleContainer.x = 2048 / 2 + 300; volumeCircleContainer.y = 1100; settingsmenu.addChild(volumeCircleContainer); // Circle background gameState.bpmCircleBg = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.1, scaleY: 1.1, alpha: 0.85 }); gameState.bpmCircleContainer.addChild(gameState.bpmCircleBg); // BPM value text gameState.bpmValueTxt = new Text2(gameState.bpm + "", { size: 54, fill: 0xffffff }); gameState.bpmValueTxt.scale.set(2, 2); gameState.bpmValueTxt.anchor.set(0.5, 0.5); gameState.bpmValueTxt.x = 0; gameState.bpmValueTxt.y = 0; gameState.bpmCircleContainer.addChild(gameState.bpmValueTxt); // BPM label text gameState.bpmLabelTxt = new Text2("BPM", { size: 24, fill: 0xffffff }); gameState.bpmLabelTxt.scale.set(2, 2); gameState.bpmLabelTxt.anchor.set(0.5, 0.5); gameState.bpmLabelTxt.x = 0; gameState.bpmLabelTxt.y = 90; gameState.bpmCircleContainer.addChild(gameState.bpmLabelTxt); // Volume circle background var volumeCircleBg = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.1, scaleY: 1.1, alpha: 0.85 }); volumeCircleContainer.addChild(volumeCircleBg); // Volume value text var volumeValueTxt = new Text2((typeof storage.soundVolume === "number" ? storage.soundVolume : 100) + "", { size: 54, fill: 0xffffff }); volumeValueTxt.scale.set(2, 2); volumeValueTxt.anchor.set(0.5, 0.5); volumeValueTxt.x = 0; volumeValueTxt.y = 0; volumeCircleContainer.addChild(volumeValueTxt); // Volume label text var volumeLabelTxt = new Text2("SOUND", { size: 24, fill: 0xffffff }); volumeLabelTxt.scale.set(2, 2); volumeLabelTxt.anchor.set(0.5, 0.5); volumeLabelTxt.x = 0; volumeLabelTxt.y = 90; volumeCircleContainer.addChild(volumeLabelTxt); // --- Drag-to-change BPM logic --- var dragActive = false; var dragStartY = 0; var dragStartBpm = 0; var lastDragStep = 0; var BPM_MIN = 40; var BPM_MAX = 120; var BPM_STEP = 5; var PIXELS_PER_STEP = 20; // Touch start (down) on circle gameState.bpmCircleContainer.down = function (x, y, obj) { dragActive = true; dragStartY = y; dragStartBpm = gameState.bpm; lastDragStep = 0; // Animate circle slightly for feedback tween(gameState.bpmCircleBg, { scaleX: 1.18, scaleY: 1.18 }, { duration: 100, yoyo: true, repeat: 1 }); }; // Touch move on circle gameState.bpmCircleContainer.move = function (x, y, obj) { if (!dragActive) { return; } var dy = dragStartY - y; // Up is positive var step = Math.floor(dy / PIXELS_PER_STEP); if (step !== lastDragStep) { var newBpm = dragStartBpm + step * BPM_STEP; if (newBpm < BPM_MIN) { newBpm = BPM_MIN; } if (newBpm > BPM_MAX) { newBpm = BPM_MAX; } if (newBpm !== gameState.bpm) { gameState.bpm = newBpm; gameState.bpmValueTxt.setText(gameState.bpm + ""); beatInterval = 60 / gameState.bpm; // Recalculate beat interval for new BPM // Regenerate beat pattern with new BPM intervals beatPattern = generateBeatPattern(gameState.bpm); nextBeatIdx = 0; // Reset beat spawning storage.bpm = gameState.bpm; // persist gameState.bpm to storage // Play scratch sound when BPM changes during settings LK.getSound('scratch').play(); if (gameState.bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = gameState.bpm; // This is the actual BPM being used for gameplay gameState.bpmDisplayTxt.setText(currentPlayingBpm + ' BPM'); } } lastDragStep = step; } }; // Touch end (up) on circle gameState.bpmCircleContainer.up = function (x, y, obj) { dragActive = false; // Animate back to normal scale tween(gameState.bpmCircleBg, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100 }); }; // --- Volume drag logic --- var volumeDragActive = false; var volumeDragStartY = 0; var volumeDragStartValue = 0; var volumeLastDragStep = 0; var VOLUME_MIN = 0; var VOLUME_MAX = 100; var VOLUME_STEP = 5; var VOLUME_PIXELS_PER_STEP = 20; // Touch start (down) on volume circle volumeCircleContainer.down = function (x, y, obj) { volumeDragActive = true; volumeDragStartY = y; volumeDragStartValue = soundVolume; volumeLastDragStep = 0; // Animate circle slightly for feedback tween(volumeCircleBg, { scaleX: 1.18, scaleY: 1.18 }, { duration: 100, yoyo: true, repeat: 1 }); }; // Touch move on volume circle volumeCircleContainer.move = function (x, y, obj) { if (!volumeDragActive) { return; } var dy = volumeDragStartY - y; // Up is positive var step = Math.floor(dy / VOLUME_PIXELS_PER_STEP); if (step !== volumeLastDragStep) { var newVolume = volumeDragStartValue + step * VOLUME_STEP; if (newVolume < VOLUME_MIN) { newVolume = VOLUME_MIN; } if (newVolume > VOLUME_MAX) { newVolume = VOLUME_MAX; } if (newVolume !== soundVolume) { soundVolume = newVolume; volumeValueTxt.setText(soundVolume + ""); storage.soundVolume = soundVolume; // persist volume to storage updateAllSoundVolumes(); // Update all sound volumes immediately // Update current playing music volume immediately var currentVolumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * currentVolumeMultiplier, loop: true }); } volumeLastDragStep = step; } }; // Touch end (up) on volume circle volumeCircleContainer.up = function (x, y, obj) { volumeDragActive = false; // Animate back to normal scale tween(volumeCircleBg, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100 }); }; // Close button (bottom right) var closeBtn = LK.getAsset('closebutton', { anchorX: 1, anchorY: 1, x: 2048 - 60, y: 2732 - 60, scaleX: 3, scaleY: 3 }); settingsmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(closeBtn, { scaleX: 0.9, scaleY: 0.9 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(closeBtn, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } }); settingsmenu.visible = false; mainMenuContainer.visible = true; }; // Remove tap-to-close on background settingsmenu.down = undefined; settingsmenu.visible = true; } // Button event handlers // Countdown logic var countdownContainer = new Container(); countdownContainer.visible = false; game.addChild(countdownContainer); // Pre-game ready screen container var readyContainer = new Container(); readyContainer.visible = false; game.addChild(readyContainer); function showStoryIntro(onComplete) { storyContainer.visible = true; storyContainer.removeChildren(); // Dark background covering full screen var storyBg = LK.getAsset('defeatbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, tint: 0x000000 }); storyContainer.addChild(storyBg); // Play chapter1music during storytelling var volumeMultiplier = soundVolume / 100; LK.playMusic('chapter1music', { volume: 0.2 * volumeMultiplier, loop: true }); // Skip button in bottom right corner var skipBtn = new Text2("SKIP", { size: 36, fill: 0xffffff }); skipBtn.scale.set(2, 2); skipBtn.anchor.set(1, 1); skipBtn.x = 2048 - 50; skipBtn.y = 2732 - 50; storyContainer.addChild(skipBtn); // Skip button click handler skipBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); // Click animation tween(skipBtn, { scaleX: 3.6, scaleY: 3.6 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(skipBtn, { scaleX: 4, scaleY: 4 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { // Keep storyContainer visible to continue story narration // storyContainer.visible = false; // storyContainer.removeChildren(); if (typeof onComplete === "function") { onComplete(); } } }); } }); }; // Chapter 1 asset with fade in effect var chapter1Asset = LK.getAsset('Chapter1', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100, alpha: 0, scaleX: 0.4, scaleY: 0.4 }); storyContainer.addChild(chapter1Asset); // Fade in effect for chapter1 asset with 0.5 second delay tween(chapter1Asset, { alpha: 1 }, { duration: 2000, delay: 500, easing: tween.easeOut }); // Echoes of the Past asset with fade in effect var echoesofthepastAsset = LK.getAsset('echoesofthepast', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 40, alpha: 0, scaleX: 0.3, scaleY: 0.3 }); storyContainer.addChild(echoesofthepastAsset); // Fade in effect for echoesofthepast asset with 0.8 second delay tween(echoesofthepastAsset, { alpha: 1 }, { duration: 2000, delay: 800, easing: tween.easeOut }); // Calculate sound duration (end - start) and fade out text when sound ends var soundDuration = (0.976 - 0.05) * 1000; // Convert to milliseconds var fadeoutDuration = 200; // 0.2 second fadeout // Play echoesofthepast sound synchronized with asset fade-in timing LK.setTimeout(function () { var echoesSound = LK.getSound('echoesofthepast'); echoesSound.play(); // Start fadeout 0.2 seconds before sound ends LK.setTimeout(function () { tween(echoesSound, { volume: 0 }, { duration: fadeoutDuration, easing: tween.easeOut }); }, soundDuration - fadeoutDuration); }, 500); // Start sound when chapter1 asset starts fading in for perfect sync // Remove assets 0.8 seconds after sound finishes (extended by 0.4 seconds) LK.setTimeout(function () { chapter1Asset.destroy(); echoesofthepastAsset.destroy(); // Add white stroke background for ch1pan1 asset var ch1pan1Stroke = LK.getAsset('ch1pan1', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, rotation: 15 * Math.PI / 180, // Convert 15 degrees to radians alpha: 0, scaleX: 0.87, scaleY: 0.87, tint: 0xffffff }); storyContainer.addChild(ch1pan1Stroke); // Add ch1pan1 asset to center screen with 15-degree rotation, starting from left side var ch1pan1Asset = LK.getAsset('ch1pan1', { anchorX: 0.5, anchorY: 0.5, x: -400, y: 2732 / 2, rotation: 15 * Math.PI / 180, // Convert 15 degrees to radians alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan1Asset); // Fade in the stroke background first tween(ch1pan1Stroke, { alpha: 1 }, { duration: 1000, easing: tween.easeOut }); // Play paperswosh sound when ch1pan1 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan1 asset from left to center with sliding motion effect tween(ch1pan1Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); // Track current panel being shown var currentPanel = 1; // Add touch handler to advance story storyContainer.down = function (x, y, obj) { // Check if chapter1 asset still exists - if so, don't show panel assets var chapter1Exists = false; for (var i = 0; i < storyContainer.children.length; i++) { var child = storyContainer.children[i]; if (child && child.texture && child.texture.baseTexture && child.texture.baseTexture.source && child.texture.baseTexture.source.src && child.texture.baseTexture.source.src.indexOf('Chapter1') !== -1) { chapter1Exists = true; break; } } // If chapter1 asset still exists, don't proceed with panel assets if (chapter1Exists) { return; } if (currentPanel === 1) { // Add ch1pan2 asset starting from right side with 2 degree left rotation var ch1pan2Asset = LK.getAsset('ch1pan2', { anchorX: 0.5, anchorY: 0.5, x: 2048 + 400, // Start from right side off-screen y: 2732 / 2, rotation: -2 * Math.PI / 180, // 2 degrees to the left (negative) alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan2Asset); // Play paperswosh sound when ch1pan2 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan2 asset from right to left tween(ch1pan2Asset, { alpha: 1, x: 2048 / 2 // Move to center }, { duration: 1000, easing: tween.easeOut }); currentPanel = 2; } else if (currentPanel === 2) { // Add ch1pan3 with random rotation (left or right) var randomRotation = (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2); // Random 2-7 degrees left or right var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var ch1pan3Asset = LK.getAsset('ch1pan3', { anchorX: 0.5, anchorY: 0.5, x: slideFromRight ? 2048 + 400 : -400, y: 2732 / 2, rotation: randomRotation * Math.PI / 180, alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan3Asset); // Play paperswosh sound when ch1pan3 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan3 asset to center tween(ch1pan3Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); currentPanel = 3; } else if (currentPanel === 3) { // Add ch1pan4 with random rotation (left or right) var randomRotation = (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2); // Random 2-7 degrees left or right var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var ch1pan4Asset = LK.getAsset('ch1pan4', { anchorX: 0.5, anchorY: 0.5, x: slideFromRight ? 2048 + 400 : -400, y: 2732 / 2, rotation: randomRotation * Math.PI / 180, alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan4Asset); // Play paperswosh sound when ch1pan4 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan4 asset to center tween(ch1pan4Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); currentPanel = 4; } else if (currentPanel === 4) { // Add ch1pan5 with random rotation (left or right) var randomRotation = (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2); // Random 2-7 degrees left or right var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var ch1pan5Asset = LK.getAsset('ch1pan5', { anchorX: 0.5, anchorY: 0.5, x: slideFromRight ? 2048 + 400 : -400, y: 2732 / 2, rotation: randomRotation * Math.PI / 180, alpha: 0, scaleX: 0.85, scaleY: 0.85 }); storyContainer.addChild(ch1pan5Asset); // Play paperswosh sound when ch1pan5 starts sliding in LK.getSound('paperswosh').play(); // Fade in and slide ch1pan5 asset to center tween(ch1pan5Asset, { alpha: 1, x: 2048 / 2 }, { duration: 1000, easing: tween.easeOut }); currentPanel = 5; } else if (currentPanel === 5) { // Fade out all panel assets when touched after ch1pan5 var allPanelAssets = []; // Collect all panel assets from storyContainer for (var i = 0; i < storyContainer.children.length; i++) { var child = storyContainer.children[i]; // Check if it's a panel asset by checking if it has panel-like properties if (child && child.anchor && child.x !== undefined && child.y !== undefined && child.alpha !== undefined) { // Skip background and skip button if (child !== storyBg && child !== skipBtn) { allPanelAssets.push(child); } } } // Fade out all panel assets simultaneously for (var i = 0; i < allPanelAssets.length; i++) { tween(allPanelAssets[i], { alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function () { // Destroy the asset after fadeout if (this.parent) { this.parent.removeChild(this); } this.destroy(); }.bind(allPanelAssets[i]) }); } // After all panels are faded out, fade out the black background and transition to tutorial tween(storyBg, { alpha: 0 }, { duration: 200, delay: 700, easing: tween.easeOut, onFinish: function onFinish() { // Remove skip button if (skipBtn && skipBtn.parent) { skipBtn.parent.removeChild(skipBtn); } skipBtn.destroy(); // Hide story container and show tutorial/game screen storyContainer.visible = false; storyContainer.removeChildren(); // Call the onComplete callback to proceed to tutorial if (typeof onComplete === "function") { onComplete(); } } }); // Remove touch handler after fadeout starts storyContainer.down = undefined; currentPanel = 6; } }; // Keep storyContainer visible to continue story narration // storyContainer.visible = false; // storyContainer.removeChildren(); if (typeof onComplete === "function") { onComplete(); } ch1pan1Stroke.destroy(); }, 500 + soundDuration + 800); // Remove assets 0.8 seconds after sound ends // For now, skip button is the only way to proceed // Skip button can still be used to proceed early } function showReadyScreen(onReady) { gamescreen.visible = true; readyContainer.visible = true; readyContainer.removeChildren(); var arrowTap = LK.getAsset('arrowtap', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X - 150, y: HIT_ZONE_Y + 250, scaleX: 3, scaleY: 3 }); readyContainer.addChild(arrowTap); // Show ready button var readyBtn = LK.getAsset('readybutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 350, scaleX: 6, scaleY: 6 }); readyContainer.addChild(readyBtn); // Add readyinfo asset on top of readybutton var readyInfo = LK.getAsset('readyinfo', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 400, scaleX: 1.1, scaleY: 1.1 }); readyContainer.addChild(readyInfo); // Track if black screen is gone var blackScreenGone = false; // Check if black screen has been removed by listening for story completion var _checkStoryComplete = function checkStoryComplete() { // If storyContainer is not visible, black screen has been removed if (!storyContainer.visible) { blackScreenGone = true; readyBtn.alpha = 1; // Enable ready button } else { // Schedule another check LK.setTimeout(_checkStoryComplete, 100); } }; // Start checking for story completion LK.setTimeout(_checkStoryComplete, 100); // Initially disable ready button readyBtn.alpha = 0.5; // Ready button click handler readyBtn.down = function (x, y, obj) { // Don't allow button press if black screen is still visible if (!blackScreenGone) { return; } // Play button click sound LK.getSound('buttonclick').play(); // Click animation tween(readyBtn, { scaleX: 7.2, scaleY: 7.2 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(readyBtn, { scaleX: 8, scaleY: 8 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { readyContainer.visible = false; readyContainer.removeChildren(); if (typeof onReady === "function") { onReady(); } } }); } }); }; } function showCountdown(onFinish) { gamescreen.visible = true; countdownContainer.visible = true; countdownContainer.removeChildren(); var numbers = ["3", "2", "1"]; var idx = 0; function showNext() { if (idx >= numbers.length) { countdownContainer.visible = false; countdownContainer.removeChildren(); // Always show gamescreen after countdown (fix blackscreen bug) gamescreen.visible = true; if (typeof onFinish === "function") { onFinish(); } return; } countdownContainer.removeChildren(); var txt = new Text2(numbers[idx], { size: 600, fill: 0xffd700 }); txt.scale.set(160, 160); txt.anchor.set(0.5, 0.5); txt.x = 2048 / 2; txt.y = 1200; txt.alpha = 0; txt.scale.x = 0.6; txt.scale.y = 0.6; countdownContainer.addChild(txt); // Animate in tween(txt, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 250, easing: tween.cubicOut, onFinish: function onFinish() { // Hold, then animate out tween(txt, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 350, delay: 400, easing: tween.cubicIn, onFinish: function onFinish() { idx++; showNext(); } }); } }); } idx = 0; showNext(); } startBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Stop menu music immediately LK.stopMusic(); // Click animation - scale inward then outward tween(startBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(startBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showStoryIntro(function () { showReadyScreen(function () { showCountdown(function () { startGame({ mode: "story" }); }); }); }); } }); } }); }; settingsBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(settingsBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(settingsBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showSettingsMenu(); } }); } }); }; creditsBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(creditsBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(creditsBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showCreditsMenu(); } }); } }); }; supportBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation - scale inward then outward tween(supportBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(supportBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showSupportMenu(); } }); } }); }; // Music mute button in bottom corner of main menu var musicMuted = typeof storage.musicMuted === "boolean" ? storage.musicMuted : false; var musicMuteBtn = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 150, y: 2732 - 150, scaleX: 0.8, scaleY: 0.8, alpha: musicMuted ? 0.5 : 1 }); mainMenuContainer.addChild(musicMuteBtn); // Music icon text on button var musicIconTxt = new Text2(musicMuted ? "M" : "♪", { size: 48, fill: 0xffffff }); musicIconTxt.scale.set(2, 2); musicIconTxt.anchor.set(0.5, 0.5); musicIconTxt.x = 2048 - 150; musicIconTxt.y = 2732 - 150; mainMenuContainer.addChild(musicIconTxt); // Music mute button handler musicMuteBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Toggle mute state musicMuted = !musicMuted; storage.musicMuted = musicMuted; // Update button appearance musicMuteBtn.alpha = musicMuted ? 0.5 : 1; musicIconTxt.setText(musicMuted ? "M" : "♪"); // Handle music playback if (musicMuted) { LK.stopMusic(); } else { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); } // Click animation tween(musicMuteBtn, { scaleX: 0.72, scaleY: 0.72 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(musicMuteBtn, { scaleX: 0.8, scaleY: 0.8 }, { duration: 120, easing: tween.easeOut }); } }); }; // Story intro screen container var storyContainer = new Container(); storyContainer.visible = false; game.addChild(storyContainer); // Add menu to game game.addChild(mainMenuContainer); // menus are already added above // Hide all gameplay UI until game starts gameState.scoreTxt.visible = false; gameState.comboTxt.visible = false; gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; // missTxt removed, only hearts for gameState.lives // timelineBar and kickTarget are now children of gamescreen, so their visibility is managed by gamescreen // Patch startGame to show gameplay UI and gamescreen var _originalStartGame = startGame; startGame = function startGame() { gameState.scoreTxt.visible = true; gameState.comboTxt.visible = true; gameState.bpmDisplayTxt.visible = true; songNameTxt.visible = true; if (gameState.bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = isTutorial ? 60 : gameState.bpm; // Tutorial always shows 60 BPM gameState.bpmDisplayTxt.setText('60 BPM'); } // missTxt removed, only hearts for gameState.lives gamescreen.visible = true; mainMenuContainer.visible = false; // overlayContainer.visible = false; // Removed, not defined _originalStartGame(); }; // On menu, hide gameplay UI and gamescreen mainMenuContainer.visible = true; gameState.scoreTxt.visible = false; gameState.comboTxt.visible = false; gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Play menu music when in main menu (only if not muted) if (!musicMuted) { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); } // Apply current sound volume settings to all sounds updateAllSoundVolumes(); // Pause button functionality pauseBtn.down = function (x, y, obj) { if (!gameState.gameActive || !gamescreen.visible) { return; } // Play button click sound LK.getSound('buttonclick').play(); // Pause the game gameState.gamePaused = true; showPauseMenu(); // No animation - button scale remains fixed at 1.6 }; // Show pause menu function function showPauseMenu() { pauseContainer.visible = true; pauseContainer.removeChildren(); // Hide BPM and song name in pause menu gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; // Pause menu background var pauseMenuBg = LK.getAsset('pausemenu', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); pauseContainer.addChild(pauseMenuBg); // Button positions var pauseButtonStartY = 1200; var pauseButtonHeight = 289; var pauseButtonSpacing = 20; // Resume button var resumeBtn = LK.getAsset('resumebutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: pauseButtonStartY, scaleX: 5, scaleY: 5 }); pauseContainer.addChild(resumeBtn); // Restart button var restartBtn = LK.getAsset('restartbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: pauseButtonStartY + pauseButtonHeight + pauseButtonSpacing, scaleX: 5, scaleY: 5 }); pauseContainer.addChild(restartBtn); // Main menu button var pauseMainMenuBtn = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: pauseButtonStartY + 2 * (pauseButtonHeight + pauseButtonSpacing), scaleX: 5, scaleY: 5 }); pauseContainer.addChild(pauseMainMenuBtn); // Resume button handler resumeBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); tween(resumeBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(resumeBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { resumeGame(); } }); } }); }; // Restart button handler restartBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); tween(restartBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(restartBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { pauseContainer.visible = false; gameState.gamePaused = false; gameState.gameActive = false; // Stop game temporarily during restart gameState.maxCombo = 0; gameState.perfectCount = 0; gameState.maxPerfectCombo = 0; // Reset gameState.lives and hearts before showing ready screen gameState.lives = MAX_LIVES; gameState.heartshields = 3; // Reset gameState.heartshields // Continue from current level - if tutorial not completed, restart tutorial if (!tutorialCompleted) { gameState.currentChapter = 1; storage.currentChapter = gameState.currentChapter; } // Remove all existing heart icons for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } // Recreate all heart icons as full gameState.heartshields heartIcons = []; var heartY = gameState.scoreTxt.y + 50; var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; for (var i = 0; i < MAX_LIVES; i++) { var heartAsset = LK.getAsset('heartshield', { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } showReadyScreen(function () { showCountdown(function () { startGame(); }); }); } }); } }); }; // Pause main menu button handler pauseMainMenuBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); tween(pauseMainMenuBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(pauseMainMenuBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { pauseContainer.visible = false; gameState.gamePaused = false; gameState.gameActive = false; gameState.maxCombo = 0; gameState.perfectCount = 0; gameState.maxPerfectCombo = 0; mainMenuContainer.visible = true; gameState.scoreTxt.visible = false; gameState.comboTxt.visible = false; gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Stop current music and play menu music (only if not muted) LK.stopMusic(); if (!musicMuted) { LK.setTimeout(function () { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); }, 100); } } }); } }); }; } // Resume game function function resumeGame() { gameState.gamePaused = false; pauseContainer.visible = false; // Show BPM and song name when resuming gameState.bpmDisplayTxt.visible = true; songNameTxt.visible = true; lastTickTime = Date.now(); // Reset time to prevent time jump } // --- Beat spawning --- function spawnBeat(beat) { var marker = new BeatMarker(); marker.x = 0; marker.y = HIT_ZONE_Y; marker.beatTime = beat.time; marker.type = beat.type; marker.active = true; marker.hit = false; marker.lastX = marker.x; marker.hasReachedHitZone = false; // Trail sistemi ekle marker.lastTrailTime = Date.now(); marker.trailInterval = 15; marker.assetType = marker.type; marker.updateTrail = function () { var now = Date.now(); if (now - this.lastTrailTime > this.trailInterval) { var colors = BEAT_COLORS[this.type] || BEAT_COLORS.circle; // Beat'in hareket yönünü belirle (sağ tarafta mı?) var isRightSide = this.x > HIT_ZONE_X; var directionMultiplier = isRightSide ? 1 : -1; for (var i = 0; i < 10; i++) { // Ana parçacık var mainParticle = createTrailParticle(this.x + (Math.random() - 0.5) * 20, this.y + (Math.random() - 0.5) * 20, colors.mainTrail); mainParticle.velocityX = (Math.random() * 2 + 1) * directionMultiplier; mainParticle.velocityY = (Math.random() - 0.5) * 3; // Yan parçacık 1 var subParticle1 = createTrailParticle(this.x + (Math.random() - 0.5) * 30, this.y + (Math.random() - 0.5) * 30, colors.subTrail1); subParticle1.width = 4; subParticle1.height = 4; subParticle1.velocityX = (Math.random() * 1.5 + 0.5) * directionMultiplier; subParticle1.velocityY = (Math.random() - 0.5) * 2; // Yan parçacık 2 var subParticle2 = createTrailParticle(this.x + (Math.random() - 0.5) * 25, this.y + (Math.random() - 0.5) * 25, colors.subTrail2); subParticle2.width = 3; subParticle2.height = 3; subParticle2.velocityX = (Math.random() * 1 + 0.5) * directionMultiplier; subParticle2.velocityY = (Math.random() - 0.5) * 1.5; // Parçacıkları oyun ekranına ekle [mainParticle, subParticle1, subParticle2].forEach(function (p) { p.fadeSpeed = 0.03; p.gravity = 0.1; p.drag = 0.98; gamescreen.addChild(p); trailParticles.push(p); }); } this.lastTrailTime = now; } }; // Add glow effect to the beat marker addGlowEffect(marker, 0x8a2be2, 0.4); beats.push(marker); gamescreen.addChild(marker); } // --- Game update --- var originalGameUpdate = game.update; game.update = function () { originalGameUpdate.call(this); // Endless modda skor milestone kontrolü ve BPM artışı if (gameMode === "endless" && gameState.score >= gameState.lastBpmUpgradeScore + 10000) { if (gameState.bpm < 120) { gameState.bpm += 5; if (gameState.bpm > 120) gameState.bpm = 120; beatInterval = 60 / gameState.bpm; beatPattern = generateBeatPattern(gameState.bpm); nextBeatIdx = 0; updateEndlessBpmAndMusic(); gameState.lastBpmUpgradeScore = gameState.score - gameState.score % 10000; // (İsteğe bağlı: BPM artış efekti veya animasyonu eklenebilir) } } // Trail sistemini güncelle updateTrailSystem(); // Tüm beatlerin trail'lerini güncelle for (var i = 0; i < beats.length; i++) { var beat = beats[i]; // Hem aktif hem de hit olmuş beatlerin trail'lerini güncelle if (beat.active && !beat.hit || beat.hit && beat.x > HIT_ZONE_X) { if (typeof beat.updateTrail === 'function') { beat.updateTrail(); } if (typeof beat.updateBeatPulse === 'function') { beat.updateBeatPulse(); } else { updateBeatPulse(beat); } } } }; // --- Input handling --- var isTouching = false; game.down = function (x, y, obj) { if (!gameState.gameActive || gameState.gamePaused) { return; } // Don't register hits during ready screen if (readyContainer.visible) { return; } // Don't register hits when gamescreen is not visible (e.g., in main menu) if (!gamescreen.visible) { return; } // Only register if touch is in hit zone var dx = x - HIT_ZONE_X; var dy = y - HIT_ZONE_Y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { isTouching = true; // Animate kick target kickTarget.destroy(); kickTarget = LK.getAsset('kickActive', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X, y: HIT_ZONE_Y }); gamescreen.addChild(kickTarget); // Check for beat hit var hit = false; for (var i = 0; i < beats.length; i++) { var beat = beats[i]; if (beat.active && !beat.hit && Math.abs(beat.x - HIT_ZONE_X) < HIT_WINDOW) { // Calculate timing accuracy var distFromCenter = Math.abs(beat.x - HIT_ZONE_X); var feedbackType = ''; var feedbackText = ''; var feedbackColor = 0xffffff; gameState.scoreAdd = 0; if (distFromCenter < 20) { feedbackType = 'perfect'; feedbackText = 'PERFECT!'; feedbackColor = 0xffe600; gameState.scoreAdd = 200; gameState.perfectCount++; // Increment perfect count perfectCombo++; // Increment consecutive perfect hits if (perfectCombo > gameState.maxPerfectCombo) { gameState.maxPerfectCombo = perfectCombo; // Track maximum consecutive perfect hits } // Intense screen shake for perfect hits, increasing with gameState.combo var perfectShakeIntensity = Math.min(15, 5 + perfectCombo * 0.5); // Max intensity of 15 var perfectShakeDuration = Math.min(400, 200 + perfectCombo * 10); // Max duration of 400ms shakeScreen(perfectShakeIntensity, perfectShakeDuration); // Perfect vuruş için parlak mor flash var flashAlpha = Math.min(0.4, 0.2 + perfectCombo * 0.01); // Combo arttıkça daha belirgin flashScreen(0x8a2be2, flashAlpha, perfectShakeDuration); } else if (distFromCenter < 40) { feedbackType = 'awesome'; feedbackText = 'AWESOME!'; feedbackColor = 0x00e6ff; gameState.scoreAdd = 150; perfectCombo = 0; // Reset perfect gameState.combo for non-perfect hit shakeScreen(4, 200); // Mild shake for awesome hits flashScreen(0x00e6ff, 0.15, 100); // Mavi flash efekti } else if (distFromCenter < 70) { feedbackType = 'good'; feedbackText = 'GOOD'; feedbackColor = 0x00ff00; gameState.scoreAdd = 100; perfectCombo = 0; // Reset perfect gameState.combo for non-perfect hit shakeScreen(3, 160); // Very mild shake for good hits flashScreen(0x00ff00, 0.1, 80); // Yeşil flash efekti } else { feedbackType = 'ok'; feedbackText = 'OK'; feedbackColor = 0xcccccc; gameState.scoreAdd = 50; perfectCombo = 0; // Reset perfect gameState.combo for non-perfect hit shakeScreen(2, 120); // Minimal shake for ok hits flashScreen(0xcccccc, 0.05, 60); // Hafif gri flash efekti } beat.hit = true; beat.active = false; hit = true; gameState.score += gameState.scoreAdd; gameState.combo += 1; // Trail renklerini güncelle updateBeatTrailOnHit(beat, feedbackType); if (gameState.combo > gameState.maxCombo) { gameState.maxCombo = gameState.combo; // Track maximum gameState.combo achieved } LK.setScore(gameState.score); gameState.scoreTxt.setText(gameState.score); gameState.comboTxt.setText('Combo: ' + gameState.combo); // Show feedback text at hit zone var feedbackTxt = new Text2(feedbackText, { size: 60, fill: feedbackColor }); feedbackTxt.scale.set(2, 2); feedbackTxt.anchor.set(0.5, 0.5); feedbackTxt.x = HIT_ZONE_X; feedbackTxt.y = HIT_ZONE_Y - 180; game.addChild(feedbackTxt); // Show gameState.combo multiplier for perfect hits if (feedbackType === 'perfect' && perfectCombo > 1) { gameState.comboMultiplierTxt = new Text2('x' + perfectCombo, { size: 21, fill: 0xff8c00 }); gameState.comboMultiplierTxt.scale.set(4, 4); gameState.comboMultiplierTxt.anchor.set(0, 0.5); gameState.comboMultiplierTxt.x = HIT_ZONE_X + 195; gameState.comboMultiplierTxt.y = HIT_ZONE_Y - 220; game.addChild(gameState.comboMultiplierTxt); tween(gameState.comboMultiplierTxt, { alpha: 0, y: gameState.comboMultiplierTxt.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { gameState.comboMultiplierTxt.destroy(); } }); } tween(feedbackTxt, { alpha: 0, y: feedbackTxt.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { feedbackTxt.destroy(); } }); beat.showFeedback('good'); // Play sounds based on selection - all selected sounds if (selectedKickSound !== '') { LK.getSound(selectedKickSound).play(); } if (selectedHihatSound !== '') { LK.getSound(selectedHihatSound).play(); } if (selectedSnareClapSound !== '') { LK.getSound(selectedSnareClapSound).play(); } if (selectedPercussionSound !== '') { LK.getSound(selectedPercussionSound).play(); } LK.effects.flashObject(kickTarget, feedbackColor, 200); // Replace the beat's current asset based on timing quality while maintaining position and movement var newAssetId; var tintColor = 0xffffff; if (feedbackType === 'perfect') { // Perfect hits: perfectcircle asset newAssetId = 'perfectcircle'; tintColor = 0xffffff; } else if (feedbackType === 'awesome' || feedbackType === 'good' || feedbackType === 'ok') { // Good/awesome/ok hits: green kickActive newAssetId = 'kickActive'; tintColor = 0x00ff00; } // Replace the beat's visual asset while maintaining its position and movement if (beat.parent) { // Store parent and current position var beatParent = beat.parent; var currentX = beat.x; var currentY = beat.y; // Remove the old beat marker from its parent beatParent.removeChild(beat); // Create new asset with current position var newBeatAsset = LK.getAsset(newAssetId, { anchorX: 0.5, anchorY: 0.5, x: currentX, y: currentY, tint: tintColor, width: 60, height: 59.77 }); // Add the new asset to the stored parent reference beatParent.addChild(newBeatAsset); // Update the beat reference to point to the new asset beats[i] = newBeatAsset; // Copy over important properties newBeatAsset.beatTime = beat.beatTime; newBeatAsset.type = beat.type; newBeatAsset.active = false; // Mark as inactive since it was hit newBeatAsset.hit = true; newBeatAsset.assetType = newAssetId; // Trail renkleri için asset tipini güncelle newBeatAsset.lastX = currentX; newBeatAsset.hasReachedHitZone = true; // Trail özelliklerini aktar newBeatAsset.lastTrailTime = Date.now(); newBeatAsset.trailInterval = 15; newBeatAsset.updateTrail = beat.updateTrail; updateBeatTrailOnHit(newBeatAsset, feedbackType); // Destroy the original beat asset beat.destroy(); } // BPM remains constant at settings value - no difficulty increase during gameplay break; } } if (!hit) { // Missed (tapped at wrong time) misses++; gameState.combo = 0; perfectCombo = 0; // Reset perfect gameState.combo on wrong time // missTxt removed, only hearts for gameState.lives gameState.comboTxt.setText(''); // Play sounds based on selection - all selected sounds if (selectedKickSound !== '') { LK.getSound(selectedKickSound).play(); } if (selectedHihatSound !== '') { LK.getSound(selectedHihatSound).play(); } if (selectedSnareClapSound !== '') { LK.getSound(selectedSnareClapSound).play(); } if (selectedPercussionSound !== '') { LK.getSound(selectedPercussionSound).play(); } LK.effects.flashObject(kickTarget, 0xff0000, 200); // Show WRONG TIME feedback text at hit zone var wrongTimeText = new Text2('WRONG TIME', { size: 60, fill: 0xff4500 }); wrongTimeText.scale.set(2, 2); wrongTimeText.anchor.set(0.5, 0.5); wrongTimeText.x = HIT_ZONE_X; wrongTimeText.y = HIT_ZONE_Y - 180; game.addChild(wrongTimeText); tween(wrongTimeText, { alpha: 0, y: wrongTimeText.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { wrongTimeText.destroy(); } }); // Only lose a life if countdown is not visible if (!countdownContainer.visible) { // Handle heartshield system for wrong time hits if (gameState.heartshields > 0) { // Convert leftmost heartshield to heart (gameState.heartshields are lost left to right) var shieldIndex = 3 - gameState.heartshields; // Get the leftmost heartshield index if (heartIcons[shieldIndex]) { var parent = heartIcons[shieldIndex].parent; if (parent) { parent.removeChild(heartIcons[shieldIndex]); } heartIcons[shieldIndex] = LK.getAsset('heart', { anchorX: 0, anchorY: 0, x: heartStartX + shieldIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[shieldIndex]); } gameState.heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All gameState.heartshields are gone, now lose hearts // Lose a life and update heart icon if (gameState.lives > 0) { var lostIndex = MAX_LIVES - gameState.lives; // soldan sağa kaybetmek için if (heartIcons[lostIndex]) { var parent = heartIcons[lostIndex].parent; if (parent) { parent.removeChild(heartIcons[lostIndex]); } heartIcons[lostIndex] = LK.getAsset('lostheart', { anchorX: 0, anchorY: 0, x: heartStartX + lostIndex * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartIcons[lostIndex]); } gameState.lives--; } if (gameState.lives <= 0) { gameOver(); } } } } } }; game.up = function (x, y, obj) { isTouching = false; // Restore kick target kickTarget.destroy(); kickTarget = LK.getAsset('kickTarget', { anchorX: 0.5, anchorY: 0.5, x: HIT_ZONE_X, y: HIT_ZONE_Y }); gamescreen.addChild(kickTarget); }; // --- Game over --- function gameOver() { gameState.gameActive = false; LK.effects.flashScreen(0xff0000, 800); showDefeatScreen(); LK.stopMusic(); } // --- Glow Effect for Purple Elements --- function addGlowEffect(element, glowColor, intensity) { if (!element || !element.visible) { return; } // Create glow animation using tint and alpha var originalTint = element.tint || 0xffffff; var glowTint = glowColor || 0xaa00ff; // Default purple glow var glowIntensity = intensity || 0.3; // Pulsing glow effect function startGlow() { tween(element, { alpha: 1 - glowIntensity }, { duration: 800, easing: tween.easeInOut, yoyo: true, repeat: -1 // Infinite repeat }); } startGlow(); } // Apply glow to purple elements function applyGlowToVioletElements() { // Apply glow to kick target (blue/violet color) if (kickTarget) { addGlowEffect(kickTarget, 0x8a2be2, 0.2); } // Apply glow to main menu buttons (purple/violet elements) if (startBtn) { addGlowEffect(startBtn, 0x8a2be2, 0.15); } if (settingsBtn) { addGlowEffect(settingsBtn, 0x8a2be2, 0.15); } if (creditsBtn) { addGlowEffect(creditsBtn, 0x8a2be2, 0.15); } if (supportBtn) { addGlowEffect(supportBtn, 0x8a2be2, 0.15); } // Apply glow to BPM circle container if it exists if (typeof gameState.bpmCircleContainer !== 'undefined' && gameState.bpmCircleContainer) { addGlowEffect(gameState.bpmCircleContainer, 0x8a2be2, 0.2); } } // Initialize glow effects applyGlowToVioletElements(); // --- Win condition --- function checkWin() { if (gameState.score >= 5000) { gameState.gameActive = false; LK.effects.flashScreen(0x00ff00, 800); LK.showYouWin(); LK.stopMusic(); } } // --- Defeat Screen Implementation --- var defeatContainer = new Container(); defeatContainer.visible = false; game.addChild(defeatContainer); gameState.maxCombo = 0; // Track maximum gameState.combo achieved gameState.perfectCount = 0; // Track number of perfect hits var perfectCombo = 0; // Track current consecutive perfect hits gameState.maxPerfectCombo = 0; // Track maximum consecutive perfect hits gameState.lastBpmUpgradeScore = 0; // Track last gameState.score where BPM was upgraded gameState.gamePaused = false; // Track if game is paused var pauseContainer = new Container(); pauseContainer.visible = false; game.addChild(pauseContainer); function showDefeatScreen() { defeatContainer.visible = true; defeatContainer.removeChildren(); gamescreen.visible = false; gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; // Add defeat background using defeatbg asset var defeatBg = LK.getAsset('defeatbg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); defeatContainer.addChild(defeatBg); // Add blurred background behind statistics var blurredBg = LK.getAsset('blurred', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1185, scaleX: 1.35, scaleY: 1.35, alpha: 1 }); defeatContainer.addChild(blurredBg); // Game Over title using gameover asset var gameOverAsset = LK.getAsset('gameover', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 600, scaleX: 0.5, scaleY: 0.5 }); defeatContainer.addChild(gameOverAsset); // Statistics arranged vertically with 5px spacing var statsStartY = 990; var statsSpacing = 5; var currentY = statsStartY; // Score display var finalScoreTxt = new Text2("Score: " + gameState.score, { size: 54, fill: 0xffffff }); finalScoreTxt.scale.set(2, 2); finalScoreTxt.anchor.set(0, 0.5); finalScoreTxt.x = 2048 / 2 - 550; finalScoreTxt.y = currentY; defeatContainer.addChild(finalScoreTxt); currentY += finalScoreTxt.height + statsSpacing; // Max gameState.combo display gameState.maxComboTxt = new Text2("Max Combo: " + gameState.maxCombo, { size: 54, fill: 0x00ff00 }); gameState.maxComboTxt.scale.set(2, 2); gameState.maxComboTxt.anchor.set(0, 0.5); gameState.maxComboTxt.x = 2048 / 2 - 550; gameState.maxComboTxt.y = currentY; defeatContainer.addChild(gameState.maxComboTxt); currentY += gameState.maxComboTxt.height + statsSpacing; // Perfect count display gameState.perfectCountTxt = new Text2("Perfect: " + gameState.perfectCount, { size: 54, fill: 0xffe600 }); gameState.perfectCountTxt.scale.set(2, 2); gameState.perfectCountTxt.anchor.set(0, 0.5); gameState.perfectCountTxt.x = 2048 / 2 - 550; gameState.perfectCountTxt.y = currentY; defeatContainer.addChild(gameState.perfectCountTxt); currentY += gameState.perfectCountTxt.height + statsSpacing; // Max perfect gameState.combo display (orange color) gameState.maxPerfectComboTxt = new Text2("Max Perfect Combo: " + gameState.maxPerfectCombo, { size: 54, fill: 0xff8c00 }); gameState.maxPerfectComboTxt.scale.set(2, 2); gameState.maxPerfectComboTxt.anchor.set(0, 0.5); gameState.maxPerfectComboTxt.x = 2048 / 2 - 550; gameState.maxPerfectComboTxt.y = currentY; defeatContainer.addChild(gameState.maxPerfectComboTxt); // Calculate button positions var defeatButtonStartY = 1700; var defeatButtonHeight = 289; var defeatButtonSpacing = 20; // Tekrar Oyna (Play Again) Button var playAgainBtn = LK.getAsset('tryagain', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: defeatButtonStartY, scaleX: 5, scaleY: 5 }); defeatContainer.addChild(playAgainBtn); // Ana Menü (Main Menu) Button var mainMenuBtn = LK.getAsset('mainmenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: defeatButtonStartY + defeatButtonHeight + defeatButtonSpacing, scaleX: 5, scaleY: 5 }); defeatContainer.addChild(mainMenuBtn); // Button event handlers playAgainBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation tween(playAgainBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(playAgainBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { defeatContainer.visible = false; gameState.maxCombo = 0; // Reset max gameState.combo gameState.perfectCount = 0; // Reset perfect count gameState.maxPerfectCombo = 0; // Reset max perfect gameState.combo // Continue from current level - if tutorial not completed, restart tutorial if (!tutorialCompleted) { gameState.currentChapter = 1; storage.currentChapter = gameState.currentChapter; } showReadyScreen(function () { showCountdown(function () { startGame(); }); }); } }); } }); }; mainMenuBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Click animation tween(mainMenuBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(mainMenuBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { defeatContainer.visible = false; gameState.maxCombo = 0; // Reset max gameState.combo gameState.perfectCount = 0; // Reset perfect count gameState.maxPerfectCombo = 0; // Reset max perfect gameState.combo mainMenuContainer.visible = true; gameState.scoreTxt.visible = false; gameState.comboTxt.visible = false; gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Stop current music and play menu music with a small delay to ensure proper playback (only if not muted) LK.stopMusic(); if (!musicMuted) { LK.setTimeout(function () { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); }, 100); } } }); } }); }; } // --- Restart on game over --- LK.on('gameover', function () { showDefeatScreen(); }); LK.on('youwin', function () { startGame(); }); // Kalp ve heartshield UI güncelleme fonksiyonu function updateHeartsUI(lives, heartshields) { // Remove all existing heart icons for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = gameState.scoreTxt.y + 50; for (var i = 0; i < MAX_LIVES; i++) { var assetId; if (i < 3 - heartshields) { // Heartshield kaybedilmiş, kalp veya lostheart göster if (i < MAX_LIVES - lives) { assetId = 'lostheart'; } else { assetId = 'heart'; } } else { assetId = 'heartshield'; } var heartAsset = LK.getAsset(assetId, { anchorX: 0, anchorY: 0, x: heartStartX + i * (heartWidth + heartSpacing), y: heartY }); gamescreen.addChild(heartAsset); heartIcons.push(heartAsset); } } // Beat asset değiştirme fonksiyonu function replaceBeatAsset(beat, newAssetId, options) { if (!beat.parent) { return null; } var beatParent = beat.parent; var currentX = beat.x; var currentY = beat.y; beatParent.removeChild(beat); var assetOptions = { anchorX: 0.5, anchorY: 0.5, x: currentX, y: currentY, width: 60, height: 59.77 }; if (options) { for (var key in options) { assetOptions[key] = options[key]; } } var newBeatAsset = LK.getAsset(newAssetId, {}); beatParent.addChild(newBeatAsset); // Copy over important properties newBeatAsset.beatTime = beat.beatTime; newBeatAsset.type = beat.type; newBeatAsset.active = false; newBeatAsset.hit = true; newBeatAsset.lastX = currentX; newBeatAsset.hasReachedHitZone = true; // Trail sistemi özelliklerini kopyala ve başlat newBeatAsset.lastTrailTime = Date.now(); newBeatAsset.trailInterval = 15; newBeatAsset.assetType = newAssetId; // Trail güncelleme metodunu ekle newBeatAsset.updateTrail = function () { var now = Date.now(); if (now - this.lastTrailTime > this.trailInterval) { var colors = BEAT_COLORS[this.assetType] || BEAT_COLORS.circle; // Beat'in hareket yönünü belirle (sağ tarafta mı?) var isRightSide = this.x > HIT_ZONE_X; var directionMultiplier = isRightSide ? 1 : -1; // Parçacık sayısını ve yayılımı ayarla var particleCount = 10; var spreadX = 20; var spreadY = 20; for (var i = 0; i < particleCount; i++) { // Ana parçacık var mainParticle = createTrailParticle(this.x + (Math.random() - 0.5) * spreadX, this.y + (Math.random() - 0.5) * spreadY, colors.mainTrail); mainParticle.velocityX = (Math.random() * 2 + 1) * directionMultiplier; mainParticle.velocityY = (Math.random() - 0.5) * 3; // Yan parçacık 1 var subParticle1 = createTrailParticle(this.x + (Math.random() - 0.5) * (spreadX * 1.5), this.y + (Math.random() - 0.5) * (spreadY * 1.5), colors.subTrail1); subParticle1.width = 4; subParticle1.height = 4; subParticle1.velocityX = (Math.random() * 1.5 + 0.5) * directionMultiplier; subParticle1.velocityY = (Math.random() - 0.5) * 2; // Yan parçacık 2 var subParticle2 = createTrailParticle(this.x + (Math.random() - 0.5) * (spreadX * 1.25), this.y + (Math.random() - 0.5) * (spreadY * 1.25), colors.subTrail2); subParticle2.width = 3; subParticle2.height = 3; subParticle2.velocityX = (Math.random() * 1 + 0.5) * directionMultiplier; subParticle2.velocityY = (Math.random() - 0.5) * 1.5; // Parçacıkları oyun ekranına ekle ve özelliklerini ayarla [mainParticle, subParticle1, subParticle2].forEach(function (p) { p.fadeSpeed = 0.03; p.gravity = 0.1; p.drag = 0.98; gamescreen.addChild(p); trailParticles.push(p); }); } this.lastTrailTime = now; } }; // Trail sistemini aktif et if (!beats.includes(newBeatAsset)) { beats.push(newBeatAsset); } // Pulse efektini ekle newBeatAsset.pulseStartTime = Date.now(); newBeatAsset.baseScale = newBeatAsset.scale.x; newBeatAsset.updateBeatPulse = function () { updateBeatPulse(this); }; beat.destroy(); return newBeatAsset; } // Sidechain efekti: Kick çalarken müziği kısa süreli kısmak için function playKickWithSidechain() { // Kick çal if (selectedKickSound !== '') { LK.getSound(selectedKickSound).play(); } // Müzik volume'unu kısa süreli azalt var music = LK.getMusic && LK.getMusic(); if (music && music.setVolume) { var originalVolume = music.volume || 1; music.setVolume(originalVolume * 0.7); setTimeout(function () { music.setVolume(originalVolume); }, 120); // 120ms sonra eski volume } } // === Waveform (Sinüs Yörüngesi) Çizgisi === var waveformContainer = new Container(); gamescreen.addChild(waveformContainer); function drawWaveformLK() { waveformContainer.removeChildren(); var step = 10; var waveLength = 100; var waveAmplitude = 80; var phase = -HIT_ZONE_X / waveLength; var prev = null; for (var x = 0; x <= 2048; x += step) { var y = HIT_ZONE_Y + Math.sin(x / waveLength + phase) * waveAmplitude; if (prev) { // Mor glow çizgisi (kalın, yarı saydam) var glowLine = LK.getAsset('settingsbar', { anchorX: 0, anchorY: 0.5, x: prev.x, y: prev.y, width: step, height: 16, alpha: 0.3, tint: 0xaa00ff }); waveformContainer.addChild(glowLine); // Beyaz çizgi (ince, tam opak) var whiteLine = LK.getAsset('settingsbar', { anchorX: 0, anchorY: 0.5, x: prev.x, y: prev.y, width: step, height: 4, alpha: 1, tint: 0xffffff }); waveformContainer.addChild(whiteLine); } prev = { x: x, y: y }; } } // Oyun başında ve BPM değiştiğinde çağır: drawWaveformLK(); // Screen shake function function shakeScreen(intensity, duration) { var originalX = gamescreen.x; var originalY = gamescreen.y; var shakeCount = 0; var maxShakes = Math.floor(duration / 16.67); // 60fps function shake() { if (shakeCount >= maxShakes) { // Reset position gamescreen.x = originalX; gamescreen.y = originalY; return; } var randomX = (Math.random() - 0.5) * 2 * intensity; var randomY = (Math.random() - 0.5) * 2 * intensity; tween(gamescreen, { x: originalX + randomX, y: originalY + randomY }, { duration: 16.67, // One frame at 60fps easing: tween.easeOut, onFinish: function onFinish() { shakeCount++; shake(); } }); } shake(); } // Flash overlay for screen darkening function createFlashOverlay() { if (!window.flashOverlay) { window.flashOverlay = LK.getAsset('ch2panF', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0, tint: 0x000000 }); game.addChild(window.flashOverlay); } return window.flashOverlay; } function flashScreen(color, alpha, duration) { var overlay = createFlashOverlay(); overlay.tint = color; // Hızlı flash efekti tween(overlay, { alpha: alpha }, { duration: duration * 0.2, // Yükselme süresi easing: tween.easeOut, onFinish: function onFinish() { // Yavaşça kaybolma tween(overlay, { alpha: 0 }, { duration: duration * 0.8, // Kaybolma süresi easing: tween.easeIn }); } }); } // Trail particle system function createTrailParticle(x, y, color) { var particle = LK.getAsset('circle', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, width: 6, // Daha küçük parçacıklar height: 6, alpha: 0.5, // Biraz daha saydam tint: color }); // Fizik özellikleri particle.velocityX = 0; particle.velocityY = 0; particle.drag = 0.95; // Sürtünme particle.gravity = 0.2; // Yerçekimi particle.life = 1.0; // Yaşam süresi (1.0'dan 0'a) particle.fadeSpeed = 0.02; // Her frame'de azalacak yaşam miktarı return particle; } function updateTrailParticle(particle) { // Fizik güncellemesi particle.velocityY += particle.gravity; particle.velocityX *= particle.drag; particle.velocityY *= particle.drag; particle.x += particle.velocityX; particle.y += particle.velocityY; // Yaşam süresi ve opaklık güncellemesi particle.life -= particle.fadeSpeed; particle.alpha = particle.life * 0.6; return particle.life > 0; } var trailParticles = []; function updateTrailSystem() { // Tüm parçacıkları güncelle ve ölenleri kaldır for (var i = trailParticles.length - 1; i >= 0; i--) { var particle = trailParticles[i]; if (!updateTrailParticle(particle)) { gamescreen.removeChild(particle); trailParticles.splice(i, 1); } } } // Beat renk konfigürasyonları var BEAT_COLORS = { 'perfectcircle': { mainTrail: 0xFFD700, // Altın sarısı subTrail1: 0xFFA500, // Turuncu subTrail2: 0xFFFF00 // Sarı }, 'kickActive': { mainTrail: 0x00ff00, // Yeşil subTrail1: 0x98fb98, // Açık yeşil subTrail2: 0x32cd32 // Lime yeşil }, 'circle': { mainTrail: 0x8a2be2, // Mor subTrail1: 0x00e6ff, // Mavi subTrail2: 0xff1493 // Pembe }, 'miss': { mainTrail: 0xFF0000, // Kırmızı subTrail1: 0xFF4444, // Açık kırmızı subTrail2: 0xCC0000 // Koyu kırmızı }, 'perfect': { mainTrail: 0xFFD700, // Altın sarısı subTrail1: 0xFFA500, // Turuncu subTrail2: 0xFFFF00 // Sarı }, 'awesome': { mainTrail: 0x00FFFF, // Cyan subTrail1: 0x40E0D0, // Turkuaz subTrail2: 0x00CED1 // Koyu turkuaz }, 'good': { mainTrail: 0x32CD32, // Lime yeşil subTrail1: 0x90EE90, // Açık yeşil subTrail2: 0x228B22 // Orman yeşili }, 'ok': { mainTrail: 0x4169E1, // Royal mavi subTrail1: 0x1E90FF, // Dodger mavi subTrail2: 0x0000CD // Orta koyu mavi } }; // BPM'e göre pulse efekti function updateBeatPulse(beat) { if (!beat.pulseStartTime) { beat.pulseStartTime = Date.now(); beat.baseScale = beat.scale.x; } gameState.bpmInterval = 60000 / gameState.bpm; // BPM'i milisaniyeye çevir var timeSinceStart = Date.now() - beat.pulseStartTime; var pulsePhase = timeSinceStart % gameState.bpmInterval / gameState.bpmInterval; // Sinüs dalgası ile pulse efekti var pulseScale = 0.15; // Pulse büyüklüğü var scaleFactor = 1 + Math.sin(pulsePhase * Math.PI * 2) * pulseScale; beat.scale.x = beat.baseScale * scaleFactor; beat.scale.y = beat.baseScale * scaleFactor; } // Trail renklerini güncelleme fonksiyonu function updateTrailsColor(beat, newColorType, customColors) { if (!beat || !beat.updateTrail) { return null; } // Yeni renk tipini kaydet beat.trailColorType = newColorType; // Özel renkler verilmişse onları kullan, yoksa BEAT_COLORS'dan al var colors; if (customColors) { colors = { mainTrail: customColors.mainTrail || 0xFFFFFF, subTrail1: customColors.subTrail1 || 0xCCCCCC, subTrail2: customColors.subTrail2 || 0x999999 }; } else { colors = BEAT_COLORS[newColorType] || BEAT_COLORS.circle; } // Trail güncelleme fonksiyonunu yeni renklerle güncelle beat.updateTrail = function () { var now = Date.now(); if (now - this.lastTrailTime > this.trailInterval) { // Beat'in hareket yönünü belirle (sağ tarafta mı?) var isRightSide = this.x > HIT_ZONE_X; var directionMultiplier = isRightSide ? 1 : -1; // Parçacık sayısını ve yayılımı ayarla var particleCount = 10; var spreadX = 20; var spreadY = 20; for (var i = 0; i < particleCount; i++) { // Ana parçacık var mainParticle = createTrailParticle(this.x + (Math.random() - 0.5) * spreadX, this.y + (Math.random() - 0.5) * spreadY, colors.mainTrail); mainParticle.velocityX = (Math.random() * 2 + 1) * directionMultiplier; mainParticle.velocityY = (Math.random() - 0.5) * 3; // Yan parçacık 1 var subParticle1 = createTrailParticle(this.x + (Math.random() - 0.5) * (spreadX * 1.5), this.y + (Math.random() - 0.5) * (spreadY * 1.5), colors.subTrail1); subParticle1.width = 4; subParticle1.height = 4; subParticle1.velocityX = (Math.random() * 1.5 + 0.5) * directionMultiplier; subParticle1.velocityY = (Math.random() - 0.5) * 2; // Yan parçacık 2 var subParticle2 = createTrailParticle(this.x + (Math.random() - 0.5) * (spreadX * 1.25), this.y + (Math.random() - 0.5) * (spreadY * 1.25), colors.subTrail2); subParticle2.width = 3; subParticle2.height = 3; subParticle2.velocityX = (Math.random() * 1 + 0.5) * directionMultiplier; subParticle2.velocityY = (Math.random() - 0.5) * 1.5; // Parçacıkları oyun ekranına ekle ve özelliklerini ayarla [mainParticle, subParticle1, subParticle2].forEach(function (p) { p.fadeSpeed = 0.03; p.gravity = 0.1; p.drag = 0.98; gamescreen.addChild(p); trailParticles.push(p); }); } this.lastTrailTime = now; } }; return beat; } function updateBeatTrailOnHit(beat, hitType) { if (!beat) { return; } // Hit türüne göre renk tipini belirle var colorType; switch (hitType.toLowerCase()) { case 'perfect': colorType = 'perfect'; break; case 'awesome': colorType = 'awesome'; break; case 'good': colorType = 'good'; break; case 'ok': colorType = 'ok'; break; case 'miss': colorType = 'miss'; break; default: colorType = 'circle'; } // Trail renklerini güncelle updateTrailsColor(beat, colorType); } // === Oyun Modu Yönetimi === var gameMode = "story"; // "story", "endless", "tutorial" // ... existing code ... // === Menü Fonksiyonları === function showMainMenu() { mainMenuContainer.visible = true; gameState.scoreTxt.visible = false; gameState.comboTxt.visible = false; gameState.bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; defeatContainer.visible = false; pauseContainer.visible = false; settingsmenu.visible = false; creditsmenu.visible = false; supportmenu.visible = false; } // ... existing code ... // === Ana Menü Butonları === // Start (Story) Button startBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); LK.stopMusic(); tween(startBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(startBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; gameMode = "story"; showStoryIntro(function () { showReadyScreen(function () { showCountdown(function () { startGame({ mode: "story" }); }); }); }); } }); } }); }; // Endless Mode Button var endlessBtn = LK.getAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + 4 * (buttonHeight + buttonSpacing), scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(endlessBtn); // Endless Mode Text var endlessBtnTxt = new Text2("ENDLESS", { size: 48, fill: 0xffffff }); endlessBtnTxt.scale.set(2, 2); endlessBtnTxt.anchor.set(0.5, 0.5); endlessBtnTxt.x = 2048 / 2; endlessBtnTxt.y = buttonStartY + 4 * (buttonHeight + buttonSpacing); mainMenuContainer.addChild(endlessBtnTxt); endlessBtn.down = function (x, y, obj) { LK.getSound('buttonclick').play(); LK.stopMusic(); tween(endlessBtn, { scaleX: 0.162, scaleY: 0.162 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(endlessBtn, { scaleX: 0.18, scaleY: 0.18 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; gameMode = "endless"; showReadyScreen(function () { showCountdown(function () { startGame({ mode: "endless" }); }); }); } }); } }); }; // ... existing code ... // === startGame Fonksiyonunu Parametreli Yap === // ... existing code ... // defeat ve ana menüye dönünce gameMode'u sıfırla function resetGameMode() { gameMode = "story"; } // defeat ekranı ve ana menü butonlarında resetGameMode çağır // ... existing code ... // === Oyun State'i Merkezi Obje Altında Topla === // gameState already defined earlier in the file
===================================================================
--- original.js
+++ change.js
@@ -140,19 +140,30 @@
var BEAT_INTERVAL = 60 / INITIAL_BPM; // seconds per beat
var DIFFICULTY_STEP = 0.15; // How much to increase BPM per level
var MAX_MISSES = 5;
// --- Game state ---
+var gameState = {
+ score: 0,
+ combo: 0,
+ currentChapter: 1,
+ lives: 3,
+ heartshields: 3,
+ bpm: 100,
+ gameActive: false,
+ gamePaused: false,
+ maxCombo: 0,
+ perfectCount: 0,
+ maxPerfectCombo: 0,
+ lastBpmUpgradeScore: 0
+};
var beats = []; // Active BeatMarker objects
var beatPattern = []; // Array of {time, type}
var nextBeatIdx = 0; // Next beat to spawn
var songTime = 0; // Elapsed time in seconds
var lastTickTime = Date.now();
-gameState.score = 0;
var misses = 0;
var ignoredMisses = 0; // Track first 3 misses to ignore
-gameState.combo = 0;
var tutorialCompleted = false;
-gameState.currentChapter = 1;
// Reset storage values to ensure game always starts from beginning on reload
storage.tutorialCompleted = false;
storage.currentChapter = 1;
var isTutorial = false;
@@ -3679,27 +3690,5 @@
}
// defeat ekranı ve ana menü butonlarında resetGameMode çağır
// ... existing code ...
// === Oyun State'i Merkezi Obje Altında Topla ===
-var gameState = {
- mode: "story",
- // "story" veya "endless"
- bpm: 100,
- // Aktif BPM
- score: 0,
- // Skor
- lives: 3,
- // Kalan can
- combo: 0,
- // Kombo
- currentChapter: 1,
- // Story modunda aktif chapter
- heartshields: 3,
- // Kalkan sayısı
- maxCombo: 0,
- perfectCount: 0,
- maxPerfectCombo: 0,
- lastBpmUpgradeScore: 0,
- gameActive: false,
- gamePaused: false
- // ... diğer gerekli state değişkenleri
-};
\ No newline at end of file
+// gameState already defined earlier in the file
\ No newline at end of file
yellow ball pixel-art. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A minimalist horizontal pixel art timeline bar, 2048x40 resolution, designed for a lo-fi rhythm pixel game. The bar is sleek and thin, with subtle purple and violet tones, no numbers or markers. The style is consistent with a DJ-themed interface — soft glowing edges, pixel-perfect precision, and no background (transparent). The bar should feel modern yet retro, fitting into a dreamy neon-lit rhythm game UI.. In-Game asset. 2d. High contrast. No shadows
orange
Realistic comic book style, 2048x2048. Split-frame design: left half shows Noah mid-performance dissolving into shadows; right half shows a crumpled newspaper with the headline "DJ NOAH GONE?". Desaturated colors, somber mood. Add a transparent narrator box at the top-center with the text: "Then, silence." in comic font.. In-Game asset. 2d. High contrast. No shadows
Add white to the edges for a photo paper look
Add white to the edges for a photo paper look.
Add white to the edges for a photo paper look.
Add white to the edges for a photo paper look.
A cinematic comic book-style vertical panel, 2048x2732. Inside a moody, dimly lit underground bar, a lone DJ (Noah) stands behind a glowing DJ booth, facing forward but with his head slightly bowed. Sparse silhouettes of a few customers sit at shadowy tables, blurred by distance and smoke. Purple and blue neon lights gently illuminate the space. The atmosphere feels still, as if something important just began. A faint spotlight outlines Noah's figure. This scene conveys calm before momentum, subtle drama and emotional depth. No text or narration in the image.. In-Game asset. 2d. High contrast. No shadows
A rectangular comic-style narrator box placed near the bottom of a vertical panel (2048x2732). Dark purple background with rounded corners, white border. Inside, centered white text in clean comic font reads: "The lights may be dim, but the spark is back. Tonight, Noah spins not just music — but a second chance.". In-Game asset. 2d. High contrast. No shadows
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: A rainy city street at night, neon signs reflecting on wet pavement, Noah stands under a flickering bar sign, hood up, looking uncertain. Narrator box at the top reads: “The rain didn’t stop him — not tonight.”. In-Game asset. 2d. High contrast. No shadows
yazıyı güncelle "The place smelled of sweat, smoke, and forgotten dreams."
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: Close-up of a middle-aged woman with bold makeup, cigarette in hand, leaning over the bar. Her name tag reads “Manager.” Narrator box at the top reads: “She didn’t ask questions — just handed him the schedule.”. In-Game asset. 2d. High contrast. No shadows
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: A dusty DJ booth, some lights flickering, the equipment old but intact. Noah places his hand on the mixer with a hint of reverence. Narrator box at the top reads: “It wasn’t much — but it was a start.”. In-Game asset. 2d. High contrast. No shadows
A comic panel styled like a square photo print with a clean white border. Realistic comic art. Scene: Over-the-shoulder view of Noah at the booth, the crowd in the bar small but curious. One hand on the vinyl, lights begin to flicker rhythmically. Narrator box at the top reads: “And just like that, the night began to breathe.”. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The scene depicts a moody DJ booth inside a small urban apartment at night, overlooking a vibrant neon-lit city through large glass windows. The DJ setup is stylish but lived-in — with vinyl records, scattered sticky notes, a glowing laptop screen, and faint light reflections on the desk. Outside, rain trickles down the window, and distant buildings flicker with purples, blues, and pinks. The entire environment feels reflective, lo-fi, and intimate — as if the city hums with a quiet rhythm. No characters, no text — just a story-rich, immersive menu backdrop.. In-Game asset. 2d. High contrast. No shadows
A bold comic-style rectangular button labeled 'Start' in capital white letters. The button has sharp edges with thick black outlines and a deep purple fill. Style mimics classic graphic novel UI — solid, punchy, and energetic. No background, transparent PNG. In-Game asset. 2d. High contrast. No shadows
Write SETTINGS.
write CREDITS.
write SUPPORT US.
A bold, comic-style game title reading 'DJ NOAH' in large uppercase letters. The text features a deep electric purple gradient with sharp highlights and bold white outlines, styled like a dramatic comic book title splash. Subtle halftone textures and a radial glow enhance its dynamic look. Behind the text, abstract comic-style sound wave elements stretch outward in purple and magenta hues. The composition is punchy, modern, and dramatic — designed as a central UI graphic with transparent background for in-game use. 2D, high contrast, no drop shadow.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The scene shows a quiet corner of the same small urban apartment, viewed from a different angle than the main menu. This part of the room features a vintage sound mixing rack, ambient LED lights softly glowing in purple and blue tones, and a corkboard on the wall filled with pinned notes, small polaroid photos, and scribbled music ideas. An open sketchbook with waveform doodles lies on a side table, and worn-out headphones rest beside it. A faint rain still falls outside the partially visible window, casting reflections on the floor. The overall mood remains lo-fi, introspective, and musically charged. No characters, no text — just a lived-in, creative space for the settings menu backdrop.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The setting reveals a nostalgic, dimly lit corner of the same urban apartment from the previous scenes. This part of the room showcases a personal wall collage filled with printed photographs, old event posters, and handwritten notes — all pinned with care and glowing softly under a small warm desk lamp. A wooden shelf holds vinyl covers, retro magazines, and a framed photo of a live DJ performance. On a nearby desk, a coffee mug steams next to an analog tape recorder, gently lit by the reflection of neon city lights from outside the window. The atmosphere is rich with memories, inspiration, and creative legacy. No characters, no text — just a sentimental space for the credits menu backdrop.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical background illustration in a semi-realistic comic book style. The scene takes place in another corner of the same urban apartment — this time showing an open space near a shelf filled with music memorabilia, fan-made items, and a pinboard with stickers like “Support Local DJs”, “Upvote Love”, and pixel-style hearts. A tablet on the desk is open to a game page with a “Leave a review” banner. There’s a soft spotlight on a coffee mug with “Thank you” written on it and glowing music notes floating around it. The city lights outside still shine in purples and blues, but this part of the room feels more hopeful and connected. The vibe is warm, welcoming, and subtly celebratory. No characters or text. In-game menu background, consistent with main menu themes.. In-Game asset. 2d. High contrast. No shadows
tahta texture
turuncu-kahverengi tonları
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
kalkan mavi tonlarında olsun
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
gri
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
write MAIN MENU
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, purple and blue tones, soft gradients
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
semi-realistic comic book style, moody neon lighting, lo-fi urban atmosphere, rich textures, cinematic framing, purple and blue tones, soft gradients, ambient reflections
write RESUME
write RESTART
kick
Sound effect
miss
Sound effect
menumusic
Music
75BPMElectricDreams
Music
80BPMNeonNights
Music
85BPMCyberFlow
Music
90BPMDigitalPulse
Music
95BPMFutureBass
Music
100BPMSynthWave
Music
40BPMDeepVibes
Music
45BPMChillWave
Music
50BPMSlowMotion
Music
55BPMDreamscape
Music
60BPMLowKey
Music
65BPMRelaxed
Music
70BPMCalmVibes
Music
105BPMHyperDrive
Music
110BPMTechnoRush
Music
115BPMHighEnergy
Music
120BPMMaximum
Music
scratch
Sound effect
buttonclick
Sound effect
kick2
Sound effect
kick3
Sound effect
kick4
Sound effect
hihat
Sound effect
hihat2
Sound effect
hihat3
Sound effect
hihat4
Sound effect
snareclap
Sound effect
snareclap2
Sound effect
snareclap3
Sound effect
snareclap4
Sound effect
percussion
Sound effect
percussion2
Sound effect
percussion3
Sound effect
percussion4
Sound effect
thanks
Sound effect
echoesofthepast
Sound effect
chapter1music
Music
paperswosh
Sound effect
ch1voice1
Sound effect
ch1voice2
Sound effect
nightshift
Sound effect
chapter2music
Music