User prompt
bu 5 tane kaçırınca kaybedersin yazısını nasıl oynanırada yaz her dil için
User prompt
eğer 5 tane notayı kacırırsan oyunu kaybedersin
User prompt
skor yazısını kombo yazısıyla aynı büyüklüge getir
User prompt
skor yazısını ve sayacını biraz sola al
User prompt
combo yazısını ve sayacını biraz daha aşağı al
User prompt
dil seceneklerinde türkçeyi en sona koy malta dilini 2. sıraya 1.sırayada ingilizceyi koy
User prompt
biraz daha sol
User prompt
bu dil seçeneklerimi biraz daha sola çek
User prompt
dil kısmına ispanyolca,almanca,italyanca,fransızca,malta dillerini ekle
User prompt
fix the problem
User prompt
dil kısmı skor tablosundaki yazılar ve miss yazısı içinde geçerli olsun
User prompt
nasıl oynanır kısmında orta üst tarafta bir dil seçme kısmı yap
User prompt
skor yazısının altındaki sayı kısmını biraz daha aşağı getir kombo yazısını da biraz daha aşağı getir ve kombo yazsının altında bir kombo sayacı olsun skorda olduğu gbi
User prompt
skor sayacınıda biraz aşşağı getir
User prompt
kombo yazısı biraz daha aşağı getir
User prompt
ayarlar kısmını kaldır
User prompt
ayarlar skor kombo yazılarının rengi beyaz olsun ve ayarlar kısmına basına ekrana kücük bir beyaz ekran cıksın
User prompt
kombo sayacı çalıssın her art arda yapılan komboda sayısı artsın ve eğer bir nota kaçırılırsa sıfırlansın,ayarlar yazısını ekranın sağ alt kısmına al
User prompt
biraz daha aşağı
User prompt
ayarlar kısmını en alt kısma
User prompt
ayarlar kısmını daha aşağı getir ve skor kombo yazılarının arasındaki boşluğu biraz daha arttır
User prompt
skor tablosunun en alt kısmına bir ayarlar kısmı ekle
User prompt
nasıl oynanırda skor ve kombo tablosunun oldugu yeri gösterme
User prompt
nasıl oynanırdaki nota resmini başlanın altına koy
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var FallingNote = Container.expand(function (lane, noteType) { var self = Container.call(this); self.lane = lane; self.noteType = noteType; self.speed = 8; // Vertical speed (moving down) self.hasBeenHit = false; self.lastY = 0; // Track Y position for vertical movement // Attach the appropriate note asset based on lane with matching colors var assetName = 'noteLeft'; if (lane === 1) assetName = 'noteMiddle'; if (lane === 2) assetName = 'noteRight'; var noteGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Apply lane colors to match the new color scheme var laneBorderColors = [0x00bfff, 0xff33cc, 0xffe066]; // Brighter pastel blue, magenta, yellow // Ensure note starts with white base color before tinting noteGraphics.tint = 0xffffff; // Reset to white first noteGraphics.tint = laneBorderColors[lane]; // Add glow effect var noteGlow = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); // Ensure glow starts with white base color before tinting noteGlow.tint = 0xffffff; // Reset to white first noteGlow.tint = laneBorderColors[lane]; noteGlow.alpha = 0.3; self.setChildIndex(noteGlow, 0); // Add simplified light trail effect self.trail = []; self.update = function () { self.lastY = self.y; // Track last Y position self.y += self.speed; // Move vertically downward // Update trail if (LK.ticks % 5 === 0) { var trail = LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); // Ensure trail starts with white base color before tinting trail.tint = 0xffffff; // Reset to white first trail.tint = laneBorderColors[self.lane]; trail.alpha = 0.1; trail.x = self.x; trail.y = self.y - 10; if (self.parent) self.parent.addChild(trail); self.trail.push(trail); // Fade and remove old trail tween(trail, { alpha: 0, scaleX: 0.2, scaleY: 0.2 }, { duration: 200, onFinish: function onFinish() { if (trail.parent) trail.parent.removeChild(trail); } }); } // Clean up old trail references self.trail = self.trail.filter(function (t) { return t.parent; }); }; return self; }); var ScoreText = Container.expand(function (text, color) { var self = Container.call(this); self.life = 60; // 1 second at 60fps self.speed = -2; var textObj = new Text2(text, { size: 40, fill: color || "#ffffff" }); textObj.anchor.set(0.5, 0.5); self.addChild(textObj); self.update = function () { self.y += self.speed; self.life--; var textObj = self.getChildAt(0); textObj.alpha = self.life / 60; // Life cycle is managed by the main game loop for pooling }; return self; }); // Game constants var TargetCircle = Container.expand(function (lane) { var self = Container.call(this); self.lane = lane; self.isGlowing = false; self.glowTime = 0; // Main circle (filled) var circleGraphics = self.attachAsset('targetCircle', { anchorX: 0.5, anchorY: 0.5 }); var laneBorderColors = [0x00bfff, 0xff33cc, 0xffe066]; // Brighter pastel blue, magenta, yellow var laneFillColors = [0xe6f7ff, 0xffe6fa, 0xfffbe6]; // Very light pastel fill // Set fill color for the main circle circleGraphics.tint = laneFillColors[lane]; circleGraphics.alpha = 0.7; // Add a border effect as a second, slightly larger circle var borderCircle = self.attachAsset('targetCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.18, scaleY: 1.18 }); borderCircle.tint = laneBorderColors[lane]; borderCircle.alpha = 0.9; // Make sure border is behind the main circle self.setChildIndex(borderCircle, 0); // Glow effect self.glow = function () { self.isGlowing = true; self.glowTime = 20; // Glow for 20 frames circleGraphics.alpha = 1; borderCircle.alpha = 1; }; self.update = function () { if (self.isGlowing) { self.glowTime--; if (self.glowTime <= 0) { self.isGlowing = false; circleGraphics.alpha = 0.7; borderCircle.alpha = 0.9; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xB2E6E6 // Soft teal as fallback for gradient }); /**** * Game Code ****/ // Gradient background cache for performance var gradientBg = null; var bgTintOverlay = null; var musicBeatCounter = 0; var musicIntensity = 0; function drawGradientBg() { if (gradientBg && gradientBg.parent) { gradientBg.parent.removeChild(gradientBg); } if (bgTintOverlay && bgTintOverlay.parent) { bgTintOverlay.parent.removeChild(bgTintOverlay); } // Use a vertical gradient image asset for background // Assume 'bgGradient' is a vertical gradient image asset (2048x2732) from soft teal to mist blue gradientBg = LK.getAsset('bgGradient', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChildAt(gradientBg, 0); // Add as background // Add tint overlay for music reactive colors bgTintOverlay = LK.getAsset('bgGradient', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); bgTintOverlay.alpha = 0.3; bgTintOverlay.tint = 0x00ffff; game.addChildAt(bgTintOverlay, 1); } // Draw once at start drawGradientBg(); // Redraw on resize for dynamic fit LK.on('resize', function () { drawGradientBg(); }); // Background music // Sound effects // Lane dividers // Target circles at bottom of lanes // Note shapes for each lane with different colors // Game constants // Define lane positions early as they're used in multiple places var laneXPositions = [1024 - 300, 1024, 1024 + 300]; // Three vertical lanes // Animated background elements var bgElements = []; var bgStars = []; // Create floating background elements for (var be = 0; be < 12; be++) { var bgShape = LK.getAsset('noteLeft', { anchorX: 0.5, anchorY: 0.5, scaleX: Math.random() * 0.3 + 0.1, scaleY: Math.random() * 0.3 + 0.1 }); bgShape.x = Math.random() * 2048; bgShape.y = Math.random() * 2732; bgShape.alpha = 0.1; bgShape.tint = [0x00bfff, 0xff33cc, 0xffe066][Math.floor(Math.random() * 3)]; bgShape.speed = Math.random() * 0.5 + 0.2; bgShape.rotation = Math.random() * Math.PI * 2; game.addChildAt(bgShape, 2); bgElements.push(bgShape); } // Create light beams for (var lb = 0; lb < 3; lb++) { var lightBeam = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0, x: laneXPositions[lb], y: 0, scaleX: 0.2, scaleY: 2732 / 4 }); lightBeam.alpha = 0.05; lightBeam.tint = [0x00bfff, 0xff33cc, 0xffe066][lb]; game.addChildAt(lightBeam, 3); bgStars.push(lightBeam); } // Object Pools for Performance Optimization var floatingTextPool = []; var impactLinePool = []; var missFlashPool = []; var missLinePool = []; function getPooledFloatingText(text, color) { var floatingText; if (floatingTextPool.length > 0) { floatingText = floatingTextPool.pop(); floatingText.visible = true; } else { floatingText = new ScoreText('', '#ffffff'); } // Re-initialize floatingText.life = 60; var textObj = floatingText.getChildAt(0); textObj.setText(text); if (color && textObj.style) textObj.style.fill = color; textObj.alpha = 1; floatingText.y = 0; floatingText.alpha = 1; floatingText.scale.set(1); floatingTexts.push(floatingText); game.addChild(floatingText); return floatingText; } function releaseFloatingText(text) { for (var i = floatingTexts.length - 1; i >= 0; i--) { if (floatingTexts[i] === text) { floatingTexts.splice(i, 1); break; } } if (text.parent) text.parent.removeChild(text); text.visible = false; // Cap pool size to prevent unlimited growth if (floatingTextPool.length < 20) { floatingTextPool.push(text); } else { text.destroy(); // Destroy excess objects to free memory } } function getPooledObject(pool, createFn) { var obj; if (pool.length > 0) { obj = pool.pop(); obj.visible = true; } else { obj = createFn(); } game.addChild(obj); return obj; } function releaseObject(obj, pool) { if (obj && obj.parent) { obj.parent.removeChild(obj); obj.visible = false; // Cap pool size to prevent unlimited growth if (pool.length < 10) { pool.push(obj); } else { obj.destroy(); // Destroy excess objects to free memory } } } function getPooledImpactLine() { return getPooledObject(impactLinePool, function () { return LK.getAsset('laneDivider', {}); }); } function releaseImpactLine(line) { releaseObject(line, impactLinePool); } function getPooledMissFlash() { return getPooledObject(missFlashPool, function () { return LK.getAsset('laneDivider', {}); }); } function releaseMissFlash(flash) { releaseObject(flash, missFlashPool); } function getPooledMissLine() { return getPooledObject(missLinePool, function () { return LK.getAsset('laneDivider', {}); }); } function releaseMissLine(line) { releaseObject(line, missLinePool); } //Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property. var TARGET_Y = 2400; // Target circle Y position near bottom var HIT_ZONE = 120; // Pixels around target for hitting var PERFECT_ZONE = 50; // Pixels for perfect hit // Game variables var fallingNotes = []; var targetCircles = []; var score = 0; var combo = 0; var noteSpawnTimer = 0; var noteSpawnInterval = 60; // Spawn note every 60 frames initially var gameSpeed = 1; var floatingTexts = []; var totalNotes = 0; var hitNotes = 0; var accuracy = 100; // Track consecutive notes per lane var consecutiveNotesPerLane = [0, 0, 0]; // Track how many consecutive notes spawned in each lane var lastSpawnedLane = -1; // Track the last lane that spawned a note // Performance optimization constants var MAX_NOTES_ON_SCREEN = 15; // Cap notes to prevent memory bloat var NOTE_VISIBILITY_BUFFER = 200; // Extra pixels before note becomes visible var CLEANUP_THRESHOLD = 2900; // Y position to cleanup notes that missed target // Vertical lane dividers with glowing borders var laneBorders = []; // Add vertical lane dividers between lanes for (var ld = 0; ld < 2; ld++) { var dividerX = laneXPositions[ld] + (laneXPositions[ld + 1] - laneXPositions[ld]) / 2; var laneDivider = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: dividerX, y: 1366, scaleX: 0.5, scaleY: 2732 / 4, tint: 0x444444 }); laneDivider.alpha = 0.2; game.addChild(laneDivider); } // Add glowing lane borders for (var lb = 0; lb < 3; lb++) { var leftBorder = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: laneXPositions[lb] - 150, y: 1366, scaleX: 0.3, scaleY: 2732 / 4, tint: [0x00bfff, 0xff33cc, 0xffe066][lb] }); leftBorder.alpha = 0.4; game.addChild(leftBorder); laneBorders.push(leftBorder); var rightBorder = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: laneXPositions[lb] + 150, y: 1366, scaleX: 0.3, scaleY: 2732 / 4, tint: [0x00bfff, 0xff33cc, 0xffe066][lb] }); rightBorder.alpha = 0.4; game.addChild(rightBorder); laneBorders.push(rightBorder); } // Key remapping support var defaultKeyLabels = ['A', 'S', 'D']; var keyLabels = ['A', 'S', 'D']; var keyLabelObjects = []; // Store key label references for animation var keyGlowObjects = []; // Store glow background references var laneColors = [0xAEEFFF, 0xF7B3E6, 0xFFF9B2]; // Pastel cyan, magenta, yellow for improved contrast function updateKeyLabels() { for (var i = 0; i < 3; i++) { if (keyLabelObjects[i]) { keyLabelObjects[i].setText(keyLabels[i]); } } } // Prevent duplicate key assignments function remapKey(lane, newKey) { // Only allow remap if newKey is not already assigned var upperKey = newKey.toUpperCase(); for (var i = 0; i < 3; i++) { if (i !== lane && keyLabels[i].toUpperCase() === upperKey) { // Duplicate found, reject remap return false; } } keyLabels[lane] = upperKey; updateKeyLabels(); return true; } // Initial label creation for (var i = 0; i < 3; i++) { var target = new TargetCircle(i); target.x = laneXPositions[i]; target.y = TARGET_Y; // Fixed target position targetCircles.push(target); game.addChild(target); // Add glow background first (behind the letter) var keyGlow = LK.getAsset('targetCircle', { anchorX: 0.5, anchorY: 0.5, x: laneXPositions[i], y: TARGET_Y + 150, // Position below target circle scaleX: 2.5, scaleY: 2.5, alpha: 0.2 }); var laneBorderColors = [0x00bfff, 0xff33cc, 0xffe066]; // Brighter pastel blue, magenta, yellow keyGlow.tint = laneBorderColors[i]; keyGlowObjects.push(keyGlow); game.addChild(keyGlow); } // HUD panel background removed - no longer needed var hudPanel = null; // Keep reference for compatibility with existing code // Score display - left side of HUD var scoreText = new Text2('0', { size: 130, // Larger size for better visibility fill: 0xFFFFFF, // White text for maximum contrast dropShadow: true, dropShadowColor: 0x000000, //{2P} // Black shadow for contrast dropShadowDistance: 6, dropShadowAngle: Math.PI / 2, dropShadowBlur: 8, dropShadowAlpha: 0.9 }); scoreText.anchor.set(0.5, 0.5); var scoreLabel = new Text2('SCORE', { size: 50, // Slightly larger label fill: 0xFFFFFF, //{2W} // White for consistency dropShadow: true, dropShadowColor: 0x000000, //{2Y} // Black shadow dropShadowDistance: 3, dropShadowAngle: Math.PI / 2, dropShadowBlur: 5, dropShadowAlpha: 0.8 }); // Add semi-transparent background panel for score var scorePanel = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: 1024 - 400, y: 140, scaleX: 80, scaleY: 50, tint: 0x000000, alpha: 0.5 }); LK.gui.top.addChild(scorePanel); scoreText.x = 1024 - 400; scoreLabel.x = 1024 - 400; scoreText.y = 160; // Adjusted upwards LK.gui.top.addChild(scoreText); // Score label scoreLabel.anchor.set(0.5, 1); scoreLabel.y = 120; // Adjusted upwards LK.gui.top.addChild(scoreLabel); // Add COMBO text below score var comboLabelBelow = new Text2('COMBO', { size: 40, fill: 0xFFFFFF, dropShadow: true, dropShadowColor: 0x000000, dropShadowDistance: 2, dropShadowAngle: Math.PI / 2, dropShadowBlur: 4, dropShadowAlpha: 0.8 }); comboLabelBelow.anchor.set(0.5, 0); comboLabelBelow.x = 1024 - 400; comboLabelBelow.y = 220; LK.gui.top.addChild(comboLabelBelow); // Combo display - center of HUD var comboText = new Text2('0', { size: 115, fill: 0xFFAA00, dropShadow: true, dropShadowColor: 0xFFFFFF, dropShadowDistance: 4, dropShadowAngle: Math.PI / 2, dropShadowBlur: 6, dropShadowAlpha: 0.7 }); comboText.anchor.set(0.5, 0.5); comboText.x = 1024; comboText.y = 180; LK.gui.top.addChild(comboText); // Combo label var comboLabel = new Text2('COMBO', { size: 46, fill: 0x888888, dropShadow: true, dropShadowColor: 0xFFFFFF, dropShadowDistance: 2, dropShadowAngle: Math.PI / 2, dropShadowBlur: 4, dropShadowAlpha: 0.7 }); comboLabel.anchor.set(0.5, 1); comboLabel.x = 1024; comboLabel.y = 140; LK.gui.top.addChild(comboLabel); // Accuracy display - right side of HUD var accuracyText = new Text2('100%', { size: 115, fill: 0x00CC66, dropShadow: true, dropShadowColor: 0xFFFFFF, dropShadowDistance: 4, dropShadowAngle: Math.PI / 2, dropShadowBlur: 6, dropShadowAlpha: 0.7 }); accuracyText.anchor.set(0.5, 0.5); accuracyText.x = 1024 + 300; accuracyText.y = 180; LK.gui.top.addChild(accuracyText); // Accuracy label var accuracyLabel = new Text2('ACCURACY', { size: 46, fill: 0x888888, dropShadow: true, dropShadowColor: 0xFFFFFF, dropShadowDistance: 2, dropShadowAngle: Math.PI / 2, dropShadowBlur: 4, dropShadowAlpha: 0.7 }); accuracyLabel.anchor.set(0.5, 1); accuracyLabel.x = 1024 + 300; accuracyLabel.y = 140; LK.gui.top.addChild(accuracyLabel); // Tutorial overlay var tutorialActive = true; var tutorialContainer = new Container(); game.addChild(tutorialContainer); // Tutorial background var tutorialBg = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 512, scaleY: 683, tint: 0x000000, alpha: 0.85 }); tutorialContainer.addChild(tutorialBg); // Tutorial title var tutorialTitle = new Text2('NASIL OYNANIR', { size: 120, fill: 0xFFFFFF, dropShadow: true, dropShadowColor: 0x000000, dropShadowDistance: 4, dropShadowAngle: Math.PI / 2, dropShadowBlur: 6, dropShadowAlpha: 0.9 }); tutorialTitle.anchor.set(0.5, 0.5); tutorialTitle.x = 1024; tutorialTitle.y = 800; tutorialContainer.addChild(tutorialTitle); // Tutorial instructions var tutorialInstructions = ['Notalar aşağı düşer', 'Notalar daireye ulaştığında', 'Daireye bas!', '', 'PUANLAMA:', 'PERFECT = 3 puan', 'GOOD = 1 puan', 'Kombo bonusu ekler!', '', 'Yüksek kombo = Yüksek skor!']; var instructionY = 950; for (var ti = 0; ti < tutorialInstructions.length; ti++) { var instructLine = new Text2(tutorialInstructions[ti], { size: 60, fill: 0xFFFFFF, dropShadow: true, dropShadowColor: 0x000000, dropShadowDistance: 2, dropShadowAngle: Math.PI / 2, dropShadowBlur: 4, dropShadowAlpha: 0.8 }); instructLine.anchor.set(0.5, 0); instructLine.x = 1024; instructLine.y = instructionY; tutorialContainer.addChild(instructLine); instructionY += 95; } // Start button var startButton = new Container(); var startBg = LK.getAsset('targetCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1, tint: 0x00FF00, alpha: 0.8 }); startButton.addChild(startBg); var startText = new Text2('BAŞLA', { size: 80, fill: 0xFFFFFF, dropShadow: true, dropShadowColor: 0x000000, dropShadowDistance: 3, dropShadowAngle: Math.PI / 2, dropShadowBlur: 5, dropShadowAlpha: 0.9 }); startText.anchor.set(0.5, 0.5); startButton.addChild(startText); startButton.x = 1024; startButton.y = 2000; tutorialContainer.addChild(startButton); // Button hover effect startButton.down = function () { tween(startBg, { scaleX: 3.2, scaleY: 1.1, alpha: 1 }, { duration: 100, easing: tween.easeOut }); }; startButton.up = function () { tween(startBg, { scaleX: 3, scaleY: 1, alpha: 0.8 }, { duration: 100, easing: tween.easeOut }); // Start game tutorialActive = false; tween(tutorialContainer, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { tutorialContainer.destroy(); // Start playing music after tutorial LK.playMusic('bgmusic'); } }); }; // Visual examples var exampleNote = LK.getAsset('noteMiddle', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2150, scaleX: 1.5, scaleY: 1.5 }); exampleNote.tint = 0xff33cc; tutorialContainer.addChild(exampleNote); // Animate example note tween(exampleNote, { y: 2250, scaleX: 2, scaleY: 2 }, { duration: 1000, loop: true, yoyo: true, easing: tween.easeInOut }); // Instructions removed - no longer needed function spawnNote() { // Cap note queue - only spawn if we're under the limit if (fallingNotes.length >= MAX_NOTES_ON_SCREEN) { return; // Skip spawning to prevent memory bloat } // Create array of available lanes (lanes that haven't reached 2 consecutive notes) var availableLanes = []; for (var i = 0; i < 3; i++) { // A lane is available if it hasn't spawned 2 consecutive notes if (i !== lastSpawnedLane || consecutiveNotesPerLane[i] < 2) { availableLanes.push(i); } } // If no lanes are available (shouldn't happen with 3 lanes), reset and use all lanes if (availableLanes.length === 0) { availableLanes = [0, 1, 2]; } // Choose a random lane from available lanes var randomIndex = Math.floor(Math.random() * availableLanes.length); var selectedLane = availableLanes[randomIndex]; // Update consecutive note tracking if (selectedLane === lastSpawnedLane) { consecutiveNotesPerLane[selectedLane]++; } else { // Reset all lanes' consecutive counts for (var j = 0; j < 3; j++) { consecutiveNotesPerLane[j] = 0; } consecutiveNotesPerLane[selectedLane] = 1; } lastSpawnedLane = selectedLane; var note = new FallingNote(selectedLane); note.x = laneXPositions[selectedLane]; // Position in correct lane note.y = -NOTE_VISIBILITY_BUFFER; // Start above screen note.lastY = note.y; // Initialize lastY tracking fallingNotes.push(note); game.addChild(note); } function hitNote(lane) { var hitNote = null; var closestDistance = Infinity; // Find the closest note in the hit zone for this lane for (var i = 0; i < fallingNotes.length; i++) { var note = fallingNotes[i]; if (note.lane === lane && !note.hasBeenHit) { var distance = Math.abs(note.y - TARGET_Y); if (distance < HIT_ZONE && distance < closestDistance) { hitNote = note; closestDistance = distance; } } } if (hitNote) { hitNote.hasBeenHit = true; var points = 0; var feedback = ""; var color = "#ffffff"; if (closestDistance <= PERFECT_ZONE) { points = 3; feedback = "PERFECT!"; color = "#00ff00"; LK.getSound('perfect').play(); } else { points = 1; feedback = "GOOD!"; color = "#ffff00"; LK.getSound('hit').play(); } score += points * (1 + Math.floor(combo / 10) * 0.5); // Combo bonus combo++; hitNotes++; totalNotes++; accuracy = Math.round(hitNotes / totalNotes * 100); // Smooth score animation var targetScore = Math.floor(score); tween(scoreText, { text: targetScore }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { scoreText.setText(targetScore.toString()); } }); // Update displays comboText.setText(combo.toString()); accuracyText.setText(accuracy + '%'); // Update accuracy color based on performance if (accuracy >= 95) accuracyText.tint = 0x00FF00; // Green else if (accuracy >= 80) accuracyText.tint = 0xFFFF00; // Yellow else if (accuracy >= 60) accuracyText.tint = 0xFF8800; // Orange else accuracyText.tint = 0xFF0000; // Red // Change combo text color based on combo level var comboColor = 0xFFAA00; // Default orange if (combo >= 100) comboColor = 0xFF0000; // Red for high combo else if (combo >= 50) comboColor = 0xFF00FF; // Magenta for medium combo else if (combo >= 20) comboColor = 0x00FFFF; // Cyan for building combo else if (combo >= 10) comboColor = 0xFFFF00; // Yellow for decent combo comboText.tint = comboColor; // HUD glow pulse every 10 hits if (combo > 0 && combo % 10 === 0) { // Pulse combo text tween(comboText, { scaleX: 1.3, scaleY: 1.3 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(comboText, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.easeOut }); } }); // HUD panel glow pulse removed - no hudPanel to animate // Show streak banner var streakText = getPooledFloatingText('STREAK x' + combo + '!', '#FFD700'); streakText.x = 1024; streakText.y = 300; } // Screen-wide particle burst at major milestones if (combo === 50 || combo === 100 || combo === 200) { LK.effects.flashScreen(0xFFD700, 800); // Gold flash // HUD panel effect removed - no hudPanel to animate // Show milestone banner var milestoneText = getPooledFloatingText('★ ' + combo + ' COMBO! ★', '#FFD700'); milestoneText.x = 1024; milestoneText.y = 400; tween(milestoneText, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut }); // Add camera shake for major milestones var shakeIntensity = 15; var originalGameX = game.x; var originalGameY = game.y; tween(game, { x: originalGameX + shakeIntensity }, { duration: 50, onFinish: function onFinish() { tween(game, { x: originalGameX - shakeIntensity }, { duration: 50, onFinish: function onFinish() { tween(game, { x: originalGameX, y: originalGameY }, { duration: 50 }); } }); } }); } // Add subtle camera shake on perfect hits if (closestDistance <= PERFECT_ZONE && combo > 10) { var shakeAmount = 5; var origX = game.x; var origY = game.y; tween(game, { x: origX + shakeAmount, y: origY + shakeAmount }, { duration: 40, onFinish: function onFinish() { tween(game, { x: origX, y: origY }, { duration: 40 }); } }); } // Change background tint color on note hit if (bgTintOverlay) { var hitColors = [0x00ffff, 0xff00ff, 0xffff00]; bgTintOverlay.tint = hitColors[Math.floor(Math.random() * hitColors.length)]; tween(bgTintOverlay, { alpha: 0.5 }, { duration: 200, onFinish: function onFinish() { tween(bgTintOverlay, { alpha: 0.3 }, { duration: 400 }); } }); } // Visual feedback targetCircles[lane].glow(); // Animate key label when pressed - blaze to 100% opacity and scale to 120% if (keyLabelObjects[lane]) { // Animate the letter tween(keyLabelObjects[lane], { scaleX: 1.2, scaleY: 1.2, alpha: 1 // Blaze to 100% opacity for Perfect hits }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[lane], { scaleX: 1, scaleY: 1, alpha: 0.8 // Return to gentle glow }, { duration: 300, easing: tween.easeOut }); } }); // Animate the glow background if (keyGlowObjects[lane]) { tween(keyGlowObjects[lane], { scaleX: 3.0, scaleY: 3.0, alpha: 0.6 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(keyGlowObjects[lane], { scaleX: 2.5, scaleY: 2.5, alpha: 0.2 }, { duration: 200, easing: tween.easeOut }); } }); } } // Create vertical impact beam effect that shoots upward from the target var impactLine = getPooledImpactLine(); impactLine.anchor.set(0.5, 1); impactLine.x = laneXPositions[lane]; impactLine.y = 2600; impactLine.scale.set(closestDistance <= PERFECT_ZONE ? 3 : 2, closestDistance <= PERFECT_ZONE ? 2732 / 4 : 2000 / 4); impactLine.alpha = 0.8; var laneColors = [0x00FFFF, 0xFF00FF, 0xFFFF00]; // Cyan, Magenta, Yellow impactLine.tint = closestDistance <= PERFECT_ZONE ? 0xFFFFFF : laneColors[lane]; // White for perfect, lane color for good // Add color-matched burst effect for (var b = 0; b < 8; b++) { var burst = LK.getAsset('noteLeft', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); burst.tint = laneColors[lane]; burst.x = laneXPositions[lane]; burst.y = 2600; burst.alpha = 0.9; game.addChild(burst); var angle = Math.PI * 2 / 8 * b; var distance = closestDistance <= PERFECT_ZONE ? 200 : 120; tween(burst, { x: burst.x + Math.cos(angle) * distance, y: burst.y + Math.sin(angle) * distance, alpha: 0, scaleX: 0.05, scaleY: 0.05 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (burst.parent) burst.parent.removeChild(burst); } }); } // Animate vertical beam shooting upward tween(impactLine, { scaleY: 0, // Collapse upward alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { releaseImpactLine(impactLine); } }); // Target circle pulse effect var targetCircle = targetCircles[lane]; tween(targetCircle, { scaleX: 0.8, scaleY: 0.8 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(targetCircle, { scaleX: 1.1, scaleY: 1.1 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(targetCircle, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeOut }); } }); } }); // Floating score text var floatingText = getPooledFloatingText('+' + points, color); floatingText.x = laneXPositions[lane]; floatingText.y = TARGET_Y - 100; // Remove note hitNote.destroy(); for (var j = 0; j < fallingNotes.length; j++) { if (fallingNotes[j] === hitNote) { fallingNotes.splice(j, 1); break; } } } else { // Miss - reset combo and update accuracy if (combo > 0) { combo = 0; totalNotes++; accuracy = Math.round(hitNotes / totalNotes * 100); comboText.setText('0'); comboText.tint = 0xFFAA00; // Reset combo color to default accuracyText.setText(accuracy + '%'); // Update accuracy color if (accuracy >= 95) accuracyText.tint = 0x00FF00;else if (accuracy >= 80) accuracyText.tint = 0xFFFF00;else if (accuracy >= 60) accuracyText.tint = 0xFF8800;else accuracyText.tint = 0xFF0000; LK.getSound('miss').play(); var missText = getPooledFloatingText('MISS!', '#ff0000'); missText.x = laneXPositions[lane]; missText.y = TARGET_Y - 100; // Animate key label miss effect - desaturate and shake if (keyLabelObjects[lane]) { // Desaturate by changing tint to gray var originalTint = keyLabelObjects[lane].tint; keyLabelObjects[lane].tint = 0x888888; // Gray for desaturation var originalX = keyLabelObjects[lane].x; // Shake effect - move left and right var shakeDistance = 8; tween(keyLabelObjects[lane], { x: originalX - shakeDistance }, { duration: 75, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[lane], { x: originalX + shakeDistance }, { duration: 75, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[lane], { x: originalX, tint: originalTint // Restore original color }, { duration: 75, easing: tween.easeOut }); } }); } }); } // Flash lane red for miss feedback var laneFlash = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: laneXPositions[lane], y: 1366, scaleX: 0.8, scaleY: 2732 / 4, tint: 0xFF0000, alpha: 0.3 }); game.addChild(laneFlash); tween(laneFlash, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (laneFlash.parent) laneFlash.parent.removeChild(laneFlash); } }); } } } // Stars and particle effects storage var backgroundStars = []; var comboParticles = []; // Create animated background stars for (var s = 0; s < 30; s++) { var star = LK.getAsset('noteLeft', { anchorX: 0.5, anchorY: 0.5, scaleX: Math.random() * 0.2 + 0.05, scaleY: Math.random() * 0.2 + 0.05 }); star.x = Math.random() * 2048; star.y = Math.random() * 2732; star.alpha = Math.random() * 0.3 + 0.1; star.tint = 0xffffff; star.speed = Math.random() * 0.5 + 0.1; star.twinkleSpeed = Math.random() * 0.02 + 0.01; game.addChildAt(star, 2); backgroundStars.push(star); } // Tap detection for lanes game.down = function (x, y, obj) { // Don't process taps during tutorial if (tutorialActive) { return; } // Determine which lane was tapped var tappedLane = -1; for (var i = 0; i < 3; i++) { if (Math.abs(x - laneXPositions[i]) < 150) { tappedLane = i; break; } } if (tappedLane >= 0) { hitNote(tappedLane); } }; // Keyboard controls for mapped keys, strictly mapped to lanes and supporting remap LK.on('keyDown', function (event) { // Don't process keyboard during tutorial if (tutorialActive) { return; } var pressedKey = event.key ? event.key.toUpperCase() : ''; var lane = -1; for (var i = 0; i < 3; i++) { if (keyLabels[i].toUpperCase() === pressedKey) { lane = i; break; } } if (lane >= 0 && lane < 3) { // Only allow hit if there is a note in this lane in the hit zone var found = false; for (var j = 0; j < fallingNotes.length; j++) { var note = fallingNotes[j]; if (note.lane === lane && !note.hasBeenHit) { var distance = Math.abs(note.y - TARGET_Y); if (distance < HIT_ZONE) { found = true; break; } } } if (found) { hitNote(lane); // Visual feedback for key press if (targetCircles[lane]) { targetCircles[lane].glow(); } } else { // Incorrect timing or no note in this lane: shake and flash red if (keyLabelObjects[lane]) { var originalTint = keyLabelObjects[lane].tint; keyLabelObjects[lane].tint = 0xFF0000; // Red for error var originalX = keyLabelObjects[lane].x; var shakeDistance = 8; tween(keyLabelObjects[lane], { x: originalX - shakeDistance }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[lane], { x: originalX + shakeDistance }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[lane], { x: originalX, tint: originalTint }, { duration: 60, easing: tween.easeOut }); } }); } }); } } } }); game.update = function () { // Don't update game while tutorial is active if (tutorialActive) { return; } // Music-synced spawn: use music time if available, else fallback to timer noteSpawnTimer++; if (noteSpawnTimer >= noteSpawnInterval) { spawnNote(); noteSpawnTimer = 0; // Progressive speed increase: decrease interval as score/combo rises if (accuracy >= 85 && combo >= 10) { if (noteSpawnInterval > 12) { noteSpawnInterval -= 0.25; } } else if (accuracy < 60 && combo === 0) { if (noteSpawnInterval < 80) { noteSpawnInterval += 0.5; } } else { if (noteSpawnInterval > 18) { noteSpawnInterval -= 0.07; } } // Ball speed logic removed - this is now a rhythm game without a player ball } // Update animated background stars for (var s = 0; s < backgroundStars.length; s++) { var star = backgroundStars[s]; star.y += star.speed; star.alpha = 0.1 + Math.sin(LK.ticks * star.twinkleSpeed) * 0.2; if (star.y > 2832) { star.y = -100; star.x = Math.random() * 2048; } } // Update falling notes (now moving horizontally) for (var i = fallingNotes.length - 1; i >= 0; i--) { var note = fallingNotes[i]; // Check if note missed (passed target vertically) if (note.lastY <= TARGET_Y + HIT_ZONE && note.y > TARGET_Y + HIT_ZONE && !note.hasBeenHit) { // Note was missed combo = 0; totalNotes++; accuracy = Math.round(hitNotes / totalNotes * 100); comboText.setText('0'); comboText.tint = 0xFFAA00; // Reset combo color to default accuracyText.setText(accuracy + '%'); // Update accuracy color if (accuracy >= 95) accuracyText.tint = 0x00FF00;else if (accuracy >= 80) accuracyText.tint = 0xFFFF00;else if (accuracy >= 60) accuracyText.tint = 0xFF8800;else accuracyText.tint = 0xFF0000; LK.getSound('miss').play(); var missText = getPooledFloatingText('MISS!', '#ff0000'); missText.x = laneXPositions[note.lane]; missText.y = TARGET_Y - 100; // Animate key label miss effect for passed notes var missedLane = note.lane; if (keyLabelObjects[missedLane]) { // Desaturate by changing tint to gray var originalTint = keyLabelObjects[missedLane].tint; keyLabelObjects[missedLane].tint = 0x888888; // Gray for desaturation var originalX = keyLabelObjects[missedLane].x; // Shake effect - move left and right var shakeDistance = 8; tween(keyLabelObjects[missedLane], { x: originalX - shakeDistance }, { duration: 75, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[missedLane], { x: originalX + shakeDistance }, { duration: 75, easing: tween.easeOut, onFinish: function onFinish() { tween(keyLabelObjects[missedLane], { x: originalX, tint: originalTint // Restore original color }, { duration: 75, easing: tween.easeOut }); } }); } }); } // Flash lane red for missed note var laneFlash = LK.getAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5, x: laneXPositions[note.lane], y: 1366, scaleX: 0.8, scaleY: 2732 / 4, tint: 0xFF0000, alpha: 0.3 }); game.addChild(laneFlash); // Animate miss flash fade out tween(laneFlash, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (laneFlash.parent) laneFlash.parent.removeChild(laneFlash); } }); } // Remove notes that are off screen (bottom now) - optimized cleanup threshold if (note.y > CLEANUP_THRESHOLD) { note.destroy(); fallingNotes.splice(i, 1); } } // Music-synced pulse: every 60 frames, pulse the ball and target if (LK.ticks % 60 === 0) { musicBeatCounter++; musicIntensity = 0.5 + Math.sin(musicBeatCounter * 0.1) * 0.5; // Pulse lane borders with beat for (var lb = 0; lb < laneBorders.length; lb++) { tween(laneBorders[lb], { alpha: 0.6 * musicIntensity, scaleX: 0.8 }, { duration: 100, onFinish: function (border) { return function () { tween(border, { alpha: 0.3, scaleX: 0.5 }, { duration: 300 }); }; }(laneBorders[lb]) }); } // Pulse hit circles with music beat for (var i = 0; i < targetCircles.length; i++) { tween(targetCircles[i], { scaleX: 1.1, scaleY: 1.1 }, { duration: 80, onFinish: function (tc) { return function () { tween(tc, { scaleX: 1, scaleY: 1 }, { duration: 80 }); }; }(targetCircles[i]) }); } } // Update animated background elements for (var e = 0; e < bgElements.length; e++) { bgElements[e].y -= bgElements[e].speed; bgElements[e].rotation += 0.01; bgElements[e].alpha = 0.1 + musicIntensity * 0.1; if (bgElements[e].y < -100) { bgElements[e].y = 2832; bgElements[e].x = Math.random() * 2048; } } // Pulse light beams with music if (LK.ticks % 30 === 0 && bgStars.length > 0) { var beamIndex = Math.floor(Math.random() * bgStars.length); tween(bgStars[beamIndex], { alpha: 0.2 * musicIntensity, scaleX: 0.5 }, { duration: 200, onFinish: function onFinish() { tween(bgStars[beamIndex], { alpha: 0.05, scaleX: 0.2 }, { duration: 400 }); } }); } // Add combo streak particles if (combo > 0 && combo % 5 === 0) { for (var cp = 0; cp < 3; cp++) { var particle = LK.getAsset('noteLeft', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); particle.tint = [0x00bfff, 0xff33cc, 0xffe066][cp]; particle.x = laneXPositions[cp]; particle.y = TARGET_Y; particle.alpha = 0.8; game.addChild(particle); tween(particle, { y: particle.y - 200, alpha: 0, scaleX: 0.05, scaleY: 0.05 }, { duration: 800, onFinish: function onFinish() { if (particle.parent) particle.parent.removeChild(particle); } }); } } // Update floating texts with batch cleanup var textsToRemove = []; for (var i = floatingTexts.length - 1; i >= 0; i--) { var text = floatingTexts[i]; if (text.life <= 0) { textsToRemove.push(text); } } // Batch remove expired floating texts to reduce array operations for (var j = 0; j < textsToRemove.length; j++) { releaseFloatingText(textsToRemove[j]); } // Instructions removed - no longer shown after tutorial // Achievement milestones if (!window.achievements) window.achievements = {}; if (score >= 100 && !window.achievements['century']) { window.achievements['century'] = true; var achievementText = getPooledFloatingText('★ CENTURY SCORE! ★', '#FFD700'); achievementText.x = 1024; achievementText.y = 600; tween(achievementText, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.elasticOut }); } if (combo >= 50 && !window.achievements['combo50']) { window.achievements['combo50'] = true; var achievementText2 = getPooledFloatingText('★ 50 COMBO MASTER! ★', '#FF00FF'); achievementText2.x = 1024; achievementText2.y = 700; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var FallingNote = Container.expand(function (lane, noteType) {
var self = Container.call(this);
self.lane = lane;
self.noteType = noteType;
self.speed = 8; // Vertical speed (moving down)
self.hasBeenHit = false;
self.lastY = 0; // Track Y position for vertical movement
// Attach the appropriate note asset based on lane with matching colors
var assetName = 'noteLeft';
if (lane === 1) assetName = 'noteMiddle';
if (lane === 2) assetName = 'noteRight';
var noteGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply lane colors to match the new color scheme
var laneBorderColors = [0x00bfff, 0xff33cc, 0xffe066]; // Brighter pastel blue, magenta, yellow
// Ensure note starts with white base color before tinting
noteGraphics.tint = 0xffffff; // Reset to white first
noteGraphics.tint = laneBorderColors[lane];
// Add glow effect
var noteGlow = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
// Ensure glow starts with white base color before tinting
noteGlow.tint = 0xffffff; // Reset to white first
noteGlow.tint = laneBorderColors[lane];
noteGlow.alpha = 0.3;
self.setChildIndex(noteGlow, 0);
// Add simplified light trail effect
self.trail = [];
self.update = function () {
self.lastY = self.y; // Track last Y position
self.y += self.speed; // Move vertically downward
// Update trail
if (LK.ticks % 5 === 0) {
var trail = LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
// Ensure trail starts with white base color before tinting
trail.tint = 0xffffff; // Reset to white first
trail.tint = laneBorderColors[self.lane];
trail.alpha = 0.1;
trail.x = self.x;
trail.y = self.y - 10;
if (self.parent) self.parent.addChild(trail);
self.trail.push(trail);
// Fade and remove old trail
tween(trail, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 200,
onFinish: function onFinish() {
if (trail.parent) trail.parent.removeChild(trail);
}
});
}
// Clean up old trail references
self.trail = self.trail.filter(function (t) {
return t.parent;
});
};
return self;
});
var ScoreText = Container.expand(function (text, color) {
var self = Container.call(this);
self.life = 60; // 1 second at 60fps
self.speed = -2;
var textObj = new Text2(text, {
size: 40,
fill: color || "#ffffff"
});
textObj.anchor.set(0.5, 0.5);
self.addChild(textObj);
self.update = function () {
self.y += self.speed;
self.life--;
var textObj = self.getChildAt(0);
textObj.alpha = self.life / 60;
// Life cycle is managed by the main game loop for pooling
};
return self;
});
// Game constants
var TargetCircle = Container.expand(function (lane) {
var self = Container.call(this);
self.lane = lane;
self.isGlowing = false;
self.glowTime = 0;
// Main circle (filled)
var circleGraphics = self.attachAsset('targetCircle', {
anchorX: 0.5,
anchorY: 0.5
});
var laneBorderColors = [0x00bfff, 0xff33cc, 0xffe066]; // Brighter pastel blue, magenta, yellow
var laneFillColors = [0xe6f7ff, 0xffe6fa, 0xfffbe6]; // Very light pastel fill
// Set fill color for the main circle
circleGraphics.tint = laneFillColors[lane];
circleGraphics.alpha = 0.7;
// Add a border effect as a second, slightly larger circle
var borderCircle = self.attachAsset('targetCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.18,
scaleY: 1.18
});
borderCircle.tint = laneBorderColors[lane];
borderCircle.alpha = 0.9;
// Make sure border is behind the main circle
self.setChildIndex(borderCircle, 0);
// Glow effect
self.glow = function () {
self.isGlowing = true;
self.glowTime = 20; // Glow for 20 frames
circleGraphics.alpha = 1;
borderCircle.alpha = 1;
};
self.update = function () {
if (self.isGlowing) {
self.glowTime--;
if (self.glowTime <= 0) {
self.isGlowing = false;
circleGraphics.alpha = 0.7;
borderCircle.alpha = 0.9;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xB2E6E6 // Soft teal as fallback for gradient
});
/****
* Game Code
****/
// Gradient background cache for performance
var gradientBg = null;
var bgTintOverlay = null;
var musicBeatCounter = 0;
var musicIntensity = 0;
function drawGradientBg() {
if (gradientBg && gradientBg.parent) {
gradientBg.parent.removeChild(gradientBg);
}
if (bgTintOverlay && bgTintOverlay.parent) {
bgTintOverlay.parent.removeChild(bgTintOverlay);
}
// Use a vertical gradient image asset for background
// Assume 'bgGradient' is a vertical gradient image asset (2048x2732) from soft teal to mist blue
gradientBg = LK.getAsset('bgGradient', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChildAt(gradientBg, 0); // Add as background
// Add tint overlay for music reactive colors
bgTintOverlay = LK.getAsset('bgGradient', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
bgTintOverlay.alpha = 0.3;
bgTintOverlay.tint = 0x00ffff;
game.addChildAt(bgTintOverlay, 1);
}
// Draw once at start
drawGradientBg();
// Redraw on resize for dynamic fit
LK.on('resize', function () {
drawGradientBg();
});
// Background music
// Sound effects
// Lane dividers
// Target circles at bottom of lanes
// Note shapes for each lane with different colors
// Game constants
// Define lane positions early as they're used in multiple places
var laneXPositions = [1024 - 300, 1024, 1024 + 300]; // Three vertical lanes
// Animated background elements
var bgElements = [];
var bgStars = [];
// Create floating background elements
for (var be = 0; be < 12; be++) {
var bgShape = LK.getAsset('noteLeft', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: Math.random() * 0.3 + 0.1,
scaleY: Math.random() * 0.3 + 0.1
});
bgShape.x = Math.random() * 2048;
bgShape.y = Math.random() * 2732;
bgShape.alpha = 0.1;
bgShape.tint = [0x00bfff, 0xff33cc, 0xffe066][Math.floor(Math.random() * 3)];
bgShape.speed = Math.random() * 0.5 + 0.2;
bgShape.rotation = Math.random() * Math.PI * 2;
game.addChildAt(bgShape, 2);
bgElements.push(bgShape);
}
// Create light beams
for (var lb = 0; lb < 3; lb++) {
var lightBeam = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0,
x: laneXPositions[lb],
y: 0,
scaleX: 0.2,
scaleY: 2732 / 4
});
lightBeam.alpha = 0.05;
lightBeam.tint = [0x00bfff, 0xff33cc, 0xffe066][lb];
game.addChildAt(lightBeam, 3);
bgStars.push(lightBeam);
}
// Object Pools for Performance Optimization
var floatingTextPool = [];
var impactLinePool = [];
var missFlashPool = [];
var missLinePool = [];
function getPooledFloatingText(text, color) {
var floatingText;
if (floatingTextPool.length > 0) {
floatingText = floatingTextPool.pop();
floatingText.visible = true;
} else {
floatingText = new ScoreText('', '#ffffff');
}
// Re-initialize
floatingText.life = 60;
var textObj = floatingText.getChildAt(0);
textObj.setText(text);
if (color && textObj.style) textObj.style.fill = color;
textObj.alpha = 1;
floatingText.y = 0;
floatingText.alpha = 1;
floatingText.scale.set(1);
floatingTexts.push(floatingText);
game.addChild(floatingText);
return floatingText;
}
function releaseFloatingText(text) {
for (var i = floatingTexts.length - 1; i >= 0; i--) {
if (floatingTexts[i] === text) {
floatingTexts.splice(i, 1);
break;
}
}
if (text.parent) text.parent.removeChild(text);
text.visible = false;
// Cap pool size to prevent unlimited growth
if (floatingTextPool.length < 20) {
floatingTextPool.push(text);
} else {
text.destroy(); // Destroy excess objects to free memory
}
}
function getPooledObject(pool, createFn) {
var obj;
if (pool.length > 0) {
obj = pool.pop();
obj.visible = true;
} else {
obj = createFn();
}
game.addChild(obj);
return obj;
}
function releaseObject(obj, pool) {
if (obj && obj.parent) {
obj.parent.removeChild(obj);
obj.visible = false;
// Cap pool size to prevent unlimited growth
if (pool.length < 10) {
pool.push(obj);
} else {
obj.destroy(); // Destroy excess objects to free memory
}
}
}
function getPooledImpactLine() {
return getPooledObject(impactLinePool, function () {
return LK.getAsset('laneDivider', {});
});
}
function releaseImpactLine(line) {
releaseObject(line, impactLinePool);
}
function getPooledMissFlash() {
return getPooledObject(missFlashPool, function () {
return LK.getAsset('laneDivider', {});
});
}
function releaseMissFlash(flash) {
releaseObject(flash, missFlashPool);
}
function getPooledMissLine() {
return getPooledObject(missLinePool, function () {
return LK.getAsset('laneDivider', {});
});
}
function releaseMissLine(line) {
releaseObject(line, missLinePool);
}
//Minimalistic tween library which should be used for animations over time, including tinting / colouring an object, scaling, rotating, or changing any game object property.
var TARGET_Y = 2400; // Target circle Y position near bottom
var HIT_ZONE = 120; // Pixels around target for hitting
var PERFECT_ZONE = 50; // Pixels for perfect hit
// Game variables
var fallingNotes = [];
var targetCircles = [];
var score = 0;
var combo = 0;
var noteSpawnTimer = 0;
var noteSpawnInterval = 60; // Spawn note every 60 frames initially
var gameSpeed = 1;
var floatingTexts = [];
var totalNotes = 0;
var hitNotes = 0;
var accuracy = 100;
// Track consecutive notes per lane
var consecutiveNotesPerLane = [0, 0, 0]; // Track how many consecutive notes spawned in each lane
var lastSpawnedLane = -1; // Track the last lane that spawned a note
// Performance optimization constants
var MAX_NOTES_ON_SCREEN = 15; // Cap notes to prevent memory bloat
var NOTE_VISIBILITY_BUFFER = 200; // Extra pixels before note becomes visible
var CLEANUP_THRESHOLD = 2900; // Y position to cleanup notes that missed target
// Vertical lane dividers with glowing borders
var laneBorders = [];
// Add vertical lane dividers between lanes
for (var ld = 0; ld < 2; ld++) {
var dividerX = laneXPositions[ld] + (laneXPositions[ld + 1] - laneXPositions[ld]) / 2;
var laneDivider = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: dividerX,
y: 1366,
scaleX: 0.5,
scaleY: 2732 / 4,
tint: 0x444444
});
laneDivider.alpha = 0.2;
game.addChild(laneDivider);
}
// Add glowing lane borders
for (var lb = 0; lb < 3; lb++) {
var leftBorder = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: laneXPositions[lb] - 150,
y: 1366,
scaleX: 0.3,
scaleY: 2732 / 4,
tint: [0x00bfff, 0xff33cc, 0xffe066][lb]
});
leftBorder.alpha = 0.4;
game.addChild(leftBorder);
laneBorders.push(leftBorder);
var rightBorder = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: laneXPositions[lb] + 150,
y: 1366,
scaleX: 0.3,
scaleY: 2732 / 4,
tint: [0x00bfff, 0xff33cc, 0xffe066][lb]
});
rightBorder.alpha = 0.4;
game.addChild(rightBorder);
laneBorders.push(rightBorder);
}
// Key remapping support
var defaultKeyLabels = ['A', 'S', 'D'];
var keyLabels = ['A', 'S', 'D'];
var keyLabelObjects = []; // Store key label references for animation
var keyGlowObjects = []; // Store glow background references
var laneColors = [0xAEEFFF, 0xF7B3E6, 0xFFF9B2]; // Pastel cyan, magenta, yellow for improved contrast
function updateKeyLabels() {
for (var i = 0; i < 3; i++) {
if (keyLabelObjects[i]) {
keyLabelObjects[i].setText(keyLabels[i]);
}
}
}
// Prevent duplicate key assignments
function remapKey(lane, newKey) {
// Only allow remap if newKey is not already assigned
var upperKey = newKey.toUpperCase();
for (var i = 0; i < 3; i++) {
if (i !== lane && keyLabels[i].toUpperCase() === upperKey) {
// Duplicate found, reject remap
return false;
}
}
keyLabels[lane] = upperKey;
updateKeyLabels();
return true;
}
// Initial label creation
for (var i = 0; i < 3; i++) {
var target = new TargetCircle(i);
target.x = laneXPositions[i];
target.y = TARGET_Y; // Fixed target position
targetCircles.push(target);
game.addChild(target);
// Add glow background first (behind the letter)
var keyGlow = LK.getAsset('targetCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: laneXPositions[i],
y: TARGET_Y + 150,
// Position below target circle
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.2
});
var laneBorderColors = [0x00bfff, 0xff33cc, 0xffe066]; // Brighter pastel blue, magenta, yellow
keyGlow.tint = laneBorderColors[i];
keyGlowObjects.push(keyGlow);
game.addChild(keyGlow);
}
// HUD panel background removed - no longer needed
var hudPanel = null; // Keep reference for compatibility with existing code
// Score display - left side of HUD
var scoreText = new Text2('0', {
size: 130,
// Larger size for better visibility
fill: 0xFFFFFF,
// White text for maximum contrast
dropShadow: true,
dropShadowColor: 0x000000,
//{2P} // Black shadow for contrast
dropShadowDistance: 6,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 8,
dropShadowAlpha: 0.9
});
scoreText.anchor.set(0.5, 0.5);
var scoreLabel = new Text2('SCORE', {
size: 50,
// Slightly larger label
fill: 0xFFFFFF,
//{2W} // White for consistency
dropShadow: true,
dropShadowColor: 0x000000,
//{2Y} // Black shadow
dropShadowDistance: 3,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 5,
dropShadowAlpha: 0.8
});
// Add semi-transparent background panel for score
var scorePanel = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024 - 400,
y: 140,
scaleX: 80,
scaleY: 50,
tint: 0x000000,
alpha: 0.5
});
LK.gui.top.addChild(scorePanel);
scoreText.x = 1024 - 400;
scoreLabel.x = 1024 - 400;
scoreText.y = 160; // Adjusted upwards
LK.gui.top.addChild(scoreText);
// Score label
scoreLabel.anchor.set(0.5, 1);
scoreLabel.y = 120; // Adjusted upwards
LK.gui.top.addChild(scoreLabel);
// Add COMBO text below score
var comboLabelBelow = new Text2('COMBO', {
size: 40,
fill: 0xFFFFFF,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 2,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 4,
dropShadowAlpha: 0.8
});
comboLabelBelow.anchor.set(0.5, 0);
comboLabelBelow.x = 1024 - 400;
comboLabelBelow.y = 220;
LK.gui.top.addChild(comboLabelBelow);
// Combo display - center of HUD
var comboText = new Text2('0', {
size: 115,
fill: 0xFFAA00,
dropShadow: true,
dropShadowColor: 0xFFFFFF,
dropShadowDistance: 4,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 6,
dropShadowAlpha: 0.7
});
comboText.anchor.set(0.5, 0.5);
comboText.x = 1024;
comboText.y = 180;
LK.gui.top.addChild(comboText);
// Combo label
var comboLabel = new Text2('COMBO', {
size: 46,
fill: 0x888888,
dropShadow: true,
dropShadowColor: 0xFFFFFF,
dropShadowDistance: 2,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 4,
dropShadowAlpha: 0.7
});
comboLabel.anchor.set(0.5, 1);
comboLabel.x = 1024;
comboLabel.y = 140;
LK.gui.top.addChild(comboLabel);
// Accuracy display - right side of HUD
var accuracyText = new Text2('100%', {
size: 115,
fill: 0x00CC66,
dropShadow: true,
dropShadowColor: 0xFFFFFF,
dropShadowDistance: 4,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 6,
dropShadowAlpha: 0.7
});
accuracyText.anchor.set(0.5, 0.5);
accuracyText.x = 1024 + 300;
accuracyText.y = 180;
LK.gui.top.addChild(accuracyText);
// Accuracy label
var accuracyLabel = new Text2('ACCURACY', {
size: 46,
fill: 0x888888,
dropShadow: true,
dropShadowColor: 0xFFFFFF,
dropShadowDistance: 2,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 4,
dropShadowAlpha: 0.7
});
accuracyLabel.anchor.set(0.5, 1);
accuracyLabel.x = 1024 + 300;
accuracyLabel.y = 140;
LK.gui.top.addChild(accuracyLabel);
// Tutorial overlay
var tutorialActive = true;
var tutorialContainer = new Container();
game.addChild(tutorialContainer);
// Tutorial background
var tutorialBg = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 512,
scaleY: 683,
tint: 0x000000,
alpha: 0.85
});
tutorialContainer.addChild(tutorialBg);
// Tutorial title
var tutorialTitle = new Text2('NASIL OYNANIR', {
size: 120,
fill: 0xFFFFFF,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 4,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 6,
dropShadowAlpha: 0.9
});
tutorialTitle.anchor.set(0.5, 0.5);
tutorialTitle.x = 1024;
tutorialTitle.y = 800;
tutorialContainer.addChild(tutorialTitle);
// Tutorial instructions
var tutorialInstructions = ['Notalar aşağı düşer', 'Notalar daireye ulaştığında', 'Daireye bas!', '', 'PUANLAMA:', 'PERFECT = 3 puan', 'GOOD = 1 puan', 'Kombo bonusu ekler!', '', 'Yüksek kombo = Yüksek skor!'];
var instructionY = 950;
for (var ti = 0; ti < tutorialInstructions.length; ti++) {
var instructLine = new Text2(tutorialInstructions[ti], {
size: 60,
fill: 0xFFFFFF,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 2,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 4,
dropShadowAlpha: 0.8
});
instructLine.anchor.set(0.5, 0);
instructLine.x = 1024;
instructLine.y = instructionY;
tutorialContainer.addChild(instructLine);
instructionY += 95;
}
// Start button
var startButton = new Container();
var startBg = LK.getAsset('targetCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1,
tint: 0x00FF00,
alpha: 0.8
});
startButton.addChild(startBg);
var startText = new Text2('BAŞLA', {
size: 80,
fill: 0xFFFFFF,
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowDistance: 3,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 5,
dropShadowAlpha: 0.9
});
startText.anchor.set(0.5, 0.5);
startButton.addChild(startText);
startButton.x = 1024;
startButton.y = 2000;
tutorialContainer.addChild(startButton);
// Button hover effect
startButton.down = function () {
tween(startBg, {
scaleX: 3.2,
scaleY: 1.1,
alpha: 1
}, {
duration: 100,
easing: tween.easeOut
});
};
startButton.up = function () {
tween(startBg, {
scaleX: 3,
scaleY: 1,
alpha: 0.8
}, {
duration: 100,
easing: tween.easeOut
});
// Start game
tutorialActive = false;
tween(tutorialContainer, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
tutorialContainer.destroy();
// Start playing music after tutorial
LK.playMusic('bgmusic');
}
});
};
// Visual examples
var exampleNote = LK.getAsset('noteMiddle', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2150,
scaleX: 1.5,
scaleY: 1.5
});
exampleNote.tint = 0xff33cc;
tutorialContainer.addChild(exampleNote);
// Animate example note
tween(exampleNote, {
y: 2250,
scaleX: 2,
scaleY: 2
}, {
duration: 1000,
loop: true,
yoyo: true,
easing: tween.easeInOut
});
// Instructions removed - no longer needed
function spawnNote() {
// Cap note queue - only spawn if we're under the limit
if (fallingNotes.length >= MAX_NOTES_ON_SCREEN) {
return; // Skip spawning to prevent memory bloat
}
// Create array of available lanes (lanes that haven't reached 2 consecutive notes)
var availableLanes = [];
for (var i = 0; i < 3; i++) {
// A lane is available if it hasn't spawned 2 consecutive notes
if (i !== lastSpawnedLane || consecutiveNotesPerLane[i] < 2) {
availableLanes.push(i);
}
}
// If no lanes are available (shouldn't happen with 3 lanes), reset and use all lanes
if (availableLanes.length === 0) {
availableLanes = [0, 1, 2];
}
// Choose a random lane from available lanes
var randomIndex = Math.floor(Math.random() * availableLanes.length);
var selectedLane = availableLanes[randomIndex];
// Update consecutive note tracking
if (selectedLane === lastSpawnedLane) {
consecutiveNotesPerLane[selectedLane]++;
} else {
// Reset all lanes' consecutive counts
for (var j = 0; j < 3; j++) {
consecutiveNotesPerLane[j] = 0;
}
consecutiveNotesPerLane[selectedLane] = 1;
}
lastSpawnedLane = selectedLane;
var note = new FallingNote(selectedLane);
note.x = laneXPositions[selectedLane]; // Position in correct lane
note.y = -NOTE_VISIBILITY_BUFFER; // Start above screen
note.lastY = note.y; // Initialize lastY tracking
fallingNotes.push(note);
game.addChild(note);
}
function hitNote(lane) {
var hitNote = null;
var closestDistance = Infinity;
// Find the closest note in the hit zone for this lane
for (var i = 0; i < fallingNotes.length; i++) {
var note = fallingNotes[i];
if (note.lane === lane && !note.hasBeenHit) {
var distance = Math.abs(note.y - TARGET_Y);
if (distance < HIT_ZONE && distance < closestDistance) {
hitNote = note;
closestDistance = distance;
}
}
}
if (hitNote) {
hitNote.hasBeenHit = true;
var points = 0;
var feedback = "";
var color = "#ffffff";
if (closestDistance <= PERFECT_ZONE) {
points = 3;
feedback = "PERFECT!";
color = "#00ff00";
LK.getSound('perfect').play();
} else {
points = 1;
feedback = "GOOD!";
color = "#ffff00";
LK.getSound('hit').play();
}
score += points * (1 + Math.floor(combo / 10) * 0.5); // Combo bonus
combo++;
hitNotes++;
totalNotes++;
accuracy = Math.round(hitNotes / totalNotes * 100);
// Smooth score animation
var targetScore = Math.floor(score);
tween(scoreText, {
text: targetScore
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
scoreText.setText(targetScore.toString());
}
});
// Update displays
comboText.setText(combo.toString());
accuracyText.setText(accuracy + '%');
// Update accuracy color based on performance
if (accuracy >= 95) accuracyText.tint = 0x00FF00; // Green
else if (accuracy >= 80) accuracyText.tint = 0xFFFF00; // Yellow
else if (accuracy >= 60) accuracyText.tint = 0xFF8800; // Orange
else accuracyText.tint = 0xFF0000; // Red
// Change combo text color based on combo level
var comboColor = 0xFFAA00; // Default orange
if (combo >= 100) comboColor = 0xFF0000; // Red for high combo
else if (combo >= 50) comboColor = 0xFF00FF; // Magenta for medium combo
else if (combo >= 20) comboColor = 0x00FFFF; // Cyan for building combo
else if (combo >= 10) comboColor = 0xFFFF00; // Yellow for decent combo
comboText.tint = comboColor;
// HUD glow pulse every 10 hits
if (combo > 0 && combo % 10 === 0) {
// Pulse combo text
tween(comboText, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(comboText, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
}
});
// HUD panel glow pulse removed - no hudPanel to animate
// Show streak banner
var streakText = getPooledFloatingText('STREAK x' + combo + '!', '#FFD700');
streakText.x = 1024;
streakText.y = 300;
}
// Screen-wide particle burst at major milestones
if (combo === 50 || combo === 100 || combo === 200) {
LK.effects.flashScreen(0xFFD700, 800); // Gold flash
// HUD panel effect removed - no hudPanel to animate
// Show milestone banner
var milestoneText = getPooledFloatingText('★ ' + combo + ' COMBO! ★', '#FFD700');
milestoneText.x = 1024;
milestoneText.y = 400;
tween(milestoneText, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.elasticOut
});
// Add camera shake for major milestones
var shakeIntensity = 15;
var originalGameX = game.x;
var originalGameY = game.y;
tween(game, {
x: originalGameX + shakeIntensity
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: originalGameX - shakeIntensity
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: originalGameX,
y: originalGameY
}, {
duration: 50
});
}
});
}
});
}
// Add subtle camera shake on perfect hits
if (closestDistance <= PERFECT_ZONE && combo > 10) {
var shakeAmount = 5;
var origX = game.x;
var origY = game.y;
tween(game, {
x: origX + shakeAmount,
y: origY + shakeAmount
}, {
duration: 40,
onFinish: function onFinish() {
tween(game, {
x: origX,
y: origY
}, {
duration: 40
});
}
});
}
// Change background tint color on note hit
if (bgTintOverlay) {
var hitColors = [0x00ffff, 0xff00ff, 0xffff00];
bgTintOverlay.tint = hitColors[Math.floor(Math.random() * hitColors.length)];
tween(bgTintOverlay, {
alpha: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(bgTintOverlay, {
alpha: 0.3
}, {
duration: 400
});
}
});
}
// Visual feedback
targetCircles[lane].glow();
// Animate key label when pressed - blaze to 100% opacity and scale to 120%
if (keyLabelObjects[lane]) {
// Animate the letter
tween(keyLabelObjects[lane], {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1 // Blaze to 100% opacity for Perfect hits
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[lane], {
scaleX: 1,
scaleY: 1,
alpha: 0.8 // Return to gentle glow
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Animate the glow background
if (keyGlowObjects[lane]) {
tween(keyGlowObjects[lane], {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0.6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyGlowObjects[lane], {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.2
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
}
// Create vertical impact beam effect that shoots upward from the target
var impactLine = getPooledImpactLine();
impactLine.anchor.set(0.5, 1);
impactLine.x = laneXPositions[lane];
impactLine.y = 2600;
impactLine.scale.set(closestDistance <= PERFECT_ZONE ? 3 : 2, closestDistance <= PERFECT_ZONE ? 2732 / 4 : 2000 / 4);
impactLine.alpha = 0.8;
var laneColors = [0x00FFFF, 0xFF00FF, 0xFFFF00]; // Cyan, Magenta, Yellow
impactLine.tint = closestDistance <= PERFECT_ZONE ? 0xFFFFFF : laneColors[lane]; // White for perfect, lane color for good
// Add color-matched burst effect
for (var b = 0; b < 8; b++) {
var burst = LK.getAsset('noteLeft', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
burst.tint = laneColors[lane];
burst.x = laneXPositions[lane];
burst.y = 2600;
burst.alpha = 0.9;
game.addChild(burst);
var angle = Math.PI * 2 / 8 * b;
var distance = closestDistance <= PERFECT_ZONE ? 200 : 120;
tween(burst, {
x: burst.x + Math.cos(angle) * distance,
y: burst.y + Math.sin(angle) * distance,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (burst.parent) burst.parent.removeChild(burst);
}
});
}
// Animate vertical beam shooting upward
tween(impactLine, {
scaleY: 0,
// Collapse upward
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
releaseImpactLine(impactLine);
}
});
// Target circle pulse effect
var targetCircle = targetCircles[lane];
tween(targetCircle, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetCircle, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetCircle, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
});
// Floating score text
var floatingText = getPooledFloatingText('+' + points, color);
floatingText.x = laneXPositions[lane];
floatingText.y = TARGET_Y - 100;
// Remove note
hitNote.destroy();
for (var j = 0; j < fallingNotes.length; j++) {
if (fallingNotes[j] === hitNote) {
fallingNotes.splice(j, 1);
break;
}
}
} else {
// Miss - reset combo and update accuracy
if (combo > 0) {
combo = 0;
totalNotes++;
accuracy = Math.round(hitNotes / totalNotes * 100);
comboText.setText('0');
comboText.tint = 0xFFAA00; // Reset combo color to default
accuracyText.setText(accuracy + '%');
// Update accuracy color
if (accuracy >= 95) accuracyText.tint = 0x00FF00;else if (accuracy >= 80) accuracyText.tint = 0xFFFF00;else if (accuracy >= 60) accuracyText.tint = 0xFF8800;else accuracyText.tint = 0xFF0000;
LK.getSound('miss').play();
var missText = getPooledFloatingText('MISS!', '#ff0000');
missText.x = laneXPositions[lane];
missText.y = TARGET_Y - 100;
// Animate key label miss effect - desaturate and shake
if (keyLabelObjects[lane]) {
// Desaturate by changing tint to gray
var originalTint = keyLabelObjects[lane].tint;
keyLabelObjects[lane].tint = 0x888888; // Gray for desaturation
var originalX = keyLabelObjects[lane].x;
// Shake effect - move left and right
var shakeDistance = 8;
tween(keyLabelObjects[lane], {
x: originalX - shakeDistance
}, {
duration: 75,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[lane], {
x: originalX + shakeDistance
}, {
duration: 75,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[lane], {
x: originalX,
tint: originalTint // Restore original color
}, {
duration: 75,
easing: tween.easeOut
});
}
});
}
});
}
// Flash lane red for miss feedback
var laneFlash = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: laneXPositions[lane],
y: 1366,
scaleX: 0.8,
scaleY: 2732 / 4,
tint: 0xFF0000,
alpha: 0.3
});
game.addChild(laneFlash);
tween(laneFlash, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (laneFlash.parent) laneFlash.parent.removeChild(laneFlash);
}
});
}
}
}
// Stars and particle effects storage
var backgroundStars = [];
var comboParticles = [];
// Create animated background stars
for (var s = 0; s < 30; s++) {
var star = LK.getAsset('noteLeft', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: Math.random() * 0.2 + 0.05,
scaleY: Math.random() * 0.2 + 0.05
});
star.x = Math.random() * 2048;
star.y = Math.random() * 2732;
star.alpha = Math.random() * 0.3 + 0.1;
star.tint = 0xffffff;
star.speed = Math.random() * 0.5 + 0.1;
star.twinkleSpeed = Math.random() * 0.02 + 0.01;
game.addChildAt(star, 2);
backgroundStars.push(star);
}
// Tap detection for lanes
game.down = function (x, y, obj) {
// Don't process taps during tutorial
if (tutorialActive) {
return;
}
// Determine which lane was tapped
var tappedLane = -1;
for (var i = 0; i < 3; i++) {
if (Math.abs(x - laneXPositions[i]) < 150) {
tappedLane = i;
break;
}
}
if (tappedLane >= 0) {
hitNote(tappedLane);
}
};
// Keyboard controls for mapped keys, strictly mapped to lanes and supporting remap
LK.on('keyDown', function (event) {
// Don't process keyboard during tutorial
if (tutorialActive) {
return;
}
var pressedKey = event.key ? event.key.toUpperCase() : '';
var lane = -1;
for (var i = 0; i < 3; i++) {
if (keyLabels[i].toUpperCase() === pressedKey) {
lane = i;
break;
}
}
if (lane >= 0 && lane < 3) {
// Only allow hit if there is a note in this lane in the hit zone
var found = false;
for (var j = 0; j < fallingNotes.length; j++) {
var note = fallingNotes[j];
if (note.lane === lane && !note.hasBeenHit) {
var distance = Math.abs(note.y - TARGET_Y);
if (distance < HIT_ZONE) {
found = true;
break;
}
}
}
if (found) {
hitNote(lane);
// Visual feedback for key press
if (targetCircles[lane]) {
targetCircles[lane].glow();
}
} else {
// Incorrect timing or no note in this lane: shake and flash red
if (keyLabelObjects[lane]) {
var originalTint = keyLabelObjects[lane].tint;
keyLabelObjects[lane].tint = 0xFF0000; // Red for error
var originalX = keyLabelObjects[lane].x;
var shakeDistance = 8;
tween(keyLabelObjects[lane], {
x: originalX - shakeDistance
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[lane], {
x: originalX + shakeDistance
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[lane], {
x: originalX,
tint: originalTint
}, {
duration: 60,
easing: tween.easeOut
});
}
});
}
});
}
}
}
});
game.update = function () {
// Don't update game while tutorial is active
if (tutorialActive) {
return;
}
// Music-synced spawn: use music time if available, else fallback to timer
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
// Progressive speed increase: decrease interval as score/combo rises
if (accuracy >= 85 && combo >= 10) {
if (noteSpawnInterval > 12) {
noteSpawnInterval -= 0.25;
}
} else if (accuracy < 60 && combo === 0) {
if (noteSpawnInterval < 80) {
noteSpawnInterval += 0.5;
}
} else {
if (noteSpawnInterval > 18) {
noteSpawnInterval -= 0.07;
}
}
// Ball speed logic removed - this is now a rhythm game without a player ball
}
// Update animated background stars
for (var s = 0; s < backgroundStars.length; s++) {
var star = backgroundStars[s];
star.y += star.speed;
star.alpha = 0.1 + Math.sin(LK.ticks * star.twinkleSpeed) * 0.2;
if (star.y > 2832) {
star.y = -100;
star.x = Math.random() * 2048;
}
}
// Update falling notes (now moving horizontally)
for (var i = fallingNotes.length - 1; i >= 0; i--) {
var note = fallingNotes[i];
// Check if note missed (passed target vertically)
if (note.lastY <= TARGET_Y + HIT_ZONE && note.y > TARGET_Y + HIT_ZONE && !note.hasBeenHit) {
// Note was missed
combo = 0;
totalNotes++;
accuracy = Math.round(hitNotes / totalNotes * 100);
comboText.setText('0');
comboText.tint = 0xFFAA00; // Reset combo color to default
accuracyText.setText(accuracy + '%');
// Update accuracy color
if (accuracy >= 95) accuracyText.tint = 0x00FF00;else if (accuracy >= 80) accuracyText.tint = 0xFFFF00;else if (accuracy >= 60) accuracyText.tint = 0xFF8800;else accuracyText.tint = 0xFF0000;
LK.getSound('miss').play();
var missText = getPooledFloatingText('MISS!', '#ff0000');
missText.x = laneXPositions[note.lane];
missText.y = TARGET_Y - 100;
// Animate key label miss effect for passed notes
var missedLane = note.lane;
if (keyLabelObjects[missedLane]) {
// Desaturate by changing tint to gray
var originalTint = keyLabelObjects[missedLane].tint;
keyLabelObjects[missedLane].tint = 0x888888; // Gray for desaturation
var originalX = keyLabelObjects[missedLane].x;
// Shake effect - move left and right
var shakeDistance = 8;
tween(keyLabelObjects[missedLane], {
x: originalX - shakeDistance
}, {
duration: 75,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[missedLane], {
x: originalX + shakeDistance
}, {
duration: 75,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(keyLabelObjects[missedLane], {
x: originalX,
tint: originalTint // Restore original color
}, {
duration: 75,
easing: tween.easeOut
});
}
});
}
});
}
// Flash lane red for missed note
var laneFlash = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5,
x: laneXPositions[note.lane],
y: 1366,
scaleX: 0.8,
scaleY: 2732 / 4,
tint: 0xFF0000,
alpha: 0.3
});
game.addChild(laneFlash);
// Animate miss flash fade out
tween(laneFlash, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (laneFlash.parent) laneFlash.parent.removeChild(laneFlash);
}
});
}
// Remove notes that are off screen (bottom now) - optimized cleanup threshold
if (note.y > CLEANUP_THRESHOLD) {
note.destroy();
fallingNotes.splice(i, 1);
}
}
// Music-synced pulse: every 60 frames, pulse the ball and target
if (LK.ticks % 60 === 0) {
musicBeatCounter++;
musicIntensity = 0.5 + Math.sin(musicBeatCounter * 0.1) * 0.5;
// Pulse lane borders with beat
for (var lb = 0; lb < laneBorders.length; lb++) {
tween(laneBorders[lb], {
alpha: 0.6 * musicIntensity,
scaleX: 0.8
}, {
duration: 100,
onFinish: function (border) {
return function () {
tween(border, {
alpha: 0.3,
scaleX: 0.5
}, {
duration: 300
});
};
}(laneBorders[lb])
});
}
// Pulse hit circles with music beat
for (var i = 0; i < targetCircles.length; i++) {
tween(targetCircles[i], {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 80,
onFinish: function (tc) {
return function () {
tween(tc, {
scaleX: 1,
scaleY: 1
}, {
duration: 80
});
};
}(targetCircles[i])
});
}
}
// Update animated background elements
for (var e = 0; e < bgElements.length; e++) {
bgElements[e].y -= bgElements[e].speed;
bgElements[e].rotation += 0.01;
bgElements[e].alpha = 0.1 + musicIntensity * 0.1;
if (bgElements[e].y < -100) {
bgElements[e].y = 2832;
bgElements[e].x = Math.random() * 2048;
}
}
// Pulse light beams with music
if (LK.ticks % 30 === 0 && bgStars.length > 0) {
var beamIndex = Math.floor(Math.random() * bgStars.length);
tween(bgStars[beamIndex], {
alpha: 0.2 * musicIntensity,
scaleX: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(bgStars[beamIndex], {
alpha: 0.05,
scaleX: 0.2
}, {
duration: 400
});
}
});
}
// Add combo streak particles
if (combo > 0 && combo % 5 === 0) {
for (var cp = 0; cp < 3; cp++) {
var particle = LK.getAsset('noteLeft', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
particle.tint = [0x00bfff, 0xff33cc, 0xffe066][cp];
particle.x = laneXPositions[cp];
particle.y = TARGET_Y;
particle.alpha = 0.8;
game.addChild(particle);
tween(particle, {
y: particle.y - 200,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 800,
onFinish: function onFinish() {
if (particle.parent) particle.parent.removeChild(particle);
}
});
}
}
// Update floating texts with batch cleanup
var textsToRemove = [];
for (var i = floatingTexts.length - 1; i >= 0; i--) {
var text = floatingTexts[i];
if (text.life <= 0) {
textsToRemove.push(text);
}
}
// Batch remove expired floating texts to reduce array operations
for (var j = 0; j < textsToRemove.length; j++) {
releaseFloatingText(textsToRemove[j]);
}
// Instructions removed - no longer shown after tutorial
// Achievement milestones
if (!window.achievements) window.achievements = {};
if (score >= 100 && !window.achievements['century']) {
window.achievements['century'] = true;
var achievementText = getPooledFloatingText('★ CENTURY SCORE! ★', '#FFD700');
achievementText.x = 1024;
achievementText.y = 600;
tween(achievementText, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.elasticOut
});
}
if (combo >= 50 && !window.achievements['combo50']) {
window.achievements['combo50'] = true;
var achievementText2 = getPooledFloatingText('★ 50 COMBO MASTER! ★', '#FF00FF');
achievementText2.x = 1024;
achievementText2.y = 700;
}
};
sarı 1 sekizlik nota arka planı olmasın sade olsun 2d. In-Game asset. 2d. High contrast. No shadows
beyaz 1 nota arka planı olmasın sade olsun 2d.. In-Game asset. High contrast. No shadows BEYAZ OLCAK SİYAH DEĞİL
sarı 1 üçlük nota arka planı olmasın sade olsun 2d.. In-Game asset. 2d. High contrast. No shadows