User prompt
start ve endless assetlerini yeniden tanımla
User prompt
endless ve start buttonlarının asset idlerini güncelle
Code edit (1 edits merged)
Please save this source code
User prompt
I would love that the sound playing when you tap would blend better into the music. Like dance music and the sound is a vynil spin or something like that.
User prompt
devamiicin screeninde 3snden sonra ekrana basıldığında ana menüye atsın.
User prompt
Fix pause button functionality on game screen by properly implementing the click handler
User prompt
chapter 2 enddeki pause butona tıklandığında pausebg asseti, restart butonu ve main menü butonu gelsin
User prompt
chapter 2 enddeki pause button çalışmıyor
User prompt
ready info assetini 25px yukarı çıkar
User prompt
blurred assetini 50px yukarı çıkar
User prompt
devamiicin assetinin göründüğü ekranda pause buttonu da olsun
User prompt
tutorial bpm display text force 60 BPM
User prompt
song name text 8pxupward
User prompt
song name text 15px upward
User prompt
oyundaki bpm value ve şarkı ismi yazılarını 30px aşağı indir
User prompt
oyundaki bpm value ve şarkı ismi yazılarını 75px yukarı çıkar
User prompt
gameover assetini yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
oyundaki tüm metinleri tekrardan yüksek çözünürlükte görünecek şekilde düzenle. boyutları sabit kalsın
User prompt
creditstab yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
supporttab yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
0.3 yap
User prompt
settings menüsündeki settings assetini yeniden boyutlandır ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
0.18 yap
/**** * 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, currentLevel: 1 }); /**** * Classes ****/ // default 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; // --- Trail yayılımını BPM'e göre ayarla --- var params = getWaveParams(bpm); var spreadX = params.waveAmplitude * 0.2; // Dalga yüksekliğine orantılı var spreadY = params.waveAmplitude * 0.2; for (var i = 0; i < 10; 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 [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 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 score = 0; var misses = 0; var ignoredMisses = 0; // Track first 3 misses to ignore var combo = 0; var tutorialCompleted = false; var currentLevel = 1; // Reset storage values to ensure game always starts from beginning on reload storage.tutorialCompleted = false; storage.currentLevel = 1; var isTutorial = false; var 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 / bpm; var gameActive = true; var feedbackTimeout = null; // --- UI elements --- // --- Heart/Life UI --- // (Initialization moved after scoreTxt is defined) var MAX_LIVES = 3; var lives = MAX_LIVES; var heartIcons = []; var heartSpacing = 40; var heartshields = 3; // Number of heartshields remaining // Heart/life UI will be initialized after 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 var scoreTxt = new Text2('0', { size: 60, fill: 0xFFFFFF }); scoreTxt.scale.set(2, 2); // Scale up 2x to create more pixelated effect scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Combo text var comboTxt = new Text2('', { size: 36, fill: 0x00FF00 }); comboTxt.scale.set(2, 2); comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(comboTxt); comboTxt.y = 120; // BPM display text - 135px below combo text (moved 30px down) var bpmDisplayTxt = new Text2(bpm + ' ' + 'BPM', { size: 36, fill: 0xef69b9 }); bpmDisplayTxt.scale.set(2, 2); bpmDisplayTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bpmDisplayTxt); bpmDisplayTxt.y = 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 = 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 lives display // --- Heart/Life UI --- (moved here to ensure scoreTxt is defined) // Calculate heartY based on scoreTxt position and height var heartY = 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") { // Story modunda tutorial kontrolü gameMode = "story"; var chapterIdx = typeof currentLevel === 'number' ? currentLevel - 1 : 0; var chapter = STORY_CHAPTERS[chapterIdx] || STORY_CHAPTERS[0]; isTutorial = chapterIdx === 0 && !tutorialCompleted; bpm = chapter.bpm; var storyMusicId = chapter.musicId; var storySongName = chapter.songName; beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; combo = 0; perfectCombo = 0; lives = MAX_LIVES; heartshields = 3; lastBpmUpgradeScore = 0; // Remove any beat at the kick target at game start 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(lives, heartshields); var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = scoreTxt.y + 50; for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; heartshields = MAX_LIVES; 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); } gameActive = true; gamePaused = false; scoreTxt.setText('0'); comboTxt.setText(''); kickTarget.visible = true; LK.setScore(0); applyGlowToVioletElements(); updateAllSoundVolumes(); tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameActive && heartshields > 0) { heartshields = 0; updateHeartsUI(lives, heartshields); } } }); // --- Story modunda sabit müzik ve şarkı adı --- var musicId = storyMusicId; var volumeMultiplier = soundVolume / 100; var musicVolume = isTutorial ? 0.5 : 1; LK.playMusic(musicId, { volume: musicVolume * volumeMultiplier, loop: true }); if (bpmDisplayTxt) { bpmDisplayTxt.setText(bpm + ' BPM'); } if (songNameTxt) { songNameTxt.setText(storySongName); } // UI gösterimi scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; updateDebugText(); return; } else if (mode === "endless") { gameMode = "endless"; isTutorial = false; bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM; beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; combo = 0; perfectCombo = 0; lives = MAX_LIVES; heartshields = 3; lastBpmUpgradeScore = 0; // Remove any beat at the kick target at game start 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(lives, heartshields); var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = scoreTxt.y + 50; for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; heartshields = MAX_LIVES; 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); } gameActive = true; gamePaused = false; scoreTxt.setText('0'); comboTxt.setText(''); kickTarget.visible = true; LK.setScore(0); applyGlowToVioletElements(); updateAllSoundVolumes(); tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameActive && heartshields > 0) { heartshields = 0; updateHeartsUI(lives, heartshields); } } }); // --- Play correct BPM music (kickless) --- var musicId = '100BPMSynthWave'; if (bpm === 60) { musicId = '60BPMLowKey'; } else if (bpm === 40) { musicId = '40BPMDeepVibes'; } else if (bpm === 45) { musicId = '45BPMChillWave'; } else if (bpm === 50) { musicId = '50BPMSlowMotion'; } else if (bpm === 55) { musicId = '55BPMDreamscape'; } else if (bpm === 65) { musicId = '65BPMRelaxed'; } else if (bpm === 70) { musicId = '70BPMCalmVibes'; } else if (bpm === 75) { musicId = '75BPMElectricDreams'; } else if (bpm === 80) { musicId = '80BPMNeonNights'; } else if (bpm === 85) { musicId = '85BPMCyberFlow'; } else if (bpm === 90) { musicId = '90BPMDigitalPulse'; } else if (bpm === 95) { musicId = '95BPMFutureBass'; } else if (bpm === 100) { musicId = '100BPMSynthWave'; } else if (bpm === 105) { musicId = '105BPMHyperDrive'; } else if (bpm === 110) { musicId = '110BPMTechnoRush'; } else if (bpm === 115) { musicId = '115BPMHighEnergy'; } else if (bpm === 120) { musicId = '120BPMMaximum'; } var volumeMultiplier = soundVolume / 100; var musicVolume = 1; LK.playMusic(musicId, { volume: musicVolume * volumeMultiplier, loop: true }); if (bpmDisplayTxt) { bpmDisplayTxt.setText(bpm + ' BPM'); } if (songNameTxt) { var songName = musicId.replace(/^\d+BPM/, ''); songNameTxt.setText(songName); } // --- BPM'e göre waveform'u güncelle --- drawWaveformLK(); // UI gösterimi scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; updateDebugText(); return; } beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; combo = 0; perfectCombo = 0; lives = MAX_LIVES; heartshields = 3; lastBpmUpgradeScore = 0; // Remove any beat at the kick target at game start 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(lives, heartshields); var heartWidth = 150; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = scoreTxt.y + 50; for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; heartshields = MAX_LIVES; 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); } gameActive = true; gamePaused = false; scoreTxt.setText('0'); comboTxt.setText(''); kickTarget.visible = true; LK.setScore(0); applyGlowToVioletElements(); updateAllSoundVolumes(); tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameActive && heartshields > 0) { heartshields = 0; updateHeartsUI(lives, heartshields); } } }); // --- Play correct BPM music (kickless) --- var musicId = '100BPMSynthWave'; if (bpm === 60) { musicId = '60BPMLowKey'; } else if (bpm === 40) { musicId = '40BPMDeepVibes'; } else if (bpm === 45) { musicId = '45BPMChillWave'; } else if (bpm === 50) { musicId = '50BPMSlowMotion'; } else if (bpm === 55) { musicId = '55BPMDreamscape'; } else if (bpm === 65) { musicId = '65BPMRelaxed'; } else if (bpm === 70) { musicId = '70BPMCalmVibes'; } else if (bpm === 75) { musicId = '75BPMElectricDreams'; } else if (bpm === 80) { musicId = '80BPMNeonNights'; } else if (bpm === 85) { musicId = '85BPMCyberFlow'; } else if (bpm === 90) { musicId = '90BPMDigitalPulse'; } else if (bpm === 95) { musicId = '95BPMFutureBass'; } else if (bpm === 100) { musicId = '100BPMSynthWave'; } else if (bpm === 105) { musicId = '105BPMHyperDrive'; } else if (bpm === 110) { musicId = '110BPMTechnoRush'; } else if (bpm === 115) { musicId = '115BPMHighEnergy'; } else if (bpm === 120) { musicId = '120BPMMaximum'; } var volumeMultiplier = soundVolume / 100; var musicVolume = 1; LK.playMusic(musicId, { volume: musicVolume * volumeMultiplier, loop: true }); if (bpmDisplayTxt) { bpmDisplayTxt.setText(bpm + ' BPM'); } if (songNameTxt) { var songName = musicId.replace(/^\d+BPM/, ''); songNameTxt.setText(songName); } // --- BPM'e göre waveform'u güncelle --- drawWaveformLK(); // UI gösterimi scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; } // --- 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('start', { 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 + 4 * (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 var bpmCircleContainer = new Container(); bpmCircleContainer.x = 2048 / 2 - 300; bpmCircleContainer.y = 1100; settingsmenu.addChild(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 var bpmCircleBg = LK.getAsset('purplecircle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, scaleX: 1.1, scaleY: 1.1, alpha: 0.85 }); bpmCircleContainer.addChild(bpmCircleBg); // BPM value text var bpmValueTxt = new Text2(bpm + "", { size: 54, fill: 0xffffff }); bpmValueTxt.scale.set(2, 2); bpmValueTxt.anchor.set(0.5, 0.5); bpmValueTxt.x = 0; bpmValueTxt.y = 0; bpmCircleContainer.addChild(bpmValueTxt); // BPM label text var bpmLabelTxt = new Text2("BPM", { size: 24, fill: 0xffffff }); bpmLabelTxt.scale.set(2, 2); bpmLabelTxt.anchor.set(0.5, 0.5); bpmLabelTxt.x = 0; bpmLabelTxt.y = 90; bpmCircleContainer.addChild(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 bpmCircleContainer.down = function (x, y, obj) { dragActive = true; dragStartY = y; dragStartBpm = bpm; lastDragStep = 0; // Animate circle slightly for feedback tween(bpmCircleBg, { scaleX: 1.18, scaleY: 1.18 }, { duration: 100, yoyo: true, repeat: 1 }); }; // Touch move on circle 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 calculatedBpm = dragStartBpm + step * BPM_STEP; if (calculatedBpm < BPM_MIN) { calculatedBpm = BPM_MIN; } if (calculatedBpm > BPM_MAX) { calculatedBpm = BPM_MAX; } if (bpm !== calculatedBpm) { bpm = calculatedBpm; bpmValueTxt.setText(bpm + ""); beatInterval = 60 / bpm; // Recalculate beat interval for new BPM // Regenerate beat pattern with new BPM intervals beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; // Reset beat spawning storage.bpm = bpm; // persist bpm to storage // Play scratch sound when BPM changes during settings LK.getSound('scratch').play(); if (bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = bpm; // This is the actual BPM being used for gameplay bpmDisplayTxt.setText(currentPlayingBpm + ' BPM'); } drawWaveformLK(); // waveform'u anlık güncelle // --- İYİLEŞTİRME: Eğer endless modda oyun aktifse, BPM değişikliğini oyuna uygula --- if (gameMode === 'endless' && gameActive && !gamePaused) { // BPM'i ve beatInterval'ı güncelle beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; if (bpmDisplayTxt) { bpmDisplayTxt.setText(bpm + ' BPM'); } drawWaveformLK(); // Müzik de BPM'e göre değişsin var musicId = '100BPMSynthWave'; if (bpm === 60) { musicId = '60BPMLowKey'; } else if (bpm === 40) { musicId = '40BPMDeepVibes'; } else if (bpm === 45) { musicId = '45BPMChillWave'; } else if (bpm === 50) { musicId = '50BPMSlowMotion'; } else if (bpm === 55) { musicId = '55BPMDreamscape'; } else if (bpm === 65) { musicId = '65BPMRelaxed'; } else if (bpm === 70) { musicId = '70BPMCalmVibes'; } else if (bpm === 75) { musicId = '75BPMElectricDreams'; } else if (bpm === 80) { musicId = '80BPMNeonNights'; } else if (bpm === 85) { musicId = '85BPMCyberFlow'; } else if (bpm === 90) { musicId = '90BPMDigitalPulse'; } else if (bpm === 95) { musicId = '95BPMFutureBass'; } else if (bpm === 100) { musicId = '100BPMSynthWave'; } else if (bpm === 105) { musicId = '105BPMHyperDrive'; } else if (bpm === 110) { musicId = '110BPMTechnoRush'; } else if (bpm === 115) { musicId = '115BPMHighEnergy'; } else if (bpm === 120) { musicId = '120BPMMaximum'; } var volumeMultiplier = soundVolume / 100; LK.stopMusic(); LK.playMusic(musicId, { volume: 1 * volumeMultiplier, loop: true }); if (songNameTxt) { var songName = musicId.replace(/^\d+BPM/, ''); songNameTxt.setText(songName); } } updateDebugText(); } lastDragStep = step; } }; // Touch end (up) on circle bpmCircleContainer.up = function (x, y, obj) { dragActive = false; // Animate back to normal scale tween(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 version number to bottom left corner of main menu var versionTxt = new Text2('v1.01', { size: 24, fill: 0xffffff }); versionTxt.scale.set(1.5, 1.5); versionTxt.anchor.set(0, 1); versionTxt.x = 50; versionTxt.y = 2732 - 50; mainMenuContainer.addChild(versionTxt); // Add menu to game game.addChild(mainMenuContainer); // menus are already added above // Hide all gameplay UI until game starts scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; // missTxt removed, only hearts for 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(options) { scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; if (bpmDisplayTxt) { var currentPlayingBpm = isTutorial ? 60 : bpm; bpmDisplayTxt.setText(currentPlayingBpm + ' BPM'); } gamescreen.visible = true; mainMenuContainer.visible = false; _originalStartGame(options); }; // On menu, hide gameplay UI and gamescreen mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; 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 (!gameActive || !gamescreen.visible) { return; } // Play button click sound LK.getSound('buttonclick').play(); // Pause the game 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 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; gamePaused = false; gameActive = false; // Stop game temporarily during restart maxCombo = 0; perfectCount = 0; maxPerfectCombo = 0; // Reset lives and hearts before showing ready screen lives = MAX_LIVES; heartshields = 3; // Reset heartshields // Continue from current level - if tutorial not completed, restart tutorial if (!tutorialCompleted) { currentLevel = 1; storage.currentLevel = currentLevel; } // 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 heartshields heartIcons = []; var heartY = 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; gamePaused = false; gameActive = false; maxCombo = 0; perfectCount = 0; maxPerfectCombo = 0; mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; 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() { gamePaused = false; pauseContainer.visible = false; // Show BPM and song name when resuming 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 --- game.update = function () { if (!gameActive || gamePaused) { return; } // Time management var now = Date.now(); var dt = (now - lastTickTime) / 1000; lastTickTime = now; songTime += dt; // Prevent beat spawning until countdown is finished and gameplay is visible if (!gamescreen.visible || countdownContainer.visible) { return; } // Only allow beat spawning after countdown is finished if (!countdownContainer.visible) { // Spawn new beats with constant speed and BPM-based timing var currentBeatSpeed = getBeatSpeed(); // Always 400 px/s // Calculate travel time from left edge (x=0) to hit zone var travelTime = HIT_ZONE_X / currentBeatSpeed; // Time needed to travel from x=0 to hit zone // Spawn beats at the right time so they arrive at hit zone exactly when needed while (nextBeatIdx < beatPattern.length && beatPattern[nextBeatIdx].time - songTime <= travelTime) { spawnBeat(beatPattern[nextBeatIdx]); nextBeatIdx++; } } // Tutorial completion check - 5k points for chapter 1 if (isTutorial && score >= 5000 && !tutorialCompleted) { tutorialCompleted = true; storage.tutorialCompleted = true; currentLevel = 2; storage.currentLevel = currentLevel; // Show tutorial completion message var tutorialCompleteTxt = new Text2('CHAPTER 1 COMPLETE!', { size: 72, fill: 0x00ff00 }); tutorialCompleteTxt.scale.set(2, 2); tutorialCompleteTxt.anchor.set(0.5, 0.5); tutorialCompleteTxt.x = 2048 / 2; tutorialCompleteTxt.y = 1200; game.addChild(tutorialCompleteTxt); tween(tutorialCompleteTxt, { alpha: 0, y: tutorialCompleteTxt.y - 100 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { tutorialCompleteTxt.destroy(); } }); } // Check if player reached 5k points - pause game and show black screen (only in story mode) if (score >= 5000 && gameActive && gameMode === "story") { // Pause the game gameActive = false; gamePaused = true; // Stop current music LK.stopMusic(); // Hide all game UI elements scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Create ch1panF screen overlay var blackScreen = LK.getAsset('ch1panF', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0 }); game.addChild(blackScreen); // Play 60BPM music when ch1panF appears var volumeMultiplier = soundVolume / 100; LK.playMusic('60BPMLowKey', { volume: 1 * volumeMultiplier, loop: true }); // Fade in ch1panF screen tween(blackScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Add ch1panFnarbox asset 1 second after ch1panF appears LK.setTimeout(function () { var ch1panFnarbox = LK.getAsset('ch1panFnarbox', { anchorX: 1, anchorY: 1, x: 2048 - 50, y: 2732 - 50, alpha: 0 }); game.addChild(ch1panFnarbox); // Fade in the narbox tween(ch1panFnarbox, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Play ch1voice1 and ch1voice2 randomly within 3 seconds after narbox appears var voice1Delay = Math.random() * 3000; // Random delay between 0-3 seconds var voice2Delay = Math.random() * 3000; // Random delay between 0-3 seconds LK.setTimeout(function () { LK.getSound('ch1voice1').play(); }, voice1Delay); LK.setTimeout(function () { LK.getSound('ch1voice2').play(); }, voice2Delay); // After 3 seconds, fade out music and assets, return to black screen LK.setTimeout(function () { // Fade out the music LK.playMusic('60BPMLowKey', { fade: { start: 1, end: 0, duration: 1000 } }); // Fade out ch1panF asset tween(blackScreen, { alpha: 0 }, { duration: 1000, easing: tween.easeOut }); // Fade out ch1panFnarbox asset tween(ch1panFnarbox, { alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Remove assets after fadeout blackScreen.destroy(); ch1panFnarbox.destroy(); // Stop the music completely LK.stopMusic(); // After fadeout, add Chapter 2 assets and sound LK.setTimeout(function () { // Set background to black game.setBackgroundColor(0x000000); // Add Chapter 2 asset with fade in effect var chapter2Asset = LK.getAsset('Chapter2', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100, alpha: 0, scaleX: 0.4, scaleY: 0.4 }); game.addChild(chapter2Asset); // Add nightshift asset with fade in effect var nightshiftAsset = LK.getAsset('nightshift', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 80, // 20px yukarı çıkarıldı alpha: 0, scaleX: 0.25, scaleY: 0.25 }); game.addChild(nightshiftAsset); // Fade in chapter2 asset tween(chapter2Asset, { alpha: 1 }, { duration: 2000, easing: tween.easeOut }); // Fade in nightshift asset tween(nightshiftAsset, { alpha: 1 }, { duration: 2000, delay: 500, easing: tween.easeOut }); // Play nightshift sound when assets appear var nightshiftSound = LK.getSound('nightshift'); nightshiftSound.play(); // Get nightshift sound duration from asset definition (start:0, end:1, so use full duration) var nightshiftSoundAsset = LK.getSound('nightshift'); var nightshiftDuration = 0; // Defensive: try to get duration from sound asset, fallback to 3000ms if not available if (nightshiftSoundAsset && nightshiftSoundAsset.duration) { nightshiftDuration = nightshiftSoundAsset.duration * 1000; } else { nightshiftDuration = 3000; } // Remove chapter2 and nightshift assets when sound finishes LK.setTimeout(function () { // Remove chapter2 asset if (chapter2Asset && chapter2Asset.parent) { chapter2Asset.parent.removeChild(chapter2Asset); chapter2Asset.destroy(); } // Remove nightshift asset if (nightshiftAsset && nightshiftAsset.parent) { nightshiftAsset.parent.removeChild(nightshiftAsset); nightshiftAsset.destroy(); } // Play chapter2music after assets are removed var volumeMultiplier = soundVolume / 100; LK.playMusic('chapter2music', { volume: 0.3 * volumeMultiplier, loop: true }); // Add ch2pan1-5 assets when chapter2music starts, but only show ch2pan1 at first, and require tap to advance each panel var ch2panAssets = []; var ch2PanNames = ['ch2pan1', 'ch2pan2', 'ch2pan3', 'ch2pan4', 'ch2pan5']; var ch2CurrentPanel = 0; var ch2PanelActive = false; var ch2PanelContainer = new Container(); game.addChild(ch2PanelContainer); // Helper to show a panel by index function showCh2Panel(panelIdx) { // Show each panel (ch2pan1 through ch2pan5) before starting chapter 2 if (panelIdx < ch2PanNames.length) { // Create and show the current panel with sliding animation var slideFromRight = Math.random() < 0.5; // Randomly choose slide direction var startX = slideFromRight ? 2048 + 400 : -400; // Start from off-screen var panelAsset = LK.getAsset(ch2PanNames[panelIdx], { anchorX: 0.5, anchorY: 0.5, x: startX, y: 2732 / 2, alpha: 0, scaleX: 0.85, scaleY: 0.85, rotation: (Math.random() < 0.5 ? -1 : 1) * (Math.random() * 5 + 2) * Math.PI / 180 }); ch2PanelContainer.addChild(panelAsset); ch2panAssets.push(panelAsset); // Play paperswosh sound when panel appears LK.getSound('paperswosh').play(); // Fade in and slide the panel to center tween(panelAsset, { alpha: 1, x: 2048 / 2 // Slide to center }, { duration: 1000, easing: tween.easeOut }); ch2PanelActive = true; return; } // Only start chapter 2 after all panels have been shown if (panelIdx >= ch2PanNames.length) { var startChapter2 = function startChapter2() { // Set chapter 2 state isTutorial = false; bpm = 90; beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; combo = 0; perfectCombo = 0; lives = MAX_LIVES; heartshields = 3; lastBpmUpgradeScore = 0; // Set UI for gameplay scoreTxt.setText('0'); comboTxt.setText(''); bpmDisplayTxt.setText('90 BPM'); if (songNameTxt) { songNameTxt.setText('DigitalPulse'); } // Show gameplay UI scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; songNameTxt.visible = true; gamescreen.visible = true; mainMenuContainer.visible = false; // Play 90BPM music var volumeMultiplier = soundVolume / 100; LK.playMusic('90BPMDigitalPulse', { volume: 1 * volumeMultiplier, loop: true }); // Start countdown, then start game showCountdown(function () { // Patch win condition for chapter 2: 10K points // --- CHAPTER 2 GAMEPLAY: 90BPM, beats move at 90BPM, win at 10K points --- game.update = function () { if (!gameActive || gamePaused) { return; } // Time management var now = Date.now(); var dt = (now - lastTickTime) / 1000; lastTickTime = now; songTime += dt; // Prevent beat spawning until countdown is finished and gameplay is visible if (!gamescreen.visible || countdownContainer.visible) { return; } // Only allow beat spawning after countdown is finished if (!countdownContainer.visible) { // --- 90BPM: spawn beats at 90BPM --- var currentBeatSpeed = getBeatSpeed(); // Always 400 px/s var travelTime = HIT_ZONE_X / currentBeatSpeed; // Spawn beats at the right time so they arrive at hit zone exactly when needed while (nextBeatIdx < beatPattern.length && beatPattern[nextBeatIdx].time - songTime <= travelTime) { spawnBeat(beatPattern[nextBeatIdx]); nextBeatIdx++; } } // CH2: 10K puan - show blackscreen if (score >= 10000 && gameActive) { gameActive = false; gamePaused = true; // Stop current music LK.stopMusic(); // Hide all game UI elements scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Create black screen overlay var blackScreen = LK.getAsset('ch2panF', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0, tint: 0x000000 // Make it black }); game.addChild(blackScreen); // Add devamiicin asset on black screen var blackScreenAsset = LK.getAsset('devamiicin', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0, scaleX: 0.5, scaleY: 0.5 }); game.addChild(blackScreenAsset); // Add pause button to devamiicin screen var devamiicınPauseBtn = LK.getAsset('pause', { anchorX: 1, anchorY: 0, x: 2048 - 50, y: 220, scaleX: 1.6, scaleY: 1.6, alpha: 0 }); game.addChild(devamiicınPauseBtn); // Fade in pause button with devamiicin asset tween(devamiicınPauseBtn, { alpha: 1 }, { duration: 1000, delay: 500, easing: tween.easeOut }); // Pause button click handler for devamiicin screen devamiicınPauseBtn.down = function (x, y, obj) { // Play button click sound LK.getSound('buttonclick').play(); // Show pause menu gamePaused = true; showPauseMenu(); }; // Fade in black screen tween(blackScreen, { alpha: 1 }, { duration: 500, easing: tween.easeOut }); // Fade in devamiicin asset after black screen appears tween(blackScreenAsset, { alpha: 1 }, { duration: 1000, delay: 500, easing: tween.easeOut }); // Add tap-to-return functionality after 3 seconds var devamiicınTapEnabled = false; LK.setTimeout(function () { devamiicınTapEnabled = true; }, 3000); // Touch handler for devamiicin screen blackScreen.down = function (x, y, obj) { if (devamiicınTapEnabled) { // Play button click sound LK.getSound('buttonclick').play(); // Return to main menu LK.stopMusic(); gameActive = false; gamePaused = false; // Clean up devamiicin screen elements if (blackScreen && blackScreen.parent) { blackScreen.parent.removeChild(blackScreen); blackScreen.destroy(); } if (blackScreenAsset && blackScreenAsset.parent) { blackScreenAsset.parent.removeChild(blackScreenAsset); blackScreenAsset.destroy(); } if (devamiicınPauseBtn && devamiicınPauseBtn.parent) { devamiicınPauseBtn.parent.removeChild(devamiicınPauseBtn); devamiicınPauseBtn.destroy(); } // Show main menu mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; songNameTxt.visible = false; gamescreen.visible = false; // Play menu music (only if not muted) if (!musicMuted) { LK.setTimeout(function () { var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); }, 100); } } }; return; } // Move beats and check for misses (copy from original game.update) for (var i = beats.length - 1; i >= 0; i--) { var beat = beats[i]; var currentBeatSpeed = getBeatSpeed(); var elapsedTime = songTime - (beat.beatTime - HIT_ZONE_X / currentBeatSpeed); beat.x = elapsedTime * currentBeatSpeed; // Dalga boyunu daha belirgin yapmak için waveLength'i küçült var waveLength = 80; var phase = -HIT_ZONE_X / waveLength; beat.y = HIT_ZONE_Y + Math.sin(beat.x / waveLength + phase) * 500; if (beat.x < -400) { beat.x = -400; } if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 400) { beat.x = TIMELINE_X + TIMELINE_WIDTH + 400; } if (kickTarget && kickTarget.parent) { var kickTargetIndex = kickTarget.parent.getChildIndex(kickTarget); var beatIndex = beat.parent.getChildIndex(beat); if (beatIndex > kickTargetIndex) { beat.parent.setChildIndex(beat, kickTargetIndex); } } if (typeof beat.lastX === "undefined") { beat.lastX = beat.x; } // Only check for miss if the beat has passed the hit zone (not before) // Also, do not count as miss if the beat has not yet reached the hit zone at least once if (!beat.hit && beat.active && typeof beat.hasReachedHitZone !== "undefined" && beat.hasReachedHitZone && beat.lastX <= HIT_ZONE_X + HIT_WINDOW && beat.x > HIT_ZONE_X + HIT_WINDOW) { // Missed beat.active = false; misses++; // Replace the beat marker asset with missCircle for missed beats var missedAsset = LK.getAsset('missCircle', { anchorX: 0.5, anchorY: 0.5, x: beat.x, y: beat.y, alpha: 0.8, width: 60, height: 59.77 }); // Replace the beat's current asset with missCircle if (beat.parent) { // Store parent reference before removing beat var beatParent = beat.parent; // Remove the old beat marker from its parent beatParent.removeChild(beat); // Add the missed asset to the stored parent reference beatParent.addChild(missedAsset); // Update the beat reference to point to the new asset beats[i] = missedAsset; // Copy over important properties missedAsset.beatTime = beat.beatTime; missedAsset.type = beat.type; missedAsset.active = false; missedAsset.hit = false; missedAsset.lastX = beat.x; missedAsset.hasReachedHitZone = true; missedAsset.isMissed = true; // Destroy the original beat asset beat.destroy(); } // Handle heartshield system if (heartshields > 0) { // Convert leftmost heartshield to heart (heartshields are lost left to right) var shieldIndex = 3 - heartshields; 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]); } heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All heartshields are gone, now show miss effects and lose hearts beat.showFeedback('miss'); combo = 0; perfectCombo = 0; comboTxt.setText(''); LK.effects.flashObject(kickTarget, 0xff0000, 200); LK.getSound('miss').play(); // Show MISS feedback text at hit zone var missText = new Text2('MISS', { size: 60, fill: 0xff0000 }); missText.scale.set(2, 2); missText.anchor.set(0.5, 0.5); missText.x = HIT_ZONE_X; missText.y = HIT_ZONE_Y - 180; game.addChild(missText); tween(missText, { alpha: 0, y: missText.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { missText.destroy(); } }); // Lose a heart and update heart icon (hearts are lost left to right) if (lives > 0) { var lostIndex = MAX_LIVES - lives; 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]); } lives--; } if (lives <= 0) { gameOver(); } } } // Track if beat has reached the hit zone at least once if (typeof beat.hasReachedHitZone === "undefined") { beat.hasReachedHitZone = false; } if (!beat.hasReachedHitZone && beat.x >= HIT_ZONE_X - HIT_WINDOW) { beat.hasReachedHitZone = true; } // Remove if off screen if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 100) { beat.destroy(); beats.splice(i, 1); } // Update lastX for next frame beat.lastX = beat.x; } }; // Start game state gameActive = true; gamePaused = false; // Defensive: clear beats array for (var i = 0; i < beats.length; i++) { beats[i].destroy(); } beats = []; // Remove any beat at the kick target 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); } } // Reset hearts for (var i = 0; i < heartIcons.length; i++) { if (heartIcons[i] && heartIcons[i].parent) { heartIcons[i].parent.removeChild(heartIcons[i]); } } heartIcons = []; heartshields = MAX_LIVES; 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); } // Set timeout to remove heartshields after 3 seconds in chapter 2 tween({}, {}, { duration: 3000, onFinish: function onFinish() { if (gameActive && heartshields > 0) { heartshields = 0; updateHeartsUI(lives, heartshields); } } }); }); }; // Remove tap handler // Remove all ch2pan panels and container for (var i = 0; i < ch2panAssets.length; i++) { if (ch2panAssets[i] && ch2panAssets[i].parent) { ch2panAssets[i].parent.removeChild(ch2panAssets[i]); ch2panAssets[i].destroy(); } } if (ch2PanelContainer && ch2PanelContainer.parent) { ch2PanelContainer.parent.removeChild(ch2PanelContainer); } // Show "Get ready to feel the beat." message before starting chapter 2 var readyMessage = new Text2('Get ready to feel the beat.', { size: 60, fill: 0xffffff }); readyMessage.scale.set(2, 2); readyMessage.anchor.set(0.5, 0.5); readyMessage.x = 2048 / 2; readyMessage.y = 2732 / 2; readyMessage.alpha = 0; game.addChild(readyMessage); // Fade in the message tween(readyMessage, { alpha: 1 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Hold for 2 seconds then fade out tween(readyMessage, { alpha: 0 }, { duration: 1000, delay: 2000, easing: tween.easeOut, onFinish: function onFinish() { readyMessage.destroy(); // Start chapter 2 after message disappears startChapter2(); } }); } }); game.down = originalGameDown; ch2PanelActive = false; return; } // If not first panel, do nothing (skip all ch2pan panels) ch2PanelActive = false; } // Save original game.down so we can restore it after panels var originalGameDown = game.down; // Start with ch2pan1 ch2CurrentPanel = 0; showCh2Panel(ch2CurrentPanel); // Override game.down to advance panels on tap anywhere game.down = function (x, y, obj) { if (!ch2PanelActive) { if (typeof originalGameDown === "function") { originalGameDown(x, y, obj); } return; } // If we've shown all panels, start chapter 2 if (ch2CurrentPanel >= ch2PanNames.length - 1) { ch2PanelActive = false; // Fade out all panels simultaneously var fadeCount = ch2panAssets.length; var faded = 0; for (var i = 0; i < ch2panAssets.length; i++) { if (ch2panAssets[i] && ch2panAssets[i].parent) { tween(ch2panAssets[i], { alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function () { if (this.parent) { this.parent.removeChild(this); } this.destroy(); faded++; // After all panels faded, start chapter 2 if (faded === fadeCount) { if (ch2PanelContainer && ch2PanelContainer.parent) { ch2PanelContainer.parent.removeChild(ch2PanelContainer); } // Start chapter 2 with showCh2Panel function that handles the actual chapter 2 logic showCh2Panel(ch2PanNames.length); // This will trigger chapter 2 start } }.bind(ch2panAssets[i]) }); } } return; } // Advance to next panel ch2CurrentPanel++; showCh2Panel(ch2CurrentPanel); }; }, nightshiftDuration); }, 1000); } }); }, 3000); }, 1000); } // Check for BPM progression every 10,000 points (only for non-tutorial) if (!isTutorial && score >= 10000 && score - lastBpmUpgradeScore >= 10000) { if (bpm < 120) { // Don't increase BPM beyond 120 lastBpmUpgradeScore = score; var oldBpm = bpm; bpm = Math.min(120, bpm + 5); // Increase by 5 BPM, cap at 120 if (bpm !== oldBpm) { // Update BPM display to show current playing song BPM if (bpmDisplayTxt) { // Show the actual BPM of the currently playing song, not the settings BPM var currentPlayingBpm = bpm; // This is the actual BPM being used for gameplay bpmDisplayTxt.setText(currentPlayingBpm + ' BPM'); } // Update beat interval for new beats beatInterval = 60 / bpm; // Get new music ID var newMusicId = '100BPMSynthWave'; // Default fallback if (bpm === 40) { newMusicId = '40BPMDeepVibes'; } else if (bpm === 45) { newMusicId = '45BPMChillWave'; } else if (bpm === 50) { newMusicId = '50BPMSlowMotion'; } else if (bpm === 55) { newMusicId = '55BPMDreamscape'; } else if (bpm === 60) { newMusicId = '60BPMLowKey'; } else if (bpm === 65) { newMusicId = '65BPMRelaxed'; } else if (bpm === 70) { newMusicId = '70BPMCalmVibes'; } else if (bpm === 75) { newMusicId = '75BPMElectricDreams'; } else if (bpm === 80) { newMusicId = '80BPMNeonNights'; } else if (bpm === 85) { newMusicId = '85BPMCyberFlow'; } else if (bpm === 90) { newMusicId = '90BPMDigitalPulse'; } else if (bpm === 95) { newMusicId = '95BPMFutureBass'; } else if (bpm === 100) { newMusicId = '100BPMSynthWave'; } else if (bpm === 105) { newMusicId = '105BPMHyperDrive'; } else if (bpm === 110) { newMusicId = '110BPMTechnoRush'; } else if (bpm === 115) { newMusicId = '115BPMHighEnergy'; } else if (bpm === 120) { newMusicId = '120BPMMaximum'; } // Fade out current music and fade in new music var volumeMultiplier = soundVolume / 100; LK.stopMusic(); LK.playMusic(newMusicId, { volume: 0, loop: true, fade: { start: 0, end: 1 * volumeMultiplier, duration: 1000 } }); // Play scratch sound when BPM changes LK.getSound('scratch').play(); // Update song name display if (songNameTxt) { var songName = newMusicId.replace(/^\d+BPM/, ''); songNameTxt.setText(songName); } // --- BPM değiştiğinde waveform'u güncelle --- drawWaveformLK(); updateDebugText(); } } } // Move beats and check for misses for (var i = beats.length - 1; i >= 0; i--) { var beat = beats[i]; // Move beats at constant speed from left to right var currentBeatSpeed = getBeatSpeed(); // Always 400 px/s // Calculate elapsed time since beat was spawned var elapsedTime = songTime - (beat.beatTime - HIT_ZONE_X / currentBeatSpeed); // Move beat from left edge at constant speed beat.x = elapsedTime * currentBeatSpeed; // --- YENİ: Beat yörüngesi waveform ile senkron olsun --- var params = getWaveParams(bpm); var waveLength = params.waveLength; var waveAmplitude = params.waveAmplitude; var phase = -HIT_ZONE_X / waveLength; beat.y = HIT_ZONE_Y + Math.sin(beat.x / waveLength + phase) * waveAmplitude; // ... existing code ... // Ensure beat stays within reasonable bounds if (beat.x < -400) { beat.x = -400; } if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 400) { beat.x = TIMELINE_X + TIMELINE_WIDTH + 400; } // Ensure beatMarker always passes below kickTarget if (kickTarget && kickTarget.parent) { var kickTargetIndex = kickTarget.parent.getChildIndex(kickTarget); var beatIndex = beat.parent.getChildIndex(beat); if (beatIndex > kickTargetIndex) { beat.parent.setChildIndex(beat, kickTargetIndex); } } // Track lastX for miss detection if (typeof beat.lastX === "undefined") { beat.lastX = beat.x; } // Only check for miss if the beat has passed the hit zone (not before) // Also, do not count as miss if the beat has not yet reached the hit zone at least once if (!beat.hit && beat.active && typeof beat.hasReachedHitZone !== "undefined" && beat.hasReachedHitZone && beat.lastX <= HIT_ZONE_X + HIT_WINDOW && beat.x > HIT_ZONE_X + HIT_WINDOW) { // Missed beat.active = false; misses++; // Replace the beat marker asset with missCircle for missed beats var missedAsset = LK.getAsset('missCircle', { anchorX: 0.5, anchorY: 0.5, x: beat.x, // Start from current beat position y: beat.y, alpha: 0.8, width: 60, height: 59.77 }); // Replace the beat's current asset with missCircle if (beat.parent) { // Store parent reference before removing beat var beatParent = beat.parent; // Remove the old beat marker from its parent beatParent.removeChild(beat); // Add the missed asset to the stored parent reference beatParent.addChild(missedAsset); // Update the beat reference to point to the new asset beats[i] = missedAsset; // Copy over important properties missedAsset.beatTime = beat.beatTime; missedAsset.type = beat.type; missedAsset.active = false; missedAsset.hit = false; missedAsset.lastX = beat.x; // Use current position missedAsset.hasReachedHitZone = true; missedAsset.isMissed = true; // Mark as missed for special handling // Add empty updateTrail method to prevent errors missedAsset.updateTrail = function () {}; // Destroy the original beat asset beat.destroy(); } // Handle heartshield system if (heartshields > 0) { // Convert leftmost heartshield to heart (heartshields are lost left to right) var shieldIndex = 3 - 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]); } heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All heartshields are gone, now show miss effects and lose hearts beat.showFeedback('miss'); combo = 0; perfectCombo = 0; // Reset perfect combo on miss // missTxt removed, only hearts for lives comboTxt.setText(''); LK.effects.flashObject(kickTarget, 0xff0000, 200); // Play miss sound LK.getSound('miss').play(); // Show MISS feedback text at hit zone var missText = new Text2('MISS', { size: 60, fill: 0xff0000 }); missText.scale.set(2, 2); missText.anchor.set(0.5, 0.5); missText.x = HIT_ZONE_X; missText.y = HIT_ZONE_Y - 180; game.addChild(missText); tween(missText, { alpha: 0, y: missText.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { missText.destroy(); } }); // Lose a heart and update heart icon (hearts are lost left to right) if (lives > 0) { var lostIndex = MAX_LIVES - 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]); } lives--; } if (lives <= 0) { gameOver(); } } } // Track if beat has reached the hit zone at least once if (typeof beat.hasReachedHitZone === "undefined") { beat.hasReachedHitZone = false; } if (!beat.hasReachedHitZone && beat.x >= HIT_ZONE_X - HIT_WINDOW) { beat.hasReachedHitZone = true; } // Remove if off screen if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 100) { beat.destroy(); beats.splice(i, 1); } // Update lastX for next frame beat.lastX = beat.x; } }; // --- Input handling --- var isTouching = false; game.down = function (x, y, obj) { if (!gameActive || 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; var scoreAdd = 0; if (distFromCenter < 20) { feedbackType = 'perfect'; feedbackText = 'PERFECT!'; feedbackColor = 0xffe600; scoreAdd = 200; perfectCount++; // Increment perfect count perfectCombo++; // Increment consecutive perfect hits if (perfectCombo > maxPerfectCombo) { maxPerfectCombo = perfectCombo; // Track maximum consecutive perfect hits } // Intense screen shake for perfect hits, increasing with 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; scoreAdd = 150; perfectCombo = 0; // Reset perfect 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; scoreAdd = 100; perfectCombo = 0; // Reset perfect 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; scoreAdd = 50; perfectCombo = 0; // Reset perfect 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; score += scoreAdd; combo += 1; // Trail renklerini güncelle updateBeatTrailOnHit(beat, feedbackType); if (combo > maxCombo) { maxCombo = combo; // Track maximum combo achieved } LK.setScore(score); scoreTxt.setText(score); comboTxt.setText('Combo: ' + 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 combo multiplier for perfect hits if (feedbackType === 'perfect' && perfectCombo > 1) { var comboMultiplierTxt = new Text2('x' + perfectCombo, { size: 21, fill: 0xff8c00 }); comboMultiplierTxt.scale.set(4, 4); comboMultiplierTxt.anchor.set(0, 0.5); comboMultiplierTxt.x = HIT_ZONE_X + 195; comboMultiplierTxt.y = HIT_ZONE_Y - 220; game.addChild(comboMultiplierTxt); tween(comboMultiplierTxt, { alpha: 0, y: comboMultiplierTxt.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { 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++; combo = 0; perfectCombo = 0; // Reset perfect combo on wrong time // missTxt removed, only hearts for lives 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 (heartshields > 0) { // Convert leftmost heartshield to heart (heartshields are lost left to right) var shieldIndex = 3 - 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]); } heartshields--; // Lose one heartshield // No miss feedback, sound, or other penalties for heartshield hits } else { // All heartshields are gone, now lose hearts // Lose a life and update heart icon if (lives > 0) { var lostIndex = MAX_LIVES - 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]); } lives--; } if (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() { 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 bpmCircleContainer !== 'undefined' && bpmCircleContainer) { addGlowEffect(bpmCircleContainer, 0x8a2be2, 0.2); } } // Initialize glow effects applyGlowToVioletElements(); // --- Win condition --- function checkWin() { if (score >= 5000) { gameActive = false; LK.effects.flashScreen(0x00ff00, 800); LK.showYouWin(); LK.stopMusic(); } } // --- Defeat Screen Implementation --- var defeatContainer = new Container(); defeatContainer.visible = false; game.addChild(defeatContainer); var maxCombo = 0; // Track maximum combo achieved var perfectCount = 0; // Track number of perfect hits var perfectCombo = 0; // Track current consecutive perfect hits var maxPerfectCombo = 0; // Track maximum consecutive perfect hits var lastBpmUpgradeScore = 0; // Track last score where BPM was upgraded var 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; 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: " + 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 combo display var maxComboTxt = new Text2("Max Combo: " + maxCombo, { size: 54, fill: 0x00ff00 }); maxComboTxt.scale.set(2, 2); maxComboTxt.anchor.set(0, 0.5); maxComboTxt.x = 2048 / 2 - 550; maxComboTxt.y = currentY; defeatContainer.addChild(maxComboTxt); currentY += maxComboTxt.height + statsSpacing; // Perfect count display var perfectCountTxt = new Text2("Perfect: " + perfectCount, { size: 54, fill: 0xffe600 }); perfectCountTxt.scale.set(2, 2); perfectCountTxt.anchor.set(0, 0.5); perfectCountTxt.x = 2048 / 2 - 550; perfectCountTxt.y = currentY; defeatContainer.addChild(perfectCountTxt); currentY += perfectCountTxt.height + statsSpacing; // Max perfect combo display (orange color) var maxPerfectComboTxt = new Text2("Max Perfect Combo: " + maxPerfectCombo, { size: 54, fill: 0xff8c00 }); maxPerfectComboTxt.scale.set(2, 2); maxPerfectComboTxt.anchor.set(0, 0.5); maxPerfectComboTxt.x = 2048 / 2 - 550; maxPerfectComboTxt.y = currentY; defeatContainer.addChild(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; maxCombo = 0; // Reset max combo perfectCount = 0; // Reset perfect count maxPerfectCombo = 0; // Reset max perfect combo // Continue from current level - if tutorial not completed, restart tutorial if (!tutorialCompleted) { currentLevel = 1; storage.currentLevel = currentLevel; } 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; maxCombo = 0; // Reset max combo perfectCount = 0; // Reset perfect count maxPerfectCombo = 0; // Reset max perfect combo mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; 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 = 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 params = getWaveParams(bpm); var waveLength = params.waveLength; var waveAmplitude = params.waveAmplitude; 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 }; } } // Function to get wave parameters based on BPM function getWaveParams(bpm) { bpm = Math.max(40, Math.min(120, bpm)); var minAmp = 80, maxAmp = 500; var minLength = 80, maxLength = 120; var t = (bpm - 40) / (120 - 40); var waveAmplitude = minAmp + (maxAmp - minAmp) * t; var waveLength = minLength + (maxLength - minLength) * t; return { waveLength: waveLength, waveAmplitude: waveAmplitude }; } // Oyun başında ve BPM değiştiğinde çağır: drawWaveformLK(); // Screen shake function function shakeScreen(intensity, duration) { // Maksimum shake süresi sınırı (ör. 600ms) duration = Math.min(duration, 600); var originalX = gamescreen.x; var originalY = gamescreen.y; var originalBpmX = bpmDisplayTxt.x; var originalBpmY = bpmDisplayTxt.y; var originalSongX = songNameTxt.x; var originalSongY = songNameTxt.y; var shakeCount = 0; var maxShakes = Math.floor(duration / 16.67); // 60fps function shake() { if (shakeCount >= maxShakes) { // Son sönümleme: pozisyonu yavaşça sıfırla tween(gamescreen, { x: originalX, y: originalY }, { duration: 120, easing: tween.easeOut }); tween(bpmDisplayTxt, { x: originalBpmX, y: originalBpmY }, { duration: 120, easing: tween.easeOut }); tween(songNameTxt, { x: originalSongX, y: originalSongY }, { duration: 120, easing: tween.easeOut }); return; } // Sönümlü intensity: sona yaklaştıkça azalır var progress = shakeCount / maxShakes; var currentIntensity = intensity * (1 - progress * 0.7); // Sonlara doğru %70 azalır var randomX = (Math.random() - 0.5) * 2 * currentIntensity; var randomY = (Math.random() - 0.5) * 2 * currentIntensity; tween(gamescreen, { x: originalX + randomX, y: originalY + randomY }, { duration: 16.67, easing: tween.easeOut, onFinish: function onFinish() { shakeCount++; shake(); } }); // Textler de sallansın bpmDisplayTxt.x = originalBpmX + randomX; bpmDisplayTxt.y = originalBpmY + randomY; songNameTxt.x = originalSongX + randomX; songNameTxt.y = originalSongY + randomY; } 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); } } } // Game update fonksiyonuna trail güncellemesini ekle var originalGameUpdate = game.update; game.update = function () { originalGameUpdate.call(this); // 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); } } } }; // 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; } var bpmInterval = 60000 / bpm; // BPM'i milisaniyeye çevir var timeSinceStart = Date.now() - beat.pulseStartTime; var pulsePhase = timeSinceStart % bpmInterval / 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" // === Menü Fonksiyonları === function showMainMenu() { mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; 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('endless', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + buttonHeight + buttonSpacing, scaleX: 0.18, scaleY: 0.18 }); mainMenuContainer.addChild(endlessBtn); 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 ... // === DEBUG: Oyun Modu ve BPM Göstergesi === // Ekranın sol üst köşesine debugText ekle var debugText = new Text2('', { size: 24, fill: 0xff00ff }); debugText.anchor.set(0, 0); debugText.x = 20; debugText.y = 20; LK.gui.top.addChild(debugText); function updateDebugText() { debugText.setText('MODE: ' + gameMode + ' | BPM: ' + bpm); } // startGame fonksiyonunun başında ve mod değiştirirken çağır // ... // === Trail Particle Pool Sistemi === var PARTICLE_POOL_SIZE = 500; var particlePool = []; for (var i = 0; i < PARTICLE_POOL_SIZE; i++) { var p = createTrailParticle(0, 0, 0xffffff); p.visible = false; p.alpha = 0; particlePool.push(p); gamescreen.addChild(p); } // Pool'dan particle al function getPooledParticle(x, y, color, size) { for (var i = 0; i < particlePool.length; i++) { var p = particlePool[i]; if (!p.visible || p.life <= 0) { p.x = x; p.y = y; p.tint = color; p.life = 1.0; p.alpha = 0.5; p.visible = true; p.width = 1; p.height = 1; p.velocityX = 0; p.velocityY = 0; p.fadeSpeed = 0.12; return p; } } return null; } // createTrailParticle fonksiyonunu sadece pool için kullan // updateTrailParticle fonksiyonunu optimize et
===================================================================
--- original.js
+++ change.js
@@ -25,8 +25,52 @@
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;
+ // --- Trail yayılımını BPM'e göre ayarla ---
+ var params = getWaveParams(bpm);
+ var spreadX = params.waveAmplitude * 0.2; // Dalga yüksekliğine orantılı
+ var spreadY = params.waveAmplitude * 0.2;
+ for (var i = 0; i < 10; 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
+ [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, {
@@ -63,9 +107,9 @@
/****
* Game Code
****/
-// --- Game constants ---
+// --- Story Chapters Configuration ---
// Bass drum (kick) hit circle
// Bass drum (kick) hit circle when active
// Timeline bar
// Beat marker
@@ -73,8 +117,18 @@
// 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;
@@ -266,67 +320,279 @@
}
return pattern;
}
// --- Start game ---
-function startGame() {
+function startGame(options) {
+ options = options || {};
+ var mode = options.mode || "story";
// Reset state
for (var i = 0; i < beats.length; i++) {
beats[i].destroy();
}
beats = [];
- // Check if this is tutorial mode (chapter 1)
- if (!tutorialCompleted && currentLevel === 1) {
- isTutorial = true;
- bpm = 60; // Tutorial always starts at 60 BPM
- } else {
+ if (mode === "story") {
+ // Story modunda tutorial kontrolü
+ gameMode = "story";
+ var chapterIdx = typeof currentLevel === 'number' ? currentLevel - 1 : 0;
+ var chapter = STORY_CHAPTERS[chapterIdx] || STORY_CHAPTERS[0];
+ isTutorial = chapterIdx === 0 && !tutorialCompleted;
+ bpm = chapter.bpm;
+ var storyMusicId = chapter.musicId;
+ var storySongName = chapter.songName;
+ beatInterval = 60 / bpm;
+ beatPattern = generateBeatPattern(bpm);
+ nextBeatIdx = 0;
+ songTime = 0;
+ lastTickTime = Date.now();
+ score = 0;
+ misses = 0;
+ ignoredMisses = 0;
+ combo = 0;
+ perfectCombo = 0;
+ lives = MAX_LIVES;
+ heartshields = 3;
+ lastBpmUpgradeScore = 0;
+ // Remove any beat at the kick target at game start
+ 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(lives, heartshields);
+ var heartWidth = 150;
+ var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing;
+ var heartStartX = 2048 - 100 - totalHeartsWidth;
+ var heartY = scoreTxt.y + 50;
+ for (var i = 0; i < heartIcons.length; i++) {
+ if (heartIcons[i] && heartIcons[i].parent) {
+ heartIcons[i].parent.removeChild(heartIcons[i]);
+ }
+ }
+ heartIcons = [];
+ heartshields = MAX_LIVES;
+ 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);
+ }
+ gameActive = true;
+ gamePaused = false;
+ scoreTxt.setText('0');
+ comboTxt.setText('');
+ kickTarget.visible = true;
+ LK.setScore(0);
+ applyGlowToVioletElements();
+ updateAllSoundVolumes();
+ tween({}, {}, {
+ duration: 3000,
+ onFinish: function onFinish() {
+ if (gameActive && heartshields > 0) {
+ heartshields = 0;
+ updateHeartsUI(lives, heartshields);
+ }
+ }
+ });
+ // --- Story modunda sabit müzik ve şarkı adı ---
+ var musicId = storyMusicId;
+ var volumeMultiplier = soundVolume / 100;
+ var musicVolume = isTutorial ? 0.5 : 1;
+ LK.playMusic(musicId, {
+ volume: musicVolume * volumeMultiplier,
+ loop: true
+ });
+ if (bpmDisplayTxt) {
+ bpmDisplayTxt.setText(bpm + ' BPM');
+ }
+ if (songNameTxt) {
+ songNameTxt.setText(storySongName);
+ }
+ // UI gösterimi
+ scoreTxt.visible = true;
+ comboTxt.visible = true;
+ bpmDisplayTxt.visible = true;
+ songNameTxt.visible = true;
+ gamescreen.visible = true;
+ mainMenuContainer.visible = false;
+ updateDebugText();
+ return;
+ } else if (mode === "endless") {
+ gameMode = "endless";
isTutorial = false;
- // Use saved BPM from storage for regular play
bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM;
+ beatInterval = 60 / bpm;
+ beatPattern = generateBeatPattern(bpm);
+ nextBeatIdx = 0;
+ songTime = 0;
+ lastTickTime = Date.now();
+ score = 0;
+ misses = 0;
+ ignoredMisses = 0;
+ combo = 0;
+ perfectCombo = 0;
+ lives = MAX_LIVES;
+ heartshields = 3;
+ lastBpmUpgradeScore = 0;
+ // Remove any beat at the kick target at game start
+ 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(lives, heartshields);
+ var heartWidth = 150;
+ var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing;
+ var heartStartX = 2048 - 100 - totalHeartsWidth;
+ var heartY = scoreTxt.y + 50;
+ for (var i = 0; i < heartIcons.length; i++) {
+ if (heartIcons[i] && heartIcons[i].parent) {
+ heartIcons[i].parent.removeChild(heartIcons[i]);
+ }
+ }
+ heartIcons = [];
+ heartshields = MAX_LIVES;
+ 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);
+ }
+ gameActive = true;
+ gamePaused = false;
+ scoreTxt.setText('0');
+ comboTxt.setText('');
+ kickTarget.visible = true;
+ LK.setScore(0);
+ applyGlowToVioletElements();
+ updateAllSoundVolumes();
+ tween({}, {}, {
+ duration: 3000,
+ onFinish: function onFinish() {
+ if (gameActive && heartshields > 0) {
+ heartshields = 0;
+ updateHeartsUI(lives, heartshields);
+ }
+ }
+ });
+ // --- Play correct BPM music (kickless) ---
+ var musicId = '100BPMSynthWave';
+ if (bpm === 60) {
+ musicId = '60BPMLowKey';
+ } else if (bpm === 40) {
+ musicId = '40BPMDeepVibes';
+ } else if (bpm === 45) {
+ musicId = '45BPMChillWave';
+ } else if (bpm === 50) {
+ musicId = '50BPMSlowMotion';
+ } else if (bpm === 55) {
+ musicId = '55BPMDreamscape';
+ } else if (bpm === 65) {
+ musicId = '65BPMRelaxed';
+ } else if (bpm === 70) {
+ musicId = '70BPMCalmVibes';
+ } else if (bpm === 75) {
+ musicId = '75BPMElectricDreams';
+ } else if (bpm === 80) {
+ musicId = '80BPMNeonNights';
+ } else if (bpm === 85) {
+ musicId = '85BPMCyberFlow';
+ } else if (bpm === 90) {
+ musicId = '90BPMDigitalPulse';
+ } else if (bpm === 95) {
+ musicId = '95BPMFutureBass';
+ } else if (bpm === 100) {
+ musicId = '100BPMSynthWave';
+ } else if (bpm === 105) {
+ musicId = '105BPMHyperDrive';
+ } else if (bpm === 110) {
+ musicId = '110BPMTechnoRush';
+ } else if (bpm === 115) {
+ musicId = '115BPMHighEnergy';
+ } else if (bpm === 120) {
+ musicId = '120BPMMaximum';
+ }
+ var volumeMultiplier = soundVolume / 100;
+ var musicVolume = 1;
+ LK.playMusic(musicId, {
+ volume: musicVolume * volumeMultiplier,
+ loop: true
+ });
+ if (bpmDisplayTxt) {
+ bpmDisplayTxt.setText(bpm + ' BPM');
+ }
+ if (songNameTxt) {
+ var songName = musicId.replace(/^\d+BPM/, '');
+ songNameTxt.setText(songName);
+ }
+ // --- BPM'e göre waveform'u güncelle ---
+ drawWaveformLK();
+ // UI gösterimi
+ scoreTxt.visible = true;
+ comboTxt.visible = true;
+ bpmDisplayTxt.visible = true;
+ songNameTxt.visible = true;
+ gamescreen.visible = true;
+ mainMenuContainer.visible = false;
+ updateDebugText();
+ return;
}
- // Always use current bpm and calculate beatInterval accordingly
beatInterval = 60 / bpm;
- beatPattern = generateBeatPattern(bpm); // Generate 100,000 beats
+ beatPattern = generateBeatPattern(bpm);
nextBeatIdx = 0;
songTime = 0;
lastTickTime = Date.now();
score = 0;
misses = 0;
- ignoredMisses = 0; // Reset ignored misses counter
+ ignoredMisses = 0;
combo = 0;
- perfectCombo = 0; // Reset perfect combo
+ perfectCombo = 0;
lives = MAX_LIVES;
- heartshields = 3; // Reset heartshields
- lastBpmUpgradeScore = 0; // Reset BPM upgrade tracking
- // Remove any beat that is at the kick target at game start (defensive, in case of bug)
- // Also, prevent initial miss by ensuring no beat is at or past the hit zone at game start
+ heartshields = 3;
+ lastBpmUpgradeScore = 0;
+ // Remove any beat at the kick target at game start
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);
}
}
- // Defensive: Also ensure no beat is created at the kick target at game start
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);
}
- // Reset heart icons to full heartshields
updateHeartsUI(lives, heartshields);
- // Recalculate miss text width and heart positions on restart
var heartWidth = 150;
var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing;
var heartStartX = 2048 - 100 - totalHeartsWidth;
var heartY = scoreTxt.y + 50;
- // 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 heartshields
heartIcons = [];
- heartshields = MAX_LIVES; // Reset heartshields counter first
- lives = MAX_LIVES; // Reset lives counter to ensure proper state
+ heartshields = MAX_LIVES;
+ lives = MAX_LIVES;
for (var i = 0; i < MAX_LIVES; i++) {
var heartAsset = LK.getAsset('heartshield', {
anchorX: 0,
anchorY: 0,
@@ -339,16 +605,12 @@
gameActive = true;
gamePaused = false;
scoreTxt.setText('0');
comboTxt.setText('');
- // missTxt removed, only hearts for lives
kickTarget.visible = true;
LK.setScore(0);
- // Reapply glow effects after game restart
applyGlowToVioletElements();
- // Apply current sound volume settings
updateAllSoundVolumes();
- // Set timeout to remove heartshields after 3 seconds
tween({}, {}, {
duration: 3000,
onFinish: function onFinish() {
if (gameActive && heartshields > 0) {
@@ -357,9 +619,9 @@
}
}
});
// --- Play correct BPM music (kickless) ---
- var musicId = '100BPMSynthWave'; // Default fallback
+ var musicId = '100BPMSynthWave';
if (bpm === 60) {
musicId = '60BPMLowKey';
} else if (bpm === 40) {
musicId = '40BPMDeepVibes';
@@ -368,10 +630,8 @@
} else if (bpm === 50) {
musicId = '50BPMSlowMotion';
} else if (bpm === 55) {
musicId = '55BPMDreamscape';
- } else if (bpm === 60) {
- musicId = '60BPMLowKey';
} else if (bpm === 65) {
musicId = '65BPMRelaxed';
} else if (bpm === 70) {
musicId = '70BPMCalmVibes';
@@ -396,19 +656,29 @@
} else if (bpm === 120) {
musicId = '120BPMMaximum';
}
var volumeMultiplier = soundVolume / 100;
- var musicVolume = isTutorial ? 0.5 : 1; // Tutorial uses 0.5 volume, regular play uses 1
+ var musicVolume = 1;
LK.playMusic(musicId, {
volume: musicVolume * volumeMultiplier,
loop: true
});
- // Update song name display
+ if (bpmDisplayTxt) {
+ bpmDisplayTxt.setText(bpm + ' BPM');
+ }
if (songNameTxt) {
- // Remove BPM number and "BPM" text from the beginning
var songName = musicId.replace(/^\d+BPM/, '');
songNameTxt.setText(songName);
}
+ // --- BPM'e göre waveform'u güncelle ---
+ drawWaveformLK();
+ // UI gösterimi
+ scoreTxt.visible = true;
+ comboTxt.visible = true;
+ bpmDisplayTxt.visible = true;
+ songNameTxt.visible = true;
+ gamescreen.visible = true;
+ mainMenuContainer.visible = false;
}
// --- Main Menu Implementation ---
// Add main menu background image, anchored top-left, covering the whole game area
var mainMenuContainer = new Container();
@@ -436,9 +706,9 @@
var buttonStartY = 1000;
var buttonHeight = 289; // Approximate height with scale 5
var buttonSpacing = 5;
// Start Button
-var startBtn = LK.getAsset('startbutton', {
+var startBtn = LK.getAsset('start', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: buttonStartY,
@@ -450,9 +720,9 @@
var settingsBtn = LK.getAsset('settingsbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
- y: buttonStartY + buttonHeight + buttonSpacing,
+ y: buttonStartY + 4 * (buttonHeight + buttonSpacing),
scaleX: 0.18,
scaleY: 0.18
});
mainMenuContainer.addChild(settingsBtn);
@@ -1230,17 +1500,17 @@
}
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;
+ var calculatedBpm = dragStartBpm + step * BPM_STEP;
+ if (calculatedBpm < BPM_MIN) {
+ calculatedBpm = BPM_MIN;
}
- if (newBpm > BPM_MAX) {
- newBpm = BPM_MAX;
+ if (calculatedBpm > BPM_MAX) {
+ calculatedBpm = BPM_MAX;
}
- if (newBpm !== bpm) {
- bpm = newBpm;
+ if (bpm !== calculatedBpm) {
+ bpm = calculatedBpm;
bpmValueTxt.setText(bpm + "");
beatInterval = 60 / bpm; // Recalculate beat interval for new BPM
// Regenerate beat pattern with new BPM intervals
beatPattern = generateBeatPattern(bpm);
@@ -1252,8 +1522,68 @@
// Show the actual BPM of the currently playing song, not the settings BPM
var currentPlayingBpm = bpm; // This is the actual BPM being used for gameplay
bpmDisplayTxt.setText(currentPlayingBpm + ' BPM');
}
+ drawWaveformLK(); // waveform'u anlık güncelle
+ // --- İYİLEŞTİRME: Eğer endless modda oyun aktifse, BPM değişikliğini oyuna uygula ---
+ if (gameMode === 'endless' && gameActive && !gamePaused) {
+ // BPM'i ve beatInterval'ı güncelle
+ beatInterval = 60 / bpm;
+ beatPattern = generateBeatPattern(bpm);
+ nextBeatIdx = 0;
+ if (bpmDisplayTxt) {
+ bpmDisplayTxt.setText(bpm + ' BPM');
+ }
+ drawWaveformLK();
+ // Müzik de BPM'e göre değişsin
+ var musicId = '100BPMSynthWave';
+ if (bpm === 60) {
+ musicId = '60BPMLowKey';
+ } else if (bpm === 40) {
+ musicId = '40BPMDeepVibes';
+ } else if (bpm === 45) {
+ musicId = '45BPMChillWave';
+ } else if (bpm === 50) {
+ musicId = '50BPMSlowMotion';
+ } else if (bpm === 55) {
+ musicId = '55BPMDreamscape';
+ } else if (bpm === 65) {
+ musicId = '65BPMRelaxed';
+ } else if (bpm === 70) {
+ musicId = '70BPMCalmVibes';
+ } else if (bpm === 75) {
+ musicId = '75BPMElectricDreams';
+ } else if (bpm === 80) {
+ musicId = '80BPMNeonNights';
+ } else if (bpm === 85) {
+ musicId = '85BPMCyberFlow';
+ } else if (bpm === 90) {
+ musicId = '90BPMDigitalPulse';
+ } else if (bpm === 95) {
+ musicId = '95BPMFutureBass';
+ } else if (bpm === 100) {
+ musicId = '100BPMSynthWave';
+ } else if (bpm === 105) {
+ musicId = '105BPMHyperDrive';
+ } else if (bpm === 110) {
+ musicId = '110BPMTechnoRush';
+ } else if (bpm === 115) {
+ musicId = '115BPMHighEnergy';
+ } else if (bpm === 120) {
+ musicId = '120BPMMaximum';
+ }
+ var volumeMultiplier = soundVolume / 100;
+ LK.stopMusic();
+ LK.playMusic(musicId, {
+ volume: 1 * volumeMultiplier,
+ loop: true
+ });
+ if (songNameTxt) {
+ var songName = musicId.replace(/^\d+BPM/, '');
+ songNameTxt.setText(songName);
+ }
+ }
+ updateDebugText();
}
lastDragStep = step;
}
};
@@ -1897,9 +2227,11 @@
mainMenuContainer.visible = false;
showStoryIntro(function () {
showReadyScreen(function () {
showCountdown(function () {
- startGame();
+ startGame({
+ mode: "story"
+ });
});
});
});
}
@@ -2045,8 +2377,18 @@
// Story intro screen container
var storyContainer = new Container();
storyContainer.visible = false;
game.addChild(storyContainer);
+// Add version number to bottom left corner of main menu
+var versionTxt = new Text2('v1.01', {
+ size: 24,
+ fill: 0xffffff
+});
+versionTxt.scale.set(1.5, 1.5);
+versionTxt.anchor.set(0, 1);
+versionTxt.x = 50;
+versionTxt.y = 2732 - 50;
+mainMenuContainer.addChild(versionTxt);
// Add menu to game
game.addChild(mainMenuContainer);
// menus are already added above
// Hide all gameplay UI until game starts
@@ -2057,23 +2399,20 @@
// missTxt removed, only hearts for 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() {
+startGame = function startGame(options) {
scoreTxt.visible = true;
comboTxt.visible = true;
bpmDisplayTxt.visible = true;
songNameTxt.visible = true;
if (bpmDisplayTxt) {
- // Show the actual BPM of the currently playing song, not the settings BPM
- var currentPlayingBpm = isTutorial ? 60 : bpm; // Tutorial always shows 60 BPM
- bpmDisplayTxt.setText('60 BPM');
+ var currentPlayingBpm = isTutorial ? 60 : bpm;
+ bpmDisplayTxt.setText(currentPlayingBpm + ' BPM');
}
- // missTxt removed, only hearts for lives
gamescreen.visible = true;
mainMenuContainer.visible = false;
- // overlayContainer.visible = false; // Removed, not defined
- _originalStartGame();
+ _originalStartGame(options);
};
// On menu, hide gameplay UI and gamescreen
mainMenuContainer.visible = true;
scoreTxt.visible = false;
@@ -2297,17 +2636,58 @@
}
// --- Beat spawning ---
function spawnBeat(beat) {
var marker = new BeatMarker();
- // All beats spawn at left edge of screen (x=0)
- marker.x = 0; // Always spawn at left edge
+ 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; // initialize lastX for correct miss logic
- marker.hasReachedHitZone = false; // initialize for correct miss logic
+ 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 ---
@@ -2362,10 +2742,10 @@
tutorialCompleteTxt.destroy();
}
});
}
- // Check if player reached 5k points - pause game and show black screen
- if (score >= 5000 && gameActive) {
+ // Check if player reached 5k points - pause game and show black screen (only in story mode)
+ if (score >= 5000 && gameActive && gameMode === "story") {
// Pause the game
gameActive = false;
gamePaused = true;
// Stop current music
@@ -2763,8 +3143,12 @@
var beat = beats[i];
var currentBeatSpeed = getBeatSpeed();
var elapsedTime = songTime - (beat.beatTime - HIT_ZONE_X / currentBeatSpeed);
beat.x = elapsedTime * currentBeatSpeed;
+ // Dalga boyunu daha belirgin yapmak için waveLength'i küçült
+ var waveLength = 80;
+ var phase = -HIT_ZONE_X / waveLength;
+ beat.y = HIT_ZONE_Y + Math.sin(beat.x / waveLength + phase) * 500;
if (beat.x < -400) {
beat.x = -400;
}
if (beat.x > TIMELINE_X + TIMELINE_WIDTH + 400) {
@@ -3127,8 +3511,11 @@
if (songNameTxt) {
var songName = newMusicId.replace(/^\d+BPM/, '');
songNameTxt.setText(songName);
}
+ // --- BPM değiştiğinde waveform'u güncelle ---
+ drawWaveformLK();
+ updateDebugText();
}
}
}
// Move beats and check for misses
@@ -3139,8 +3526,15 @@
// Calculate elapsed time since beat was spawned
var elapsedTime = songTime - (beat.beatTime - HIT_ZONE_X / currentBeatSpeed);
// Move beat from left edge at constant speed
beat.x = elapsedTime * currentBeatSpeed;
+ // --- YENİ: Beat yörüngesi waveform ile senkron olsun ---
+ var params = getWaveParams(bpm);
+ var waveLength = params.waveLength;
+ var waveAmplitude = params.waveAmplitude;
+ var phase = -HIT_ZONE_X / waveLength;
+ beat.y = HIT_ZONE_Y + Math.sin(beat.x / waveLength + phase) * waveAmplitude;
+ // ... existing code ...
// Ensure beat stays within reasonable bounds
if (beat.x < -400) {
beat.x = -400;
}
@@ -3193,8 +3587,10 @@
missedAsset.hit = false;
missedAsset.lastX = beat.x; // Use current position
missedAsset.hasReachedHitZone = true;
missedAsset.isMissed = true; // Mark as missed for special handling
+ // Add empty updateTrail method to prevent errors
+ missedAsset.updateTrail = function () {};
// Destroy the original beat asset
beat.destroy();
}
// Handle heartshield system
@@ -3334,32 +3730,47 @@
perfectCombo++; // Increment consecutive perfect hits
if (perfectCombo > maxPerfectCombo) {
maxPerfectCombo = perfectCombo; // Track maximum consecutive perfect hits
}
+ // Intense screen shake for perfect hits, increasing with 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;
scoreAdd = 150;
perfectCombo = 0; // Reset perfect 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;
scoreAdd = 100;
perfectCombo = 0; // Reset perfect 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;
scoreAdd = 50;
perfectCombo = 0; // Reset perfect 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;
score += scoreAdd;
combo += 1;
+ // Trail renklerini güncelle
+ updateBeatTrailOnHit(beat, feedbackType);
if (combo > maxCombo) {
maxCombo = combo; // Track maximum combo achieved
}
LK.setScore(score);
@@ -3460,10 +3871,16 @@
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
@@ -3883,9 +4300,11 @@
}
}
// Beat asset değiştirme fonksiyonu
function replaceBeatAsset(beat, newAssetId, options) {
- if (!beat.parent) return null;
+ if (!beat.parent) {
+ return null;
+ }
var beatParent = beat.parent;
var currentX = beat.x;
var currentY = beat.y;
beatParent.removeChild(beat);
@@ -3910,7 +4329,621 @@
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;
-}
\ No newline at end of file
+}
+// 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 params = getWaveParams(bpm);
+ var waveLength = params.waveLength;
+ var waveAmplitude = params.waveAmplitude;
+ 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
+ };
+ }
+}
+// Function to get wave parameters based on BPM
+function getWaveParams(bpm) {
+ bpm = Math.max(40, Math.min(120, bpm));
+ var minAmp = 80,
+ maxAmp = 500;
+ var minLength = 80,
+ maxLength = 120;
+ var t = (bpm - 40) / (120 - 40);
+ var waveAmplitude = minAmp + (maxAmp - minAmp) * t;
+ var waveLength = minLength + (maxLength - minLength) * t;
+ return {
+ waveLength: waveLength,
+ waveAmplitude: waveAmplitude
+ };
+}
+// Oyun başında ve BPM değiştiğinde çağır:
+drawWaveformLK();
+// Screen shake function
+function shakeScreen(intensity, duration) {
+ // Maksimum shake süresi sınırı (ör. 600ms)
+ duration = Math.min(duration, 600);
+ var originalX = gamescreen.x;
+ var originalY = gamescreen.y;
+ var originalBpmX = bpmDisplayTxt.x;
+ var originalBpmY = bpmDisplayTxt.y;
+ var originalSongX = songNameTxt.x;
+ var originalSongY = songNameTxt.y;
+ var shakeCount = 0;
+ var maxShakes = Math.floor(duration / 16.67); // 60fps
+ function shake() {
+ if (shakeCount >= maxShakes) {
+ // Son sönümleme: pozisyonu yavaşça sıfırla
+ tween(gamescreen, {
+ x: originalX,
+ y: originalY
+ }, {
+ duration: 120,
+ easing: tween.easeOut
+ });
+ tween(bpmDisplayTxt, {
+ x: originalBpmX,
+ y: originalBpmY
+ }, {
+ duration: 120,
+ easing: tween.easeOut
+ });
+ tween(songNameTxt, {
+ x: originalSongX,
+ y: originalSongY
+ }, {
+ duration: 120,
+ easing: tween.easeOut
+ });
+ return;
+ }
+ // Sönümlü intensity: sona yaklaştıkça azalır
+ var progress = shakeCount / maxShakes;
+ var currentIntensity = intensity * (1 - progress * 0.7); // Sonlara doğru %70 azalır
+ var randomX = (Math.random() - 0.5) * 2 * currentIntensity;
+ var randomY = (Math.random() - 0.5) * 2 * currentIntensity;
+ tween(gamescreen, {
+ x: originalX + randomX,
+ y: originalY + randomY
+ }, {
+ duration: 16.67,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ shakeCount++;
+ shake();
+ }
+ });
+ // Textler de sallansın
+ bpmDisplayTxt.x = originalBpmX + randomX;
+ bpmDisplayTxt.y = originalBpmY + randomY;
+ songNameTxt.x = originalSongX + randomX;
+ songNameTxt.y = originalSongY + randomY;
+ }
+ 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);
+ }
+ }
+}
+// Game update fonksiyonuna trail güncellemesini ekle
+var originalGameUpdate = game.update;
+game.update = function () {
+ originalGameUpdate.call(this);
+ // 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);
+ }
+ }
+ }
+};
+// 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;
+ }
+ var bpmInterval = 60000 / bpm; // BPM'i milisaniyeye çevir
+ var timeSinceStart = Date.now() - beat.pulseStartTime;
+ var pulsePhase = timeSinceStart % bpmInterval / 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"
+// === Menü Fonksiyonları ===
+function showMainMenu() {
+ mainMenuContainer.visible = true;
+ scoreTxt.visible = false;
+ comboTxt.visible = false;
+ 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('endless', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 2048 / 2,
+ y: buttonStartY + buttonHeight + buttonSpacing,
+ scaleX: 0.18,
+ scaleY: 0.18
+});
+mainMenuContainer.addChild(endlessBtn);
+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 ...
+// === DEBUG: Oyun Modu ve BPM Göstergesi ===
+// Ekranın sol üst köşesine debugText ekle
+var debugText = new Text2('', {
+ size: 24,
+ fill: 0xff00ff
+});
+debugText.anchor.set(0, 0);
+debugText.x = 20;
+debugText.y = 20;
+LK.gui.top.addChild(debugText);
+function updateDebugText() {
+ debugText.setText('MODE: ' + gameMode + ' | BPM: ' + bpm);
+}
+// startGame fonksiyonunun başında ve mod değiştirirken çağır
+// ...
+// === Trail Particle Pool Sistemi ===
+var PARTICLE_POOL_SIZE = 500;
+var particlePool = [];
+for (var i = 0; i < PARTICLE_POOL_SIZE; i++) {
+ var p = createTrailParticle(0, 0, 0xffffff);
+ p.visible = false;
+ p.alpha = 0;
+ particlePool.push(p);
+ gamescreen.addChild(p);
+}
+// Pool'dan particle al
+function getPooledParticle(x, y, color, size) {
+ for (var i = 0; i < particlePool.length; i++) {
+ var p = particlePool[i];
+ if (!p.visible || p.life <= 0) {
+ p.x = x;
+ p.y = y;
+ p.tint = color;
+ p.life = 1.0;
+ p.alpha = 0.5;
+ p.visible = true;
+ p.width = 1;
+ p.height = 1;
+ p.velocityX = 0;
+ p.velocityY = 0;
+ p.fadeSpeed = 0.12;
+ return p;
+ }
+ }
+ return null;
+}
+// createTrailParticle fonksiyonunu sadece pool için kullan
+// updateTrailParticle fonksiyonunu optimize et
\ 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
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