User prompt
kaybolmasın aynı beatmarkerlerin aktıgÌı gibi soldan sagÌa dogÌru akmaya devam etsinler. yalnızca asset degÌişsin
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'addChild')' in or related to this line: 'beat.parent.addChild(newBeatAsset);' Line Number: 1559
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'addChild')' in or related to this line: 'beat.parent.addChild(missedAsset);' Line Number: 1282
User prompt
efekt gibi degÌil de kaçan beat veya zamanlamaya goÌre basılan beat asseti soÌyledigÌim assetlere goÌre degÌişsin
User prompt
kicktargetin sol tarafından akan beatler sarı renkte beatmarker asseti ile goÌsteriliyyor ya. good awesome veya ok zamanlamasıyla basılan beatler kicktargetin sagÌ tarafından yeşil renkte kickactive asseti ile çıksın. perfect zamanıyla basılanlar beatmarker asseti ile çıksın. kaçırılanlar ise missCircle ile çıksın.
User prompt
kicktargette good awesome veya ok zamanlamasıyla basılan beatler karşıdan kickactive asseti ile çıksın. perfect zamanıyla basılanlar beatmarker asseti ile çıksın. kaçırılanlar ise missCircle ile çıksın
User prompt
her 10k puanda sonraki bpm'e geçsin
User prompt
herhangi bir BPMde başlayan oyunda 10.000 puan kazanılınca sonraki BPMe muÌzigÌe fade out vererek sonraki muÌzigÌe de fade in vererek geçsin. 1sn olsun fade efektleri. 120 BPMe ulaştıgÌında BPM artmasın. her bpm arttıgÌında beatler de bpm'e goÌre guÌncel şekilde gelmeye başlasın. âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
oyundaki bpm ile bpm value arasında bir boşluk olsun. oÌrnegÌin 120 BPM
Code edit (1 edits merged)
Please save this source code
User prompt
şarkı adı ile bpm arasında 5px olsun
User prompt
bpm yazısı 20px yukarı çıkarılsın.
User prompt
oyun ekranında bpm yazısı oÌnce value sonra BPM yazacak şekilde guÌncellensin. oÌrnegÌin 120BPM
User prompt
oyun ekranında şarkı adı yazarken başındaki sayı ve BPM yazısı alınmadan yazılsın
User prompt
40'tan 70'e ve 100'den 120'ye kadar olan eksik muÌzik assetlerini de ekle ve oyunun dogÌru algılayacagÌı şekilde guÌncelle
User prompt
75-100 bpm arasındaki hepsine muÌzik ekledim. bpm valueye goÌre oÌrnegÌin 100bpm giriliyse 100 bpm muÌzigÌini çalsın.
User prompt
muÌzik assetlerine yeni isim ver. isimlendirme ".. BPM {rastgele muÌzik adı}" şeklinde olsun
User prompt
oyundaki bpm yazısının altına şarkı adını ekle muÌzik assetinin adı ne ise o yazsın
User prompt
8px kuÌçuÌlt boyutunu
User prompt
15px buÌyuÌt boyutunu
User prompt
20px daha sagÌa çek ve rengini turuncu yap âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
25x daha sagÌa çek 15x de yukarı
User prompt
perfect combo multiplier texti 50px sagÌa 25px uÌste çek
User prompt
perfect kombo yapıldıgÌında perfect feedbackinin sagÌ uÌstuÌnde daha sizeı kuÌçuÌk bir şekilde x1, x3 vb. şekilde kombo sayısı yazsın
User prompt
istatistik yazı çoÌzuÌnuÌrluÌgÌuÌnuÌ bi tık arttıralım
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bpm: 100, soundVolume: 100 }); /**** * 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 // 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: 0x6ec6f5 // light blue sky }); /**** * Game Code ****/ // --- BPM-specific music assets (kickless versions) --- // Music (looping, short demo) // Sound for kick // Good feedback // Miss feedback // Beat marker // Timeline bar // Bass drum (kick) hit circle when active // Bass drum (kick) hit circle // --- 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 is constant - beats always move at the same speed function getBeatSpeed() { return 400; // Constant speed in pixels per second } 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 bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM; var soundVolume = typeof storage.soundVolume === "number" ? storage.soundVolume : 100; // Function to update all sound volumes based on soundVolume setting function updateAllSoundVolumes() { var volumeMultiplier = soundVolume / 100; // Update kick sound volume var kickSound = LK.getSound('kick'); if (kickSound && kickSound.setVolume) { kickSound.setVolume(1 * 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: 20, fill: 0xFFFFFF }); scoreTxt.scale.set(6, 6); // Scale up 6x to create more pixelated effect scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Combo text var comboTxt = new Text2('', { size: 12, fill: 0x00FF00 }); comboTxt.scale.set(6, 6); comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(comboTxt); comboTxt.y = 120; // BPM display text - 200px below combo text var bpmDisplayTxt = new Text2('BPM: ' + bpm, { size: 12, fill: 0xef69b9 }); bpmDisplayTxt.scale.set(6, 6); bpmDisplayTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bpmDisplayTxt); bpmDisplayTxt.y = comboTxt.y + 200; // 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; // Score text ile aynı hizada var heartWidth = 100; 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; var totalBeats = 100000; // Target 100,000 total beats for (var beatIndex = 0; beatIndex < totalBeats; beatIndex++) { pattern.push({ time: beatIndex * interval, type: 'kick' }); } return pattern; } // --- Start game --- function startGame() { // Reset state for (var i = 0; i < beats.length; i++) { beats[i].destroy(); } beats = []; // Always use current bpm from settings and calculate beatInterval accordingly beatInterval = 60 / bpm; beatPattern = generateBeatPattern(bpm); // Generate 100,000 beats nextBeatIdx = 0; songTime = 0; lastTickTime = Date.now(); score = 0; misses = 0; ignoredMisses = 0; // Reset ignored misses counter combo = 0; perfectCombo = 0; // Reset perfect combo lives = MAX_LIVES; heartshields = 3; // Reset heartshields // 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 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 // Recalculate miss text width and heart positions on restart var heartWidth = 100; var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing; var heartStartX = 2048 - 100 - totalHeartsWidth; var heartY = scoreTxt.y; // 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 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; 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(); // --- Play correct BPM music (kickless) --- var musicId = 'music_100bpm_nokick'; if (bpm === 75) { musicId = 'music_75bpm_nokick'; } else if (bpm === 80) { musicId = 'music_80bpm_nokick'; } else if (bpm === 85) { musicId = 'music_85bpm_nokick'; } else if (bpm === 90) { musicId = 'music_90bpm_nokick'; } else if (bpm === 95) { musicId = 'music_95bpm_nokick'; } else if (bpm === 100) { musicId = 'music_100bpm_nokick'; } var volumeMultiplier = soundVolume / 100; LK.playMusic(musicId, { volume: 1 * volumeMultiplier, loop: true }); } // --- 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: 8, scaleY: 8 }); mainMenuContainer.addChild(djAsset); // Calculate button positions with 5px spacing var buttonStartY = 1000; var buttonHeight = 289; // Approximate height with scale 5 var buttonSpacing = 5; // Start Button var startBtn = LK.getAsset('startbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY, scaleX: 5, scaleY: 5 }); mainMenuContainer.addChild(startBtn); // Settings Button var settingsBtn = LK.getAsset('settingsbutton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: buttonStartY + buttonHeight + buttonSpacing, scaleX: 5, scaleY: 5 }); 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: 5, scaleY: 5 }); 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: 5, scaleY: 5 }); 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: 10, scaleY: 10 }); creditsmenu.addChild(creditsTab); // Close button (bottom right) var closeBtn = new Text2("KAPAT", { size: 15, fill: 0xffffff }); closeBtn.scale.set(6, 6); closeBtn.anchor.set(1, 1); closeBtn.x = 2048 - 60; closeBtn.y = 2732 - 60; creditsmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // 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: 10, scaleY: 10 }); supportmenu.addChild(supportTab); // Close button (bottom right) var closeBtn = new Text2("KAPAT", { size: 15, fill: 0xffffff }); closeBtn.scale.set(6, 6); closeBtn.anchor.set(1, 1); closeBtn.x = 2048 - 60; closeBtn.y = 2732 - 60; supportmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // 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; } // 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 txt = new Text2("Settings", { size: 18, fill: 0xffffff }); txt.scale.set(6, 6); txt.anchor.set(0.5, 0.5); txt.x = 2048 / 2; txt.y = 700; settingsmenu.addChild(txt); // --- BPM CIRCLE UI --- // Container for the whole BPM control var bpmCircleContainer = new Container(); bpmCircleContainer.x = 2048 / 2 - 300; bpmCircleContainer.y = 1100; settingsmenu.addChild(bpmCircleContainer); // --- 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('kickTarget', { 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: 18, fill: 0xff8c00 }); bpmValueTxt.scale.set(6, 6); 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: 8, fill: 0xffffff }); bpmLabelTxt.scale.set(6, 6); bpmLabelTxt.anchor.set(0.5, 0.5); bpmLabelTxt.x = 0; bpmLabelTxt.y = 90; bpmCircleContainer.addChild(bpmLabelTxt); // Volume circle background var volumeCircleBg = LK.getAsset('kickTarget', { 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: 18, fill: 0xff8c00 }); volumeValueTxt.scale.set(6, 6); volumeValueTxt.anchor.set(0.5, 0.5); volumeValueTxt.x = 0; volumeValueTxt.y = 0; volumeCircleContainer.addChild(volumeValueTxt); // Volume label text var volumeLabelTxt = new Text2("SES", { size: 8, fill: 0xffffff }); volumeLabelTxt.scale.set(6, 6); 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 newBpm = dragStartBpm + step * BPM_STEP; if (newBpm < BPM_MIN) { newBpm = BPM_MIN; } if (newBpm > BPM_MAX) { newBpm = BPM_MAX; } if (newBpm !== bpm) { bpm = newBpm; bpmValueTxt.setText(bpm + ""); beatInterval = 60 / bpm; storage.bpm = bpm; // persist bpm to storage if (bpmDisplayTxt) { bpmDisplayTxt.setText('BPM: ' + bpm); } } 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 = new Text2("KAPAT", { size: 15, fill: 0xffffff }); closeBtn.scale.set(6, 6); closeBtn.anchor.set(1, 1); closeBtn.x = 2048 - 60; closeBtn.y = 2732 - 60; settingsmenu.addChild(closeBtn); closeBtn.down = function (x, y, obj) { // 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 showReadyScreen(onReady) { gamescreen.visible = true; readyContainer.visible = true; readyContainer.removeChildren(); // Show arrowtap area 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); // Ready button click handler readyBtn.down = function (x, y, obj) { // 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(); // Only show gamescreen if game is active (startGame will set it visible) if (!gameActive) { gamescreen.visible = false; } 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) { // Stop menu music immediately and fade out over 1 second LK.stopMusic(); var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, fade: { start: 1, end: 0, duration: 1000 } }); // Click animation - scale inward then outward tween(startBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(startBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showReadyScreen(function () { showCountdown(function () { startGame(); }); }); } }); } }); }; settingsBtn.down = function (x, y, obj) { // Click animation - scale inward then outward tween(settingsBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(settingsBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showSettingsMenu(); } }); } }); }; creditsBtn.down = function (x, y, obj) { // Click animation - scale inward then outward tween(creditsBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(creditsBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showCreditsMenu(); } }); } }); }; supportBtn.down = function (x, y, obj) { // Click animation - scale inward then outward tween(supportBtn, { scaleX: 4.5, scaleY: 4.5 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(supportBtn, { scaleX: 5, scaleY: 5 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { mainMenuContainer.visible = false; showSupportMenu(); } }); } }); }; // 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; // 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() { scoreTxt.visible = true; comboTxt.visible = true; bpmDisplayTxt.visible = true; if (bpmDisplayTxt) { bpmDisplayTxt.setText('BPM: ' + bpm); } // missTxt removed, only hearts for lives gamescreen.visible = true; mainMenuContainer.visible = false; // overlayContainer.visible = false; // Removed, not defined _originalStartGame(); }; // On menu, hide gameplay UI and gamescreen mainMenuContainer.visible = true; scoreTxt.visible = false; comboTxt.visible = false; bpmDisplayTxt.visible = false; gamescreen.visible = false; // Play menu music when in main menu var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); // Apply current sound volume settings to all sounds updateAllSoundVolumes(); // --- Beat spawning --- function spawnBeat(beat) { var marker = new BeatMarker(); // Spawn beats from the leftmost edge of the screen (completely off-screen) marker.x = -200; // Start further left to ensure beats come from outside the visible area 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 beats.push(marker); gamescreen.addChild(marker); } // --- Game update --- game.update = function () { if (!gameActive) { 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 if it's time - using constant beat speed but BPM-based spacing var currentBeatSpeed = getBeatSpeed(); var spawnDistance = currentBeatSpeed * (60 / bpm); // Distance between beats based on BPM // Calculate the extra time needed for beats to travel from left edge to timeline start var extraTravelTime = (TIMELINE_X + 200) / currentBeatSpeed; // Time to travel from spawn point to timeline start while (nextBeatIdx < beatPattern.length && beatPattern[nextBeatIdx].time - songTime < TIMELINE_WIDTH / currentBeatSpeed + extraTravelTime) { spawnBeat(beatPattern[nextBeatIdx]); nextBeatIdx++; } } // Move beats and check for misses for (var i = beats.length - 1; i >= 0; i--) { var beat = beats[i]; // Calculate progress using constant speed - beats move at same speed regardless of BPM var currentBeatSpeed = getBeatSpeed(); var timeToHit = beat.beatTime - songTime; // Use constant speed for movement, BPM only affects spacing between beats var beatSpacing = currentBeatSpeed * (60 / bpm); // Space between beats based on BPM // Calculate total travel distance from left edge to beyond hit zone var totalTravelDistance = TIMELINE_X + TIMELINE_WIDTH + 200 + 200; // Include the extra 200px from spawn point var extraTravelTime = (TIMELINE_X + 200) / currentBeatSpeed; // Time to travel from spawn point to timeline start var pxFromStart = (songTime - beat.beatTime + totalTravelDistance / currentBeatSpeed - extraTravelTime) * currentBeatSpeed; beat.x = -200 + Math.max(0, Math.min(totalTravelDistance, pxFromStart)); // 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++; // 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: 20, fill: 0xff0000 }); missText.scale.set(6, 6); 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) { 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 } } else if (distFromCenter < 40) { feedbackType = 'awesome'; feedbackText = 'AWESOME!'; feedbackColor = 0x00e6ff; scoreAdd = 150; perfectCombo = 0; // Reset perfect combo for non-perfect hit } else if (distFromCenter < 70) { feedbackType = 'good'; feedbackText = 'GOOD'; feedbackColor = 0x00ff00; scoreAdd = 100; perfectCombo = 0; // Reset perfect combo for non-perfect hit } else { feedbackType = 'ok'; feedbackText = 'OK'; feedbackColor = 0xcccccc; scoreAdd = 50; perfectCombo = 0; // Reset perfect combo for non-perfect hit } beat.hit = true; beat.active = false; hit = true; score += scoreAdd; combo += 1; 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: 20, fill: feedbackColor }); feedbackTxt.scale.set(6, 6); feedbackTxt.anchor.set(0.5, 0.5); feedbackTxt.x = HIT_ZONE_X; feedbackTxt.y = HIT_ZONE_Y - 180; game.addChild(feedbackTxt); tween(feedbackTxt, { alpha: 0, y: feedbackTxt.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { feedbackTxt.destroy(); } }); beat.showFeedback('good'); LK.getSound('kick').play(); // Play kick sound to complete the music LK.effects.flashObject(kickTarget, feedbackColor, 200); // 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(''); LK.getSound('kick').play(); // Play kick sound even if not a perfect hit LK.effects.flashObject(kickTarget, 0xff0000, 200); // Show WRONG TIME feedback text at hit zone var wrongTimeText = new Text2('WRONG TIME', { size: 20, fill: 0xff4500 }); wrongTimeText.scale.set(6, 6); 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 function showDefeatScreen() { defeatContainer.visible = true; defeatContainer.removeChildren(); gamescreen.visible = false; bpmDisplayTxt.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: 1235, 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: 8, scaleY: 8 }); 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: 22, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); finalScoreTxt.scale.set(6, 6); 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: 22, fill: 0x00ff00, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); maxComboTxt.scale.set(6, 6); 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: 22, fill: 0xffe600, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); perfectCountTxt.scale.set(6, 6); 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: 22, fill: 0xff8c00, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); maxPerfectComboTxt.scale.set(6, 6); 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) { // 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 showReadyScreen(function () { showCountdown(function () { startGame(); }); }); } }); } }); }; mainMenuBtn.down = function (x, y, obj) { // 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; gamescreen.visible = false; // Play menu music var volumeMultiplier = soundVolume / 100; LK.playMusic('menumusic', { volume: 0.8 * volumeMultiplier, loop: true }); } }); } }); }; } // --- Restart on game over --- LK.on('gameover', function () { showDefeatScreen(); }); LK.on('youwin', function () { startGame(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
bpm: 100,
soundVolume: 100
});
/****
* 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
// 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: 0x6ec6f5 // light blue sky
});
/****
* Game Code
****/
// --- BPM-specific music assets (kickless versions) ---
// Music (looping, short demo)
// Sound for kick
// Good feedback
// Miss feedback
// Beat marker
// Timeline bar
// Bass drum (kick) hit circle when active
// Bass drum (kick) hit circle
// --- 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 is constant - beats always move at the same speed
function getBeatSpeed() {
return 400; // Constant speed in pixels per second
}
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 bpm = typeof storage.bpm === "number" ? storage.bpm : INITIAL_BPM;
var soundVolume = typeof storage.soundVolume === "number" ? storage.soundVolume : 100;
// Function to update all sound volumes based on soundVolume setting
function updateAllSoundVolumes() {
var volumeMultiplier = soundVolume / 100;
// Update kick sound volume
var kickSound = LK.getSound('kick');
if (kickSound && kickSound.setVolume) {
kickSound.setVolume(1 * 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: 20,
fill: 0xFFFFFF
});
scoreTxt.scale.set(6, 6); // Scale up 6x to create more pixelated effect
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Combo text
var comboTxt = new Text2('', {
size: 12,
fill: 0x00FF00
});
comboTxt.scale.set(6, 6);
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.y = 120;
// BPM display text - 200px below combo text
var bpmDisplayTxt = new Text2('BPM: ' + bpm, {
size: 12,
fill: 0xef69b9
});
bpmDisplayTxt.scale.set(6, 6);
bpmDisplayTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(bpmDisplayTxt);
bpmDisplayTxt.y = comboTxt.y + 200;
// 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; // Score text ile aynı hizada
var heartWidth = 100;
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;
var totalBeats = 100000; // Target 100,000 total beats
for (var beatIndex = 0; beatIndex < totalBeats; beatIndex++) {
pattern.push({
time: beatIndex * interval,
type: 'kick'
});
}
return pattern;
}
// --- Start game ---
function startGame() {
// Reset state
for (var i = 0; i < beats.length; i++) {
beats[i].destroy();
}
beats = [];
// Always use current bpm from settings and calculate beatInterval accordingly
beatInterval = 60 / bpm;
beatPattern = generateBeatPattern(bpm); // Generate 100,000 beats
nextBeatIdx = 0;
songTime = 0;
lastTickTime = Date.now();
score = 0;
misses = 0;
ignoredMisses = 0; // Reset ignored misses counter
combo = 0;
perfectCombo = 0; // Reset perfect combo
lives = MAX_LIVES;
heartshields = 3; // Reset heartshields
// 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
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
// Recalculate miss text width and heart positions on restart
var heartWidth = 100;
var totalHeartsWidth = MAX_LIVES * heartWidth + (MAX_LIVES - 1) * heartSpacing;
var heartStartX = 2048 - 100 - totalHeartsWidth;
var heartY = scoreTxt.y;
// 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
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;
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();
// --- Play correct BPM music (kickless) ---
var musicId = 'music_100bpm_nokick';
if (bpm === 75) {
musicId = 'music_75bpm_nokick';
} else if (bpm === 80) {
musicId = 'music_80bpm_nokick';
} else if (bpm === 85) {
musicId = 'music_85bpm_nokick';
} else if (bpm === 90) {
musicId = 'music_90bpm_nokick';
} else if (bpm === 95) {
musicId = 'music_95bpm_nokick';
} else if (bpm === 100) {
musicId = 'music_100bpm_nokick';
}
var volumeMultiplier = soundVolume / 100;
LK.playMusic(musicId, {
volume: 1 * volumeMultiplier,
loop: true
});
}
// --- 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: 8,
scaleY: 8
});
mainMenuContainer.addChild(djAsset);
// Calculate button positions with 5px spacing
var buttonStartY = 1000;
var buttonHeight = 289; // Approximate height with scale 5
var buttonSpacing = 5;
// Start Button
var startBtn = LK.getAsset('startbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: buttonStartY,
scaleX: 5,
scaleY: 5
});
mainMenuContainer.addChild(startBtn);
// Settings Button
var settingsBtn = LK.getAsset('settingsbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: buttonStartY + buttonHeight + buttonSpacing,
scaleX: 5,
scaleY: 5
});
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: 5,
scaleY: 5
});
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: 5,
scaleY: 5
});
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: 10,
scaleY: 10
});
creditsmenu.addChild(creditsTab);
// Close button (bottom right)
var closeBtn = new Text2("KAPAT", {
size: 15,
fill: 0xffffff
});
closeBtn.scale.set(6, 6);
closeBtn.anchor.set(1, 1);
closeBtn.x = 2048 - 60;
closeBtn.y = 2732 - 60;
creditsmenu.addChild(closeBtn);
closeBtn.down = function (x, y, obj) {
// 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: 10,
scaleY: 10
});
supportmenu.addChild(supportTab);
// Close button (bottom right)
var closeBtn = new Text2("KAPAT", {
size: 15,
fill: 0xffffff
});
closeBtn.scale.set(6, 6);
closeBtn.anchor.set(1, 1);
closeBtn.x = 2048 - 60;
closeBtn.y = 2732 - 60;
supportmenu.addChild(closeBtn);
closeBtn.down = function (x, y, obj) {
// 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;
}
// 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 txt = new Text2("Settings", {
size: 18,
fill: 0xffffff
});
txt.scale.set(6, 6);
txt.anchor.set(0.5, 0.5);
txt.x = 2048 / 2;
txt.y = 700;
settingsmenu.addChild(txt);
// --- BPM CIRCLE UI ---
// Container for the whole BPM control
var bpmCircleContainer = new Container();
bpmCircleContainer.x = 2048 / 2 - 300;
bpmCircleContainer.y = 1100;
settingsmenu.addChild(bpmCircleContainer);
// --- 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('kickTarget', {
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: 18,
fill: 0xff8c00
});
bpmValueTxt.scale.set(6, 6);
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: 8,
fill: 0xffffff
});
bpmLabelTxt.scale.set(6, 6);
bpmLabelTxt.anchor.set(0.5, 0.5);
bpmLabelTxt.x = 0;
bpmLabelTxt.y = 90;
bpmCircleContainer.addChild(bpmLabelTxt);
// Volume circle background
var volumeCircleBg = LK.getAsset('kickTarget', {
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: 18,
fill: 0xff8c00
});
volumeValueTxt.scale.set(6, 6);
volumeValueTxt.anchor.set(0.5, 0.5);
volumeValueTxt.x = 0;
volumeValueTxt.y = 0;
volumeCircleContainer.addChild(volumeValueTxt);
// Volume label text
var volumeLabelTxt = new Text2("SES", {
size: 8,
fill: 0xffffff
});
volumeLabelTxt.scale.set(6, 6);
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 newBpm = dragStartBpm + step * BPM_STEP;
if (newBpm < BPM_MIN) {
newBpm = BPM_MIN;
}
if (newBpm > BPM_MAX) {
newBpm = BPM_MAX;
}
if (newBpm !== bpm) {
bpm = newBpm;
bpmValueTxt.setText(bpm + "");
beatInterval = 60 / bpm;
storage.bpm = bpm; // persist bpm to storage
if (bpmDisplayTxt) {
bpmDisplayTxt.setText('BPM: ' + bpm);
}
}
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 = new Text2("KAPAT", {
size: 15,
fill: 0xffffff
});
closeBtn.scale.set(6, 6);
closeBtn.anchor.set(1, 1);
closeBtn.x = 2048 - 60;
closeBtn.y = 2732 - 60;
settingsmenu.addChild(closeBtn);
closeBtn.down = function (x, y, obj) {
// 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 showReadyScreen(onReady) {
gamescreen.visible = true;
readyContainer.visible = true;
readyContainer.removeChildren();
// Show arrowtap area
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);
// Ready button click handler
readyBtn.down = function (x, y, obj) {
// 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();
// Only show gamescreen if game is active (startGame will set it visible)
if (!gameActive) {
gamescreen.visible = false;
}
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) {
// Stop menu music immediately and fade out over 1 second
LK.stopMusic();
var volumeMultiplier = soundVolume / 100;
LK.playMusic('menumusic', {
volume: 0.8 * volumeMultiplier,
fade: {
start: 1,
end: 0,
duration: 1000
}
});
// Click animation - scale inward then outward
tween(startBtn, {
scaleX: 4.5,
scaleY: 4.5
}, {
duration: 80,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(startBtn, {
scaleX: 5,
scaleY: 5
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
mainMenuContainer.visible = false;
showReadyScreen(function () {
showCountdown(function () {
startGame();
});
});
}
});
}
});
};
settingsBtn.down = function (x, y, obj) {
// Click animation - scale inward then outward
tween(settingsBtn, {
scaleX: 4.5,
scaleY: 4.5
}, {
duration: 80,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(settingsBtn, {
scaleX: 5,
scaleY: 5
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
mainMenuContainer.visible = false;
showSettingsMenu();
}
});
}
});
};
creditsBtn.down = function (x, y, obj) {
// Click animation - scale inward then outward
tween(creditsBtn, {
scaleX: 4.5,
scaleY: 4.5
}, {
duration: 80,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(creditsBtn, {
scaleX: 5,
scaleY: 5
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
mainMenuContainer.visible = false;
showCreditsMenu();
}
});
}
});
};
supportBtn.down = function (x, y, obj) {
// Click animation - scale inward then outward
tween(supportBtn, {
scaleX: 4.5,
scaleY: 4.5
}, {
duration: 80,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(supportBtn, {
scaleX: 5,
scaleY: 5
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
mainMenuContainer.visible = false;
showSupportMenu();
}
});
}
});
};
// 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;
// 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() {
scoreTxt.visible = true;
comboTxt.visible = true;
bpmDisplayTxt.visible = true;
if (bpmDisplayTxt) {
bpmDisplayTxt.setText('BPM: ' + bpm);
}
// missTxt removed, only hearts for lives
gamescreen.visible = true;
mainMenuContainer.visible = false;
// overlayContainer.visible = false; // Removed, not defined
_originalStartGame();
};
// On menu, hide gameplay UI and gamescreen
mainMenuContainer.visible = true;
scoreTxt.visible = false;
comboTxt.visible = false;
bpmDisplayTxt.visible = false;
gamescreen.visible = false;
// Play menu music when in main menu
var volumeMultiplier = soundVolume / 100;
LK.playMusic('menumusic', {
volume: 0.8 * volumeMultiplier,
loop: true
});
// Apply current sound volume settings to all sounds
updateAllSoundVolumes();
// --- Beat spawning ---
function spawnBeat(beat) {
var marker = new BeatMarker();
// Spawn beats from the leftmost edge of the screen (completely off-screen)
marker.x = -200; // Start further left to ensure beats come from outside the visible area
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
beats.push(marker);
gamescreen.addChild(marker);
}
// --- Game update ---
game.update = function () {
if (!gameActive) {
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 if it's time - using constant beat speed but BPM-based spacing
var currentBeatSpeed = getBeatSpeed();
var spawnDistance = currentBeatSpeed * (60 / bpm); // Distance between beats based on BPM
// Calculate the extra time needed for beats to travel from left edge to timeline start
var extraTravelTime = (TIMELINE_X + 200) / currentBeatSpeed; // Time to travel from spawn point to timeline start
while (nextBeatIdx < beatPattern.length && beatPattern[nextBeatIdx].time - songTime < TIMELINE_WIDTH / currentBeatSpeed + extraTravelTime) {
spawnBeat(beatPattern[nextBeatIdx]);
nextBeatIdx++;
}
}
// Move beats and check for misses
for (var i = beats.length - 1; i >= 0; i--) {
var beat = beats[i];
// Calculate progress using constant speed - beats move at same speed regardless of BPM
var currentBeatSpeed = getBeatSpeed();
var timeToHit = beat.beatTime - songTime;
// Use constant speed for movement, BPM only affects spacing between beats
var beatSpacing = currentBeatSpeed * (60 / bpm); // Space between beats based on BPM
// Calculate total travel distance from left edge to beyond hit zone
var totalTravelDistance = TIMELINE_X + TIMELINE_WIDTH + 200 + 200; // Include the extra 200px from spawn point
var extraTravelTime = (TIMELINE_X + 200) / currentBeatSpeed; // Time to travel from spawn point to timeline start
var pxFromStart = (songTime - beat.beatTime + totalTravelDistance / currentBeatSpeed - extraTravelTime) * currentBeatSpeed;
beat.x = -200 + Math.max(0, Math.min(totalTravelDistance, pxFromStart));
// 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++;
// 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: 20,
fill: 0xff0000
});
missText.scale.set(6, 6);
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) {
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
}
} else if (distFromCenter < 40) {
feedbackType = 'awesome';
feedbackText = 'AWESOME!';
feedbackColor = 0x00e6ff;
scoreAdd = 150;
perfectCombo = 0; // Reset perfect combo for non-perfect hit
} else if (distFromCenter < 70) {
feedbackType = 'good';
feedbackText = 'GOOD';
feedbackColor = 0x00ff00;
scoreAdd = 100;
perfectCombo = 0; // Reset perfect combo for non-perfect hit
} else {
feedbackType = 'ok';
feedbackText = 'OK';
feedbackColor = 0xcccccc;
scoreAdd = 50;
perfectCombo = 0; // Reset perfect combo for non-perfect hit
}
beat.hit = true;
beat.active = false;
hit = true;
score += scoreAdd;
combo += 1;
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: 20,
fill: feedbackColor
});
feedbackTxt.scale.set(6, 6);
feedbackTxt.anchor.set(0.5, 0.5);
feedbackTxt.x = HIT_ZONE_X;
feedbackTxt.y = HIT_ZONE_Y - 180;
game.addChild(feedbackTxt);
tween(feedbackTxt, {
alpha: 0,
y: feedbackTxt.y - 100
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
feedbackTxt.destroy();
}
});
beat.showFeedback('good');
LK.getSound('kick').play(); // Play kick sound to complete the music
LK.effects.flashObject(kickTarget, feedbackColor, 200);
// 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('');
LK.getSound('kick').play(); // Play kick sound even if not a perfect hit
LK.effects.flashObject(kickTarget, 0xff0000, 200);
// Show WRONG TIME feedback text at hit zone
var wrongTimeText = new Text2('WRONG TIME', {
size: 20,
fill: 0xff4500
});
wrongTimeText.scale.set(6, 6);
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
function showDefeatScreen() {
defeatContainer.visible = true;
defeatContainer.removeChildren();
gamescreen.visible = false;
bpmDisplayTxt.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: 1235,
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: 8,
scaleY: 8
});
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: 22,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
finalScoreTxt.scale.set(6, 6);
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: 22,
fill: 0x00ff00,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
maxComboTxt.scale.set(6, 6);
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: 22,
fill: 0xffe600,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
perfectCountTxt.scale.set(6, 6);
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: 22,
fill: 0xff8c00,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
maxPerfectComboTxt.scale.set(6, 6);
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) {
// 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
showReadyScreen(function () {
showCountdown(function () {
startGame();
});
});
}
});
}
});
};
mainMenuBtn.down = function (x, y, obj) {
// 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;
gamescreen.visible = false;
// Play menu music
var volumeMultiplier = soundVolume / 100;
LK.playMusic('menumusic', {
volume: 0.8 * volumeMultiplier,
loop: true
});
}
});
}
});
};
}
// --- Restart on game over ---
LK.on('gameover', function () {
showDefeatScreen();
});
LK.on('youwin', function () {
startGame();
});
empty heart
A 2048x2732 vertical pixel art background for the main menu of a rhythm game. The scene shows a moody, neon-lit cityscape at night through a big window behind a cozy, minimal DJ setup. The foreground includes soft lights, vinyl records, and glowing cables. Background buildings feature soft pulsing lights and pixel-style clouds or stars. The mood is dreamy, lo-fi, and rhythmic, with purple, indigo, and cyan tones. No characters or text, just a calm and stylish menu background.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical pixel art background for a settings screen in a pixel-art rhythm game. The scene shows a cozy side corner of the same DJ studio seen in the main menu, with shelves full of vinyls, tangled audio cables, studio monitors, and an analog BPM dial glowing softly. A small desk lamp casts a warm light over a notepad and some buttons. The lighting is purple and blue, calm and focused. The mood is quiet, technical, and slightly futuristic â perfect for adjusting settings.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical pixel art background for a credits screen in a rhythm-based pixel art game. The scene shows a cozy wall in the same neon-lit DJ studio, filled with pinned polaroid photos, signed posters, music flyers, sticky notes, and handwritten thank-you notes. Soft glowing fairy lights hang above. The lighting is soft pink, violet and blue, with a nostalgic, emotional, and heartfelt vibe. No characters or UI â just a decorative wall filled with creative memories and gratitude.. In-Game asset. 2d. High contrast. No shadows
A single rectangular pixel art button labeled âStartâ in a purple neon tone, designed for a lo-fi rhythm-based pixel art game. The button has soft glowing edges, a subtle pixel shadow, and a clean 1-bit pixel font. The overall mood is cozy and dreamy, matching a neon-lit DJ studio aesthetic. The background should be transparent.. In-Game asset. 2d. High contrast. No shadows
Write SETTINGS instead of START.
Write CREDITS instead of START.
Write SUPPORT US instead of SETTINGS.
A bold and stylish pixel art logo text for the game title âDJ RHYTHMCOLICâ designed for a lo-fi rhythm pixel art game. The text is large, vibrant purple with neon glow effects, featuring a retro pixel font that looks futuristic and energetic. Behind the text, subtle pixelated neon sound waves and small music notes float gently in purple and blue hues, blending with a cozy DJ studio atmosphere. The background is transparent or very dark to highlight the glowing title. The style matches a dreamy, neon-lit nighttime vibe with pixel-perfect detail.. In-Game asset. 2d. High contrast. No shadows
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
A pixel art arrow starting from a glowing "TAP" label in retro pixel font, pointing diagonally from the bottom right toward the upper left, as if guiding the player to tap that area. The arrow is sleek, with a smooth curve or angled segments, styled in purple or violet neon tones with a soft glow effect. The design matches a lo-fi rhythm game aesthetic. The "TAP" label is positioned at the tail of the arrow and glows subtly. No background â transparent.. In-Game asset. 2d. High contrast. No shadows
A pixel art button with the word "READY" written in bold, retro pixel font. The button is rectangular with slightly rounded corners, styled in purple and violet tones with a soft glowing border to match a lo-fi rhythm game's aesthetic. The "READY" text is centered, white or light-colored for contrast, with pixel-perfect sharpness. The button has a slightly raised 3D appearance and no background (transparent). Designed for use in a minimalist, neon-themed rhythm game UI.. In-Game asset. 2d. High contrast. No shadows
A cozy pixel art bar interior viewed from the DJ's perspective. A dimly lit, moody atmosphere with purple and deep blue neon tones. Visible DJ desk with mixer and speakers in the foreground, blurred bar counter and patrons in the background. Small glowing lights, bottles on shelves, soft lighting, and retro vibes. Resolution: 2048x2732. No characters in front, focus on ambiance and depth.. In-Game asset. 2d. High contrast. No shadows
pixel-art purple heart. In-Game asset. 2d. High contrast. No shadows
heart with blue shield
A transparent background pixel art UI table showing game credits. Each credit line is inside a separate softly glowing purple pixel-style rectangular box. Pixel font text is centered in each box and reads: "Credits" "Tasarım & Kod: FRVR.Ava.Combo[POGAAS].v1.0" "Inspired by Ada Lovelace" "Prompt Engineering by cielique" No background or shadows. Only the pixel table with glowing boxes and readable pixel-style text. Maintain a clean, retro-modern design suitable for overlaying on a DJ bar scene. Resolution: 2048x2732, vertically aligned.. In-Game asset. 2d. High contrast. No shadows
A transparent background pixel art UI panel with "Support Us" title and a list of support actions. Each action is inside a separate glowing purple pixel-style rectangular box. Centered pixel font for text. The boxes are aligned vertically, styled like a clean UI overlay, no background or shadows. Resolution: 2048x2732. Text inside each box: "Support Us" "â Upvote the game" "â Share with your friends" "â Leave a comment" "â Send us your ideas" The overall design should feel fun, inviting, and in harmony with a DJ rhythm game's UI theme.. In-Game asset. 2d. High contrast. No shadows
A 2048x2732 vertical pixel art background for a âSupport Usâ screen in a rhythm-based pixel art game. The scene continues the cozy neon-lit DJ room theme from the main menu, but zooms in slightly on the desk. A pixel-art tip jar labeled âThank you!â, a laptop covered in music-themed stickers, coffee mugs, and glowing synth equipment are visible. Lighting remains dreamy and lo-fi with purples and soft blues. The mood feels warm, humble, and inviting. No characters or text â just the environment.. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
Pixel art âTry Againâ button, retro arcade style, purple tones, soft shadows, chunky text, fits rhythm game UI.. In-Game asset. 2d. High contrast. No shadows
change text with MAIN MENU
Pixel art âGame Overâ text, bold retro arcade style, purple tones, glitch-free, clean and dramatic for rhythm game UI.. In-Game asset. 2d. High contrast. No shadows
Pixel art background, 2048x2732 resolution, defeat screen for a rhythm game set in a moody bar. Dim lighting, purple and dark tones, empty DJ booth, quiet atmosphere, no characters or text.. In-Game asset. 2d. High contrast. No shadows
orange
CLOSE
purple
daha koyu mor ve koÌşeler daha yuvarlak
soft edge square, violet purple, pixel art. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Pixel art tutorial table UI, clean layout with text blocks, purple-themed, 8-bit retro style, no background, no icons, fits game start screen "[How to Play] âą Tap the button in time with the rhythm! âą Earn points and combos by staying on beat. âą Want to change speed or volume? â Check out the Settings menu for BPM & sound options. Get ready to groove!". In-Game asset. 2d. High contrast. No shadows
Pixel art pause button, purple color, rounded corners, 8-bit UI style, minimal design, no background, 80x80 size, suitable for mobile rhythm game interface. In-Game asset. 2d. High contrast. No shadows
A minimalist pixel art "Paused" menu screen in vertical format (2048x2732), with a retro DJ/bar theme. At the top center, the word "PAUSED" in large, glowing purple pixel letters. The rest of the screen should be clean and dark, with subtle lighting or atmospheric effects suggesting a nightclub or DJ booth. Leave the central and lower space empty for placing UI buttons (Resume, Restart, Settings, Main Menu). Smooth, moody pixel background, matching a futuristic arcade vibe.. In-Game asset. 2d. High contrast. No shadows
change text with RESUME
change text with 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