User prompt
okay add these - **Combo system** - Track consecutive hits and display a combo counter that grows with each successful hit, resetting on misses - **Hit timing feedback** - Show "Perfect!", "Good", or "Miss" text that appears briefly when shooting notes βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
i need variable music notes
User prompt
hey, i changed music and now its gone can you fix
User prompt
add a area that deletes music notes, below the target box
User prompt
set bullet speed to 31
User prompt
Add reactive music behavior to the current game. The background music should only play when the player successfully hits a note inside the colored ring at the correct time. If the player shoots early, late, or misses the note, the music should temporarily stop or glitch for a short moment (e.g. 0.3 seconds), and then resume. Also, play a distinct "error" sound effect when this happens, like a short beep or distortion. The flow of the rhythm must stay intact overall. βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
this is a question do not add anything: i want this rhytm based so like when a note inside circle and we succesfully hit the note correct place part of music we added should be come out
User prompt
question:i want this rhytm based like piano games where you click piano notes to create music how can we do this
User prompt
OK, it seems to notes are counting when fully inside box, this is not good, they have to count as score even their little part inside or close to chamber
User prompt
add 3 more (total 4) targetbox and for eachone dropping notes
User prompt
thats great, but make it a little bigger
User prompt
okay make the bullet how it must
User prompt
bullets are too wide, fix it make more upwards
User prompt
increase bullet speed to 26
User prompt
2: increase bullet speed to 26 3: gun follows the mouse βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
1: make bullets.. bullet size not wide, 2: increase bullet speed to 26 3: gun follows the mouse
User prompt
i mean grew the bullet upwards
User prompt
1: revert the bullets make them vertical
User prompt
1: make bullets flat 2: increase bullet speed to 26 3: gun follows the mouse βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
User prompt
nice, now add a floating gun to attached to player, bullets comes from this gun and increase speed of bullets βͺπ‘ Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Rhythm Strike
Initial prompt
"Create a concept for a rhythm-shooter game with a top-down view. At the top of the screen, display an inverted musical staff with five horizontal lines. Musical notes scroll downwards from the top of the screen, each note aligned with one of the five lines of the staff. Directly below the inverted staff, there are five static target boxes. Each box is perfectly aligned with one of the five staff lines above it. At the very bottom of the screen is the player's character. The character's position follows the mouse cursor's horizontal movement. When the player clicks, the character fires a projectile directly towards the mouse cursor's position. The main gameplay goal is to shoot the correct target box at the exact moment a musical note is passing over it. A successful hit on a target box at the right time awards the player points and triggers a positive visual effect."
/**** * 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
bgmusic
Music
rock_track
Music
electronic_track
Music
shake
Sound effect
kick
Sound effect
cymbal
Sound effect
drums
Sound effect
bass
Sound effect
saxophone
Sound effect
hihat
Sound effect
guitar
Sound effect
piano
Sound effect
violin
Sound effect