/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Note = Container.expand(function (trackIndex, isPowerNote) { var self = Container.call(this); self.trackIndex = trackIndex; self.isPowerNote = isPowerNote || false; self.speed = 7; self.hasBeenTapped = false; self.hasReducedLife = false; // Flag to track if this note already caused life loss self.lastY = 0; var noteGraphics = self.attachAsset(self.isPowerNote ? 'powerNote' : 'note', { anchorX: 0.5, anchorY: 0.5 }); // Apply sharpness enhancement to note graphics if (noteGraphics.filters) { noteGraphics.filters = []; } else { noteGraphics.filters = []; } // Enhance contrast for sharper appearance noteGraphics.alpha = 1.0; if (self.isPowerNote) { noteGraphics.tint = 0xFFD93D; self.scale.set(1.2); } self.update = function () { if (self.lastY === undefined) self.lastY = self.y; if (self.lastInTargetZone === undefined) self.lastInTargetZone = false; // Check if note just entered target zone var targetY = 1650; // unifiedTargetZone.y var inTargetZone = Math.abs(self.y - targetY) <= 150; // Expanded target zone range // Additional check: ensure no other note is currently in target zone var otherNoteInZone = false; for (var k = 0; k < notes.length; k++) { var otherNote = notes[k]; if (otherNote !== self && !otherNote.hasBeenTapped && Math.abs(otherNote.y - targetY) <= 100) { otherNoteInZone = true; break; } } if (!self.lastInTargetZone && inTargetZone && !self.hasBeenTapped && !otherNoteInZone) { // Note just entered target zone and no other note is present - trigger explosion self.triggerExplosion(); } // Check if note passed bottom of target zone without being tapped (missed) var targetZoneBottom = targetY + 150; // Bottom of target zone if (self.lastY <= targetZoneBottom && self.y > targetZoneBottom && !self.hasBeenTapped && !self.hasReducedLife) { // Note passed bottom of target zone - play miss sound and reduce lives only once LK.getSound('miss').play(); updateLives(-1); self.hasReducedLife = true; // Mark that this note has already caused life loss } self.lastY = self.y; self.lastInTargetZone = inTargetZone; self.y += self.speed; }; self.checkTiming = function () { var targetY = unifiedTargetZone.y; var distance = Math.abs(self.y - targetY); if (distance <= 45) { // Expanded perfect range return 'perfect'; } else if (distance <= 90) { // Expanded good range return 'good'; } return 'miss'; }; self.triggerExplosion = function () { if (self.hasBeenTapped) return; var timing = self.checkTiming(); if (timing !== 'miss') { self.hasBeenTapped = true; var points = 0; if (timing === 'perfect') { points = 1; LK.getSound('perfect').play(); createParticleEffect(self.x, self.y, self.isPowerNote ? 0xFFD93D : 0x00FF00); } else if (timing === 'good') { points = 1; LK.getSound('tap').play(); createParticleEffect(self.x, self.y, 0x87CEEB); } updateScore(points); unifiedTargetZone.flash(); // Trigger explosion animation on corresponding bottom object var bottomObjects = [leftObject, centerObject, rightObject]; if (bottomObjects[self.trackIndex]) { var targetObject = bottomObjects[self.trackIndex]; // Flash the bottom object var originalAlpha = targetObject.alpha; tween(targetObject, { alpha: 1.0 }, { duration: 100, onFinish: function onFinish() { tween(targetObject, { alpha: originalAlpha }, { duration: 200 }); } }); // Scale pulse effect var originalScale = targetObject.scaleX; tween(targetObject, { scaleX: originalScale * 1.1, scaleY: originalScale * 1.1 }, { duration: 150, onFinish: function onFinish() { tween(targetObject, { scaleX: originalScale, scaleY: originalScale }, { duration: 150 }); } }); } // Explode the note tween(self, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.destroy(); for (var j = 0; j < notes.length; j++) { if (notes[j] === self) { notes.splice(j, 1); break; } } } }); } }; return self; }); var TargetZone = Container.expand(function (trackIndex) { var self = Container.call(this); self.trackIndex = trackIndex; var zoneGraphics = self.attachAsset('targetZone', { anchorX: 0.5, anchorY: 0.5 }); zoneGraphics.alpha = 1.0; self.flash = function () { zoneGraphics.alpha = 1.0; // Remove fade animation to keep target zone fully visible }; return self; }); var Track = Container.expand(function (trackIndex) { var self = Container.call(this); self.trackIndex = trackIndex; var trackGraphics = self.attachAsset('track', { anchorX: 0.5, anchorY: 0 }); trackGraphics.alpha = 0.1; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ // Game variables var notes = []; var tracks = []; var targetZones = []; var numTracks = 3; var trackWidth = 200; // Reduced from 300 to bring tracks closer var gameWidth = 2048; var score = 0; var lives = 5; // Player starts with 5 lives var noteSpawnTimer = 0; var noteSpawnInterval = 90; // Faster baseline spawning var gameSpeed = 1; var difficultyTimer = 0; // Note queue system to prevent simultaneous target zone entry var noteQueue = []; var lastTargetZoneEntry = 0; var minTimeBetweenEntries = 60; // Reduced minimum frames for better rhythm flow // Vertical spacing system to prevent notes from overlapping var minVerticalSpacing = 200; // Reduced spacing for more dynamic gameplay // Bottom objects for explosion effects var leftObject, centerObject, rightObject; // Strip line references for sway effects var leftStripLine, centerStripLine, rightStripLine; // UI elements var scoreTxt = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0, 0); scoreTxt.x = 120; // Position away from platform menu icon scoreTxt.y = 220; // Move even further down from top LK.gui.topLeft.addChild(scoreTxt); // Create array to hold heart graphics for lives display var heartsArray = []; var heartsContainer = new Container(); heartsContainer.x = 120; // Same x as score heartsContainer.y = 380; // Move hearts further down below score text LK.gui.topLeft.addChild(heartsContainer); // Initialize 5 hearts for (var h = 0; h < 5; h++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); heart.x = h * 60; // Space hearts 60 pixels apart heart.y = 0; heart.alpha = 1.0; // Start with full opacity heartsArray.push(heart); heartsContainer.addChild(heart); } // Add background image var background = LK.getAsset('background', { anchorX: 0, anchorY: 0 }); background.x = 0; background.y = 0; background.tint = 0x000000; game.addChild(background); // Initialize tracks and unified target zone var startX = (gameWidth - numTracks * trackWidth) / 2 + trackWidth / 2; for (var i = 0; i < numTracks; i++) { var track = new Track(i); track.x = startX + i * trackWidth; track.y = 0; tracks.push(track); game.addChild(track); } // Create single unified target zone covering all tracks var unifiedTargetZone = new TargetZone(0); unifiedTargetZone.x = gameWidth / 2; // Center of screen unifiedTargetZone.y = 1650; // Move target zone slightly down // Scale the target zone to cover all tracks unifiedTargetZone.scale.x = numTracks * trackWidth / 350; // Scale to cover all track widths with new spacing unifiedTargetZone.scale.y = 1.5; // Expand target zone height by 50% // Add sharpness filter for better visual clarity if (unifiedTargetZone.filters) { unifiedTargetZone.filters = []; } else { unifiedTargetZone.filters = []; } // Apply contrast and brightness adjustments for sharper appearance unifiedTargetZone.alpha = 1.0; targetZones.push(unifiedTargetZone); // Add unified background covering entire play area var unifiedBg = LK.getAsset('unifiedBackground', { anchorX: 0.5, anchorY: 0 }); unifiedBg.x = gameWidth / 2; // Center horizontally unifiedBg.y = 0; // Start from top of screen // Crop background to fit exactly within game area without extending beyond screen unifiedBg.width = gameWidth; // Fit exactly to game width (2048px) unifiedBg.height = 2732; // Fit exactly to game height (2732px) unifiedBg.alpha = 0.8; // Slightly transparent to show game elements clearly game.addChild(unifiedBg); // Re-add strip lines and unified target zone to foreground for (var i = 0; i < numTracks; i++) { // Add strip line for each track (moved to foreground) - end at target zone var stripLine = LK.getAsset('stripLine', { anchorX: 0.5, anchorY: 0 }); stripLine.x = startX + i * trackWidth; stripLine.y = 0; // Set strip line height to end at target zone stripLine.height = unifiedTargetZone.y + 200 * unifiedTargetZone.scale.y / 2; // End at bottom of target zone stripLine.alpha = 1.0; // Increase alpha for sharper appearance // Apply sharpness enhancement if (stripLine.filters) { stripLine.filters = []; } else { stripLine.filters = []; } // Store original position for vibration reset stripLine.originalX = stripLine.x; game.addChild(stripLine); // Store references to strip lines if (i === 0) leftStripLine = stripLine;else if (i === 1) centerStripLine = stripLine;else if (i === 2) rightStripLine = stripLine; } // Add unified target zone to foreground game.addChild(unifiedTargetZone); // Create rounded border around unified target zone var borderThickness = 8; var targetZoneWidth = 350 * unifiedTargetZone.scale.x; var targetZoneHeight = 200 * unifiedTargetZone.scale.y; // Use scaled height // Top border var topBorder = LK.getAsset('stripLine', { anchorX: 0.5, anchorY: 0.5 }); topBorder.width = targetZoneWidth - borderThickness; topBorder.height = borderThickness; topBorder.x = unifiedTargetZone.x; topBorder.y = unifiedTargetZone.y - targetZoneHeight / 2 - borderThickness / 2; topBorder.alpha = 1.0; game.addChild(topBorder); // Bottom border var bottomBorder = LK.getAsset('stripLine', { anchorX: 0.5, anchorY: 0.5 }); bottomBorder.width = targetZoneWidth - borderThickness; bottomBorder.height = borderThickness; bottomBorder.x = unifiedTargetZone.x; bottomBorder.y = unifiedTargetZone.y + targetZoneHeight / 2 + borderThickness / 2; bottomBorder.alpha = 1.0; game.addChild(bottomBorder); // Left border var leftBorder = LK.getAsset('stripLine', { anchorX: 0.5, anchorY: 0.5 }); leftBorder.width = borderThickness; leftBorder.height = targetZoneHeight - borderThickness; leftBorder.x = unifiedTargetZone.x - targetZoneWidth / 2 - borderThickness / 2; leftBorder.y = unifiedTargetZone.y; leftBorder.alpha = 1.0; game.addChild(leftBorder); // Right border var rightBorder = LK.getAsset('stripLine', { anchorX: 0.5, anchorY: 0.5 }); rightBorder.width = borderThickness; rightBorder.height = targetZoneHeight - borderThickness; rightBorder.x = unifiedTargetZone.x + targetZoneWidth / 2 + borderThickness / 2; rightBorder.y = unifiedTargetZone.y; rightBorder.alpha = 1.0; game.addChild(rightBorder); // Add rounded corners with ellipses // Top-left corner var topLeftCorner = LK.getAsset('cornerEllipse', { anchorX: 0.5, anchorY: 0.5 }); topLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2; topLeftCorner.y = unifiedTargetZone.y - targetZoneHeight / 2; topLeftCorner.alpha = 1.0; game.addChild(topLeftCorner); // Top-right corner var topRightCorner = LK.getAsset('cornerEllipse', { anchorX: 0.5, anchorY: 0.5 }); topRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2; topRightCorner.y = unifiedTargetZone.y - targetZoneHeight / 2; topRightCorner.alpha = 1.0; game.addChild(topRightCorner); // Bottom-left corner var bottomLeftCorner = LK.getAsset('cornerEllipse', { anchorX: 0.5, anchorY: 0.5 }); bottomLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2; bottomLeftCorner.y = unifiedTargetZone.y + targetZoneHeight / 2; bottomLeftCorner.alpha = 1.0; game.addChild(bottomLeftCorner); // Bottom-right corner var bottomRightCorner = LK.getAsset('cornerEllipse', { anchorX: 0.5, anchorY: 0.5 }); bottomRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2; bottomRightCorner.y = unifiedTargetZone.y + targetZoneHeight / 2; bottomRightCorner.alpha = 1.0; game.addChild(bottomRightCorner); // Rhythm pattern system for music-synchronized note spawning var rhythmPatterns = { 'bgmusic': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0], // Simple steady beat pattern with occasional variations 'bgmusic2': [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0], // Upbeat syncopated rhythm with more complexity 'bgmusic3': [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1], // Fast-paced pattern with rhythmic variations 'bgmusic4': [1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1], // Varied mixed pattern with interesting gaps 'bgmusic5': [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0] // Moderate sparse pattern with musical phrasing }; // Enhanced rhythm synchronization system var currentBeatIndex = 0; var beatsPerMinute = { 'bgmusic': 120, // Corrected BPM values for proper synchronization 'bgmusic2': 128, 'bgmusic3': 130, 'bgmusic4': 125, 'bgmusic5': 115 }; var musicStartFrame = 0; var beatAccumulator = 0; var lastSpawnTime = 0; var framesPerBeat = 0; var nextBeatFrame = 0; var rhythmCalibration = { 'bgmusic': 0, // Removed frame offset calibration - using direct synchronization 'bgmusic2': 0, 'bgmusic3': 0, 'bgmusic4': 0, 'bgmusic5': 0 }; // Music timing variables for precise rhythm synchronization var musicContext = null; var musicStartedAt = 0; var lastMusicTime = 0; // Helper functions function spawnNote() { var trackIndex = Math.floor(Math.random() * numTracks); var isPowerNote = Math.random() < 0.1; // Add to queue instead of spawning directly noteQueue.push({ trackIndex: trackIndex, isPowerNote: isPowerNote, readyTime: LK.ticks + Math.max(0, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks) }); } function spawnRhythmNote() { // Only spawn notes if music is playing and we have a rhythm pattern if (!musicStarted || !currentMusicTrack || !rhythmPatterns[currentMusicTrack]) { return; } // Calculate precise beat timing based on actual music playback time var currentBPM = beatsPerMinute[currentMusicTrack] || 120; var beatsPerSecond = currentBPM / 60; var secondsPerBeat = 1 / beatsPerSecond; // Use actual music playback time instead of frame counting var musicElapsedSeconds = (LK.ticks - musicStartFrame) / 60; // Convert frames to seconds (60 FPS) var currentBeatFloat = musicElapsedSeconds / secondsPerBeat; var targetBeatIndex = Math.floor(currentBeatFloat); // Check if we've crossed into a new beat if (targetBeatIndex > currentBeatIndex) { currentBeatIndex = targetBeatIndex; var pattern = rhythmPatterns[currentMusicTrack]; var patternIndex = currentBeatIndex % pattern.length; var shouldSpawn = pattern[patternIndex]; if (shouldSpawn === 1) { // Better track distribution with more variation var trackIndex; if (currentBeatIndex % 4 === 0) { trackIndex = 1; // Center track on strong beats } else { trackIndex = Math.floor(Math.random() * numTracks); } var isPowerNote = currentBeatIndex % 16 === 0 ? true : Math.random() < 0.05; // Power notes less frequent // Calculate note spawn timing to hit target zone exactly on beat var noteSpeed = 7; // Fixed note speed var targetY = 1650; // Target zone Y position var spawnY = -50; // Spawn Y position var distanceToTarget = targetY - spawnY; var framesToTarget = distanceToTarget / noteSpeed; var secondsToTarget = framesToTarget / 60; // Convert to seconds // Spawn note early so it arrives at target zone exactly on the musical beat var nextBeatTime = (targetBeatIndex + 1) * secondsPerBeat; var spawnDelay = Math.max(0, (nextBeatTime - musicElapsedSeconds - secondsToTarget) * 60); // Convert back to frames // Add to queue with precise timing for musical synchronization noteQueue.push({ trackIndex: trackIndex, isPowerNote: isPowerNote, readyTime: LK.ticks + Math.floor(spawnDelay) + Math.max(3, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks) }); lastSpawnTime = LK.ticks; } } } function processNoteQueue() { for (var i = noteQueue.length - 1; i >= 0; i--) { var queuedNote = noteQueue[i]; if (LK.ticks >= queuedNote.readyTime) { // Check if any note is currently in or approaching target zone var canSpawn = true; var targetY = 1650; var spawnY = -50; var targetZoneBuffer = 250; // Increased buffer zone for expanded target for (var j = 0; j < notes.length; j++) { var existingNote = notes[j]; // Check if note is in extended target zone area (larger buffer for safety) if (Math.abs(existingNote.y - targetY) <= targetZoneBuffer) { canSpawn = false; break; } // Check if note will reach target zone soon (prediction based on speed) var timeToTarget = (targetY - existingNote.y) / existingNote.speed; if (timeToTarget > 0 && timeToTarget <= 50) { // Within 50 frames of reaching target canSpawn = false; break; } // Check vertical spacing collision - prevent notes from being too close vertically if (Math.abs(existingNote.y - spawnY) < minVerticalSpacing) { canSpawn = false; break; } } if (canSpawn) { // Spawn the note var note = new Note(queuedNote.trackIndex, queuedNote.isPowerNote); // Position notes exactly on track center lines var noteX = startX + queuedNote.trackIndex * trackWidth; note.x = noteX; note.y = spawnY; note.speed = 7; // Fixed speed constant - reduced to 7 for gameplay balance notes.push(note); game.addChild(note); lastTargetZoneEntry = LK.ticks; noteQueue.splice(i, 1); } else { // Delay this note by adding minimum time queuedNote.readyTime = LK.ticks + minTimeBetweenEntries; } } } } function updateScore(points) { score += points; scoreTxt.setText('Score: ' + score); LK.setScore(score); } function updateLives(change) { lives += change; // Update heart display based on current lives for (var h = 0; h < heartsArray.length; h++) { if (h < lives) { heartsArray[h].alpha = 1.0; // Show heart } else { heartsArray[h].alpha = 0; // Make lost heart completely invisible } } if (lives <= 0) { LK.showGameOver(); } } function createParticleEffect(x, y, color) { // Create enhanced particle effects with increased count var totalParticles = 80; // Significantly increased particle count var primaryParticles = 35; var secondaryParticles = 25; var trailParticles = 20; // Primary burst particles - fast moving with enhanced visuals for (var i = 0; i < primaryParticles; i++) { var particle = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = x; particle.y = y; // Enhanced color with gradient-like effect var colorVariation = 0x303030 * Math.random(); particle.tint = (color || 0xFFD93D) + colorVariation; particle.alpha = 0.95 + Math.random() * 0.05; game.addChild(particle); // Calculate random direction and speed for each particle var angle = i / primaryParticles * Math.PI * 2 + (Math.random() - 0.5) * 2.2; var speed = 800 + Math.random() * 700; // Significantly increased speed range var targetX = x + Math.cos(angle) * speed; var targetY = y + Math.sin(angle) * speed; // Enhanced particle scaling with better proportions particle.scaleX = 3.5 + Math.random() * 3.0; // Larger particle size for better impact particle.scaleY = particle.scaleX + (Math.random() - 0.5) * 0.8; // More asymmetry for dynamic feel // Animate particle flying outward with enhanced effects tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.05, // Smoother scale transition scaleY: 0.05, rotation: (Math.random() - 0.5) * Math.PI * 16 // Increased rotation for more dynamic motion }, { duration: 1000 + Math.random() * 800, // Longer duration for better visual impact easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Secondary slower particles for depth with improved visuals for (var i = 0; i < secondaryParticles; i++) { var particle = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = x + (Math.random() - 0.5) * 60; // Increased initial spread particle.y = y + (Math.random() - 0.5) * 60; // Enhanced secondary particle coloring var secondaryColor = color || 0xFFD93D; var brightness = 0.8 + Math.random() * 0.2; particle.tint = secondaryColor * brightness; particle.alpha = 0.85 + Math.random() * 0.15; game.addChild(particle); var angle = Math.random() * Math.PI * 2; var speed = 450 + Math.random() * 350; // Increased secondary particle speed var targetX = x + Math.cos(angle) * speed; var targetY = y + Math.sin(angle) * speed; // Enhanced secondary particle scaling particle.scaleX = 2.5 + Math.random() * 2.0; // Larger secondary particles particle.scaleY = particle.scaleX * (0.7 + Math.random() * 0.6); // More varied proportions // Delayed animation for staggered effect with enhanced timing tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, // Smoother scale transition scaleY: 0.1, rotation: (Math.random() - 0.5) * Math.PI * 14 // Increased rotation }, { duration: 1200 + Math.random() * 1000, // Longer duration for better visual flow easing: tween.easeInOut, onFinish: function onFinish() { particle.destroy(); } }); } // Enhanced trail particles that move upward for (var i = 0; i < trailParticles; i++) { var particle = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = x + (Math.random() - 0.5) * 80; // Increased initial spread particle.y = y; // Enhanced trail particle coloring with stronger glow effect var trailColor = color || 0xFFD93D; var glowIntensity = 0.9 + Math.random() * 0.1; particle.tint = trailColor * glowIntensity; particle.alpha = 0.8 + Math.random() * 0.2; game.addChild(particle); // Enhanced trail particle scaling with larger proportions particle.scaleX = 4.0 + Math.random() * 3.0; // Significantly larger trail particles particle.scaleY = particle.scaleX * (0.5 + Math.random() * 1.0); // More dramatic shape variations // Animate upward with enhanced gravity-like effect tween(particle, { x: particle.x + (Math.random() - 0.5) * 300, // Increased horizontal spread y: y - 500 - Math.random() * 350, // Significantly enhanced upward movement alpha: 0, scaleX: 0.2, // Smoother scale transition scaleY: 0.2, rotation: (Math.random() - 0.5) * Math.PI * 12 // Increased rotation for more dynamic motion }, { duration: 1500 + Math.random() * 1200, // Longer duration for better visual impact easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } } function checkNoteInTrack(trackIndex) { for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (note.trackIndex === trackIndex && !note.hasBeenTapped) { var timing = note.checkTiming(); if (timing !== 'miss') { note.hasBeenTapped = true; var points = 0; if (timing === 'perfect') { points = 1; LK.getSound('perfect').play(); createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00); } else if (timing === 'good') { points = 1; LK.getSound('tap').play(); createParticleEffect(note.x, note.y, 0x87CEEB); } updateScore(points); // Start background music on first successful hit or after music ends if (!musicStarted) { currentMusicIndex = 0; // Start with first track for consistency currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStarted = true; musicStartTime = LK.ticks; musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync musicRepeatCount = 0; // Reset rhythm timing for new music with proper initialization currentBeatIndex = 0; // Start at 0 for immediate beat detection beatAccumulator = 0; // Reset beat accumulator for precise timing lastSpawnTime = 0; // Reset last spawn time } else if (waitingForNoteAfterMusicEnd) { // Start next music track after previous one ended currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length; currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStartTime = LK.ticks; musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync musicFinished = false; waitingForNoteAfterMusicEnd = false; // Reset rhythm timing for new music with proper initialization currentBeatIndex = 0; // Start at 0 for immediate beat detection beatAccumulator = 0; // Reset beat accumulator for precise timing lastSpawnTime = 0; // Reset last spawn time } unifiedTargetZone.flash(); note.destroy(); notes.splice(i, 1); return true; } } } return false; } // Touch handlers game.down = function (x, y, obj) { for (var i = 0; i < numTracks; i++) { var trackX = startX + i * trackWidth; if (x >= trackX - trackWidth / 2 && x <= trackX + trackWidth / 2) { if (!checkNoteInTrack(i)) { // Removed miss sound and life reduction for missed taps - only notes escaping target zone should reduce lives and play miss sound } break; } } }; // Main game loop game.update = function () { // Rhythm-based note spawning when music is playing if (musicStarted && currentMusicTrack) { // Use precise music synchronization for rhythm-based spawning spawnRhythmNote(); } else { // Fallback to timer-based spawning when no music is playing noteSpawnTimer++; if (noteSpawnTimer >= noteSpawnInterval) { spawnNote(); noteSpawnTimer = 0; } } // Process note queue to ensure no simultaneous target zone entries processNoteQueue(); // Update difficulty difficultyTimer++; if (difficultyTimer >= 1800) { // Every 30 seconds gameSpeed += 0.2; noteSpawnInterval = Math.max(20, noteSpawnInterval - 2); difficultyTimer = 0; } // Update notes for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; // Check if note passed beyond target zone - let it pass slightly further before removing var targetY = 1650; // unifiedTargetZone.y var targetZoneBottom = targetY + 200 * 1.5 / 2; // Bottom of expanded target zone var disappearY = targetZoneBottom + 100; // Allow note to pass 100px beyond target zone if (!note.hasBeenTapped && note.lastY <= disappearY && note.y > disappearY) { note.destroy(); notes.splice(i, 1); continue; } // Remove notes that are off screen if (note.y > 2800) { note.destroy(); notes.splice(i, 1); } } // Clean up off-screen particles var allChildren = game.children.slice(); // Create a copy to avoid modification during iteration for (var i = allChildren.length - 1; i >= 0; i--) { var child = allChildren[i]; // Check if this is a particle (has particle-like properties) if (child && child.x !== undefined && child.y !== undefined && child.width <= 10 && child.height <= 10) { // Remove particles that are far off-screen or destroyed if (child.x < -500 || child.x > 2548 || child.y < -500 || child.y > 3232 || child.destroyed) { if (child.destroy) { child.destroy(); } else { game.removeChild(child); } } } } // Create continuous particle effects around the three objects enhanceObjectVisuals(); // Check for music track changes every 60 seconds (1 minute) with fade out if (musicStarted && LK.ticks % 60 === 0) { // Check every second var timeElapsed = (LK.ticks - musicStartTime) / 60; // Convert to seconds since music started var musicChangeInterval = 60; // 60 seconds (1 minute) per music change // Start fade out 2 seconds before music ends if (timeElapsed >= musicChangeInterval - 2 && timeElapsed < musicChangeInterval && !musicFinished) { // Fade out over 2 seconds LK.playMusic(currentMusicTrack, { fade: { start: 1, end: 0, duration: 2000 } }); } // Check if current music should have finished if (timeElapsed >= musicChangeInterval && !musicFinished) { musicFinished = true; waitingForNoteAfterMusicEnd = true; LK.stopMusic(); // Stop current music and wait for note hit } } // Win condition if (score >= 10000) { LK.showYouWin(); } }; // Add 3 objects at bottom of target zone (left, center, right) var targetBottomY = unifiedTargetZone.y + targetZoneHeight / 2 + 520; // Move objects down very slightly var objectSpacing = targetZoneWidth / 1.0; // Further increased spacing for better separation between objects // Left object leftObject = LK.getAsset('leftObjectAsset', { anchorX: 0.5, anchorY: 0.5 }); leftObject.x = unifiedTargetZone.x - objectSpacing; leftObject.y = targetBottomY; // Scale up the left object leftObject.scaleX = 2.5; leftObject.scaleY = 2.5; // Animate color change for left object tween(leftObject, { tint: 0xFFD700 // Change to gold color }, { duration: 1500, easing: tween.easeInOut }); leftObject.alpha = 1.0; // Increase alpha for sharper appearance // Apply sharpness enhancement to left object if (leftObject.filters) { leftObject.filters = []; } else { leftObject.filters = []; } game.addChild(leftObject); // Center object centerObject = LK.getAsset('centerObjectAsset', { anchorX: 0.5, anchorY: 0.5 }); centerObject.x = unifiedTargetZone.x; centerObject.y = targetBottomY; // Scale up the center object centerObject.scaleX = 2.5; centerObject.scaleY = 2.5; // Apply orange tint to center object tween(centerObject, { tint: 0xFF6600 // Change to bright orange color }, { duration: 1500, easing: tween.easeInOut }); centerObject.alpha = 1.0; // Increase alpha for sharper appearance // Apply sharpness enhancement to center object if (centerObject.filters) { centerObject.filters = []; } else { centerObject.filters = []; } game.addChild(centerObject); // Right object rightObject = LK.getAsset('rightObjectAsset', { anchorX: 0.5, anchorY: 0.5 }); rightObject.x = unifiedTargetZone.x + objectSpacing; rightObject.y = targetBottomY; // Scale up the right object rightObject.scaleX = 2.5; rightObject.scaleY = 2.5; // Animate color change for right object tween(rightObject, { tint: 0x9370DB // Change to medium purple color }, { duration: 1500, easing: tween.easeInOut }); rightObject.alpha = 1.0; // Increase alpha for sharper appearance // Apply sharpness enhancement to right object if (rightObject.filters) { rightObject.filters = []; } else { rightObject.filters = []; } game.addChild(rightObject); // Objects are now properly sized - no extreme scaling needed // Move objects to topmost layer for better visibility game.removeChild(leftObject); game.addChild(leftObject); game.removeChild(centerObject); game.addChild(centerObject); game.removeChild(rightObject); game.addChild(rightObject); // Add touch handlers to the 3 bottom objects leftObject.down = function (x, y, obj) { // Add guitar string vibration effect to left track strip line if (leftStripLine) { // Use stored original position instead of current position var originalX = leftStripLine.originalX; var _vibrateString = function vibrateString() { if (vibrationCount >= maxVibrations) { // Reset to original position when vibration completes tween(leftStripLine, { x: originalX }, { duration: 100, easing: tween.easeOut }); return; } var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations); var direction = vibrationCount % 2 === 0 ? 1 : -1; tween(leftStripLine, { x: originalX + amplitude * direction }, { duration: 40, easing: tween.easeInOut, onFinish: function onFinish() { vibrationCount++; _vibrateString(); } }); }; // Guitar string effect - rapid horizontal vibrations that decay var vibrationCount = 0; var maxVibrations = 15; var baseAmplitude = 25; _vibrateString(); } // Check for notes in left track (track 0) within target zone for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (note.trackIndex === 0 && !note.hasBeenTapped) { var timing = note.checkTiming(); if (timing !== 'miss') { // Explode the note note.hasBeenTapped = true; var points = 0; if (timing === 'perfect') { points = 1; LK.getSound('perfect').play(); createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00); } else if (timing === 'good') { points = 1; LK.getSound('tap').play(); createParticleEffect(note.x, note.y, 0x87CEEB); } updateScore(points); // Start background music on first successful hit or after music ends if (!musicStarted) { currentMusicIndex = Math.floor(Math.random() * musicTracks.length); currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStarted = true; musicStartTime = LK.ticks; musicRepeatCount = 0; } else if (waitingForNoteAfterMusicEnd) { // Start next music track after previous one ended currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length; currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStartTime = LK.ticks; musicFinished = false; waitingForNoteAfterMusicEnd = false; } unifiedTargetZone.flash(); // Scale animation for explosion effect tween(note, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 200, onFinish: function onFinish() { note.destroy(); for (var j = 0; j < notes.length; j++) { if (notes[j] === note) { notes.splice(j, 1); break; } } } }); break; } } } }; centerObject.down = function (x, y, obj) { // Add guitar string vibration effect to center track strip line if (centerStripLine) { // Use stored original position instead of current position var originalX = centerStripLine.originalX; var _vibrateString2 = function vibrateString() { if (vibrationCount >= maxVibrations) { // Reset to original position when vibration completes tween(centerStripLine, { x: originalX }, { duration: 100, easing: tween.easeOut }); return; } var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations); var direction = vibrationCount % 2 === 0 ? 1 : -1; tween(centerStripLine, { x: originalX + amplitude * direction }, { duration: 40, easing: tween.easeInOut, onFinish: function onFinish() { vibrationCount++; _vibrateString2(); } }); }; // Guitar string effect - rapid horizontal vibrations that decay var vibrationCount = 0; var maxVibrations = 15; var baseAmplitude = 25; _vibrateString2(); } // Check for notes in center track (track 1) within target zone for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (note.trackIndex === 1 && !note.hasBeenTapped) { var timing = note.checkTiming(); if (timing !== 'miss') { // Explode the note note.hasBeenTapped = true; var points = 0; if (timing === 'perfect') { points = 1; LK.getSound('perfect').play(); createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00); } else if (timing === 'good') { points = 1; LK.getSound('tap').play(); createParticleEffect(note.x, note.y, 0x87CEEB); } updateScore(points); // Start background music on first successful hit or after music ends if (!musicStarted) { currentMusicIndex = Math.floor(Math.random() * musicTracks.length); currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStarted = true; musicStartTime = LK.ticks; musicRepeatCount = 0; } else if (waitingForNoteAfterMusicEnd) { // Start next music track after previous one ended currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length; currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStartTime = LK.ticks; musicFinished = false; waitingForNoteAfterMusicEnd = false; } unifiedTargetZone.flash(); // Scale animation for explosion effect tween(note, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 200, onFinish: function onFinish() { note.destroy(); for (var j = 0; j < notes.length; j++) { if (notes[j] === note) { notes.splice(j, 1); break; } } } }); break; } } } }; rightObject.down = function (x, y, obj) { // Add guitar string vibration effect to right track strip line if (rightStripLine) { // Use stored original position instead of current position var originalX = rightStripLine.originalX; var _vibrateString3 = function vibrateString() { if (vibrationCount >= maxVibrations) { // Reset to original position when vibration completes tween(rightStripLine, { x: originalX }, { duration: 100, easing: tween.easeOut }); return; } var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations); var direction = vibrationCount % 2 === 0 ? 1 : -1; tween(rightStripLine, { x: originalX + amplitude * direction }, { duration: 40, easing: tween.easeInOut, onFinish: function onFinish() { vibrationCount++; _vibrateString3(); } }); }; // Guitar string effect - rapid horizontal vibrations that decay var vibrationCount = 0; var maxVibrations = 15; var baseAmplitude = 25; _vibrateString3(); } // Check for notes in right track (track 2) within target zone for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (note.trackIndex === 2 && !note.hasBeenTapped) { var timing = note.checkTiming(); if (timing !== 'miss') { // Explode the note note.hasBeenTapped = true; var points = 0; if (timing === 'perfect') { points = 1; LK.getSound('perfect').play(); createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00); } else if (timing === 'good') { points = 1; LK.getSound('tap').play(); createParticleEffect(note.x, note.y, 0x87CEEB); } updateScore(points); // Start background music on first successful hit or after music ends if (!musicStarted) { currentMusicIndex = Math.floor(Math.random() * musicTracks.length); currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStarted = true; musicStartTime = LK.ticks; musicRepeatCount = 0; } else if (waitingForNoteAfterMusicEnd) { // Start next music track after previous one ended currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length; currentMusicTrack = musicTracks[currentMusicIndex]; LK.playMusic(currentMusicTrack); musicStartTime = LK.ticks; musicFinished = false; waitingForNoteAfterMusicEnd = false; } unifiedTargetZone.flash(); // Scale animation for explosion effect tween(note, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 200, onFinish: function onFinish() { note.destroy(); for (var j = 0; j < notes.length; j++) { if (notes[j] === note) { notes.splice(j, 1); break; } } } }); break; } } } }; // Music will start when first note is hit var musicStarted = false; var musicTracks = ['bgmusic', 'bgmusic2', 'bgmusic3', 'bgmusic4', 'bgmusic5']; var currentMusicTrack = null; var musicRepeatCount = 0; var targetRepeatCount = 3; var musicStartTime = 0; var currentMusicIndex = 0; var musicFinished = false; var waitingForNoteAfterMusicEnd = false; // Orbital particle effects removed - variables no longer needed // Optimized visual effects function function enhanceObjectVisuals() { // Orbital particle effects removed }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Note = Container.expand(function (trackIndex, isPowerNote) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.isPowerNote = isPowerNote || false;
self.speed = 7;
self.hasBeenTapped = false;
self.hasReducedLife = false; // Flag to track if this note already caused life loss
self.lastY = 0;
var noteGraphics = self.attachAsset(self.isPowerNote ? 'powerNote' : 'note', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply sharpness enhancement to note graphics
if (noteGraphics.filters) {
noteGraphics.filters = [];
} else {
noteGraphics.filters = [];
}
// Enhance contrast for sharper appearance
noteGraphics.alpha = 1.0;
if (self.isPowerNote) {
noteGraphics.tint = 0xFFD93D;
self.scale.set(1.2);
}
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
if (self.lastInTargetZone === undefined) self.lastInTargetZone = false;
// Check if note just entered target zone
var targetY = 1650; // unifiedTargetZone.y
var inTargetZone = Math.abs(self.y - targetY) <= 150; // Expanded target zone range
// Additional check: ensure no other note is currently in target zone
var otherNoteInZone = false;
for (var k = 0; k < notes.length; k++) {
var otherNote = notes[k];
if (otherNote !== self && !otherNote.hasBeenTapped && Math.abs(otherNote.y - targetY) <= 100) {
otherNoteInZone = true;
break;
}
}
if (!self.lastInTargetZone && inTargetZone && !self.hasBeenTapped && !otherNoteInZone) {
// Note just entered target zone and no other note is present - trigger explosion
self.triggerExplosion();
}
// Check if note passed bottom of target zone without being tapped (missed)
var targetZoneBottom = targetY + 150; // Bottom of target zone
if (self.lastY <= targetZoneBottom && self.y > targetZoneBottom && !self.hasBeenTapped && !self.hasReducedLife) {
// Note passed bottom of target zone - play miss sound and reduce lives only once
LK.getSound('miss').play();
updateLives(-1);
self.hasReducedLife = true; // Mark that this note has already caused life loss
}
self.lastY = self.y;
self.lastInTargetZone = inTargetZone;
self.y += self.speed;
};
self.checkTiming = function () {
var targetY = unifiedTargetZone.y;
var distance = Math.abs(self.y - targetY);
if (distance <= 45) {
// Expanded perfect range
return 'perfect';
} else if (distance <= 90) {
// Expanded good range
return 'good';
}
return 'miss';
};
self.triggerExplosion = function () {
if (self.hasBeenTapped) return;
var timing = self.checkTiming();
if (timing !== 'miss') {
self.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(self.x, self.y, self.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(self.x, self.y, 0x87CEEB);
}
updateScore(points);
unifiedTargetZone.flash();
// Trigger explosion animation on corresponding bottom object
var bottomObjects = [leftObject, centerObject, rightObject];
if (bottomObjects[self.trackIndex]) {
var targetObject = bottomObjects[self.trackIndex];
// Flash the bottom object
var originalAlpha = targetObject.alpha;
tween(targetObject, {
alpha: 1.0
}, {
duration: 100,
onFinish: function onFinish() {
tween(targetObject, {
alpha: originalAlpha
}, {
duration: 200
});
}
});
// Scale pulse effect
var originalScale = targetObject.scaleX;
tween(targetObject, {
scaleX: originalScale * 1.1,
scaleY: originalScale * 1.1
}, {
duration: 150,
onFinish: function onFinish() {
tween(targetObject, {
scaleX: originalScale,
scaleY: originalScale
}, {
duration: 150
});
}
});
}
// Explode the note
tween(self, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === self) {
notes.splice(j, 1);
break;
}
}
}
});
}
};
return self;
});
var TargetZone = Container.expand(function (trackIndex) {
var self = Container.call(this);
self.trackIndex = trackIndex;
var zoneGraphics = self.attachAsset('targetZone', {
anchorX: 0.5,
anchorY: 0.5
});
zoneGraphics.alpha = 1.0;
self.flash = function () {
zoneGraphics.alpha = 1.0;
// Remove fade animation to keep target zone fully visible
};
return self;
});
var Track = Container.expand(function (trackIndex) {
var self = Container.call(this);
self.trackIndex = trackIndex;
var trackGraphics = self.attachAsset('track', {
anchorX: 0.5,
anchorY: 0
});
trackGraphics.alpha = 0.1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var notes = [];
var tracks = [];
var targetZones = [];
var numTracks = 3;
var trackWidth = 200; // Reduced from 300 to bring tracks closer
var gameWidth = 2048;
var score = 0;
var lives = 5; // Player starts with 5 lives
var noteSpawnTimer = 0;
var noteSpawnInterval = 90; // Faster baseline spawning
var gameSpeed = 1;
var difficultyTimer = 0;
// Note queue system to prevent simultaneous target zone entry
var noteQueue = [];
var lastTargetZoneEntry = 0;
var minTimeBetweenEntries = 60; // Reduced minimum frames for better rhythm flow
// Vertical spacing system to prevent notes from overlapping
var minVerticalSpacing = 200; // Reduced spacing for more dynamic gameplay
// Bottom objects for explosion effects
var leftObject, centerObject, rightObject;
// Strip line references for sway effects
var leftStripLine, centerStripLine, rightStripLine;
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120; // Position away from platform menu icon
scoreTxt.y = 220; // Move even further down from top
LK.gui.topLeft.addChild(scoreTxt);
// Create array to hold heart graphics for lives display
var heartsArray = [];
var heartsContainer = new Container();
heartsContainer.x = 120; // Same x as score
heartsContainer.y = 380; // Move hearts further down below score text
LK.gui.topLeft.addChild(heartsContainer);
// Initialize 5 hearts
for (var h = 0; h < 5; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = h * 60; // Space hearts 60 pixels apart
heart.y = 0;
heart.alpha = 1.0; // Start with full opacity
heartsArray.push(heart);
heartsContainer.addChild(heart);
}
// Add background image
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
background.x = 0;
background.y = 0;
background.tint = 0x000000;
game.addChild(background);
// Initialize tracks and unified target zone
var startX = (gameWidth - numTracks * trackWidth) / 2 + trackWidth / 2;
for (var i = 0; i < numTracks; i++) {
var track = new Track(i);
track.x = startX + i * trackWidth;
track.y = 0;
tracks.push(track);
game.addChild(track);
}
// Create single unified target zone covering all tracks
var unifiedTargetZone = new TargetZone(0);
unifiedTargetZone.x = gameWidth / 2; // Center of screen
unifiedTargetZone.y = 1650; // Move target zone slightly down
// Scale the target zone to cover all tracks
unifiedTargetZone.scale.x = numTracks * trackWidth / 350; // Scale to cover all track widths with new spacing
unifiedTargetZone.scale.y = 1.5; // Expand target zone height by 50%
// Add sharpness filter for better visual clarity
if (unifiedTargetZone.filters) {
unifiedTargetZone.filters = [];
} else {
unifiedTargetZone.filters = [];
}
// Apply contrast and brightness adjustments for sharper appearance
unifiedTargetZone.alpha = 1.0;
targetZones.push(unifiedTargetZone);
// Add unified background covering entire play area
var unifiedBg = LK.getAsset('unifiedBackground', {
anchorX: 0.5,
anchorY: 0
});
unifiedBg.x = gameWidth / 2; // Center horizontally
unifiedBg.y = 0; // Start from top of screen
// Crop background to fit exactly within game area without extending beyond screen
unifiedBg.width = gameWidth; // Fit exactly to game width (2048px)
unifiedBg.height = 2732; // Fit exactly to game height (2732px)
unifiedBg.alpha = 0.8; // Slightly transparent to show game elements clearly
game.addChild(unifiedBg);
// Re-add strip lines and unified target zone to foreground
for (var i = 0; i < numTracks; i++) {
// Add strip line for each track (moved to foreground) - end at target zone
var stripLine = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0
});
stripLine.x = startX + i * trackWidth;
stripLine.y = 0;
// Set strip line height to end at target zone
stripLine.height = unifiedTargetZone.y + 200 * unifiedTargetZone.scale.y / 2; // End at bottom of target zone
stripLine.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement
if (stripLine.filters) {
stripLine.filters = [];
} else {
stripLine.filters = [];
}
// Store original position for vibration reset
stripLine.originalX = stripLine.x;
game.addChild(stripLine);
// Store references to strip lines
if (i === 0) leftStripLine = stripLine;else if (i === 1) centerStripLine = stripLine;else if (i === 2) rightStripLine = stripLine;
}
// Add unified target zone to foreground
game.addChild(unifiedTargetZone);
// Create rounded border around unified target zone
var borderThickness = 8;
var targetZoneWidth = 350 * unifiedTargetZone.scale.x;
var targetZoneHeight = 200 * unifiedTargetZone.scale.y; // Use scaled height
// Top border
var topBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
topBorder.width = targetZoneWidth - borderThickness;
topBorder.height = borderThickness;
topBorder.x = unifiedTargetZone.x;
topBorder.y = unifiedTargetZone.y - targetZoneHeight / 2 - borderThickness / 2;
topBorder.alpha = 1.0;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
bottomBorder.width = targetZoneWidth - borderThickness;
bottomBorder.height = borderThickness;
bottomBorder.x = unifiedTargetZone.x;
bottomBorder.y = unifiedTargetZone.y + targetZoneHeight / 2 + borderThickness / 2;
bottomBorder.alpha = 1.0;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
leftBorder.width = borderThickness;
leftBorder.height = targetZoneHeight - borderThickness;
leftBorder.x = unifiedTargetZone.x - targetZoneWidth / 2 - borderThickness / 2;
leftBorder.y = unifiedTargetZone.y;
leftBorder.alpha = 1.0;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
rightBorder.width = borderThickness;
rightBorder.height = targetZoneHeight - borderThickness;
rightBorder.x = unifiedTargetZone.x + targetZoneWidth / 2 + borderThickness / 2;
rightBorder.y = unifiedTargetZone.y;
rightBorder.alpha = 1.0;
game.addChild(rightBorder);
// Add rounded corners with ellipses
// Top-left corner
var topLeftCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
topLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2;
topLeftCorner.y = unifiedTargetZone.y - targetZoneHeight / 2;
topLeftCorner.alpha = 1.0;
game.addChild(topLeftCorner);
// Top-right corner
var topRightCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
topRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2;
topRightCorner.y = unifiedTargetZone.y - targetZoneHeight / 2;
topRightCorner.alpha = 1.0;
game.addChild(topRightCorner);
// Bottom-left corner
var bottomLeftCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
bottomLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2;
bottomLeftCorner.y = unifiedTargetZone.y + targetZoneHeight / 2;
bottomLeftCorner.alpha = 1.0;
game.addChild(bottomLeftCorner);
// Bottom-right corner
var bottomRightCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
bottomRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2;
bottomRightCorner.y = unifiedTargetZone.y + targetZoneHeight / 2;
bottomRightCorner.alpha = 1.0;
game.addChild(bottomRightCorner);
// Rhythm pattern system for music-synchronized note spawning
var rhythmPatterns = {
'bgmusic': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0],
// Simple steady beat pattern with occasional variations
'bgmusic2': [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0],
// Upbeat syncopated rhythm with more complexity
'bgmusic3': [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
// Fast-paced pattern with rhythmic variations
'bgmusic4': [1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1],
// Varied mixed pattern with interesting gaps
'bgmusic5': [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0] // Moderate sparse pattern with musical phrasing
};
// Enhanced rhythm synchronization system
var currentBeatIndex = 0;
var beatsPerMinute = {
'bgmusic': 120,
// Corrected BPM values for proper synchronization
'bgmusic2': 128,
'bgmusic3': 130,
'bgmusic4': 125,
'bgmusic5': 115
};
var musicStartFrame = 0;
var beatAccumulator = 0;
var lastSpawnTime = 0;
var framesPerBeat = 0;
var nextBeatFrame = 0;
var rhythmCalibration = {
'bgmusic': 0,
// Removed frame offset calibration - using direct synchronization
'bgmusic2': 0,
'bgmusic3': 0,
'bgmusic4': 0,
'bgmusic5': 0
};
// Music timing variables for precise rhythm synchronization
var musicContext = null;
var musicStartedAt = 0;
var lastMusicTime = 0;
// Helper functions
function spawnNote() {
var trackIndex = Math.floor(Math.random() * numTracks);
var isPowerNote = Math.random() < 0.1;
// Add to queue instead of spawning directly
noteQueue.push({
trackIndex: trackIndex,
isPowerNote: isPowerNote,
readyTime: LK.ticks + Math.max(0, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks)
});
}
function spawnRhythmNote() {
// Only spawn notes if music is playing and we have a rhythm pattern
if (!musicStarted || !currentMusicTrack || !rhythmPatterns[currentMusicTrack]) {
return;
}
// Calculate precise beat timing based on actual music playback time
var currentBPM = beatsPerMinute[currentMusicTrack] || 120;
var beatsPerSecond = currentBPM / 60;
var secondsPerBeat = 1 / beatsPerSecond;
// Use actual music playback time instead of frame counting
var musicElapsedSeconds = (LK.ticks - musicStartFrame) / 60; // Convert frames to seconds (60 FPS)
var currentBeatFloat = musicElapsedSeconds / secondsPerBeat;
var targetBeatIndex = Math.floor(currentBeatFloat);
// Check if we've crossed into a new beat
if (targetBeatIndex > currentBeatIndex) {
currentBeatIndex = targetBeatIndex;
var pattern = rhythmPatterns[currentMusicTrack];
var patternIndex = currentBeatIndex % pattern.length;
var shouldSpawn = pattern[patternIndex];
if (shouldSpawn === 1) {
// Better track distribution with more variation
var trackIndex;
if (currentBeatIndex % 4 === 0) {
trackIndex = 1; // Center track on strong beats
} else {
trackIndex = Math.floor(Math.random() * numTracks);
}
var isPowerNote = currentBeatIndex % 16 === 0 ? true : Math.random() < 0.05; // Power notes less frequent
// Calculate note spawn timing to hit target zone exactly on beat
var noteSpeed = 7; // Fixed note speed
var targetY = 1650; // Target zone Y position
var spawnY = -50; // Spawn Y position
var distanceToTarget = targetY - spawnY;
var framesToTarget = distanceToTarget / noteSpeed;
var secondsToTarget = framesToTarget / 60; // Convert to seconds
// Spawn note early so it arrives at target zone exactly on the musical beat
var nextBeatTime = (targetBeatIndex + 1) * secondsPerBeat;
var spawnDelay = Math.max(0, (nextBeatTime - musicElapsedSeconds - secondsToTarget) * 60); // Convert back to frames
// Add to queue with precise timing for musical synchronization
noteQueue.push({
trackIndex: trackIndex,
isPowerNote: isPowerNote,
readyTime: LK.ticks + Math.floor(spawnDelay) + Math.max(3, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks)
});
lastSpawnTime = LK.ticks;
}
}
}
function processNoteQueue() {
for (var i = noteQueue.length - 1; i >= 0; i--) {
var queuedNote = noteQueue[i];
if (LK.ticks >= queuedNote.readyTime) {
// Check if any note is currently in or approaching target zone
var canSpawn = true;
var targetY = 1650;
var spawnY = -50;
var targetZoneBuffer = 250; // Increased buffer zone for expanded target
for (var j = 0; j < notes.length; j++) {
var existingNote = notes[j];
// Check if note is in extended target zone area (larger buffer for safety)
if (Math.abs(existingNote.y - targetY) <= targetZoneBuffer) {
canSpawn = false;
break;
}
// Check if note will reach target zone soon (prediction based on speed)
var timeToTarget = (targetY - existingNote.y) / existingNote.speed;
if (timeToTarget > 0 && timeToTarget <= 50) {
// Within 50 frames of reaching target
canSpawn = false;
break;
}
// Check vertical spacing collision - prevent notes from being too close vertically
if (Math.abs(existingNote.y - spawnY) < minVerticalSpacing) {
canSpawn = false;
break;
}
}
if (canSpawn) {
// Spawn the note
var note = new Note(queuedNote.trackIndex, queuedNote.isPowerNote);
// Position notes exactly on track center lines
var noteX = startX + queuedNote.trackIndex * trackWidth;
note.x = noteX;
note.y = spawnY;
note.speed = 7; // Fixed speed constant - reduced to 7 for gameplay balance
notes.push(note);
game.addChild(note);
lastTargetZoneEntry = LK.ticks;
noteQueue.splice(i, 1);
} else {
// Delay this note by adding minimum time
queuedNote.readyTime = LK.ticks + minTimeBetweenEntries;
}
}
}
}
function updateScore(points) {
score += points;
scoreTxt.setText('Score: ' + score);
LK.setScore(score);
}
function updateLives(change) {
lives += change;
// Update heart display based on current lives
for (var h = 0; h < heartsArray.length; h++) {
if (h < lives) {
heartsArray[h].alpha = 1.0; // Show heart
} else {
heartsArray[h].alpha = 0; // Make lost heart completely invisible
}
}
if (lives <= 0) {
LK.showGameOver();
}
}
function createParticleEffect(x, y, color) {
// Create enhanced particle effects with increased count
var totalParticles = 80; // Significantly increased particle count
var primaryParticles = 35;
var secondaryParticles = 25;
var trailParticles = 20;
// Primary burst particles - fast moving with enhanced visuals
for (var i = 0; i < primaryParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x;
particle.y = y;
// Enhanced color with gradient-like effect
var colorVariation = 0x303030 * Math.random();
particle.tint = (color || 0xFFD93D) + colorVariation;
particle.alpha = 0.95 + Math.random() * 0.05;
game.addChild(particle);
// Calculate random direction and speed for each particle
var angle = i / primaryParticles * Math.PI * 2 + (Math.random() - 0.5) * 2.2;
var speed = 800 + Math.random() * 700; // Significantly increased speed range
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Enhanced particle scaling with better proportions
particle.scaleX = 3.5 + Math.random() * 3.0; // Larger particle size for better impact
particle.scaleY = particle.scaleX + (Math.random() - 0.5) * 0.8; // More asymmetry for dynamic feel
// Animate particle flying outward with enhanced effects
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
// Smoother scale transition
scaleY: 0.05,
rotation: (Math.random() - 0.5) * Math.PI * 16 // Increased rotation for more dynamic motion
}, {
duration: 1000 + Math.random() * 800,
// Longer duration for better visual impact
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Secondary slower particles for depth with improved visuals
for (var i = 0; i < secondaryParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x + (Math.random() - 0.5) * 60; // Increased initial spread
particle.y = y + (Math.random() - 0.5) * 60;
// Enhanced secondary particle coloring
var secondaryColor = color || 0xFFD93D;
var brightness = 0.8 + Math.random() * 0.2;
particle.tint = secondaryColor * brightness;
particle.alpha = 0.85 + Math.random() * 0.15;
game.addChild(particle);
var angle = Math.random() * Math.PI * 2;
var speed = 450 + Math.random() * 350; // Increased secondary particle speed
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Enhanced secondary particle scaling
particle.scaleX = 2.5 + Math.random() * 2.0; // Larger secondary particles
particle.scaleY = particle.scaleX * (0.7 + Math.random() * 0.6); // More varied proportions
// Delayed animation for staggered effect with enhanced timing
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
// Smoother scale transition
scaleY: 0.1,
rotation: (Math.random() - 0.5) * Math.PI * 14 // Increased rotation
}, {
duration: 1200 + Math.random() * 1000,
// Longer duration for better visual flow
easing: tween.easeInOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Enhanced trail particles that move upward
for (var i = 0; i < trailParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x + (Math.random() - 0.5) * 80; // Increased initial spread
particle.y = y;
// Enhanced trail particle coloring with stronger glow effect
var trailColor = color || 0xFFD93D;
var glowIntensity = 0.9 + Math.random() * 0.1;
particle.tint = trailColor * glowIntensity;
particle.alpha = 0.8 + Math.random() * 0.2;
game.addChild(particle);
// Enhanced trail particle scaling with larger proportions
particle.scaleX = 4.0 + Math.random() * 3.0; // Significantly larger trail particles
particle.scaleY = particle.scaleX * (0.5 + Math.random() * 1.0); // More dramatic shape variations
// Animate upward with enhanced gravity-like effect
tween(particle, {
x: particle.x + (Math.random() - 0.5) * 300,
// Increased horizontal spread
y: y - 500 - Math.random() * 350,
// Significantly enhanced upward movement
alpha: 0,
scaleX: 0.2,
// Smoother scale transition
scaleY: 0.2,
rotation: (Math.random() - 0.5) * Math.PI * 12 // Increased rotation for more dynamic motion
}, {
duration: 1500 + Math.random() * 1200,
// Longer duration for better visual impact
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
function checkNoteInTrack(trackIndex) {
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === trackIndex && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = 0; // Start with first track for consistency
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync
musicRepeatCount = 0;
// Reset rhythm timing for new music with proper initialization
currentBeatIndex = 0; // Start at 0 for immediate beat detection
beatAccumulator = 0; // Reset beat accumulator for precise timing
lastSpawnTime = 0; // Reset last spawn time
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
// Reset rhythm timing for new music with proper initialization
currentBeatIndex = 0; // Start at 0 for immediate beat detection
beatAccumulator = 0; // Reset beat accumulator for precise timing
lastSpawnTime = 0; // Reset last spawn time
}
unifiedTargetZone.flash();
note.destroy();
notes.splice(i, 1);
return true;
}
}
}
return false;
}
// Touch handlers
game.down = function (x, y, obj) {
for (var i = 0; i < numTracks; i++) {
var trackX = startX + i * trackWidth;
if (x >= trackX - trackWidth / 2 && x <= trackX + trackWidth / 2) {
if (!checkNoteInTrack(i)) {
// Removed miss sound and life reduction for missed taps - only notes escaping target zone should reduce lives and play miss sound
}
break;
}
}
};
// Main game loop
game.update = function () {
// Rhythm-based note spawning when music is playing
if (musicStarted && currentMusicTrack) {
// Use precise music synchronization for rhythm-based spawning
spawnRhythmNote();
} else {
// Fallback to timer-based spawning when no music is playing
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
}
}
// Process note queue to ensure no simultaneous target zone entries
processNoteQueue();
// Update difficulty
difficultyTimer++;
if (difficultyTimer >= 1800) {
// Every 30 seconds
gameSpeed += 0.2;
noteSpawnInterval = Math.max(20, noteSpawnInterval - 2);
difficultyTimer = 0;
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Check if note passed beyond target zone - let it pass slightly further before removing
var targetY = 1650; // unifiedTargetZone.y
var targetZoneBottom = targetY + 200 * 1.5 / 2; // Bottom of expanded target zone
var disappearY = targetZoneBottom + 100; // Allow note to pass 100px beyond target zone
if (!note.hasBeenTapped && note.lastY <= disappearY && note.y > disappearY) {
note.destroy();
notes.splice(i, 1);
continue;
}
// Remove notes that are off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
}
// Clean up off-screen particles
var allChildren = game.children.slice(); // Create a copy to avoid modification during iteration
for (var i = allChildren.length - 1; i >= 0; i--) {
var child = allChildren[i];
// Check if this is a particle (has particle-like properties)
if (child && child.x !== undefined && child.y !== undefined && child.width <= 10 && child.height <= 10) {
// Remove particles that are far off-screen or destroyed
if (child.x < -500 || child.x > 2548 || child.y < -500 || child.y > 3232 || child.destroyed) {
if (child.destroy) {
child.destroy();
} else {
game.removeChild(child);
}
}
}
}
// Create continuous particle effects around the three objects
enhanceObjectVisuals();
// Check for music track changes every 60 seconds (1 minute) with fade out
if (musicStarted && LK.ticks % 60 === 0) {
// Check every second
var timeElapsed = (LK.ticks - musicStartTime) / 60; // Convert to seconds since music started
var musicChangeInterval = 60; // 60 seconds (1 minute) per music change
// Start fade out 2 seconds before music ends
if (timeElapsed >= musicChangeInterval - 2 && timeElapsed < musicChangeInterval && !musicFinished) {
// Fade out over 2 seconds
LK.playMusic(currentMusicTrack, {
fade: {
start: 1,
end: 0,
duration: 2000
}
});
}
// Check if current music should have finished
if (timeElapsed >= musicChangeInterval && !musicFinished) {
musicFinished = true;
waitingForNoteAfterMusicEnd = true;
LK.stopMusic(); // Stop current music and wait for note hit
}
}
// Win condition
if (score >= 10000) {
LK.showYouWin();
}
};
// Add 3 objects at bottom of target zone (left, center, right)
var targetBottomY = unifiedTargetZone.y + targetZoneHeight / 2 + 520; // Move objects down very slightly
var objectSpacing = targetZoneWidth / 1.0; // Further increased spacing for better separation between objects
// Left object
leftObject = LK.getAsset('leftObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
leftObject.x = unifiedTargetZone.x - objectSpacing;
leftObject.y = targetBottomY;
// Scale up the left object
leftObject.scaleX = 2.5;
leftObject.scaleY = 2.5;
// Animate color change for left object
tween(leftObject, {
tint: 0xFFD700 // Change to gold color
}, {
duration: 1500,
easing: tween.easeInOut
});
leftObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to left object
if (leftObject.filters) {
leftObject.filters = [];
} else {
leftObject.filters = [];
}
game.addChild(leftObject);
// Center object
centerObject = LK.getAsset('centerObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
centerObject.x = unifiedTargetZone.x;
centerObject.y = targetBottomY;
// Scale up the center object
centerObject.scaleX = 2.5;
centerObject.scaleY = 2.5;
// Apply orange tint to center object
tween(centerObject, {
tint: 0xFF6600 // Change to bright orange color
}, {
duration: 1500,
easing: tween.easeInOut
});
centerObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to center object
if (centerObject.filters) {
centerObject.filters = [];
} else {
centerObject.filters = [];
}
game.addChild(centerObject);
// Right object
rightObject = LK.getAsset('rightObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
rightObject.x = unifiedTargetZone.x + objectSpacing;
rightObject.y = targetBottomY;
// Scale up the right object
rightObject.scaleX = 2.5;
rightObject.scaleY = 2.5;
// Animate color change for right object
tween(rightObject, {
tint: 0x9370DB // Change to medium purple color
}, {
duration: 1500,
easing: tween.easeInOut
});
rightObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to right object
if (rightObject.filters) {
rightObject.filters = [];
} else {
rightObject.filters = [];
}
game.addChild(rightObject);
// Objects are now properly sized - no extreme scaling needed
// Move objects to topmost layer for better visibility
game.removeChild(leftObject);
game.addChild(leftObject);
game.removeChild(centerObject);
game.addChild(centerObject);
game.removeChild(rightObject);
game.addChild(rightObject);
// Add touch handlers to the 3 bottom objects
leftObject.down = function (x, y, obj) {
// Add guitar string vibration effect to left track strip line
if (leftStripLine) {
// Use stored original position instead of current position
var originalX = leftStripLine.originalX;
var _vibrateString = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(leftStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(leftStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString();
}
// Check for notes in left track (track 0) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 0 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
centerObject.down = function (x, y, obj) {
// Add guitar string vibration effect to center track strip line
if (centerStripLine) {
// Use stored original position instead of current position
var originalX = centerStripLine.originalX;
var _vibrateString2 = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(centerStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(centerStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString2();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString2();
}
// Check for notes in center track (track 1) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 1 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
rightObject.down = function (x, y, obj) {
// Add guitar string vibration effect to right track strip line
if (rightStripLine) {
// Use stored original position instead of current position
var originalX = rightStripLine.originalX;
var _vibrateString3 = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(rightStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(rightStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString3();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString3();
}
// Check for notes in right track (track 2) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 2 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
// Music will start when first note is hit
var musicStarted = false;
var musicTracks = ['bgmusic', 'bgmusic2', 'bgmusic3', 'bgmusic4', 'bgmusic5'];
var currentMusicTrack = null;
var musicRepeatCount = 0;
var targetRepeatCount = 3;
var musicStartTime = 0;
var currentMusicIndex = 0;
var musicFinished = false;
var waitingForNoteAfterMusicEnd = false;
// Orbital particle effects removed - variables no longer needed
// Optimized visual effects function
function enhanceObjectVisuals() {
// Orbital particle effects removed
}
Gitar gövde kısmı
Do müzik notası
Mavi pastel renkli daire. In-Game asset. 2d. High contrast. No shadows
Kalp. In-Game asset. 2d. High contrast. No shadows
Gitar aksesuarı turuncu pastel renk. In-Game asset. 2d. High contrast. No shadows
Kırmızı pastel renk gitar çalma aksesuarı. In-Game asset. 2d. High contrast. No shadows