/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 33; self.targetX = 0; self.targetY = 0; self.directionX = 0; self.directionY = 0; self.update = function () { self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; }; return self; }); var Note = Container.expand(function () { var self = Container.call(this); // Default note type self.noteType = 'normal'; self.pointValue = 100; self.speed = 3; // Initialize with normal note graphics var noteGraphics = self.attachAsset('note', { anchorX: 0.5, anchorY: 0.5 }); self.graphics = noteGraphics; self.lane = 0; self.hasTriggered = false; self.pulseTimer = 0; // Method to set note type with different properties self.setNoteType = function (type) { self.noteType = type; // Remove current graphics self.removeChild(self.graphics); switch (type) { case 'fast': // Mix different note assets for variety var fastAssets = ['noteRed', 'notePurple']; var fastAsset = fastAssets[Math.floor(Math.random() * fastAssets.length)]; self.graphics = self.attachAsset(fastAsset, { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; self.pointValue = 150; break; case 'slow': // Mix different note assets for variety var slowAssets = ['noteBlue', 'note']; var slowAsset = slowAssets[Math.floor(Math.random() * slowAssets.length)]; self.graphics = self.attachAsset(slowAsset, { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.5; self.pointValue = 200; break; case 'bonus': self.graphics = self.attachAsset('noteGold', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2.5; self.pointValue = 300; break; case 'challenge': self.graphics = self.attachAsset('notePurple', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4; self.pointValue = 250; break; case 'multiShot': self.graphics = self.attachAsset('powerUpGreen', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 150; break; case 'slowMotion': self.graphics = self.attachAsset('powerUpGreen', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 150; break; case 'scoreMultiplier': self.graphics = self.attachAsset('powerUpGreen', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 150; break; case 'movingTarget': self.graphics = self.attachAsset('powerUpRed', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 100; break; case 'laserBeam': self.graphics = self.attachAsset('powerUpBlue', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 200; break; case 'shield': self.graphics = self.attachAsset('powerUpBlue', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 150; break; case 'noteDestroyer': self.graphics = self.attachAsset('powerUpBlue', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 300; break; case 'reverseControls': self.graphics = self.attachAsset('powerUpRed', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 100; break; case 'shrinkingTargets': self.graphics = self.attachAsset('powerUpRed', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.pointValue = 100; break; default: self.graphics = self.attachAsset('note', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; self.pointValue = 100; } }; // Method to play sound based on note type self.playNoteSound = function () { switch (self.noteType) { case 'fast': // Mix different sounds for fast notes var fastSounds = ['shake', 'guitar']; var randomFastSound = fastSounds[Math.floor(Math.random() * fastSounds.length)]; LK.getSound(randomFastSound).play(); break; case 'slow': // Mix different sounds for slow notes var slowSounds = ['kick', 'piano', 'violin']; var randomSlowSound = slowSounds[Math.floor(Math.random() * slowSounds.length)]; LK.getSound(randomSlowSound).play(); break; case 'bonus': LK.getSound('hihat').play(); break; case 'challenge': LK.getSound('cymbal').play(); break; case 'multiShot': case 'slowMotion': case 'scoreMultiplier': case 'movingTarget': case 'laserBeam': case 'shield': case 'noteDestroyer': case 'reverseControls': case 'shrinkingTargets': LK.getSound('bass').play(); break; default: LK.getSound('violin').play(); } }; self.update = function () { self.y += self.speed; // Add visual effects for special notes if (self.noteType === 'bonus') { // Golden notes pulse self.pulseTimer++; var scale = 1 + Math.sin(self.pulseTimer * 0.2) * 0.1; self.graphics.scaleX = scale; self.graphics.scaleY = scale; } else if (self.noteType === 'challenge') { // Purple notes rotate self.graphics.rotation += 0.05; } else if (self.noteType === 'multiShot' || self.noteType === 'slowMotion' || self.noteType === 'scoreMultiplier') { // Good power-ups glow green self.pulseTimer++; var scale = 1 + Math.sin(self.pulseTimer * 0.3) * 0.15; self.graphics.scaleX = scale; self.graphics.scaleY = scale; } else if (self.noteType === 'laserBeam' || self.noteType === 'shield' || self.noteType === 'noteDestroyer') { // New power-ups glow blue self.pulseTimer++; var scale = 1 + Math.sin(self.pulseTimer * 0.35) * 0.18; self.graphics.scaleX = scale; self.graphics.scaleY = scale; } else if (self.noteType === 'movingTarget' || self.noteType === 'reverseControls' || self.noteType === 'shrinkingTargets') { // Bad power-ups pulse red self.pulseTimer++; var scale = 1 + Math.sin(self.pulseTimer * 0.4) * 0.2; self.graphics.scaleX = scale; self.graphics.scaleY = scale; } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Add floating gun var gun = self.attachAsset('gun', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -50 }); self.gun = gun; // Method to rotate gun towards target with smooth animation self.aimGunAt = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - (self.y - 50); // Account for gun offset var targetRotation = Math.atan2(dy, dx); // Smooth rotation animation tween(self.gun, { rotation: targetRotation }, { duration: 150, easing: tween.easeOut }); }; return self; }); var Spike = Container.expand(function () { var self = Container.call(this); var spikeGraphics = self.attachAsset('spike', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); self.speed = 8.0; self.lane = 0; self.pulseTimer = 0; self.update = function () { self.y += self.speed; // Add pulsing danger effect self.pulseTimer++; var scale = 0.7 + Math.sin(self.pulseTimer * 0.4) * 0.14; spikeGraphics.scaleX = scale; spikeGraphics.scaleY = scale; // Add rotation for more menacing look spikeGraphics.rotation += 0.08; }; return self; }); var TargetBox = Container.expand(function () { var self = Container.call(this); var targetGraphics = self.attachAsset('targetBox', { anchorX: 0.5, anchorY: 0.5 }); self.lane = 0; self.isActive = false; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xF0F8FF, name: 'Cats Are Rhythmic' }); /**** * Game Code ****/ // Game state variables var gameState = 'menu'; // 'menu' or 'playing' var startMenuContainer; var musicButtons = []; // Game variables var player; var bullets = []; var notes = []; var spikes = []; var targetBoxes = []; var staffLines = []; var aimLine; var deletionAreaY; var targetBoxPositions = [512, 853, 1194, 1536]; // Define globally for access throughout the game var score = 0; var staffY = 400; var staffSpacing = 80; // Level system variables var currentLevel = storage.currentLevel || 1; var levelScore = storage.levelScore || 0; // Score accumulated in current level var totalScore = storage.totalScore || 0; // Total score across all levels var levelThresholds = [0, 1000, 2500, 5000, 8000, 12000, 17000, 23000, 30000, 38000, 47000, 57000, 68000, 80000, 93000, 107000, 122000, 138000, 155000, 173000]; // Score needed for each level var maxLevel = levelThresholds.length; var mouseX = 1024; var noteSpawnTimer = 0; var noteSpawnInterval = 90; // frames between note spawns var spikeSpawnTimer = 0; var spikeSpawnInterval = 300; // frames between spike spawns (much less frequent than notes) var musicPlaying = false; var musicGlitchTimer = 0; var musicGlitchDuration = 18; // 0.3 seconds at 60fps // Music selection variables var currentMusicTrack = 'bgmusic'; var musicTracks = ['bgmusic', 'rock_track', 'electronic_track']; var musicTrackNames = ['Electronic Track', 'Rock Track', 'Soul Track']; // Note preview system var previewNotes = []; var nextNoteTypes = []; // Combo system variables var combo = 0; var maxCombo = 0; // Feedback text variables var feedbackTexts = []; // Power-up system variables var multiShotActive = false; var multiShotTimer = 0; var multiShotDuration = 300; // 5 seconds at 60fps var slowMotionActive = false; var slowMotionTimer = 0; var slowMotionDuration = 300; // 5 seconds at 60fps var scoreMultiplierActive = false; var scoreMultiplierTimer = 0; var scoreMultiplierDuration = 300; // 5 seconds at 60fps var movingTargetActive = false; var movingTargetTimer = 0; var movingTargetDuration = 240; // 4 seconds at 60fps // Power-up display variables var powerUpDisplayContainer; var powerUpIconDisplay; var powerUpNameDisplay; var powerUpTimerDisplay; var powerUpProgressBar; var activePowerUpType = null; var activePowerUpMaxDuration = 0; // New power-up system variables var laserBeamActive = false; var laserBeamTimer = 0; var laserBeamDuration = 180; // 3 seconds at 60fps var laserBeamGraphics = null; var shieldActive = false; var shieldHits = 0; var maxShieldHits = 1; var shieldGraphics = null; var noteDestroyerActive = false; var noteDestroyerTimer = 0; var noteDestroyerDuration = 1; // Instant effect, 1 frame duration // Bad power-up system variables var reverseControlsActive = false; var reverseControlsTimer = 0; var reverseControlsDuration = 300; // 5 seconds at 60fps var shrinkingTargetsActive = false; var shrinkingTargetsTimer = 0; var shrinkingTargetsDuration = 240; // 4 seconds at 60fps var originalTargetSizes = []; // Create start menu function createStartMenu() { startMenuContainer = new Container(); startMenuContainer.x = 1024; // Center of screen startMenuContainer.y = 1366; // Center of screen game.addChild(startMenuContainer); // Game title var titleText = new Text2('Cats Are Rhythmic', { size: 120, fill: 0x87CEEB }); titleText.anchor.set(0.5, 0.5); titleText.y = -400; startMenuContainer.addChild(titleText); // Subtitle var subtitleText = new Text2('Choose Your Music', { size: 80, fill: 0x87CEEB }); subtitleText.anchor.set(0.5, 0.5); subtitleText.y = -300; startMenuContainer.addChild(subtitleText); // Create music selection buttons var buttonSpacing = 200; var startY = -100; for (var i = 0; i < musicTracks.length; i++) { var buttonContainer = new Container(); buttonContainer.x = 0; buttonContainer.y = startY + i * buttonSpacing; startMenuContainer.addChild(buttonContainer); // Button background var buttonBg = LK.getAsset('targetBox', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5, tint: 0x4444FF }); buttonContainer.addChild(buttonBg); // Button text var buttonText = new Text2(musicTrackNames[i], { size: 60, fill: 0x87CEEB }); buttonText.anchor.set(0.5, 0.5); buttonContainer.addChild(buttonText); // Store button data buttonContainer.musicIndex = i; buttonContainer.background = buttonBg; buttonContainer.text = buttonText; // Add click handler buttonContainer.down = function (x, y, obj) { selectMusic(this.musicIndex); }; musicButtons.push(buttonContainer); } // Music warning instruction var musicWarningText = new Text2('MUSIC WONT START TILL YOU HIT A MUSIC NOTE!', { size: 45, fill: 0xFF6666 }); musicWarningText.anchor.set(0.5, 0.5); musicWarningText.y = 450; startMenuContainer.addChild(musicWarningText); // Instructions var instructionText = new Text2('Tap a music option to start the game', { size: 50, fill: 0x87CEEB }); instructionText.anchor.set(0.5, 0.5); instructionText.y = 500; startMenuContainer.addChild(instructionText); } function selectMusic(musicIndex) { currentMusicTrack = musicTracks[musicIndex]; // Update active music display activeMusicDisplay.setText('Music: ' + musicTrackNames[musicIndex]); // Animate menu exit tween(startMenuContainer, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { startMenuContainer.destroy(); startMenuContainer = null; musicButtons = []; startGame(); } }); } function startGame() { gameState = 'playing'; setupGameElements(); } function setupGameElements() { // Setup staff lines var staffY = 400; var staffSpacing = 80; for (var i = 0; i < 5; i++) { var staffLine = game.addChild(LK.getAsset('staffLine', { anchorX: 0, anchorY: 0.5, x: 0, y: staffY + i * staffSpacing })); staffLines.push(staffLine); } // Setup note preview indicators var targetBoxPositions = [512, 853, 1194, 1536]; for (var i = 0; i < 4; i++) { var previewNote = LK.getAsset('note', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, alpha: 0.8, x: targetBoxPositions[i], y: staffY - 100 }); previewNote.visible = false; game.addChild(previewNote); previewNotes.push(previewNote); nextNoteTypes.push(null); } // Setup target boxes var targetY = staffY + 4 * staffSpacing + 200; var targetBoxPositions = [512, 853, 1194, 1536]; // 4 evenly spaced positions for (var i = 0; i < 4; i++) { var targetBox = game.addChild(new TargetBox()); targetBox.x = targetBoxPositions[i]; targetBox.y = targetY; targetBox.lane = i; targetBoxes.push(targetBox); } // Setup deletion area below target boxes deletionAreaY = targetY + 250; // Initialize level system updateLevelDisplay(); increaseDifficultyForLevel(); // Create visible secret wall var secretWall = game.addChild(LK.getAsset('secretWall', { anchorX: 0, anchorY: 0.5, x: 0, y: deletionAreaY })); // Setup player player = game.addChild(new Player()); player.x = 1024; player.y = 2500; // Create aim assistance line aimLine = LK.getAsset('staffLine', { anchorX: 0, anchorY: 0.5, width: 2, height: 2, alpha: 0.3, tint: 0xFF0000 }); game.addChild(aimLine); } // Setup score display var scoreTxt = new Text2('Score: 0', { size: 80, fill: 0x000000 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Setup level display var levelTxt = new Text2('Level: ' + currentLevel, { size: 60, fill: 0x0066CC }); levelTxt.anchor.set(0, 0); levelTxt.x = 20; levelTxt.y = 20; LK.gui.topLeft.addChild(levelTxt); // Setup level progress display var levelProgressTxt = new Text2('', { size: 45, fill: 0x666666 }); levelProgressTxt.anchor.set(0, 0); levelProgressTxt.x = 20; levelProgressTxt.y = 90; LK.gui.topLeft.addChild(levelProgressTxt); // Setup combo display var comboTxt = new Text2('', { size: 60, fill: 0xFF6600 }); comboTxt.anchor.set(0.5, 0); comboTxt.y = 90; // Position below score LK.gui.top.addChild(comboTxt); // Display active music (non-interactive) var activeMusicDisplay = new Text2('Music: ' + musicTrackNames[0], { size: 50, fill: 0x000000 }); activeMusicDisplay.anchor.set(1, 0); activeMusicDisplay.x = -20; // Position from right edge activeMusicDisplay.y = 20; LK.gui.topRight.addChild(activeMusicDisplay); // Create power-up display container on left side of screen powerUpDisplayContainer = new Container(); powerUpDisplayContainer.x = 50; powerUpDisplayContainer.y = 200; powerUpDisplayContainer.alpha = 0; LK.gui.left.addChild(powerUpDisplayContainer); // Power-up icon display powerUpIconDisplay = LK.getAsset('powerUpGreen', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); powerUpIconDisplay.x = 40; powerUpIconDisplay.y = 40; powerUpDisplayContainer.addChild(powerUpIconDisplay); // Power-up name text powerUpNameDisplay = new Text2('', { size: 40, fill: 0x00FF00 }); powerUpNameDisplay.anchor.set(0, 0.5); powerUpNameDisplay.x = 90; powerUpNameDisplay.y = 25; powerUpDisplayContainer.addChild(powerUpNameDisplay); // Power-up timer text powerUpTimerDisplay = new Text2('', { size: 30, fill: 0xFFFFFF }); powerUpTimerDisplay.anchor.set(0, 0.5); powerUpTimerDisplay.x = 90; powerUpTimerDisplay.y = 55; powerUpDisplayContainer.addChild(powerUpTimerDisplay); // Power-up progress bar background var progressBarBg = LK.getAsset('staffLine', { anchorX: 0, anchorY: 0.5, width: 200, height: 8, tint: 0x333333 }); progressBarBg.x = 90; progressBarBg.y = 75; powerUpDisplayContainer.addChild(progressBarBg); // Power-up progress bar fill powerUpProgressBar = LK.getAsset('staffLine', { anchorX: 0, anchorY: 0.5, width: 200, height: 8, tint: 0x00FF00 }); powerUpProgressBar.x = 90; powerUpProgressBar.y = 75; powerUpDisplayContainer.addChild(powerUpProgressBar); // Initialize start menu createStartMenu(); // Input handling game.move = function (x, y, obj) { if (gameState !== 'playing') return; mouseX = x; // Apply reverse controls effect if (reverseControlsActive) { var centerX = 1024; var distanceFromCenter = x - centerX; player.x = centerX - distanceFromCenter; } else { player.x = x; } // Continuously aim gun at mouse position player.aimGunAt(x, y); // Update aim line var gunX = player.x; var gunY = player.y - 50; var dx = x - gunX; var dy = y - gunY; var distance = Math.sqrt(dx * dx + dy * dy); aimLine.x = gunX; aimLine.y = gunY; aimLine.width = Math.min(distance, 300); // Limit line length aimLine.rotation = Math.atan2(dy, dx); }; game.down = function (x, y, obj) { if (gameState !== 'playing') return; // Aim gun at target position player.aimGunAt(x, y); if (multiShotActive) { // Create 3 bullets in spread pattern for (var i = 0; i < 3; i++) { var bullet = game.addChild(new Bullet()); bullet.x = player.x; bullet.y = player.y - 50; // Spawn from gun position // Calculate direction to mouse position with spread var dx = x - player.x; var dy = y - (player.y - 50); // Account for gun offset var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var angle = Math.atan2(dy, dx); var spreadAngle = (i - 1) * 0.3; // -0.3, 0, 0.3 radians spread bullet.directionX = Math.cos(angle + spreadAngle); bullet.directionY = Math.sin(angle + spreadAngle); } bullets.push(bullet); } } else { // Create single bullet from gun position var bullet = game.addChild(new Bullet()); bullet.x = player.x; bullet.y = player.y - 50; // Spawn from gun position // Calculate direction to mouse position var dx = x - player.x; var dy = y - (player.y - 50); // Account for gun offset var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { bullet.directionX = dx / distance; bullet.directionY = dy / distance; } bullets.push(bullet); } }; // Generate note type based on probabilities function generateNoteType() { var random = Math.random(); if (random < 0.05) { return 'bonus'; // 5% chance for bonus notes } else if (random < 0.1) { return 'challenge'; // 5% chance for challenge notes } else if (random < 0.2) { return 'fast'; // 10% chance for fast notes } else if (random < 0.3) { return 'slow'; // 10% chance for slow notes } else if (random < 0.33) { return 'multiShot'; // 3% chance for multi-shot power-up } else if (random < 0.36) { return 'slowMotion'; // 3% chance for slow motion power-up } else if (random < 0.39) { return 'scoreMultiplier'; // 3% chance for score multiplier power-up } else if (random < 0.42) { return 'movingTarget'; // 3% chance for moving target power-up } else if (random < 0.445) { return 'laserBeam'; // 2.5% chance for laser beam power-up } else if (random < 0.47) { return 'shield'; // 2.5% chance for shield power-up } else if (random < 0.49) { return 'noteDestroyer'; // 2% chance for note destroyer power-up } else if (random < 0.515) { return 'reverseControls'; // 2.5% chance for reverse controls power-up } else if (random < 0.54) { return 'shrinkingTargets'; // 2.5% chance for shrinking targets power-up } else { return 'normal'; // 46% chance for normal notes } } // Update note preview display function updateNotePreview(lane, noteType) { if (lane >= 0 && lane < previewNotes.length) { var preview = previewNotes[lane]; nextNoteTypes[lane] = noteType; // Set preview note appearance based on type preview.removeChildren(); var assetId = 'note'; var tintColor = 0xFFFFFF; switch (noteType) { case 'fast': assetId = 'noteRed'; break; case 'slow': assetId = 'noteBlue'; break; case 'bonus': assetId = 'noteGold'; break; case 'challenge': assetId = 'notePurple'; break; case 'multiShot': case 'slowMotion': case 'scoreMultiplier': assetId = 'powerUpGreen'; break; case 'movingTarget': case 'reverseControls': case 'shrinkingTargets': assetId = 'powerUpRed'; break; case 'laserBeam': case 'shield': case 'noteDestroyer': assetId = 'powerUpBlue'; break; } var previewGraphics = LK.getAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, alpha: 0.8 }); preview.addChild(previewGraphics); preview.visible = true; // Animate preview appearance tween(preview, { alpha: 1.0, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut }); // Hide preview after delay tween(preview, { alpha: 0.6, scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeIn }); } } // Spawn notes function spawnNote() { var lane = Math.floor(Math.random() * 4); var note = game.addChild(new Note()); var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions note.x = targetBoxPositions[lane]; note.y = staffY + lane * staffSpacing - 200; note.lane = lane; // Use generated note type var noteType = generateNoteType(); note.setNoteType(noteType); // Show preview for next note var nextLane = Math.floor(Math.random() * 4); var nextNoteType = generateNoteType(); updateNotePreview(nextLane, nextNoteType); notes.push(note); } // Spawn spikes function spawnSpike() { var lane = Math.floor(Math.random() * 4); var spike = game.addChild(new Spike()); var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions spike.x = targetBoxPositions[lane]; spike.y = staffY + lane * staffSpacing - 200; spike.lane = lane; spikes.push(spike); } // Check if note is in timing window function isNoteInTimingWindow(note, targetBox) { var noteTop = note.y - 35; // Top of note (note height is 70, so 35 from center) var noteBottom = note.y + 35; // Bottom of note var targetTop = targetBox.y - 65; // Expanded target area (target height is 130, so 65 from center) var targetBottom = targetBox.y + 65; // Expanded target area // Check if any part of the note overlaps with the expanded target area return noteBottom >= targetTop && noteTop <= targetBottom; } // Get timing quality based on note position relative to target center function getTimingQuality(note, targetBox) { var distance = Math.abs(note.y - targetBox.y); if (distance <= 20) { return 'Perfect!'; } else if (distance <= 40) { return 'Good'; } else { return 'Hit'; } } // Create particle burst effect function createParticleBurst(x, y, color, count) { for (var i = 0; i < count; i++) { var particle = LK.getAsset('bullet', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: color }); particle.x = x; particle.y = y; game.addChild(particle); // Random direction and speed var angle = Math.PI * 2 * i / count + (Math.random() - 0.5) * 0.5; var speed = 100 + Math.random() * 100; var targetX = x + Math.cos(angle) * speed; var targetY = y + Math.sin(angle) * speed; // Animate particle outward tween(particle, { x: targetX, y: targetY, scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 800 + Math.random() * 400, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } } // Check and handle level progression function checkLevelUp() { if (currentLevel < maxLevel) { var nextLevelThreshold = levelThresholds[currentLevel]; if (totalScore >= nextLevelThreshold) { levelUp(); } } } // Handle level up function levelUp() { var previousLevel = currentLevel; currentLevel++; // Save level progress storage.currentLevel = currentLevel; storage.totalScore = totalScore; storage.levelScore = 0; // Reset level score for new level levelScore = 0; // Create animated level pop-up display var levelPopUpContainer = new Container(); levelPopUpContainer.x = 1024; // Center of screen levelPopUpContainer.y = 1366; // Center of screen levelPopUpContainer.alpha = 0; levelPopUpContainer.scaleX = 0.1; levelPopUpContainer.scaleY = 0.1; game.addChild(levelPopUpContainer); // Background for pop-up var popUpBg = LK.getAsset('targetBox', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 3, tint: 0x000080, alpha: 0.9 }); levelPopUpContainer.addChild(popUpBg); // Level text var levelPopUpText = new Text2('LEVEL ' + currentLevel, { size: 120, fill: 0xFFD700 }); levelPopUpText.anchor.set(0.5, 0.5); levelPopUpText.y = -30; levelPopUpContainer.addChild(levelPopUpText); // Level up text var levelUpText = new Text2('LEVEL UP!', { size: 80, fill: 0x00FF00 }); levelUpText.anchor.set(0.5, 0.5); levelUpText.y = 60; levelPopUpContainer.addChild(levelUpText); // Animate pop-up appearance tween(levelPopUpContainer, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.easeOut }); // Add pulsing effect to level text tween(levelPopUpText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeInOut }); tween(levelPopUpText, { scaleX: 1, scaleY: 1 }, { duration: 800, easing: tween.easeInOut }); // Remove pop-up after delay tween(levelPopUpContainer, { alpha: 0, scaleX: 0.8, scaleY: 0.8, y: 1200 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { levelPopUpContainer.destroy(); } }); // Create level up feedback createFeedbackText('LEVEL UP!', 1024, 800, 0xFFD700); createFeedbackText('Level ' + currentLevel, 1024, 860, 0x0066CC); // Flash screen gold for level up LK.effects.flashScreen(0xFFD700, 800); // Update level display updateLevelDisplay(); // Increase difficulty with level increaseDifficultyForLevel(); } // Update level display function updateLevelDisplay() { if (levelTxt) { levelTxt.setText('Level: ' + currentLevel); } if (currentLevel < maxLevel) { var nextThreshold = levelThresholds[currentLevel]; var progress = totalScore - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0); var needed = nextThreshold - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0); if (levelProgressTxt) { levelProgressTxt.setText(progress + ' / ' + needed); } } else { if (levelProgressTxt) { levelProgressTxt.setText('MAX LEVEL'); } } } // Increase difficulty based on current level function increaseDifficultyForLevel() { // Reduce spawn intervals based on level noteSpawnInterval = Math.max(30, 90 - (currentLevel - 1) * 3); spikeSpawnInterval = Math.max(120, 300 - (currentLevel - 1) * 8); } // Create feedback text with animation function createFeedbackText(text, x, y, color) { var feedbackText = new Text2(text, { size: 50, fill: color }); feedbackText.anchor.set(0.5, 0.5); feedbackText.x = x; feedbackText.y = y; feedbackText.alpha = 1; feedbackText.scaleX = 0.5; feedbackText.scaleY = 0.5; game.addChild(feedbackText); feedbackTexts.push(feedbackText); // Animate text appearance tween(feedbackText, { scaleX: 1.2, scaleY: 1.2, y: y - 50 }, { duration: 300, easing: tween.easeOut }); // Fade out and remove tween(feedbackText, { alpha: 0, y: y - 100 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { feedbackText.destroy(); var index = feedbackTexts.indexOf(feedbackText); if (index > -1) { feedbackTexts.splice(index, 1); } } }); } // Show power-up effect display function showPowerUpDisplay(powerUpType, duration) { activePowerUpType = powerUpType; activePowerUpMaxDuration = duration; // Set power-up name and icon based on type var powerUpName = ''; var iconAsset = 'powerUpGreen'; var textColor = 0x00FF00; switch (powerUpType) { case 'multiShot': powerUpName = 'Multi-Shot'; iconAsset = 'powerUpGreen'; textColor = 0x00FF00; break; case 'slowMotion': powerUpName = 'Slow Motion'; iconAsset = 'powerUpGreen'; textColor = 0x00FFFF; break; case 'scoreMultiplier': powerUpName = 'Score x2'; iconAsset = 'powerUpGreen'; textColor = 0xFFD700; break; case 'movingTarget': powerUpName = 'Moving Targets'; iconAsset = 'powerUpRed'; textColor = 0xFF0000; break; case 'laserBeam': powerUpName = 'Laser Beam'; iconAsset = 'powerUpBlue'; textColor = 0x00FFFF; break; case 'shield': powerUpName = 'Shield'; iconAsset = 'powerUpBlue'; textColor = 0x00FFFF; break; case 'noteDestroyer': powerUpName = 'Spike Destroyer'; iconAsset = 'powerUpBlue'; textColor = 0x00FFFF; break; case 'reverseControls': powerUpName = 'Reverse Controls'; iconAsset = 'powerUpRed'; textColor = 0xFF0000; break; case 'shrinkingTargets': powerUpName = 'Shrinking Targets'; iconAsset = 'powerUpRed'; textColor = 0xFF0000; break; } // Update display elements powerUpDisplayContainer.removeChild(powerUpIconDisplay); powerUpIconDisplay = LK.getAsset(iconAsset, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); powerUpIconDisplay.x = 40; powerUpIconDisplay.y = 40; powerUpDisplayContainer.addChild(powerUpIconDisplay); powerUpNameDisplay.setText(powerUpName); powerUpNameDisplay.fill = textColor; powerUpProgressBar.tint = textColor; // Animate display appearance tween(powerUpDisplayContainer, { alpha: 1, scaleX: 1.1, scaleY: 1.1 }, { duration: 300, easing: tween.easeOut }); tween(powerUpDisplayContainer, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } // Hide power-up effect display function hidePowerUpDisplay() { activePowerUpType = null; activePowerUpMaxDuration = 0; tween(powerUpDisplayContainer, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, easing: tween.easeIn }); } // Game update loop game.update = function () { if (gameState !== 'playing') return; // Spawn notes noteSpawnTimer++; if (noteSpawnTimer >= noteSpawnInterval) { spawnNote(); noteSpawnTimer = 0; } // Spawn spikes spikeSpawnTimer++; if (spikeSpawnTimer >= spikeSpawnInterval) { spawnSpike(); spikeSpawnTimer = 0; } // Update and check bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Remove bullets that go off screen if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) { bullet.destroy(); bullets.splice(i, 1); continue; } // Spikes are no longer affected by bullets - they can pass through everything // Check collision with target boxes for (var j = 0; j < targetBoxes.length; j++) { var targetBox = targetBoxes[j]; if (bullet.intersects(targetBox)) { // Check if there's a note in timing window for this lane var hitNote = null; for (var k = 0; k < notes.length; k++) { var note = notes[k]; if (note.lane === targetBox.lane && !note.hasTriggered && isNoteInTimingWindow(note, targetBox)) { hitNote = note; break; } } if (hitNote) { // Get timing quality for feedback var timingQuality = getTimingQuality(hitNote, targetBox); var basePoints = hitNote.pointValue; var bonusPoints = 0; var feedbackColor = 0x00FF00; // Handle power-up activation if (hitNote.noteType === 'multiShot') { multiShotActive = true; multiShotTimer = multiShotDuration; showPowerUpDisplay('multiShot', multiShotDuration); createFeedbackText('Multi-Shot!', targetBox.x, targetBox.y - 120, 0x00FF00); } else if (hitNote.noteType === 'slowMotion') { slowMotionActive = true; slowMotionTimer = slowMotionDuration; showPowerUpDisplay('slowMotion', slowMotionDuration); createFeedbackText('Slow Motion!', targetBox.x, targetBox.y - 120, 0x00FF00); } else if (hitNote.noteType === 'scoreMultiplier') { scoreMultiplierActive = true; scoreMultiplierTimer = scoreMultiplierDuration; showPowerUpDisplay('scoreMultiplier', scoreMultiplierDuration); createFeedbackText('Score x2!', targetBox.x, targetBox.y - 120, 0x00FF00); } else if (hitNote.noteType === 'movingTarget') { movingTargetActive = true; movingTargetTimer = movingTargetDuration; showPowerUpDisplay('movingTarget', movingTargetDuration); createFeedbackText('Moving Targets!', targetBox.x, targetBox.y - 120, 0xFF0000); } else if (hitNote.noteType === 'laserBeam') { laserBeamActive = true; laserBeamTimer = laserBeamDuration; showPowerUpDisplay('laserBeam', laserBeamDuration); createFeedbackText('Laser Beam!', targetBox.x, targetBox.y - 120, 0x00FFFF); // Create laser beam visual effect if (laserBeamGraphics) { laserBeamGraphics.destroy(); } laserBeamGraphics = LK.getAsset('laserBeam', { anchorX: 0.5, anchorY: 0, x: player.x, y: 0, height: 2732, alpha: 0.8 }); game.addChild(laserBeamGraphics); } else if (hitNote.noteType === 'shield') { if (!shieldActive) { shieldActive = true; shieldHits = 0; showPowerUpDisplay('shield', 999); // Show indefinitely until used createFeedbackText('Shield Active!', targetBox.x, targetBox.y - 120, 0x00FFFF); // Create shield visual effect if (shieldGraphics) { shieldGraphics.destroy(); } shieldGraphics = LK.getAsset('shieldEffect', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); shieldGraphics.x = 0; shieldGraphics.y = 0; player.addChild(shieldGraphics); // Animate shield appearance tween(shieldGraphics, { alpha: 0.6, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeOut }); } } else if (hitNote.noteType === 'noteDestroyer') { noteDestroyerActive = true; noteDestroyerTimer = noteDestroyerDuration; showPowerUpDisplay('noteDestroyer', noteDestroyerDuration); createFeedbackText('Spike Destroyer!', targetBox.x, targetBox.y - 120, 0x00FFFF); // Immediately destroy all spikes on screen for (var s = spikes.length - 1; s >= 0; s--) { var spike = spikes[s]; createParticleBurst(spike.x, spike.y, 0xFF4444, 8); spike.destroy(); spikes.splice(s, 1); } // Add bonus points for destroyed spikes var destroyedCount = spikes.length; if (destroyedCount > 0) { score += destroyedCount * 50; createFeedbackText('+' + destroyedCount * 50 + ' Spike Bonus!', player.x, player.y - 150, 0x00FFFF); } } else if (hitNote.noteType === 'reverseControls') { reverseControlsActive = true; reverseControlsTimer = reverseControlsDuration; showPowerUpDisplay('reverseControls', reverseControlsDuration); createFeedbackText('Reverse Controls!', targetBox.x, targetBox.y - 120, 0xFF0000); } else if (hitNote.noteType === 'shrinkingTargets') { shrinkingTargetsActive = true; shrinkingTargetsTimer = shrinkingTargetsDuration; showPowerUpDisplay('shrinkingTargets', shrinkingTargetsDuration); createFeedbackText('Shrinking Targets!', targetBox.x, targetBox.y - 120, 0xFF0000); // Store original target box sizes originalTargetSizes = []; for (var t = 0; t < targetBoxes.length; t++) { originalTargetSizes.push({ scaleX: targetBoxes[t].scaleX, scaleY: targetBoxes[t].scaleY }); // Animate targets shrinking tween(targetBoxes[t], { scaleX: 0.5, scaleY: 0.5 }, { duration: 500, easing: tween.easeOut }); } } // Apply combo multiplier and timing bonus combo++; if (combo > maxCombo) { maxCombo = combo; } // Timing bonuses if (timingQuality === 'Perfect!') { bonusPoints = Math.floor(basePoints * 0.5); feedbackColor = 0xFFD700; // Gold } else if (timingQuality === 'Good') { bonusPoints = Math.floor(basePoints * 0.2); feedbackColor = 0x00FF00; // Green } else { feedbackColor = 0x88FF88; // Light green } // Combo multiplier (every 5 combo adds 10% bonus) var comboMultiplier = 1 + Math.floor(combo / 5) * 0.1; // Score multiplier power-up if (scoreMultiplierActive) { comboMultiplier *= 2; } var totalPoints = Math.floor((basePoints + bonusPoints) * comboMultiplier); score += totalPoints; levelScore += totalPoints; totalScore += totalPoints; // Check for level up checkLevelUp(); // Save progress storage.levelScore = levelScore; storage.totalScore = totalScore; hitNote.hasTriggered = true; // Create feedback text createFeedbackText(timingQuality, targetBox.x, targetBox.y - 80, feedbackColor); // Create particle burst effect var particleColor = feedbackColor; var particleCount = 8; if (hitNote.noteType === 'bonus') { particleColor = 0xFFD700; particleCount = 12; // More particles for bonus notes } else if (hitNote.noteType === 'challenge') { particleColor = 0x8844FF; } else if (hitNote.noteType === 'fast') { particleColor = 0xFF4444; } else if (hitNote.noteType === 'slow') { particleColor = 0x4444FF; } createParticleBurst(hitNote.x, hitNote.y, particleColor, particleCount); // Show combo if >= 5 if (combo >= 5) { comboTxt.setText('Combo x' + combo); // Scale combo text briefly for emphasis tween(comboTxt, { scaleX: 1.3, scaleY: 1.3 }, { duration: 150, easing: tween.easeOut }); tween(comboTxt, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.easeIn }); } // Flash different colors based on note type var flashColor = 0x00FF00; // Default green if (hitNote.noteType === 'bonus') flashColor = 0xFFD700; // Gold else if (hitNote.noteType === 'challenge') flashColor = 0x8844FF; // Purple else if (hitNote.noteType === 'fast') flashColor = 0xFF4444; // Red else if (hitNote.noteType === 'slow') flashColor = 0x4444FF; // Blue LK.effects.flashObject(targetBox, flashColor, 500); // Play note-specific sound hitNote.playNoteSound(); // Start or resume music on successful hit if (!musicPlaying && musicGlitchTimer === 0) { try { LK.playMusic(currentMusicTrack); musicPlaying = true; } catch (e) { console.log('Error playing music:', currentMusicTrack); musicPlaying = false; } } else if (musicGlitchTimer > 0) { // Resume music after glitch try { LK.playMusic(currentMusicTrack); musicGlitchTimer = 0; } catch (e) { console.log('Error resuming music:', currentMusicTrack); } } // Remove the note hitNote.destroy(); var noteIndex = notes.indexOf(hitNote); if (noteIndex > -1) { notes.splice(noteIndex, 1); } } // Remove bullet bullet.destroy(); bullets.splice(i, 1); // Update score display scoreTxt.setText('Score: ' + score); LK.setScore(totalScore); // Use total score for leaderboards // Update level display updateLevelDisplay(); break; } } } // Update power-up timers if (multiShotTimer > 0) { multiShotTimer--; if (multiShotTimer === 0) { multiShotActive = false; if (activePowerUpType === 'multiShot') { hidePowerUpDisplay(); } } } if (slowMotionTimer > 0) { slowMotionTimer--; if (slowMotionTimer === 0) { slowMotionActive = false; if (activePowerUpType === 'slowMotion') { hidePowerUpDisplay(); } } } if (scoreMultiplierTimer > 0) { scoreMultiplierTimer--; if (scoreMultiplierTimer === 0) { scoreMultiplierActive = false; if (activePowerUpType === 'scoreMultiplier') { hidePowerUpDisplay(); } } } if (movingTargetTimer > 0) { movingTargetTimer--; if (movingTargetTimer === 0) { movingTargetActive = false; if (activePowerUpType === 'movingTarget') { hidePowerUpDisplay(); } // Reset target boxes to original positions for (var j = 0; j < targetBoxes.length; j++) { targetBoxes[j].x = targetBoxPositions[j]; } } } // Update new power-up timers if (laserBeamTimer > 0) { laserBeamTimer--; if (laserBeamTimer === 0) { laserBeamActive = false; if (laserBeamGraphics) { laserBeamGraphics.destroy(); laserBeamGraphics = null; } if (activePowerUpType === 'laserBeam') { hidePowerUpDisplay(); } } } if (noteDestroyerTimer > 0) { noteDestroyerTimer--; if (noteDestroyerTimer === 0) { noteDestroyerActive = false; if (activePowerUpType === 'noteDestroyer') { hidePowerUpDisplay(); } } } // Update bad power-up timers if (reverseControlsTimer > 0) { reverseControlsTimer--; if (reverseControlsTimer === 0) { reverseControlsActive = false; if (activePowerUpType === 'reverseControls') { hidePowerUpDisplay(); } } } if (shrinkingTargetsTimer > 0) { shrinkingTargetsTimer--; if (shrinkingTargetsTimer === 0) { shrinkingTargetsActive = false; // Restore original target box sizes for (var t = 0; t < targetBoxes.length; t++) { if (originalTargetSizes[t]) { tween(targetBoxes[t], { scaleX: originalTargetSizes[t].scaleX, scaleY: originalTargetSizes[t].scaleY }, { duration: 500, easing: tween.easeOut }); } } if (activePowerUpType === 'shrinkingTargets') { hidePowerUpDisplay(); } } } // Handle laser beam collision with notes if (laserBeamActive && laserBeamGraphics) { // Update laser position to follow player horizontally but stay at top laserBeamGraphics.x = player.x; laserBeamGraphics.y = 0; // Check laser collision with all notes for (var l = notes.length - 1; l >= 0; l--) { var note = notes[l]; if (Math.abs(note.x - laserBeamGraphics.x) < 50 && note.y < player.y) { // Laser hit this note var laserPoints = note.pointValue; if (scoreMultiplierActive) { laserPoints *= 2; } score += laserPoints; createFeedbackText('+' + laserPoints, note.x, note.y - 50, 0x00FFFF); createParticleBurst(note.x, note.y, 0x00FFFF, 6); note.destroy(); notes.splice(l, 1); } } } // Update power-up display timer and progress bar if (activePowerUpType && activePowerUpMaxDuration > 0) { var currentTimer = 0; if (activePowerUpType === 'multiShot') currentTimer = multiShotTimer;else if (activePowerUpType === 'slowMotion') currentTimer = slowMotionTimer;else if (activePowerUpType === 'scoreMultiplier') currentTimer = scoreMultiplierTimer;else if (activePowerUpType === 'movingTarget') currentTimer = movingTargetTimer;else if (activePowerUpType === 'laserBeam') currentTimer = laserBeamTimer;else if (activePowerUpType === 'reverseControls') currentTimer = reverseControlsTimer;else if (activePowerUpType === 'shrinkingTargets') currentTimer = shrinkingTargetsTimer;else if (activePowerUpType === 'shield') { // Shield shows number of hits remaining instead of timer powerUpTimerDisplay.setText('Hits: ' + (maxShieldHits - shieldHits)); powerUpProgressBar.width = 200 * ((maxShieldHits - shieldHits) / maxShieldHits); } else if (activePowerUpType === 'noteDestroyer') currentTimer = noteDestroyerTimer; // Update timer text (convert frames to seconds) for timed power-ups if (activePowerUpType !== 'shield') { var secondsLeft = Math.ceil(currentTimer / 60); powerUpTimerDisplay.setText(secondsLeft + 's'); // Update progress bar var progress = currentTimer / activePowerUpMaxDuration; powerUpProgressBar.width = 200 * progress; } } // Move target boxes if moving target power-up is active if (movingTargetActive) { for (var j = 0; j < targetBoxes.length; j++) { var targetBox = targetBoxes[j]; var originalX = targetBoxPositions[j]; var offset = Math.sin(LK.ticks * 0.05 + j * 0.5) * 100; targetBox.x = originalX + offset; } } // Update and check notes for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; // Apply slow motion effect if (slowMotionActive) { note.speed *= 0.5; } // Check if note has passed through deletion area if (note.y > deletionAreaY && !note.hasTriggered) { // Only trigger game over for regular music notes, not power-ups if (note.noteType !== 'multiShot' && note.noteType !== 'slowMotion' && note.noteType !== 'scoreMultiplier' && note.noteType !== 'movingTarget' && note.noteType !== 'laserBeam' && note.noteType !== 'shield' && note.noteType !== 'noteDestroyer' && note.noteType !== 'reverseControls' && note.noteType !== 'shrinkingTargets') { // Note crashed into secret wall - game over LK.showGameOver(); return; // Exit update loop since game is over } else { // Power-up notes just get removed without penalty note.destroy(); notes.splice(i, 1); continue; } } // Remove notes that go off screen if (note.y > 2800) { note.destroy(); notes.splice(i, 1); } // Restore note speed after slow motion effect if (slowMotionActive) { note.speed *= 2; // Restore original speed for next frame } } // Update and check spikes for (var i = spikes.length - 1; i >= 0; i--) { var spike = spikes[i]; // Apply slow motion effect if (slowMotionActive) { spike.speed *= 0.5; } // Check collision with player - game over if spike hits player (unless shield is active) if (spike.intersects(player)) { if (shieldActive && shieldHits < maxShieldHits) { // Shield absorbs the hit shieldHits++; createFeedbackText('Shield Hit!', player.x, player.y - 100, 0x00FFFF); createParticleBurst(spike.x, spike.y, 0x00FFFF, 10); LK.effects.flashObject(player, 0x00FFFF, 500); // Remove the spike spike.destroy(); spikes.splice(i, 1); // Deactivate shield if max hits reached if (shieldHits >= maxShieldHits) { shieldActive = false; if (shieldGraphics) { tween(shieldGraphics, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { shieldGraphics.destroy(); shieldGraphics = null; } }); } if (activePowerUpType === 'shield') { hidePowerUpDisplay(); } createFeedbackText('Shield Depleted!', player.x, player.y - 120, 0xFF0000); } continue; } else { // No shield or shield depleted - game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; // Exit update loop since game is over } } // Spikes can pass through the secret line - no collision check needed // Remove spikes that go off screen if (spike.y > 2800) { spike.destroy(); spikes.splice(i, 1); } // Restore spike speed after slow motion effect if (slowMotionActive) { spike.speed *= 2; // Restore original speed for next frame } } // Handle music glitch timer if (musicGlitchTimer > 0) { musicGlitchTimer--; if (musicGlitchTimer === 0 && musicPlaying) { // Resume music after glitch period try { LK.playMusic(currentMusicTrack); } catch (e) { console.log('Error resuming music after glitch:', currentMusicTrack); } } } // Increase difficulty over time if (LK.ticks % 1800 === 0) { // Every 30 seconds noteSpawnInterval = Math.max(30, noteSpawnInterval - 5); // Increase note speed slightly for (var i = 0; i < notes.length; i++) { notes[i].speed = Math.min(6, notes[i].speed + 0.1); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 33;
self.targetX = 0;
self.targetY = 0;
self.directionX = 0;
self.directionY = 0;
self.update = function () {
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
};
return self;
});
var Note = Container.expand(function () {
var self = Container.call(this);
// Default note type
self.noteType = 'normal';
self.pointValue = 100;
self.speed = 3;
// Initialize with normal note graphics
var noteGraphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics = noteGraphics;
self.lane = 0;
self.hasTriggered = false;
self.pulseTimer = 0;
// Method to set note type with different properties
self.setNoteType = function (type) {
self.noteType = type;
// Remove current graphics
self.removeChild(self.graphics);
switch (type) {
case 'fast':
// Mix different note assets for variety
var fastAssets = ['noteRed', 'notePurple'];
var fastAsset = fastAssets[Math.floor(Math.random() * fastAssets.length)];
self.graphics = self.attachAsset(fastAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.pointValue = 150;
break;
case 'slow':
// Mix different note assets for variety
var slowAssets = ['noteBlue', 'note'];
var slowAsset = slowAssets[Math.floor(Math.random() * slowAssets.length)];
self.graphics = self.attachAsset(slowAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5;
self.pointValue = 200;
break;
case 'bonus':
self.graphics = self.attachAsset('noteGold', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2.5;
self.pointValue = 300;
break;
case 'challenge':
self.graphics = self.attachAsset('notePurple', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.pointValue = 250;
break;
case 'multiShot':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'slowMotion':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'scoreMultiplier':
self.graphics = self.attachAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'movingTarget':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
case 'laserBeam':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 200;
break;
case 'shield':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 150;
break;
case 'noteDestroyer':
self.graphics = self.attachAsset('powerUpBlue', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 300;
break;
case 'reverseControls':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
case 'shrinkingTargets':
self.graphics = self.attachAsset('powerUpRed', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.pointValue = 100;
break;
default:
self.graphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
self.pointValue = 100;
}
};
// Method to play sound based on note type
self.playNoteSound = function () {
switch (self.noteType) {
case 'fast':
// Mix different sounds for fast notes
var fastSounds = ['shake', 'guitar'];
var randomFastSound = fastSounds[Math.floor(Math.random() * fastSounds.length)];
LK.getSound(randomFastSound).play();
break;
case 'slow':
// Mix different sounds for slow notes
var slowSounds = ['kick', 'piano', 'violin'];
var randomSlowSound = slowSounds[Math.floor(Math.random() * slowSounds.length)];
LK.getSound(randomSlowSound).play();
break;
case 'bonus':
LK.getSound('hihat').play();
break;
case 'challenge':
LK.getSound('cymbal').play();
break;
case 'multiShot':
case 'slowMotion':
case 'scoreMultiplier':
case 'movingTarget':
case 'laserBeam':
case 'shield':
case 'noteDestroyer':
case 'reverseControls':
case 'shrinkingTargets':
LK.getSound('bass').play();
break;
default:
LK.getSound('violin').play();
}
};
self.update = function () {
self.y += self.speed;
// Add visual effects for special notes
if (self.noteType === 'bonus') {
// Golden notes pulse
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.2) * 0.1;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'challenge') {
// Purple notes rotate
self.graphics.rotation += 0.05;
} else if (self.noteType === 'multiShot' || self.noteType === 'slowMotion' || self.noteType === 'scoreMultiplier') {
// Good power-ups glow green
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.3) * 0.15;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'laserBeam' || self.noteType === 'shield' || self.noteType === 'noteDestroyer') {
// New power-ups glow blue
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.35) * 0.18;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
} else if (self.noteType === 'movingTarget' || self.noteType === 'reverseControls' || self.noteType === 'shrinkingTargets') {
// Bad power-ups pulse red
self.pulseTimer++;
var scale = 1 + Math.sin(self.pulseTimer * 0.4) * 0.2;
self.graphics.scaleX = scale;
self.graphics.scaleY = scale;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add floating gun
var gun = self.attachAsset('gun', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.gun = gun;
// Method to rotate gun towards target with smooth animation
self.aimGunAt = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - (self.y - 50); // Account for gun offset
var targetRotation = Math.atan2(dy, dx);
// Smooth rotation animation
tween(self.gun, {
rotation: targetRotation
}, {
duration: 150,
easing: tween.easeOut
});
};
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
var spikeGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
self.speed = 8.0;
self.lane = 0;
self.pulseTimer = 0;
self.update = function () {
self.y += self.speed;
// Add pulsing danger effect
self.pulseTimer++;
var scale = 0.7 + Math.sin(self.pulseTimer * 0.4) * 0.14;
spikeGraphics.scaleX = scale;
spikeGraphics.scaleY = scale;
// Add rotation for more menacing look
spikeGraphics.rotation += 0.08;
};
return self;
});
var TargetBox = Container.expand(function () {
var self = Container.call(this);
var targetGraphics = self.attachAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 0;
self.isActive = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xF0F8FF,
name: 'Cats Are Rhythmic'
});
/****
* Game Code
****/
// Game state variables
var gameState = 'menu'; // 'menu' or 'playing'
var startMenuContainer;
var musicButtons = [];
// Game variables
var player;
var bullets = [];
var notes = [];
var spikes = [];
var targetBoxes = [];
var staffLines = [];
var aimLine;
var deletionAreaY;
var targetBoxPositions = [512, 853, 1194, 1536]; // Define globally for access throughout the game
var score = 0;
var staffY = 400;
var staffSpacing = 80;
// Level system variables
var currentLevel = storage.currentLevel || 1;
var levelScore = storage.levelScore || 0; // Score accumulated in current level
var totalScore = storage.totalScore || 0; // Total score across all levels
var levelThresholds = [0, 1000, 2500, 5000, 8000, 12000, 17000, 23000, 30000, 38000, 47000, 57000, 68000, 80000, 93000, 107000, 122000, 138000, 155000, 173000]; // Score needed for each level
var maxLevel = levelThresholds.length;
var mouseX = 1024;
var noteSpawnTimer = 0;
var noteSpawnInterval = 90; // frames between note spawns
var spikeSpawnTimer = 0;
var spikeSpawnInterval = 300; // frames between spike spawns (much less frequent than notes)
var musicPlaying = false;
var musicGlitchTimer = 0;
var musicGlitchDuration = 18; // 0.3 seconds at 60fps
// Music selection variables
var currentMusicTrack = 'bgmusic';
var musicTracks = ['bgmusic', 'rock_track', 'electronic_track'];
var musicTrackNames = ['Electronic Track', 'Rock Track', 'Soul Track'];
// Note preview system
var previewNotes = [];
var nextNoteTypes = [];
// Combo system variables
var combo = 0;
var maxCombo = 0;
// Feedback text variables
var feedbackTexts = [];
// Power-up system variables
var multiShotActive = false;
var multiShotTimer = 0;
var multiShotDuration = 300; // 5 seconds at 60fps
var slowMotionActive = false;
var slowMotionTimer = 0;
var slowMotionDuration = 300; // 5 seconds at 60fps
var scoreMultiplierActive = false;
var scoreMultiplierTimer = 0;
var scoreMultiplierDuration = 300; // 5 seconds at 60fps
var movingTargetActive = false;
var movingTargetTimer = 0;
var movingTargetDuration = 240; // 4 seconds at 60fps
// Power-up display variables
var powerUpDisplayContainer;
var powerUpIconDisplay;
var powerUpNameDisplay;
var powerUpTimerDisplay;
var powerUpProgressBar;
var activePowerUpType = null;
var activePowerUpMaxDuration = 0;
// New power-up system variables
var laserBeamActive = false;
var laserBeamTimer = 0;
var laserBeamDuration = 180; // 3 seconds at 60fps
var laserBeamGraphics = null;
var shieldActive = false;
var shieldHits = 0;
var maxShieldHits = 1;
var shieldGraphics = null;
var noteDestroyerActive = false;
var noteDestroyerTimer = 0;
var noteDestroyerDuration = 1; // Instant effect, 1 frame duration
// Bad power-up system variables
var reverseControlsActive = false;
var reverseControlsTimer = 0;
var reverseControlsDuration = 300; // 5 seconds at 60fps
var shrinkingTargetsActive = false;
var shrinkingTargetsTimer = 0;
var shrinkingTargetsDuration = 240; // 4 seconds at 60fps
var originalTargetSizes = [];
// Create start menu
function createStartMenu() {
startMenuContainer = new Container();
startMenuContainer.x = 1024; // Center of screen
startMenuContainer.y = 1366; // Center of screen
game.addChild(startMenuContainer);
// Game title
var titleText = new Text2('Cats Are Rhythmic', {
size: 120,
fill: 0x87CEEB
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -400;
startMenuContainer.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Choose Your Music', {
size: 80,
fill: 0x87CEEB
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.y = -300;
startMenuContainer.addChild(subtitleText);
// Create music selection buttons
var buttonSpacing = 200;
var startY = -100;
for (var i = 0; i < musicTracks.length; i++) {
var buttonContainer = new Container();
buttonContainer.x = 0;
buttonContainer.y = startY + i * buttonSpacing;
startMenuContainer.addChild(buttonContainer);
// Button background
var buttonBg = LK.getAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5,
tint: 0x4444FF
});
buttonContainer.addChild(buttonBg);
// Button text
var buttonText = new Text2(musicTrackNames[i], {
size: 60,
fill: 0x87CEEB
});
buttonText.anchor.set(0.5, 0.5);
buttonContainer.addChild(buttonText);
// Store button data
buttonContainer.musicIndex = i;
buttonContainer.background = buttonBg;
buttonContainer.text = buttonText;
// Add click handler
buttonContainer.down = function (x, y, obj) {
selectMusic(this.musicIndex);
};
musicButtons.push(buttonContainer);
}
// Music warning instruction
var musicWarningText = new Text2('MUSIC WONT START TILL YOU HIT A MUSIC NOTE!', {
size: 45,
fill: 0xFF6666
});
musicWarningText.anchor.set(0.5, 0.5);
musicWarningText.y = 450;
startMenuContainer.addChild(musicWarningText);
// Instructions
var instructionText = new Text2('Tap a music option to start the game', {
size: 50,
fill: 0x87CEEB
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 500;
startMenuContainer.addChild(instructionText);
}
function selectMusic(musicIndex) {
currentMusicTrack = musicTracks[musicIndex];
// Update active music display
activeMusicDisplay.setText('Music: ' + musicTrackNames[musicIndex]);
// Animate menu exit
tween(startMenuContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
startMenuContainer.destroy();
startMenuContainer = null;
musicButtons = [];
startGame();
}
});
}
function startGame() {
gameState = 'playing';
setupGameElements();
}
function setupGameElements() {
// Setup staff lines
var staffY = 400;
var staffSpacing = 80;
for (var i = 0; i < 5; i++) {
var staffLine = game.addChild(LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: staffY + i * staffSpacing
}));
staffLines.push(staffLine);
}
// Setup note preview indicators
var targetBoxPositions = [512, 853, 1194, 1536];
for (var i = 0; i < 4; i++) {
var previewNote = LK.getAsset('note', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8,
x: targetBoxPositions[i],
y: staffY - 100
});
previewNote.visible = false;
game.addChild(previewNote);
previewNotes.push(previewNote);
nextNoteTypes.push(null);
}
// Setup target boxes
var targetY = staffY + 4 * staffSpacing + 200;
var targetBoxPositions = [512, 853, 1194, 1536]; // 4 evenly spaced positions
for (var i = 0; i < 4; i++) {
var targetBox = game.addChild(new TargetBox());
targetBox.x = targetBoxPositions[i];
targetBox.y = targetY;
targetBox.lane = i;
targetBoxes.push(targetBox);
}
// Setup deletion area below target boxes
deletionAreaY = targetY + 250;
// Initialize level system
updateLevelDisplay();
increaseDifficultyForLevel();
// Create visible secret wall
var secretWall = game.addChild(LK.getAsset('secretWall', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: deletionAreaY
}));
// Setup player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2500;
// Create aim assistance line
aimLine = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 2,
height: 2,
alpha: 0.3,
tint: 0xFF0000
});
game.addChild(aimLine);
}
// Setup score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Setup level display
var levelTxt = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0x0066CC
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 20;
levelTxt.y = 20;
LK.gui.topLeft.addChild(levelTxt);
// Setup level progress display
var levelProgressTxt = new Text2('', {
size: 45,
fill: 0x666666
});
levelProgressTxt.anchor.set(0, 0);
levelProgressTxt.x = 20;
levelProgressTxt.y = 90;
LK.gui.topLeft.addChild(levelProgressTxt);
// Setup combo display
var comboTxt = new Text2('', {
size: 60,
fill: 0xFF6600
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 90; // Position below score
LK.gui.top.addChild(comboTxt);
// Display active music (non-interactive)
var activeMusicDisplay = new Text2('Music: ' + musicTrackNames[0], {
size: 50,
fill: 0x000000
});
activeMusicDisplay.anchor.set(1, 0);
activeMusicDisplay.x = -20; // Position from right edge
activeMusicDisplay.y = 20;
LK.gui.topRight.addChild(activeMusicDisplay);
// Create power-up display container on left side of screen
powerUpDisplayContainer = new Container();
powerUpDisplayContainer.x = 50;
powerUpDisplayContainer.y = 200;
powerUpDisplayContainer.alpha = 0;
LK.gui.left.addChild(powerUpDisplayContainer);
// Power-up icon display
powerUpIconDisplay = LK.getAsset('powerUpGreen', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
powerUpIconDisplay.x = 40;
powerUpIconDisplay.y = 40;
powerUpDisplayContainer.addChild(powerUpIconDisplay);
// Power-up name text
powerUpNameDisplay = new Text2('', {
size: 40,
fill: 0x00FF00
});
powerUpNameDisplay.anchor.set(0, 0.5);
powerUpNameDisplay.x = 90;
powerUpNameDisplay.y = 25;
powerUpDisplayContainer.addChild(powerUpNameDisplay);
// Power-up timer text
powerUpTimerDisplay = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
powerUpTimerDisplay.anchor.set(0, 0.5);
powerUpTimerDisplay.x = 90;
powerUpTimerDisplay.y = 55;
powerUpDisplayContainer.addChild(powerUpTimerDisplay);
// Power-up progress bar background
var progressBarBg = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 8,
tint: 0x333333
});
progressBarBg.x = 90;
progressBarBg.y = 75;
powerUpDisplayContainer.addChild(progressBarBg);
// Power-up progress bar fill
powerUpProgressBar = LK.getAsset('staffLine', {
anchorX: 0,
anchorY: 0.5,
width: 200,
height: 8,
tint: 0x00FF00
});
powerUpProgressBar.x = 90;
powerUpProgressBar.y = 75;
powerUpDisplayContainer.addChild(powerUpProgressBar);
// Initialize start menu
createStartMenu();
// Input handling
game.move = function (x, y, obj) {
if (gameState !== 'playing') return;
mouseX = x;
// Apply reverse controls effect
if (reverseControlsActive) {
var centerX = 1024;
var distanceFromCenter = x - centerX;
player.x = centerX - distanceFromCenter;
} else {
player.x = x;
}
// Continuously aim gun at mouse position
player.aimGunAt(x, y);
// Update aim line
var gunX = player.x;
var gunY = player.y - 50;
var dx = x - gunX;
var dy = y - gunY;
var distance = Math.sqrt(dx * dx + dy * dy);
aimLine.x = gunX;
aimLine.y = gunY;
aimLine.width = Math.min(distance, 300); // Limit line length
aimLine.rotation = Math.atan2(dy, dx);
};
game.down = function (x, y, obj) {
if (gameState !== 'playing') return;
// Aim gun at target position
player.aimGunAt(x, y);
if (multiShotActive) {
// Create 3 bullets in spread pattern
for (var i = 0; i < 3; i++) {
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y - 50; // Spawn from gun position
// Calculate direction to mouse position with spread
var dx = x - player.x;
var dy = y - (player.y - 50); // Account for gun offset
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var angle = Math.atan2(dy, dx);
var spreadAngle = (i - 1) * 0.3; // -0.3, 0, 0.3 radians spread
bullet.directionX = Math.cos(angle + spreadAngle);
bullet.directionY = Math.sin(angle + spreadAngle);
}
bullets.push(bullet);
}
} else {
// Create single bullet from gun position
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y - 50; // Spawn from gun position
// Calculate direction to mouse position
var dx = x - player.x;
var dy = y - (player.y - 50); // Account for gun offset
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.directionX = dx / distance;
bullet.directionY = dy / distance;
}
bullets.push(bullet);
}
};
// Generate note type based on probabilities
function generateNoteType() {
var random = Math.random();
if (random < 0.05) {
return 'bonus'; // 5% chance for bonus notes
} else if (random < 0.1) {
return 'challenge'; // 5% chance for challenge notes
} else if (random < 0.2) {
return 'fast'; // 10% chance for fast notes
} else if (random < 0.3) {
return 'slow'; // 10% chance for slow notes
} else if (random < 0.33) {
return 'multiShot'; // 3% chance for multi-shot power-up
} else if (random < 0.36) {
return 'slowMotion'; // 3% chance for slow motion power-up
} else if (random < 0.39) {
return 'scoreMultiplier'; // 3% chance for score multiplier power-up
} else if (random < 0.42) {
return 'movingTarget'; // 3% chance for moving target power-up
} else if (random < 0.445) {
return 'laserBeam'; // 2.5% chance for laser beam power-up
} else if (random < 0.47) {
return 'shield'; // 2.5% chance for shield power-up
} else if (random < 0.49) {
return 'noteDestroyer'; // 2% chance for note destroyer power-up
} else if (random < 0.515) {
return 'reverseControls'; // 2.5% chance for reverse controls power-up
} else if (random < 0.54) {
return 'shrinkingTargets'; // 2.5% chance for shrinking targets power-up
} else {
return 'normal'; // 46% chance for normal notes
}
}
// Update note preview display
function updateNotePreview(lane, noteType) {
if (lane >= 0 && lane < previewNotes.length) {
var preview = previewNotes[lane];
nextNoteTypes[lane] = noteType;
// Set preview note appearance based on type
preview.removeChildren();
var assetId = 'note';
var tintColor = 0xFFFFFF;
switch (noteType) {
case 'fast':
assetId = 'noteRed';
break;
case 'slow':
assetId = 'noteBlue';
break;
case 'bonus':
assetId = 'noteGold';
break;
case 'challenge':
assetId = 'notePurple';
break;
case 'multiShot':
case 'slowMotion':
case 'scoreMultiplier':
assetId = 'powerUpGreen';
break;
case 'movingTarget':
case 'reverseControls':
case 'shrinkingTargets':
assetId = 'powerUpRed';
break;
case 'laserBeam':
case 'shield':
case 'noteDestroyer':
assetId = 'powerUpBlue';
break;
}
var previewGraphics = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
});
preview.addChild(previewGraphics);
preview.visible = true;
// Animate preview appearance
tween(preview, {
alpha: 1.0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
// Hide preview after delay
tween(preview, {
alpha: 0.6,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
}
// Spawn notes
function spawnNote() {
var lane = Math.floor(Math.random() * 4);
var note = game.addChild(new Note());
var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions
note.x = targetBoxPositions[lane];
note.y = staffY + lane * staffSpacing - 200;
note.lane = lane;
// Use generated note type
var noteType = generateNoteType();
note.setNoteType(noteType);
// Show preview for next note
var nextLane = Math.floor(Math.random() * 4);
var nextNoteType = generateNoteType();
updateNotePreview(nextLane, nextNoteType);
notes.push(note);
}
// Spawn spikes
function spawnSpike() {
var lane = Math.floor(Math.random() * 4);
var spike = game.addChild(new Spike());
var targetBoxPositions = [512, 853, 1194, 1536]; // Match target box positions
spike.x = targetBoxPositions[lane];
spike.y = staffY + lane * staffSpacing - 200;
spike.lane = lane;
spikes.push(spike);
}
// Check if note is in timing window
function isNoteInTimingWindow(note, targetBox) {
var noteTop = note.y - 35; // Top of note (note height is 70, so 35 from center)
var noteBottom = note.y + 35; // Bottom of note
var targetTop = targetBox.y - 65; // Expanded target area (target height is 130, so 65 from center)
var targetBottom = targetBox.y + 65; // Expanded target area
// Check if any part of the note overlaps with the expanded target area
return noteBottom >= targetTop && noteTop <= targetBottom;
}
// Get timing quality based on note position relative to target center
function getTimingQuality(note, targetBox) {
var distance = Math.abs(note.y - targetBox.y);
if (distance <= 20) {
return 'Perfect!';
} else if (distance <= 40) {
return 'Good';
} else {
return 'Hit';
}
}
// Create particle burst effect
function createParticleBurst(x, y, color, count) {
for (var i = 0; i < count; i++) {
var particle = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: color
});
particle.x = x;
particle.y = y;
game.addChild(particle);
// Random direction and speed
var angle = Math.PI * 2 * i / count + (Math.random() - 0.5) * 0.5;
var speed = 100 + Math.random() * 100;
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Animate particle outward
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
// Check and handle level progression
function checkLevelUp() {
if (currentLevel < maxLevel) {
var nextLevelThreshold = levelThresholds[currentLevel];
if (totalScore >= nextLevelThreshold) {
levelUp();
}
}
}
// Handle level up
function levelUp() {
var previousLevel = currentLevel;
currentLevel++;
// Save level progress
storage.currentLevel = currentLevel;
storage.totalScore = totalScore;
storage.levelScore = 0; // Reset level score for new level
levelScore = 0;
// Create animated level pop-up display
var levelPopUpContainer = new Container();
levelPopUpContainer.x = 1024; // Center of screen
levelPopUpContainer.y = 1366; // Center of screen
levelPopUpContainer.alpha = 0;
levelPopUpContainer.scaleX = 0.1;
levelPopUpContainer.scaleY = 0.1;
game.addChild(levelPopUpContainer);
// Background for pop-up
var popUpBg = LK.getAsset('targetBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 3,
tint: 0x000080,
alpha: 0.9
});
levelPopUpContainer.addChild(popUpBg);
// Level text
var levelPopUpText = new Text2('LEVEL ' + currentLevel, {
size: 120,
fill: 0xFFD700
});
levelPopUpText.anchor.set(0.5, 0.5);
levelPopUpText.y = -30;
levelPopUpContainer.addChild(levelPopUpText);
// Level up text
var levelUpText = new Text2('LEVEL UP!', {
size: 80,
fill: 0x00FF00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.y = 60;
levelPopUpContainer.addChild(levelUpText);
// Animate pop-up appearance
tween(levelPopUpContainer, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Add pulsing effect to level text
tween(levelPopUpText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut
});
tween(levelPopUpText, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeInOut
});
// Remove pop-up after delay
tween(levelPopUpContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
y: 1200
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
levelPopUpContainer.destroy();
}
});
// Create level up feedback
createFeedbackText('LEVEL UP!', 1024, 800, 0xFFD700);
createFeedbackText('Level ' + currentLevel, 1024, 860, 0x0066CC);
// Flash screen gold for level up
LK.effects.flashScreen(0xFFD700, 800);
// Update level display
updateLevelDisplay();
// Increase difficulty with level
increaseDifficultyForLevel();
}
// Update level display
function updateLevelDisplay() {
if (levelTxt) {
levelTxt.setText('Level: ' + currentLevel);
}
if (currentLevel < maxLevel) {
var nextThreshold = levelThresholds[currentLevel];
var progress = totalScore - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0);
var needed = nextThreshold - (currentLevel > 1 ? levelThresholds[currentLevel - 1] : 0);
if (levelProgressTxt) {
levelProgressTxt.setText(progress + ' / ' + needed);
}
} else {
if (levelProgressTxt) {
levelProgressTxt.setText('MAX LEVEL');
}
}
}
// Increase difficulty based on current level
function increaseDifficultyForLevel() {
// Reduce spawn intervals based on level
noteSpawnInterval = Math.max(30, 90 - (currentLevel - 1) * 3);
spikeSpawnInterval = Math.max(120, 300 - (currentLevel - 1) * 8);
}
// Create feedback text with animation
function createFeedbackText(text, x, y, color) {
var feedbackText = new Text2(text, {
size: 50,
fill: color
});
feedbackText.anchor.set(0.5, 0.5);
feedbackText.x = x;
feedbackText.y = y;
feedbackText.alpha = 1;
feedbackText.scaleX = 0.5;
feedbackText.scaleY = 0.5;
game.addChild(feedbackText);
feedbackTexts.push(feedbackText);
// Animate text appearance
tween(feedbackText, {
scaleX: 1.2,
scaleY: 1.2,
y: y - 50
}, {
duration: 300,
easing: tween.easeOut
});
// Fade out and remove
tween(feedbackText, {
alpha: 0,
y: y - 100
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
feedbackText.destroy();
var index = feedbackTexts.indexOf(feedbackText);
if (index > -1) {
feedbackTexts.splice(index, 1);
}
}
});
}
// Show power-up effect display
function showPowerUpDisplay(powerUpType, duration) {
activePowerUpType = powerUpType;
activePowerUpMaxDuration = duration;
// Set power-up name and icon based on type
var powerUpName = '';
var iconAsset = 'powerUpGreen';
var textColor = 0x00FF00;
switch (powerUpType) {
case 'multiShot':
powerUpName = 'Multi-Shot';
iconAsset = 'powerUpGreen';
textColor = 0x00FF00;
break;
case 'slowMotion':
powerUpName = 'Slow Motion';
iconAsset = 'powerUpGreen';
textColor = 0x00FFFF;
break;
case 'scoreMultiplier':
powerUpName = 'Score x2';
iconAsset = 'powerUpGreen';
textColor = 0xFFD700;
break;
case 'movingTarget':
powerUpName = 'Moving Targets';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
case 'laserBeam':
powerUpName = 'Laser Beam';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'shield':
powerUpName = 'Shield';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'noteDestroyer':
powerUpName = 'Spike Destroyer';
iconAsset = 'powerUpBlue';
textColor = 0x00FFFF;
break;
case 'reverseControls':
powerUpName = 'Reverse Controls';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
case 'shrinkingTargets':
powerUpName = 'Shrinking Targets';
iconAsset = 'powerUpRed';
textColor = 0xFF0000;
break;
}
// Update display elements
powerUpDisplayContainer.removeChild(powerUpIconDisplay);
powerUpIconDisplay = LK.getAsset(iconAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
powerUpIconDisplay.x = 40;
powerUpIconDisplay.y = 40;
powerUpDisplayContainer.addChild(powerUpIconDisplay);
powerUpNameDisplay.setText(powerUpName);
powerUpNameDisplay.fill = textColor;
powerUpProgressBar.tint = textColor;
// Animate display appearance
tween(powerUpDisplayContainer, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut
});
tween(powerUpDisplayContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
// Hide power-up effect display
function hidePowerUpDisplay() {
activePowerUpType = null;
activePowerUpMaxDuration = 0;
tween(powerUpDisplayContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeIn
});
}
// Game update loop
game.update = function () {
if (gameState !== 'playing') return;
// Spawn notes
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
}
// Spawn spikes
spikeSpawnTimer++;
if (spikeSpawnTimer >= spikeSpawnInterval) {
spawnSpike();
spikeSpawnTimer = 0;
}
// Update and check bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Remove bullets that go off screen
if (bullet.x < -50 || bullet.x > 2098 || bullet.y < -50 || bullet.y > 2782) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Spikes are no longer affected by bullets - they can pass through everything
// Check collision with target boxes
for (var j = 0; j < targetBoxes.length; j++) {
var targetBox = targetBoxes[j];
if (bullet.intersects(targetBox)) {
// Check if there's a note in timing window for this lane
var hitNote = null;
for (var k = 0; k < notes.length; k++) {
var note = notes[k];
if (note.lane === targetBox.lane && !note.hasTriggered && isNoteInTimingWindow(note, targetBox)) {
hitNote = note;
break;
}
}
if (hitNote) {
// Get timing quality for feedback
var timingQuality = getTimingQuality(hitNote, targetBox);
var basePoints = hitNote.pointValue;
var bonusPoints = 0;
var feedbackColor = 0x00FF00;
// Handle power-up activation
if (hitNote.noteType === 'multiShot') {
multiShotActive = true;
multiShotTimer = multiShotDuration;
showPowerUpDisplay('multiShot', multiShotDuration);
createFeedbackText('Multi-Shot!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'slowMotion') {
slowMotionActive = true;
slowMotionTimer = slowMotionDuration;
showPowerUpDisplay('slowMotion', slowMotionDuration);
createFeedbackText('Slow Motion!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'scoreMultiplier') {
scoreMultiplierActive = true;
scoreMultiplierTimer = scoreMultiplierDuration;
showPowerUpDisplay('scoreMultiplier', scoreMultiplierDuration);
createFeedbackText('Score x2!', targetBox.x, targetBox.y - 120, 0x00FF00);
} else if (hitNote.noteType === 'movingTarget') {
movingTargetActive = true;
movingTargetTimer = movingTargetDuration;
showPowerUpDisplay('movingTarget', movingTargetDuration);
createFeedbackText('Moving Targets!', targetBox.x, targetBox.y - 120, 0xFF0000);
} else if (hitNote.noteType === 'laserBeam') {
laserBeamActive = true;
laserBeamTimer = laserBeamDuration;
showPowerUpDisplay('laserBeam', laserBeamDuration);
createFeedbackText('Laser Beam!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Create laser beam visual effect
if (laserBeamGraphics) {
laserBeamGraphics.destroy();
}
laserBeamGraphics = LK.getAsset('laserBeam', {
anchorX: 0.5,
anchorY: 0,
x: player.x,
y: 0,
height: 2732,
alpha: 0.8
});
game.addChild(laserBeamGraphics);
} else if (hitNote.noteType === 'shield') {
if (!shieldActive) {
shieldActive = true;
shieldHits = 0;
showPowerUpDisplay('shield', 999); // Show indefinitely until used
createFeedbackText('Shield Active!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Create shield visual effect
if (shieldGraphics) {
shieldGraphics.destroy();
}
shieldGraphics = LK.getAsset('shieldEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
shieldGraphics.x = 0;
shieldGraphics.y = 0;
player.addChild(shieldGraphics);
// Animate shield appearance
tween(shieldGraphics, {
alpha: 0.6,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut
});
}
} else if (hitNote.noteType === 'noteDestroyer') {
noteDestroyerActive = true;
noteDestroyerTimer = noteDestroyerDuration;
showPowerUpDisplay('noteDestroyer', noteDestroyerDuration);
createFeedbackText('Spike Destroyer!', targetBox.x, targetBox.y - 120, 0x00FFFF);
// Immediately destroy all spikes on screen
for (var s = spikes.length - 1; s >= 0; s--) {
var spike = spikes[s];
createParticleBurst(spike.x, spike.y, 0xFF4444, 8);
spike.destroy();
spikes.splice(s, 1);
}
// Add bonus points for destroyed spikes
var destroyedCount = spikes.length;
if (destroyedCount > 0) {
score += destroyedCount * 50;
createFeedbackText('+' + destroyedCount * 50 + ' Spike Bonus!', player.x, player.y - 150, 0x00FFFF);
}
} else if (hitNote.noteType === 'reverseControls') {
reverseControlsActive = true;
reverseControlsTimer = reverseControlsDuration;
showPowerUpDisplay('reverseControls', reverseControlsDuration);
createFeedbackText('Reverse Controls!', targetBox.x, targetBox.y - 120, 0xFF0000);
} else if (hitNote.noteType === 'shrinkingTargets') {
shrinkingTargetsActive = true;
shrinkingTargetsTimer = shrinkingTargetsDuration;
showPowerUpDisplay('shrinkingTargets', shrinkingTargetsDuration);
createFeedbackText('Shrinking Targets!', targetBox.x, targetBox.y - 120, 0xFF0000);
// Store original target box sizes
originalTargetSizes = [];
for (var t = 0; t < targetBoxes.length; t++) {
originalTargetSizes.push({
scaleX: targetBoxes[t].scaleX,
scaleY: targetBoxes[t].scaleY
});
// Animate targets shrinking
tween(targetBoxes[t], {
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Apply combo multiplier and timing bonus
combo++;
if (combo > maxCombo) {
maxCombo = combo;
}
// Timing bonuses
if (timingQuality === 'Perfect!') {
bonusPoints = Math.floor(basePoints * 0.5);
feedbackColor = 0xFFD700; // Gold
} else if (timingQuality === 'Good') {
bonusPoints = Math.floor(basePoints * 0.2);
feedbackColor = 0x00FF00; // Green
} else {
feedbackColor = 0x88FF88; // Light green
}
// Combo multiplier (every 5 combo adds 10% bonus)
var comboMultiplier = 1 + Math.floor(combo / 5) * 0.1;
// Score multiplier power-up
if (scoreMultiplierActive) {
comboMultiplier *= 2;
}
var totalPoints = Math.floor((basePoints + bonusPoints) * comboMultiplier);
score += totalPoints;
levelScore += totalPoints;
totalScore += totalPoints;
// Check for level up
checkLevelUp();
// Save progress
storage.levelScore = levelScore;
storage.totalScore = totalScore;
hitNote.hasTriggered = true;
// Create feedback text
createFeedbackText(timingQuality, targetBox.x, targetBox.y - 80, feedbackColor);
// Create particle burst effect
var particleColor = feedbackColor;
var particleCount = 8;
if (hitNote.noteType === 'bonus') {
particleColor = 0xFFD700;
particleCount = 12; // More particles for bonus notes
} else if (hitNote.noteType === 'challenge') {
particleColor = 0x8844FF;
} else if (hitNote.noteType === 'fast') {
particleColor = 0xFF4444;
} else if (hitNote.noteType === 'slow') {
particleColor = 0x4444FF;
}
createParticleBurst(hitNote.x, hitNote.y, particleColor, particleCount);
// Show combo if >= 5
if (combo >= 5) {
comboTxt.setText('Combo x' + combo);
// Scale combo text briefly for emphasis
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut
});
tween(comboTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
// Flash different colors based on note type
var flashColor = 0x00FF00; // Default green
if (hitNote.noteType === 'bonus') flashColor = 0xFFD700; // Gold
else if (hitNote.noteType === 'challenge') flashColor = 0x8844FF; // Purple
else if (hitNote.noteType === 'fast') flashColor = 0xFF4444; // Red
else if (hitNote.noteType === 'slow') flashColor = 0x4444FF; // Blue
LK.effects.flashObject(targetBox, flashColor, 500);
// Play note-specific sound
hitNote.playNoteSound();
// Start or resume music on successful hit
if (!musicPlaying && musicGlitchTimer === 0) {
try {
LK.playMusic(currentMusicTrack);
musicPlaying = true;
} catch (e) {
console.log('Error playing music:', currentMusicTrack);
musicPlaying = false;
}
} else if (musicGlitchTimer > 0) {
// Resume music after glitch
try {
LK.playMusic(currentMusicTrack);
musicGlitchTimer = 0;
} catch (e) {
console.log('Error resuming music:', currentMusicTrack);
}
}
// Remove the note
hitNote.destroy();
var noteIndex = notes.indexOf(hitNote);
if (noteIndex > -1) {
notes.splice(noteIndex, 1);
}
}
// Remove bullet
bullet.destroy();
bullets.splice(i, 1);
// Update score display
scoreTxt.setText('Score: ' + score);
LK.setScore(totalScore); // Use total score for leaderboards
// Update level display
updateLevelDisplay();
break;
}
}
}
// Update power-up timers
if (multiShotTimer > 0) {
multiShotTimer--;
if (multiShotTimer === 0) {
multiShotActive = false;
if (activePowerUpType === 'multiShot') {
hidePowerUpDisplay();
}
}
}
if (slowMotionTimer > 0) {
slowMotionTimer--;
if (slowMotionTimer === 0) {
slowMotionActive = false;
if (activePowerUpType === 'slowMotion') {
hidePowerUpDisplay();
}
}
}
if (scoreMultiplierTimer > 0) {
scoreMultiplierTimer--;
if (scoreMultiplierTimer === 0) {
scoreMultiplierActive = false;
if (activePowerUpType === 'scoreMultiplier') {
hidePowerUpDisplay();
}
}
}
if (movingTargetTimer > 0) {
movingTargetTimer--;
if (movingTargetTimer === 0) {
movingTargetActive = false;
if (activePowerUpType === 'movingTarget') {
hidePowerUpDisplay();
}
// Reset target boxes to original positions
for (var j = 0; j < targetBoxes.length; j++) {
targetBoxes[j].x = targetBoxPositions[j];
}
}
}
// Update new power-up timers
if (laserBeamTimer > 0) {
laserBeamTimer--;
if (laserBeamTimer === 0) {
laserBeamActive = false;
if (laserBeamGraphics) {
laserBeamGraphics.destroy();
laserBeamGraphics = null;
}
if (activePowerUpType === 'laserBeam') {
hidePowerUpDisplay();
}
}
}
if (noteDestroyerTimer > 0) {
noteDestroyerTimer--;
if (noteDestroyerTimer === 0) {
noteDestroyerActive = false;
if (activePowerUpType === 'noteDestroyer') {
hidePowerUpDisplay();
}
}
}
// Update bad power-up timers
if (reverseControlsTimer > 0) {
reverseControlsTimer--;
if (reverseControlsTimer === 0) {
reverseControlsActive = false;
if (activePowerUpType === 'reverseControls') {
hidePowerUpDisplay();
}
}
}
if (shrinkingTargetsTimer > 0) {
shrinkingTargetsTimer--;
if (shrinkingTargetsTimer === 0) {
shrinkingTargetsActive = false;
// Restore original target box sizes
for (var t = 0; t < targetBoxes.length; t++) {
if (originalTargetSizes[t]) {
tween(targetBoxes[t], {
scaleX: originalTargetSizes[t].scaleX,
scaleY: originalTargetSizes[t].scaleY
}, {
duration: 500,
easing: tween.easeOut
});
}
}
if (activePowerUpType === 'shrinkingTargets') {
hidePowerUpDisplay();
}
}
}
// Handle laser beam collision with notes
if (laserBeamActive && laserBeamGraphics) {
// Update laser position to follow player horizontally but stay at top
laserBeamGraphics.x = player.x;
laserBeamGraphics.y = 0;
// Check laser collision with all notes
for (var l = notes.length - 1; l >= 0; l--) {
var note = notes[l];
if (Math.abs(note.x - laserBeamGraphics.x) < 50 && note.y < player.y) {
// Laser hit this note
var laserPoints = note.pointValue;
if (scoreMultiplierActive) {
laserPoints *= 2;
}
score += laserPoints;
createFeedbackText('+' + laserPoints, note.x, note.y - 50, 0x00FFFF);
createParticleBurst(note.x, note.y, 0x00FFFF, 6);
note.destroy();
notes.splice(l, 1);
}
}
}
// Update power-up display timer and progress bar
if (activePowerUpType && activePowerUpMaxDuration > 0) {
var currentTimer = 0;
if (activePowerUpType === 'multiShot') currentTimer = multiShotTimer;else if (activePowerUpType === 'slowMotion') currentTimer = slowMotionTimer;else if (activePowerUpType === 'scoreMultiplier') currentTimer = scoreMultiplierTimer;else if (activePowerUpType === 'movingTarget') currentTimer = movingTargetTimer;else if (activePowerUpType === 'laserBeam') currentTimer = laserBeamTimer;else if (activePowerUpType === 'reverseControls') currentTimer = reverseControlsTimer;else if (activePowerUpType === 'shrinkingTargets') currentTimer = shrinkingTargetsTimer;else if (activePowerUpType === 'shield') {
// Shield shows number of hits remaining instead of timer
powerUpTimerDisplay.setText('Hits: ' + (maxShieldHits - shieldHits));
powerUpProgressBar.width = 200 * ((maxShieldHits - shieldHits) / maxShieldHits);
} else if (activePowerUpType === 'noteDestroyer') currentTimer = noteDestroyerTimer;
// Update timer text (convert frames to seconds) for timed power-ups
if (activePowerUpType !== 'shield') {
var secondsLeft = Math.ceil(currentTimer / 60);
powerUpTimerDisplay.setText(secondsLeft + 's');
// Update progress bar
var progress = currentTimer / activePowerUpMaxDuration;
powerUpProgressBar.width = 200 * progress;
}
}
// Move target boxes if moving target power-up is active
if (movingTargetActive) {
for (var j = 0; j < targetBoxes.length; j++) {
var targetBox = targetBoxes[j];
var originalX = targetBoxPositions[j];
var offset = Math.sin(LK.ticks * 0.05 + j * 0.5) * 100;
targetBox.x = originalX + offset;
}
}
// Update and check notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Apply slow motion effect
if (slowMotionActive) {
note.speed *= 0.5;
}
// Check if note has passed through deletion area
if (note.y > deletionAreaY && !note.hasTriggered) {
// Only trigger game over for regular music notes, not power-ups
if (note.noteType !== 'multiShot' && note.noteType !== 'slowMotion' && note.noteType !== 'scoreMultiplier' && note.noteType !== 'movingTarget' && note.noteType !== 'laserBeam' && note.noteType !== 'shield' && note.noteType !== 'noteDestroyer' && note.noteType !== 'reverseControls' && note.noteType !== 'shrinkingTargets') {
// Note crashed into secret wall - game over
LK.showGameOver();
return; // Exit update loop since game is over
} else {
// Power-up notes just get removed without penalty
note.destroy();
notes.splice(i, 1);
continue;
}
}
// Remove notes that go off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
// Restore note speed after slow motion effect
if (slowMotionActive) {
note.speed *= 2; // Restore original speed for next frame
}
}
// Update and check spikes
for (var i = spikes.length - 1; i >= 0; i--) {
var spike = spikes[i];
// Apply slow motion effect
if (slowMotionActive) {
spike.speed *= 0.5;
}
// Check collision with player - game over if spike hits player (unless shield is active)
if (spike.intersects(player)) {
if (shieldActive && shieldHits < maxShieldHits) {
// Shield absorbs the hit
shieldHits++;
createFeedbackText('Shield Hit!', player.x, player.y - 100, 0x00FFFF);
createParticleBurst(spike.x, spike.y, 0x00FFFF, 10);
LK.effects.flashObject(player, 0x00FFFF, 500);
// Remove the spike
spike.destroy();
spikes.splice(i, 1);
// Deactivate shield if max hits reached
if (shieldHits >= maxShieldHits) {
shieldActive = false;
if (shieldGraphics) {
tween(shieldGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
shieldGraphics.destroy();
shieldGraphics = null;
}
});
}
if (activePowerUpType === 'shield') {
hidePowerUpDisplay();
}
createFeedbackText('Shield Depleted!', player.x, player.y - 120, 0xFF0000);
}
continue;
} else {
// No shield or shield depleted - game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return; // Exit update loop since game is over
}
}
// Spikes can pass through the secret line - no collision check needed
// Remove spikes that go off screen
if (spike.y > 2800) {
spike.destroy();
spikes.splice(i, 1);
}
// Restore spike speed after slow motion effect
if (slowMotionActive) {
spike.speed *= 2; // Restore original speed for next frame
}
}
// Handle music glitch timer
if (musicGlitchTimer > 0) {
musicGlitchTimer--;
if (musicGlitchTimer === 0 && musicPlaying) {
// Resume music after glitch period
try {
LK.playMusic(currentMusicTrack);
} catch (e) {
console.log('Error resuming music after glitch:', currentMusicTrack);
}
}
}
// Increase difficulty over time
if (LK.ticks % 1800 === 0) {
// Every 30 seconds
noteSpawnInterval = Math.max(30, noteSpawnInterval - 5);
// Increase note speed slightly
for (var i = 0; i < notes.length; i++) {
notes[i].speed = Math.min(6, notes[i].speed + 0.1);
}
}
};
Music Note. In-Game asset. 2d. High contrast. No shadows
a circle empty inside, at the edges there is rainbow curly things. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A simple blue cartoon cat standing upright, no accessories or effects, clean and minimal style, no musical notes or particles, not too stylish, designed as the main character for a rhythm game with a cold, minimal theme, light outlines and smooth shading, no background. In-Game asset. 2d. High contrast. No shadows
A dumb looking flat fish like a sardine looking up, ice blue color.. In-Game asset. 2d. High contrast. No shadows
A simple ice blue fishbone icon, clean vector style, no background, minimal design, symmetrical and centered, soft lines, suitable for a 2D rhythm game UI element. In-Game asset. 2d. High contrast. No shadows
Ice blue with a bright color stroke music note. In-Game asset. 2d. High contrast. No shadows
Ice blue with a dark color stroke music notes (note must be simetric).. In-Game asset. 2d. High contrast. No shadows
Good Power Up Green color. In-Game asset. 2d. High contrast. No shadows
Red Color, delete "GOOD" Write "BAD"
Red Spike "Music Note" shape. In-Game asset. 2d. High contrast. No shadows
circle shape, empty inside, transparent , stroke is navy blue.. In-Game asset. 2d. High contrast. No shadows
A huge Laser beam, red. In-Game asset. 2d. High contrast. No shadows