/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var BackgroundGrid = Container.expand(function () { var self = Container.call(this); // Create horizontal grid lines self.horizontalLines = []; for (var i = 0; i < 15; i++) { var line = self.attachAsset('gridLine', { anchorX: 0, anchorY: 0.5, alpha: 0.8 }); line.y = i * 200 - 400; line.tint = 0x00FFFF; // Start with bright cyan self.horizontalLines.push(line); } // Create vertical grid lines self.verticalLines = []; for (var j = 0; j < 8; j++) { var vLine = self.attachAsset('gridLineVertical', { anchorX: 0.5, anchorY: 0, alpha: 0.7 }); vLine.x = j * 300; vLine.tint = 0xFF00FF; // Start with bright magenta self.verticalLines.push(vLine); } self.update = function () { // Move horizontal lines down for (var i = 0; i < self.horizontalLines.length; i++) { var line = self.horizontalLines[i]; line.y += 2; // Reset line position when it goes off screen if (line.y > 2800) { line.y = -100; } // Enhanced neon glow effect - brighter and more vibrant var distanceFromCenter = Math.abs(line.y - 1366); var maxDistance = 1366; var baseBrightness = 0.8; // Much brighter base var fadeEffect = 1 - distanceFromCenter / maxDistance; line.alpha = Math.max(0.4, baseBrightness * fadeEffect); // Minimum brightness of 0.4 // Add pulsing neon colors var pulsePhase = (LK.ticks + i * 10) * 0.05; var pulseBrightness = 0.8 + 0.4 * Math.sin(pulsePhase); line.alpha = line.alpha * pulseBrightness; // Cycle through neon colors var colorPhase = (LK.ticks * 0.01 + i * 0.2) % (Math.PI * 2); if (colorPhase < Math.PI * 0.66) { line.tint = 0x00FFFF; // Bright cyan } else if (colorPhase < Math.PI * 1.33) { line.tint = 0xFF00FF; // Bright magenta } else { line.tint = 0xFFFF00; // Bright yellow } } // Animate vertical lines with neon glow for (var j = 0; j < self.verticalLines.length; j++) { var vLine = self.verticalLines[j]; // Enhanced brightness for vertical lines vLine.alpha = 0.7 + 0.3 * Math.sin((LK.ticks + j * 15) * 0.03); // Color cycling for vertical lines var vColorPhase = (LK.ticks * 0.008 + j * 0.3) % (Math.PI * 2); if (vColorPhase < Math.PI * 0.5) { vLine.tint = 0x00FFFF; // Cyan } else if (vColorPhase < Math.PI) { vLine.tint = 0xFF0066; // Hot pink } else if (vColorPhase < Math.PI * 1.5) { vLine.tint = 0x66FF00; // Electric lime } else { vLine.tint = 0xFF6600; // Electric orange } } }; return self; }); var Block = Container.expand(function () { var self = Container.call(this); // Array of available block assets var blockAssets = ['block1', 'block2', 'block3', 'block4']; // Randomly select a block asset var selectedAsset = blockAssets[Math.floor(Math.random() * blockAssets.length)]; var blockGraphics = self.attachAsset(selectedAsset, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.speed = 8; self.lane = 0; self.caught = false; // Random neon colors var colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0066, 0x66ff00]; blockGraphics.tint = colors[Math.floor(Math.random() * colors.length)]; self.update = function () { self.y += self.speed; }; return self; }); var BombBlock = Container.expand(function () { var self = Container.call(this); var bombGraphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.speed = 8; self.lane = 0; self.caught = false; // Distinctive red color for danger bombGraphics.tint = 0xff0000; // Add warning pulsing animation to make it stand out as dangerous tween(bombGraphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(bombGraphics, { scaleX: 0.8, scaleY: 0.8 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { // Restart the pulsing animation if (!self.caught) { tween(bombGraphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeInOut }); } } }); } }); self.update = function () { self.y += self.speed; }; return self; }); var BoosterBlock = Container.expand(function () { var self = Container.call(this); var boosterGraphics = self.attachAsset('booster', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.speed = 8; self.lane = 0; self.caught = false; // Distinctive golden/yellow color for booster boosterGraphics.tint = 0xFFD700; // Add pulsing animation to make it stand out tween(boosterGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(boosterGraphics, { scaleX: 0.8, scaleY: 0.8 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { // Restart the pulsing animation if (!self.caught) { tween(boosterGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 500, easing: tween.easeInOut }); } } }); } }); self.update = function () { self.y += self.speed; }; return self; }); var Life = Container.expand(function () { var self = Container.call(this); var heartGraphics = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0 }); // Add neon glow effect with retrowave pink heartGraphics.tint = 0xff0066; self.setActive = function (active) { if (active) { heartGraphics.alpha = 1.0; heartGraphics.tint = 0xff0066; // Bright neon pink } else { heartGraphics.alpha = 0.3; heartGraphics.tint = 0x440022; // Dim dark pink } }; return self; }); var MicrophoneIcon = Container.expand(function () { var self = Container.call(this); // Create microphone icon using a shape asset var micGraphics = self.attachAsset('micBar', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 1.5 }); // Style the microphone with synthwave neon colors micGraphics.tint = 0x00ffcc; micGraphics.alpha = 0.6; // Audio detection state self.isActive = false; self.lastVolume = 0; self.update = function () { if (gameMode === 'microphone') { var currentVolume = facekit.volume; // Update visual state based on audio levels if (currentVolume > 0.3) { // Active state - bright glow if (!self.isActive) { self.isActive = true; tween(micGraphics, { tint: 0x00ff66, alpha: 1.0, scaleX: 2.4, scaleY: 1.8 }, { duration: 100, easing: tween.easeOut }); } // Beat detection - extra bright flash if (detectBeat()) { tween(micGraphics, { tint: 0xffffff, scaleX: 3.0, scaleY: 2.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(micGraphics, { tint: 0x00ff66, scaleX: 2.4, scaleY: 1.8 }, { duration: 200, easing: tween.easeIn }); } }); } } else if (currentVolume > 0.1) { // Medium activity - moderate glow if (self.isActive) { tween(micGraphics, { tint: 0x00ffcc, alpha: 0.8, scaleX: 2.2, scaleY: 1.6 }, { duration: 200, easing: tween.easeInOut }); } self.isActive = false; } else { // Inactive state - dim glow with subtle pulsing if (self.isActive) { self.isActive = false; tween(micGraphics, { tint: 0x004466, alpha: 0.5, scaleX: 2.0, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut }); } // Subtle pulsing to show mic is available var pulsePhase = LK.ticks * 0.02; micGraphics.alpha = 0.4 + 0.2 * Math.sin(pulsePhase); } self.lastVolume = currentVolume; } }; return self; }); var Ship = Container.expand(function (shipType) { var self = Container.call(this); // Use the provided shipType or default to classicShip var assetType = shipType || 'classicShip'; var shipGraphics = self.attachAsset(assetType, { anchorX: 0.5, anchorY: 0.5, scaleX: 3.0, scaleY: 3.0 }); // Add glow effect shipGraphics.filters = []; self.currentLane = 1; // 0 = left, 1 = center, 2 = right self.targetX = 1024; // center position self.update = function () { // Smooth movement to target lane var diff = self.targetX - self.x; if (Math.abs(diff) > 2) { self.x += diff * 0.15; } else { self.x = self.targetX; } }; self.moveToLane = function (lane) { self.currentLane = lane; if (lane === 0) { self.targetX = 341; // left lane } else if (lane === 1) { self.targetX = 1024; // center lane } else if (lane === 2) { self.targetX = 1707; // right lane } // Add movement animation tween(shipGraphics, { scaleX: 3.6, scaleY: 2.4 }, { duration: 100, onFinish: function onFinish() { tween(shipGraphics, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); } }); }; return self; }); var UnlockableShip = Container.expand(function (shipData) { var self = Container.call(this); // Use ship data to create the ship graphics var shipGraphics = self.attachAsset(shipData.assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); // Apply the ship's unique color shipGraphics.tint = shipData.color; // Store ship properties self.shipData = shipData; self.extraLives = shipData.extraLives; // Add glow effect for unlocked ships self.addGlowEffect = function () { tween(shipGraphics, { alpha: 1.2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(shipGraphics, { alpha: 0.8 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { self.addGlowEffect(); } }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0a0a0a }); /**** * Game Code ****/ // Create synthwave background with gradient effect game.setBackgroundColor(0x270050); // Create moving background grid var backgroundGrid = game.addChild(new BackgroundGrid()); // Array of synthwave tracks for classic mode var synthwaveTracks = ['synthwave1', 'synthwave2', 'synthwave3', 'lofitrack', 'retrowave']; // Ship unlocking system var unlockedShips = storage.unlockedShips || []; var availableShipTypes = ['ship1', 'ship2', 'ship3', 'ship4', 'ship5']; var shipColors = [0xff0066, 0x00ffcc, 0xffcc00, 0x66ff00, 0xcc00ff]; var shipNames = ['Neon Striker', 'Cyber Cruiser', 'Solar Surfer', 'Plasma Rider', 'Void Walker']; // Generate random ship data function generateRandomShip() { var randomIndex = Math.floor(Math.random() * availableShipTypes.length); var baseColor = shipColors[randomIndex]; // Add some randomization to the color var colorVariation = Math.floor(Math.random() * 0x333333); var finalColor = baseColor + colorVariation & 0xffffff; return { id: 'ship_' + Date.now() + '_' + Math.floor(Math.random() * 1000), assetId: availableShipTypes[randomIndex], color: finalColor, name: shipNames[randomIndex] + ' MK-' + (Math.floor(Math.random() * 9) + 1), extraLives: 1, unlocked: true }; } // Current selected ship var currentShipData = null; var currentTrackIndex = 0; // Index of currently playing track var completedTracks = []; // Array to track which tracks have been completed var musicStartTime = 0; // Time when current music track started var trackDuration = 60000; // Approximate track duration in milliseconds (60 seconds) // Game state var gameMode = 'menu'; // 'menu', 'classic', 'microphone' var ship; var blocks = []; var score = 0; var combo = 0; var bestCombo = 0; var lives = 3; var maxLives = 3; var lifeDisplays = []; var lastSpawnTime = 0; var spawnInterval = 1000; // 1 second var gameSpeed = 1.0; // Speed multiplier for the game var baseBlockSpeed = 8; // Base speed for blocks var currentMusicTrack = null; // Track current playing music // Beat detection variables var audioBuffer = []; var beatThreshold = 0.4; var lastBeatTime = 0; var minBeatInterval = 200; // Minimum ms between beats var volumeHistory = []; var bufferSize = 10; var beatSensitivity = 1.5; // Lane positions - equal width lanes var lanePositions = [341, 1024, 1707]; // UI Elements var scoreTxt = new Text2('Score: 0', { size: 60, fill: 0x00FFFF }); scoreTxt.anchor.set(0, 0); scoreTxt.x = 50; scoreTxt.y = 50; LK.gui.topLeft.addChild(scoreTxt); var comboTxt = new Text2('Combo: 0', { size: 50, fill: 0xFF00FF }); comboTxt.anchor.set(0, 0); comboTxt.x = 50; comboTxt.y = 120; LK.gui.topLeft.addChild(comboTxt); // Create life display hearts in top right for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) { var lifeHeart = new Life(); lifeHeart.x = -80 - lifeIndex * 80; // Position from right edge lifeHeart.y = 60; lifeHeart.setActive(true); lifeDisplays.push(lifeHeart); LK.gui.topRight.addChild(lifeHeart); } // Create menu window frame with neon border var menuFrameBorder = LK.getAsset('menuFrameBorder', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 }); menuFrameBorder.x = 0; menuFrameBorder.y = -100; LK.gui.center.addChild(menuFrameBorder); // Create semi-transparent background panel var menuFrame = LK.getAsset('menuFrame', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); menuFrame.x = 0; menuFrame.y = -100; LK.gui.center.addChild(menuFrame); // Add pulsing animation to border tween(menuFrameBorder, { alpha: 0.8 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(menuFrameBorder, { alpha: 0.4 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { // Restart the pulsing animation tween(menuFrameBorder, { alpha: 0.8 }, { duration: 1000, easing: tween.easeInOut }); } }); } }); // Create mode selection buttons with neon glow outline var classicButton = new Text2('1. CLASSIC MODE\nBuilt-in synthwave music', { size: 60, fill: 0x00FFFF, stroke: 0x00FFFF, strokeThickness: 3 }); classicButton.anchor.set(0.5, 0.5); classicButton.x = 0; classicButton.y = -200; LK.gui.center.addChild(classicButton); var micButton = new Text2('2. MICROPHONE MODE\nSync with your music', { size: 60, fill: 0xFF00FF, stroke: 0xFF00FF, strokeThickness: 3 }); micButton.anchor.set(0.5, 0.5); micButton.x = 0; micButton.y = -50; LK.gui.center.addChild(micButton); var instructionTxt = new Text2('TAP A BUTTON TO SELECT MODE', { size: 50, fill: 0xFFFFFF }); instructionTxt.anchor.set(0.5, 0.5); instructionTxt.y = 150; LK.gui.center.addChild(instructionTxt); // Ship unlock UI elements (initially hidden) var unlockFrameBorder = LK.getAsset('unlockBorder', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); unlockFrameBorder.x = 0; unlockFrameBorder.y = 0; LK.gui.center.addChild(unlockFrameBorder); var unlockFrame = LK.getAsset('unlockFrame', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); unlockFrame.x = 0; unlockFrame.y = 0; LK.gui.center.addChild(unlockFrame); var unlockTitleTxt = new Text2('SHIP UNLOCKED!', { size: 60, fill: 0x00FFFF }); unlockTitleTxt.anchor.set(0.5, 0.5); unlockTitleTxt.x = 0; unlockTitleTxt.y = -120; unlockTitleTxt.alpha = 0; LK.gui.center.addChild(unlockTitleTxt); var unlockDescTxt = new Text2('', { size: 40, fill: 0xFFFFFF }); unlockDescTxt.anchor.set(0.5, 0.5); unlockDescTxt.x = 0; unlockDescTxt.y = -60; unlockDescTxt.alpha = 0; LK.gui.center.addChild(unlockDescTxt); var unlockShipDisplay = null; var unlockContinueTxt = new Text2('TAP TO CONTINUE', { size: 50, fill: 0xFF00FF }); unlockContinueTxt.anchor.set(0.5, 0.5); unlockContinueTxt.x = 0; unlockContinueTxt.y = 120; unlockContinueTxt.alpha = 0; LK.gui.center.addChild(unlockContinueTxt); // Create microphone icon (initially hidden) var microphoneIcon = new MicrophoneIcon(); microphoneIcon.x = -80; microphoneIcon.y = 200; microphoneIcon.visible = false; LK.gui.topRight.addChild(microphoneIcon); // Create camera disable button var cameraButton = new Text2('CAM', { size: 40, fill: 0x00ffff, stroke: 0x00ffff, strokeThickness: 2 }); cameraButton.anchor.set(0.5, 0.5); cameraButton.x = -80; cameraButton.y = 300; cameraButton.visible = false; LK.gui.topRight.addChild(cameraButton); // Camera state tracking var cameraEnabled = true; // Create lane dividers var lane1 = game.addChild(LK.getAsset('lane1', { x: 682, y: 0 })); var lane2 = game.addChild(LK.getAsset('lane2', { x: 1366, y: 0 })); // Create ship ship = game.addChild(new Ship()); ship.x = 1024; ship.y = 2400; function startGame(mode) { gameMode = mode; score = 0; combo = 0; bestCombo = 0; // Calculate lives based on current ship var baseLives = 3; var extraLives = currentShipData ? currentShipData.extraLives : 0; lives = baseLives + extraLives; maxLives = lives; blocks = []; lastSpawnTime = 0; gameSpeed = 1.0; // Reset game speed currentTrackIndex = 0; // Reset track progression completedTracks = []; // Reset completed tracks musicStartTime = 0; // Reset music timer // Reset and update life display for new max lives // Remove existing life displays for (var i = 0; i < lifeDisplays.length; i++) { lifeDisplays[i].destroy(); } lifeDisplays = []; // Create new life displays based on current max lives for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) { var lifeHeart = new Life(); lifeHeart.x = -80 - lifeIndex * 80; lifeHeart.y = 60; lifeHeart.setActive(true); lifeDisplays.push(lifeHeart); LK.gui.topRight.addChild(lifeHeart); } // Reset beat detection variables audioBuffer = []; volumeHistory = []; lastBeatTime = 0; // Hide menu elements classicButton.visible = false; micButton.visible = false; instructionTxt.visible = false; menuFrame.visible = false; menuFrameBorder.visible = false; // Destroy existing ship and create new one with appropriate asset if (ship) { ship.destroy(); } // Create ship with mode-specific asset if (mode === 'classic') { ship = game.addChild(new Ship('classicShip')); } else { ship = game.addChild(new Ship('microphoneShip')); } ship.x = 1024; ship.y = 2400; // Start background music only in classic mode if (mode === 'classic') { // Shuffle the playlist for randomized track order for (var i = synthwaveTracks.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = synthwaveTracks[i]; synthwaveTracks[i] = synthwaveTracks[j]; synthwaveTracks[j] = temp; } // Start with first track in shuffled sequence currentMusicTrack = synthwaveTracks[currentTrackIndex]; LK.playMusic(currentMusicTrack); musicStartTime = Date.now(); // Track when music started using current time // Hide microphone icon and camera button in classic mode microphoneIcon.visible = false; cameraButton.visible = false; } else { // Stop background music in microphone mode to focus on external audio currentMusicTrack = null; LK.stopMusic(); // Facekit plugin automatically handles microphone access when volume is accessed // No initialization needed - just use facekit.volume for beat detection // Show microphone icon and camera button in microphone mode microphoneIcon.visible = true; cameraButton.visible = true; // Reset camera state when starting microphone mode cameraEnabled = true; cameraButton.setText('CAM'); cameraButton.fill = 0x00ffff; cameraButton.stroke = 0x00ffff; } updateUI(); } function spawnBlock() { var block; var random = Math.random(); // 10% chance to spawn a bomb block if (random < 0.10) { block = new BombBlock(); // 15% chance to spawn a booster block } else if (random < 0.25) { block = new BoosterBlock(); } else { block = new Block(); } var lane; if (gameMode === 'microphone') { // Create more musical patterns based on audio intensity var intensity = facekit.volume; if (intensity > 0.8) { // High intensity - spawn in multiple lanes or center lane = Math.random() > 0.5 ? 1 : Math.floor(Math.random() * 3); } else if (intensity > 0.5) { // Medium intensity - prefer outer lanes lane = Math.random() > 0.5 ? 0 : 2; } else { // Low intensity - single random lane lane = Math.floor(Math.random() * 3); } } else { // Classic mode - random lane lane = Math.floor(Math.random() * 3); } block.lane = lane; block.x = lanePositions[lane]; block.y = -50; block.speed = baseBlockSpeed * gameSpeed; // Apply game speed multiplier blocks.push(block); game.addChild(block); } function updateUI() { scoreTxt.setText('Score: ' + score); comboTxt.setText('Combo: ' + combo + ' (Best: ' + bestCombo + ')'); } function checkCollisions() { for (var i = blocks.length - 1; i >= 0; i--) { var block = blocks[i]; // Check if block is in ship's catch zone if (!block.caught && block.y >= ship.y - 100 && block.y <= ship.y + 100) { if (block.lane === ship.currentLane) { // Block caught! block.caught = true; // Check if it's a bomb block if (block instanceof BombBlock) { // Bomb effects - lose 2 lives and reset combo lives -= 2; combo = 0; // Update life display for lost lives for (var lifeIdx = Math.max(0, lives); lifeIdx < Math.min(lives + 2, lifeDisplays.length); lifeIdx++) { lifeDisplays[lifeIdx].setActive(false); // Add dramatic life loss animation - capture lifeIdx in closure (function (capturedIdx) { tween(lifeDisplays[capturedIdx], { scaleX: 2.0, scaleY: 2.0 }, { duration: 300, onFinish: function onFinish() { tween(lifeDisplays[capturedIdx], { scaleX: 1.0, scaleY: 1.0 }, { duration: 300 }); } }); })(lifeIdx); } // Dramatic visual feedback for bomb LK.effects.flashScreen(0xff0000, 800); LK.effects.flashObject(ship, 0xff0000, 800); // Play miss sound for bomb hit LK.getSound('miss').play(); // Check for game over if (lives <= 0) { // Unlock a new ship before showing game over var newShip = unlockNewShip(); currentShipData = newShip; // Delay game over to show ship unlock LK.setTimeout(function () { LK.showGameOver(); }, 3000); return; } // Check if it's a booster block } else if (block instanceof BoosterBlock) { // Booster effects score += 25 + combo * 3; // More points for booster combo += 2; // Extra combo bonus // Increase game speed (cap at 2.5x) gameSpeed = Math.min(gameSpeed + 0.2, 2.5); // Apply speed to all existing blocks for (var j = 0; j < blocks.length; j++) { blocks[j].speed = baseBlockSpeed * gameSpeed; } // Restart music with faster tempo (only in classic mode) if (gameMode === 'classic' && currentMusicTrack) { LK.stopMusic(); // Small delay before restarting music to avoid audio glitches LK.setTimeout(function () { LK.playMusic(currentMusicTrack); }, 50); } // Special visual feedback for booster LK.effects.flashScreen(0xFFD700, 300); LK.effects.flashObject(ship, 0xFFD700, 500); // Play layered synthwave sounds for booster LK.getSound('synthBass').play(); LK.setTimeout(function () { LK.getSound('synthLead').play(); }, 100); } else { // Regular block score += 10 + combo * 2; combo++; } if (combo > bestCombo) { bestCombo = combo; } // Visual feedback LK.effects.flashObject(block, 0xffffff, 200); tween(block, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200 }); // Play musical sound that matches the current track var trackSounds = { 'synthwave1': ['synthBass', 'synthLead', 'synthDrum', 'synthChord'], 'synthwave2': ['synthLead', 'synthChord', 'synthBass', 'synthDrum'], 'synthwave3': ['synthDrum', 'synthBass', 'synthLead', 'synthChord'], 'lofitrack': ['synthBass', 'synthChord'], 'retrowave': ['synthLead', 'synthDrum', 'synthChord'] }; var soundsForTrack = trackSounds[currentMusicTrack] || ['synthBass', 'synthLead', 'synthDrum', 'synthChord']; // Calculate beat position based on music timing to stay in rhythm var baseBPM = 120; var currentBPM = baseBPM * gameSpeed; var beatLength = 60000 / currentBPM; // Beat duration in ms var timeElapsed = Date.now() - musicStartTime; var beatPosition = Math.floor(timeElapsed / beatLength % soundsForTrack.length); var rhythmicSound = soundsForTrack[beatPosition]; LK.getSound(rhythmicSound).play(); updateUI(); // Remove block block.destroy(); blocks.splice(i, 1); continue; } } // Check if block missed (passed ship) if (!block.caught && block.y > ship.y + 150) { if (block.lane === ship.currentLane) { // Block missed - lose life and reset combo lives--; combo = 0; // Update life display if (lives >= 0 && lives < lifeDisplays.length) { var currentLifeIndex = lives; // Capture current lives value lifeDisplays[currentLifeIndex].setActive(false); // Add life loss animation tween(lifeDisplays[currentLifeIndex], { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, onFinish: function onFinish() { tween(lifeDisplays[currentLifeIndex], { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }); } LK.effects.flashScreen(0xff0000, 300); LK.getSound('miss').play(); // Check for game over if (lives <= 0) { LK.showGameOver(); return; } updateUI(); } block.caught = true; // Mark as processed } // Remove blocks that are off screen if (block.y > 2800) { block.destroy(); blocks.splice(i, 1); } } } function showShipUnlock(newShipData) { // Show unlock frame with animation tween(unlockFrameBorder, { alpha: 0.8 }, { duration: 300 }); tween(unlockFrame, { alpha: 0.9 }, { duration: 300 }); tween(unlockTitleTxt, { alpha: 1 }, { duration: 300 }); // Update description text unlockDescTxt.setText(newShipData.name + '\n+1 Extra Life'); tween(unlockDescTxt, { alpha: 1 }, { duration: 300 }); // Create and display the unlocked ship if (unlockShipDisplay) { unlockShipDisplay.destroy(); } unlockShipDisplay = new UnlockableShip(newShipData); unlockShipDisplay.x = 0; unlockShipDisplay.y = 20; LK.gui.center.addChild(unlockShipDisplay); unlockShipDisplay.addGlowEffect(); tween(unlockContinueTxt, { alpha: 1 }, { duration: 300 }); // Add pulsing animation to continue text tween(unlockContinueTxt, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(unlockContinueTxt, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(unlockContinueTxt, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOut }); } }); } }); } function hideShipUnlock() { tween(unlockFrameBorder, { alpha: 0 }, { duration: 300 }); tween(unlockFrame, { alpha: 0 }, { duration: 300 }); tween(unlockTitleTxt, { alpha: 0 }, { duration: 300 }); tween(unlockDescTxt, { alpha: 0 }, { duration: 300 }); tween(unlockContinueTxt, { alpha: 0 }, { duration: 300 }); if (unlockShipDisplay) { tween(unlockShipDisplay, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { unlockShipDisplay.destroy(); unlockShipDisplay = null; } }); } } function unlockNewShip() { // Generate a new random ship var newShip = generateRandomShip(); // Add to unlocked ships unlockedShips.push(newShip); // Keep only the 5 most recent ships if (unlockedShips.length > 5) { unlockedShips = unlockedShips.slice(-5); } // Save to storage storage.unlockedShips = unlockedShips; // Show unlock animation showShipUnlock(newShip); return newShip; } function progressToNextTrack() { if (gameMode !== 'classic') { return; } // Mark current track as completed if (completedTracks.indexOf(synthwaveTracks[currentTrackIndex]) === -1) { completedTracks.push(synthwaveTracks[currentTrackIndex]); } // Move to next track currentTrackIndex++; // Check if all tracks have been completed if (currentTrackIndex >= synthwaveTracks.length) { // Unlock a new ship before showing victory var newShip = unlockNewShip(); currentShipData = newShip; // Delay victory screen to show ship unlock LK.setTimeout(function () { LK.showYouWin(); }, 3000); return; } // Play next track currentMusicTrack = synthwaveTracks[currentTrackIndex]; LK.playMusic(currentMusicTrack); musicStartTime = Date.now(); // Reset music timer using current time } function detectBeat() { var currentVolume = facekit.volume; var currentTime = Date.now(); // Add current volume to history buffer volumeHistory.push(currentVolume); if (volumeHistory.length > bufferSize) { volumeHistory.shift(); } // Calculate average volume from buffer var avgVolume = 0; for (var i = 0; i < volumeHistory.length; i++) { avgVolume += volumeHistory[i]; } avgVolume = avgVolume / volumeHistory.length; // Detect beat: current volume significantly higher than average var volumeSpike = currentVolume > avgVolume * beatSensitivity; var timeSinceLastBeat = currentTime - lastBeatTime; var beatDetected = volumeSpike && currentVolume > beatThreshold && timeSinceLastBeat > minBeatInterval; if (beatDetected) { lastBeatTime = currentTime; // Add visual feedback for beat detection LK.effects.flashScreen(0x440088, 100); return true; } return false; } function shouldSpawnBlock() { if (gameMode === 'microphone') { // Spawn based on beat detection from microphone input return detectBeat(); } else { // Classic mode - spawn every second return LK.ticks - lastSpawnTime >= spawnInterval / (1000 / 60); } } // Button event handlers classicButton.down = function (x, y, obj) { if (gameMode === 'menu') { // Add button press animation tween(classicButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, onFinish: function onFinish() { tween(classicButton, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); startGame('classic'); } }; micButton.down = function (x, y, obj) { if (gameMode === 'menu') { // Add button press animation tween(micButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, onFinish: function onFinish() { tween(micButton, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); startGame('microphone'); } }; // Camera button event handler cameraButton.down = function (x, y, obj) { if (gameMode === 'microphone') { // Toggle camera state cameraEnabled = !cameraEnabled; // Update button appearance based on state if (cameraEnabled) { cameraButton.setText('CAM'); cameraButton.fill = 0x00ffff; cameraButton.stroke = 0x00ffff; } else { cameraButton.setText('CAM\nOFF'); cameraButton.fill = 0xff0066; cameraButton.stroke = 0xff0066; } // Add button press animation tween(cameraButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(cameraButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100 }); } }); // Visual feedback LK.effects.flashObject(cameraButton, 0xffffff, 200); } }; // Touch controls for lane switching during gameplay game.down = function (x, y, obj) { if (gameMode === 'menu') { return; } // Check if unlock screen is visible if (unlockFrame.alpha > 0) { hideShipUnlock(); return; } // Lane switching if (x < 1024) { // Left side tapped - move left if (ship.currentLane > 0) { ship.moveToLane(ship.currentLane - 1); } } else { // Right side tapped - move right if (ship.currentLane < 2) { ship.moveToLane(ship.currentLane + 1); } } }; // Main game update game.update = function () { if (gameMode === 'menu') { // Hide microphone icon and camera button in menu microphoneIcon.visible = false; cameraButton.visible = false; return; } // Check for track progression in classic mode if (gameMode === 'classic' && musicStartTime > 0) { var currentTime = Date.now(); var timeElapsed = currentTime - musicStartTime; // Check if current track duration has been reached if (timeElapsed >= trackDuration) { progressToNextTrack(); } } // Spawn blocks if (shouldSpawnBlock()) { spawnBlock(); lastSpawnTime = LK.ticks; } // Check collisions checkCollisions(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var BackgroundGrid = Container.expand(function () {
var self = Container.call(this);
// Create horizontal grid lines
self.horizontalLines = [];
for (var i = 0; i < 15; i++) {
var line = self.attachAsset('gridLine', {
anchorX: 0,
anchorY: 0.5,
alpha: 0.8
});
line.y = i * 200 - 400;
line.tint = 0x00FFFF; // Start with bright cyan
self.horizontalLines.push(line);
}
// Create vertical grid lines
self.verticalLines = [];
for (var j = 0; j < 8; j++) {
var vLine = self.attachAsset('gridLineVertical', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.7
});
vLine.x = j * 300;
vLine.tint = 0xFF00FF; // Start with bright magenta
self.verticalLines.push(vLine);
}
self.update = function () {
// Move horizontal lines down
for (var i = 0; i < self.horizontalLines.length; i++) {
var line = self.horizontalLines[i];
line.y += 2;
// Reset line position when it goes off screen
if (line.y > 2800) {
line.y = -100;
}
// Enhanced neon glow effect - brighter and more vibrant
var distanceFromCenter = Math.abs(line.y - 1366);
var maxDistance = 1366;
var baseBrightness = 0.8; // Much brighter base
var fadeEffect = 1 - distanceFromCenter / maxDistance;
line.alpha = Math.max(0.4, baseBrightness * fadeEffect); // Minimum brightness of 0.4
// Add pulsing neon colors
var pulsePhase = (LK.ticks + i * 10) * 0.05;
var pulseBrightness = 0.8 + 0.4 * Math.sin(pulsePhase);
line.alpha = line.alpha * pulseBrightness;
// Cycle through neon colors
var colorPhase = (LK.ticks * 0.01 + i * 0.2) % (Math.PI * 2);
if (colorPhase < Math.PI * 0.66) {
line.tint = 0x00FFFF; // Bright cyan
} else if (colorPhase < Math.PI * 1.33) {
line.tint = 0xFF00FF; // Bright magenta
} else {
line.tint = 0xFFFF00; // Bright yellow
}
}
// Animate vertical lines with neon glow
for (var j = 0; j < self.verticalLines.length; j++) {
var vLine = self.verticalLines[j];
// Enhanced brightness for vertical lines
vLine.alpha = 0.7 + 0.3 * Math.sin((LK.ticks + j * 15) * 0.03);
// Color cycling for vertical lines
var vColorPhase = (LK.ticks * 0.008 + j * 0.3) % (Math.PI * 2);
if (vColorPhase < Math.PI * 0.5) {
vLine.tint = 0x00FFFF; // Cyan
} else if (vColorPhase < Math.PI) {
vLine.tint = 0xFF0066; // Hot pink
} else if (vColorPhase < Math.PI * 1.5) {
vLine.tint = 0x66FF00; // Electric lime
} else {
vLine.tint = 0xFF6600; // Electric orange
}
}
};
return self;
});
var Block = Container.expand(function () {
var self = Container.call(this);
// Array of available block assets
var blockAssets = ['block1', 'block2', 'block3', 'block4'];
// Randomly select a block asset
var selectedAsset = blockAssets[Math.floor(Math.random() * blockAssets.length)];
var blockGraphics = self.attachAsset(selectedAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Random neon colors
var colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0066, 0x66ff00];
blockGraphics.tint = colors[Math.floor(Math.random() * colors.length)];
self.update = function () {
self.y += self.speed;
};
return self;
});
var BombBlock = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Distinctive red color for danger
bombGraphics.tint = 0xff0000;
// Add warning pulsing animation to make it stand out as dangerous
tween(bombGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bombGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
if (!self.caught) {
tween(bombGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut
});
}
}
});
}
});
self.update = function () {
self.y += self.speed;
};
return self;
});
var BoosterBlock = Container.expand(function () {
var self = Container.call(this);
var boosterGraphics = self.attachAsset('booster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.speed = 8;
self.lane = 0;
self.caught = false;
// Distinctive golden/yellow color for booster
boosterGraphics.tint = 0xFFD700;
// Add pulsing animation to make it stand out
tween(boosterGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(boosterGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
if (!self.caught) {
tween(boosterGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
});
}
});
self.update = function () {
self.y += self.speed;
};
return self;
});
var Life = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
// Add neon glow effect with retrowave pink
heartGraphics.tint = 0xff0066;
self.setActive = function (active) {
if (active) {
heartGraphics.alpha = 1.0;
heartGraphics.tint = 0xff0066; // Bright neon pink
} else {
heartGraphics.alpha = 0.3;
heartGraphics.tint = 0x440022; // Dim dark pink
}
};
return self;
});
var MicrophoneIcon = Container.expand(function () {
var self = Container.call(this);
// Create microphone icon using a shape asset
var micGraphics = self.attachAsset('micBar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 1.5
});
// Style the microphone with synthwave neon colors
micGraphics.tint = 0x00ffcc;
micGraphics.alpha = 0.6;
// Audio detection state
self.isActive = false;
self.lastVolume = 0;
self.update = function () {
if (gameMode === 'microphone') {
var currentVolume = facekit.volume;
// Update visual state based on audio levels
if (currentVolume > 0.3) {
// Active state - bright glow
if (!self.isActive) {
self.isActive = true;
tween(micGraphics, {
tint: 0x00ff66,
alpha: 1.0,
scaleX: 2.4,
scaleY: 1.8
}, {
duration: 100,
easing: tween.easeOut
});
}
// Beat detection - extra bright flash
if (detectBeat()) {
tween(micGraphics, {
tint: 0xffffff,
scaleX: 3.0,
scaleY: 2.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(micGraphics, {
tint: 0x00ff66,
scaleX: 2.4,
scaleY: 1.8
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
} else if (currentVolume > 0.1) {
// Medium activity - moderate glow
if (self.isActive) {
tween(micGraphics, {
tint: 0x00ffcc,
alpha: 0.8,
scaleX: 2.2,
scaleY: 1.6
}, {
duration: 200,
easing: tween.easeInOut
});
}
self.isActive = false;
} else {
// Inactive state - dim glow with subtle pulsing
if (self.isActive) {
self.isActive = false;
tween(micGraphics, {
tint: 0x004466,
alpha: 0.5,
scaleX: 2.0,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
}
// Subtle pulsing to show mic is available
var pulsePhase = LK.ticks * 0.02;
micGraphics.alpha = 0.4 + 0.2 * Math.sin(pulsePhase);
}
self.lastVolume = currentVolume;
}
};
return self;
});
var Ship = Container.expand(function (shipType) {
var self = Container.call(this);
// Use the provided shipType or default to classicShip
var assetType = shipType || 'classicShip';
var shipGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
});
// Add glow effect
shipGraphics.filters = [];
self.currentLane = 1; // 0 = left, 1 = center, 2 = right
self.targetX = 1024; // center position
self.update = function () {
// Smooth movement to target lane
var diff = self.targetX - self.x;
if (Math.abs(diff) > 2) {
self.x += diff * 0.15;
} else {
self.x = self.targetX;
}
};
self.moveToLane = function (lane) {
self.currentLane = lane;
if (lane === 0) {
self.targetX = 341; // left lane
} else if (lane === 1) {
self.targetX = 1024; // center lane
} else if (lane === 2) {
self.targetX = 1707; // right lane
}
// Add movement animation
tween(shipGraphics, {
scaleX: 3.6,
scaleY: 2.4
}, {
duration: 100,
onFinish: function onFinish() {
tween(shipGraphics, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
}
});
};
return self;
});
var UnlockableShip = Container.expand(function (shipData) {
var self = Container.call(this);
// Use ship data to create the ship graphics
var shipGraphics = self.attachAsset(shipData.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Apply the ship's unique color
shipGraphics.tint = shipData.color;
// Store ship properties
self.shipData = shipData;
self.extraLives = shipData.extraLives;
// Add glow effect for unlocked ships
self.addGlowEffect = function () {
tween(shipGraphics, {
alpha: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shipGraphics, {
alpha: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.addGlowEffect();
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
// Create synthwave background with gradient effect
game.setBackgroundColor(0x270050);
// Create moving background grid
var backgroundGrid = game.addChild(new BackgroundGrid());
// Array of synthwave tracks for classic mode
var synthwaveTracks = ['synthwave1', 'synthwave2', 'synthwave3', 'lofitrack', 'retrowave'];
// Ship unlocking system
var unlockedShips = storage.unlockedShips || [];
var availableShipTypes = ['ship1', 'ship2', 'ship3', 'ship4', 'ship5'];
var shipColors = [0xff0066, 0x00ffcc, 0xffcc00, 0x66ff00, 0xcc00ff];
var shipNames = ['Neon Striker', 'Cyber Cruiser', 'Solar Surfer', 'Plasma Rider', 'Void Walker'];
// Generate random ship data
function generateRandomShip() {
var randomIndex = Math.floor(Math.random() * availableShipTypes.length);
var baseColor = shipColors[randomIndex];
// Add some randomization to the color
var colorVariation = Math.floor(Math.random() * 0x333333);
var finalColor = baseColor + colorVariation & 0xffffff;
return {
id: 'ship_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
assetId: availableShipTypes[randomIndex],
color: finalColor,
name: shipNames[randomIndex] + ' MK-' + (Math.floor(Math.random() * 9) + 1),
extraLives: 1,
unlocked: true
};
}
// Current selected ship
var currentShipData = null;
var currentTrackIndex = 0; // Index of currently playing track
var completedTracks = []; // Array to track which tracks have been completed
var musicStartTime = 0; // Time when current music track started
var trackDuration = 60000; // Approximate track duration in milliseconds (60 seconds)
// Game state
var gameMode = 'menu'; // 'menu', 'classic', 'microphone'
var ship;
var blocks = [];
var score = 0;
var combo = 0;
var bestCombo = 0;
var lives = 3;
var maxLives = 3;
var lifeDisplays = [];
var lastSpawnTime = 0;
var spawnInterval = 1000; // 1 second
var gameSpeed = 1.0; // Speed multiplier for the game
var baseBlockSpeed = 8; // Base speed for blocks
var currentMusicTrack = null; // Track current playing music
// Beat detection variables
var audioBuffer = [];
var beatThreshold = 0.4;
var lastBeatTime = 0;
var minBeatInterval = 200; // Minimum ms between beats
var volumeHistory = [];
var bufferSize = 10;
var beatSensitivity = 1.5;
// Lane positions - equal width lanes
var lanePositions = [341, 1024, 1707];
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0x00FFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 50;
scoreTxt.y = 50;
LK.gui.topLeft.addChild(scoreTxt);
var comboTxt = new Text2('Combo: 0', {
size: 50,
fill: 0xFF00FF
});
comboTxt.anchor.set(0, 0);
comboTxt.x = 50;
comboTxt.y = 120;
LK.gui.topLeft.addChild(comboTxt);
// Create life display hearts in top right
for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) {
var lifeHeart = new Life();
lifeHeart.x = -80 - lifeIndex * 80; // Position from right edge
lifeHeart.y = 60;
lifeHeart.setActive(true);
lifeDisplays.push(lifeHeart);
LK.gui.topRight.addChild(lifeHeart);
}
// Create menu window frame with neon border
var menuFrameBorder = LK.getAsset('menuFrameBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
menuFrameBorder.x = 0;
menuFrameBorder.y = -100;
LK.gui.center.addChild(menuFrameBorder);
// Create semi-transparent background panel
var menuFrame = LK.getAsset('menuFrame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
menuFrame.x = 0;
menuFrame.y = -100;
LK.gui.center.addChild(menuFrame);
// Add pulsing animation to border
tween(menuFrameBorder, {
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(menuFrameBorder, {
alpha: 0.4
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the pulsing animation
tween(menuFrameBorder, {
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
}
});
// Create mode selection buttons with neon glow outline
var classicButton = new Text2('1. CLASSIC MODE\nBuilt-in synthwave music', {
size: 60,
fill: 0x00FFFF,
stroke: 0x00FFFF,
strokeThickness: 3
});
classicButton.anchor.set(0.5, 0.5);
classicButton.x = 0;
classicButton.y = -200;
LK.gui.center.addChild(classicButton);
var micButton = new Text2('2. MICROPHONE MODE\nSync with your music', {
size: 60,
fill: 0xFF00FF,
stroke: 0xFF00FF,
strokeThickness: 3
});
micButton.anchor.set(0.5, 0.5);
micButton.x = 0;
micButton.y = -50;
LK.gui.center.addChild(micButton);
var instructionTxt = new Text2('TAP A BUTTON TO SELECT MODE', {
size: 50,
fill: 0xFFFFFF
});
instructionTxt.anchor.set(0.5, 0.5);
instructionTxt.y = 150;
LK.gui.center.addChild(instructionTxt);
// Ship unlock UI elements (initially hidden)
var unlockFrameBorder = LK.getAsset('unlockBorder', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
unlockFrameBorder.x = 0;
unlockFrameBorder.y = 0;
LK.gui.center.addChild(unlockFrameBorder);
var unlockFrame = LK.getAsset('unlockFrame', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
unlockFrame.x = 0;
unlockFrame.y = 0;
LK.gui.center.addChild(unlockFrame);
var unlockTitleTxt = new Text2('SHIP UNLOCKED!', {
size: 60,
fill: 0x00FFFF
});
unlockTitleTxt.anchor.set(0.5, 0.5);
unlockTitleTxt.x = 0;
unlockTitleTxt.y = -120;
unlockTitleTxt.alpha = 0;
LK.gui.center.addChild(unlockTitleTxt);
var unlockDescTxt = new Text2('', {
size: 40,
fill: 0xFFFFFF
});
unlockDescTxt.anchor.set(0.5, 0.5);
unlockDescTxt.x = 0;
unlockDescTxt.y = -60;
unlockDescTxt.alpha = 0;
LK.gui.center.addChild(unlockDescTxt);
var unlockShipDisplay = null;
var unlockContinueTxt = new Text2('TAP TO CONTINUE', {
size: 50,
fill: 0xFF00FF
});
unlockContinueTxt.anchor.set(0.5, 0.5);
unlockContinueTxt.x = 0;
unlockContinueTxt.y = 120;
unlockContinueTxt.alpha = 0;
LK.gui.center.addChild(unlockContinueTxt);
// Create microphone icon (initially hidden)
var microphoneIcon = new MicrophoneIcon();
microphoneIcon.x = -80;
microphoneIcon.y = 200;
microphoneIcon.visible = false;
LK.gui.topRight.addChild(microphoneIcon);
// Create camera disable button
var cameraButton = new Text2('CAM', {
size: 40,
fill: 0x00ffff,
stroke: 0x00ffff,
strokeThickness: 2
});
cameraButton.anchor.set(0.5, 0.5);
cameraButton.x = -80;
cameraButton.y = 300;
cameraButton.visible = false;
LK.gui.topRight.addChild(cameraButton);
// Camera state tracking
var cameraEnabled = true;
// Create lane dividers
var lane1 = game.addChild(LK.getAsset('lane1', {
x: 682,
y: 0
}));
var lane2 = game.addChild(LK.getAsset('lane2', {
x: 1366,
y: 0
}));
// Create ship
ship = game.addChild(new Ship());
ship.x = 1024;
ship.y = 2400;
function startGame(mode) {
gameMode = mode;
score = 0;
combo = 0;
bestCombo = 0;
// Calculate lives based on current ship
var baseLives = 3;
var extraLives = currentShipData ? currentShipData.extraLives : 0;
lives = baseLives + extraLives;
maxLives = lives;
blocks = [];
lastSpawnTime = 0;
gameSpeed = 1.0; // Reset game speed
currentTrackIndex = 0; // Reset track progression
completedTracks = []; // Reset completed tracks
musicStartTime = 0; // Reset music timer
// Reset and update life display for new max lives
// Remove existing life displays
for (var i = 0; i < lifeDisplays.length; i++) {
lifeDisplays[i].destroy();
}
lifeDisplays = [];
// Create new life displays based on current max lives
for (var lifeIndex = 0; lifeIndex < maxLives; lifeIndex++) {
var lifeHeart = new Life();
lifeHeart.x = -80 - lifeIndex * 80;
lifeHeart.y = 60;
lifeHeart.setActive(true);
lifeDisplays.push(lifeHeart);
LK.gui.topRight.addChild(lifeHeart);
}
// Reset beat detection variables
audioBuffer = [];
volumeHistory = [];
lastBeatTime = 0;
// Hide menu elements
classicButton.visible = false;
micButton.visible = false;
instructionTxt.visible = false;
menuFrame.visible = false;
menuFrameBorder.visible = false;
// Destroy existing ship and create new one with appropriate asset
if (ship) {
ship.destroy();
}
// Create ship with mode-specific asset
if (mode === 'classic') {
ship = game.addChild(new Ship('classicShip'));
} else {
ship = game.addChild(new Ship('microphoneShip'));
}
ship.x = 1024;
ship.y = 2400;
// Start background music only in classic mode
if (mode === 'classic') {
// Shuffle the playlist for randomized track order
for (var i = synthwaveTracks.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = synthwaveTracks[i];
synthwaveTracks[i] = synthwaveTracks[j];
synthwaveTracks[j] = temp;
}
// Start with first track in shuffled sequence
currentMusicTrack = synthwaveTracks[currentTrackIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = Date.now(); // Track when music started using current time
// Hide microphone icon and camera button in classic mode
microphoneIcon.visible = false;
cameraButton.visible = false;
} else {
// Stop background music in microphone mode to focus on external audio
currentMusicTrack = null;
LK.stopMusic();
// Facekit plugin automatically handles microphone access when volume is accessed
// No initialization needed - just use facekit.volume for beat detection
// Show microphone icon and camera button in microphone mode
microphoneIcon.visible = true;
cameraButton.visible = true;
// Reset camera state when starting microphone mode
cameraEnabled = true;
cameraButton.setText('CAM');
cameraButton.fill = 0x00ffff;
cameraButton.stroke = 0x00ffff;
}
updateUI();
}
function spawnBlock() {
var block;
var random = Math.random();
// 10% chance to spawn a bomb block
if (random < 0.10) {
block = new BombBlock();
// 15% chance to spawn a booster block
} else if (random < 0.25) {
block = new BoosterBlock();
} else {
block = new Block();
}
var lane;
if (gameMode === 'microphone') {
// Create more musical patterns based on audio intensity
var intensity = facekit.volume;
if (intensity > 0.8) {
// High intensity - spawn in multiple lanes or center
lane = Math.random() > 0.5 ? 1 : Math.floor(Math.random() * 3);
} else if (intensity > 0.5) {
// Medium intensity - prefer outer lanes
lane = Math.random() > 0.5 ? 0 : 2;
} else {
// Low intensity - single random lane
lane = Math.floor(Math.random() * 3);
}
} else {
// Classic mode - random lane
lane = Math.floor(Math.random() * 3);
}
block.lane = lane;
block.x = lanePositions[lane];
block.y = -50;
block.speed = baseBlockSpeed * gameSpeed; // Apply game speed multiplier
blocks.push(block);
game.addChild(block);
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
comboTxt.setText('Combo: ' + combo + ' (Best: ' + bestCombo + ')');
}
function checkCollisions() {
for (var i = blocks.length - 1; i >= 0; i--) {
var block = blocks[i];
// Check if block is in ship's catch zone
if (!block.caught && block.y >= ship.y - 100 && block.y <= ship.y + 100) {
if (block.lane === ship.currentLane) {
// Block caught!
block.caught = true;
// Check if it's a bomb block
if (block instanceof BombBlock) {
// Bomb effects - lose 2 lives and reset combo
lives -= 2;
combo = 0;
// Update life display for lost lives
for (var lifeIdx = Math.max(0, lives); lifeIdx < Math.min(lives + 2, lifeDisplays.length); lifeIdx++) {
lifeDisplays[lifeIdx].setActive(false);
// Add dramatic life loss animation - capture lifeIdx in closure
(function (capturedIdx) {
tween(lifeDisplays[capturedIdx], {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 300,
onFinish: function onFinish() {
tween(lifeDisplays[capturedIdx], {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300
});
}
});
})(lifeIdx);
}
// Dramatic visual feedback for bomb
LK.effects.flashScreen(0xff0000, 800);
LK.effects.flashObject(ship, 0xff0000, 800);
// Play miss sound for bomb hit
LK.getSound('miss').play();
// Check for game over
if (lives <= 0) {
// Unlock a new ship before showing game over
var newShip = unlockNewShip();
currentShipData = newShip;
// Delay game over to show ship unlock
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
return;
}
// Check if it's a booster block
} else if (block instanceof BoosterBlock) {
// Booster effects
score += 25 + combo * 3; // More points for booster
combo += 2; // Extra combo bonus
// Increase game speed (cap at 2.5x)
gameSpeed = Math.min(gameSpeed + 0.2, 2.5);
// Apply speed to all existing blocks
for (var j = 0; j < blocks.length; j++) {
blocks[j].speed = baseBlockSpeed * gameSpeed;
}
// Restart music with faster tempo (only in classic mode)
if (gameMode === 'classic' && currentMusicTrack) {
LK.stopMusic();
// Small delay before restarting music to avoid audio glitches
LK.setTimeout(function () {
LK.playMusic(currentMusicTrack);
}, 50);
}
// Special visual feedback for booster
LK.effects.flashScreen(0xFFD700, 300);
LK.effects.flashObject(ship, 0xFFD700, 500);
// Play layered synthwave sounds for booster
LK.getSound('synthBass').play();
LK.setTimeout(function () {
LK.getSound('synthLead').play();
}, 100);
} else {
// Regular block
score += 10 + combo * 2;
combo++;
}
if (combo > bestCombo) {
bestCombo = combo;
}
// Visual feedback
LK.effects.flashObject(block, 0xffffff, 200);
tween(block, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200
});
// Play musical sound that matches the current track
var trackSounds = {
'synthwave1': ['synthBass', 'synthLead', 'synthDrum', 'synthChord'],
'synthwave2': ['synthLead', 'synthChord', 'synthBass', 'synthDrum'],
'synthwave3': ['synthDrum', 'synthBass', 'synthLead', 'synthChord'],
'lofitrack': ['synthBass', 'synthChord'],
'retrowave': ['synthLead', 'synthDrum', 'synthChord']
};
var soundsForTrack = trackSounds[currentMusicTrack] || ['synthBass', 'synthLead', 'synthDrum', 'synthChord'];
// Calculate beat position based on music timing to stay in rhythm
var baseBPM = 120;
var currentBPM = baseBPM * gameSpeed;
var beatLength = 60000 / currentBPM; // Beat duration in ms
var timeElapsed = Date.now() - musicStartTime;
var beatPosition = Math.floor(timeElapsed / beatLength % soundsForTrack.length);
var rhythmicSound = soundsForTrack[beatPosition];
LK.getSound(rhythmicSound).play();
updateUI();
// Remove block
block.destroy();
blocks.splice(i, 1);
continue;
}
}
// Check if block missed (passed ship)
if (!block.caught && block.y > ship.y + 150) {
if (block.lane === ship.currentLane) {
// Block missed - lose life and reset combo
lives--;
combo = 0;
// Update life display
if (lives >= 0 && lives < lifeDisplays.length) {
var currentLifeIndex = lives; // Capture current lives value
lifeDisplays[currentLifeIndex].setActive(false);
// Add life loss animation
tween(lifeDisplays[currentLifeIndex], {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(lifeDisplays[currentLifeIndex], {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
}
LK.effects.flashScreen(0xff0000, 300);
LK.getSound('miss').play();
// Check for game over
if (lives <= 0) {
LK.showGameOver();
return;
}
updateUI();
}
block.caught = true; // Mark as processed
}
// Remove blocks that are off screen
if (block.y > 2800) {
block.destroy();
blocks.splice(i, 1);
}
}
}
function showShipUnlock(newShipData) {
// Show unlock frame with animation
tween(unlockFrameBorder, {
alpha: 0.8
}, {
duration: 300
});
tween(unlockFrame, {
alpha: 0.9
}, {
duration: 300
});
tween(unlockTitleTxt, {
alpha: 1
}, {
duration: 300
});
// Update description text
unlockDescTxt.setText(newShipData.name + '\n+1 Extra Life');
tween(unlockDescTxt, {
alpha: 1
}, {
duration: 300
});
// Create and display the unlocked ship
if (unlockShipDisplay) {
unlockShipDisplay.destroy();
}
unlockShipDisplay = new UnlockableShip(newShipData);
unlockShipDisplay.x = 0;
unlockShipDisplay.y = 20;
LK.gui.center.addChild(unlockShipDisplay);
unlockShipDisplay.addGlowEffect();
tween(unlockContinueTxt, {
alpha: 1
}, {
duration: 300
});
// Add pulsing animation to continue text
tween(unlockContinueTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unlockContinueTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unlockContinueTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
});
}
function hideShipUnlock() {
tween(unlockFrameBorder, {
alpha: 0
}, {
duration: 300
});
tween(unlockFrame, {
alpha: 0
}, {
duration: 300
});
tween(unlockTitleTxt, {
alpha: 0
}, {
duration: 300
});
tween(unlockDescTxt, {
alpha: 0
}, {
duration: 300
});
tween(unlockContinueTxt, {
alpha: 0
}, {
duration: 300
});
if (unlockShipDisplay) {
tween(unlockShipDisplay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
unlockShipDisplay.destroy();
unlockShipDisplay = null;
}
});
}
}
function unlockNewShip() {
// Generate a new random ship
var newShip = generateRandomShip();
// Add to unlocked ships
unlockedShips.push(newShip);
// Keep only the 5 most recent ships
if (unlockedShips.length > 5) {
unlockedShips = unlockedShips.slice(-5);
}
// Save to storage
storage.unlockedShips = unlockedShips;
// Show unlock animation
showShipUnlock(newShip);
return newShip;
}
function progressToNextTrack() {
if (gameMode !== 'classic') {
return;
}
// Mark current track as completed
if (completedTracks.indexOf(synthwaveTracks[currentTrackIndex]) === -1) {
completedTracks.push(synthwaveTracks[currentTrackIndex]);
}
// Move to next track
currentTrackIndex++;
// Check if all tracks have been completed
if (currentTrackIndex >= synthwaveTracks.length) {
// Unlock a new ship before showing victory
var newShip = unlockNewShip();
currentShipData = newShip;
// Delay victory screen to show ship unlock
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
return;
}
// Play next track
currentMusicTrack = synthwaveTracks[currentTrackIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = Date.now(); // Reset music timer using current time
}
function detectBeat() {
var currentVolume = facekit.volume;
var currentTime = Date.now();
// Add current volume to history buffer
volumeHistory.push(currentVolume);
if (volumeHistory.length > bufferSize) {
volumeHistory.shift();
}
// Calculate average volume from buffer
var avgVolume = 0;
for (var i = 0; i < volumeHistory.length; i++) {
avgVolume += volumeHistory[i];
}
avgVolume = avgVolume / volumeHistory.length;
// Detect beat: current volume significantly higher than average
var volumeSpike = currentVolume > avgVolume * beatSensitivity;
var timeSinceLastBeat = currentTime - lastBeatTime;
var beatDetected = volumeSpike && currentVolume > beatThreshold && timeSinceLastBeat > minBeatInterval;
if (beatDetected) {
lastBeatTime = currentTime;
// Add visual feedback for beat detection
LK.effects.flashScreen(0x440088, 100);
return true;
}
return false;
}
function shouldSpawnBlock() {
if (gameMode === 'microphone') {
// Spawn based on beat detection from microphone input
return detectBeat();
} else {
// Classic mode - spawn every second
return LK.ticks - lastSpawnTime >= spawnInterval / (1000 / 60);
}
}
// Button event handlers
classicButton.down = function (x, y, obj) {
if (gameMode === 'menu') {
// Add button press animation
tween(classicButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(classicButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
startGame('classic');
}
};
micButton.down = function (x, y, obj) {
if (gameMode === 'menu') {
// Add button press animation
tween(micButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(micButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
startGame('microphone');
}
};
// Camera button event handler
cameraButton.down = function (x, y, obj) {
if (gameMode === 'microphone') {
// Toggle camera state
cameraEnabled = !cameraEnabled;
// Update button appearance based on state
if (cameraEnabled) {
cameraButton.setText('CAM');
cameraButton.fill = 0x00ffff;
cameraButton.stroke = 0x00ffff;
} else {
cameraButton.setText('CAM\nOFF');
cameraButton.fill = 0xff0066;
cameraButton.stroke = 0xff0066;
}
// Add button press animation
tween(cameraButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
onFinish: function onFinish() {
tween(cameraButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
// Visual feedback
LK.effects.flashObject(cameraButton, 0xffffff, 200);
}
};
// Touch controls for lane switching during gameplay
game.down = function (x, y, obj) {
if (gameMode === 'menu') {
return;
}
// Check if unlock screen is visible
if (unlockFrame.alpha > 0) {
hideShipUnlock();
return;
}
// Lane switching
if (x < 1024) {
// Left side tapped - move left
if (ship.currentLane > 0) {
ship.moveToLane(ship.currentLane - 1);
}
} else {
// Right side tapped - move right
if (ship.currentLane < 2) {
ship.moveToLane(ship.currentLane + 1);
}
}
};
// Main game update
game.update = function () {
if (gameMode === 'menu') {
// Hide microphone icon and camera button in menu
microphoneIcon.visible = false;
cameraButton.visible = false;
return;
}
// Check for track progression in classic mode
if (gameMode === 'classic' && musicStartTime > 0) {
var currentTime = Date.now();
var timeElapsed = currentTime - musicStartTime;
// Check if current track duration has been reached
if (timeElapsed >= trackDuration) {
progressToNextTrack();
}
}
// Spawn blocks
if (shouldSpawnBlock()) {
spawnBlock();
lastSpawnTime = LK.ticks;
}
// Check collisions
checkCollisions();
};
synthwave neon glow audiosurf or f-zero like ship. In-Game asset. 2d. High contrast. No shadows
faint glowing outlines with a shimmer effect retro synthwave style. In-Game asset. 2d. High contrast. No shadows
a musical note thats bright and neon thats also really cool looking. In-Game asset. 2d. High contrast. No shadows
synthwave bright neon glow audiosurf or f-zero like ship In-Game asset. 2d. High contrast. No shadows
synthwave bright neon glow audiosurf or f-zero like ship In-Game asset. 2d. High contrast. No shadows. facing upright vertical
a musical note thats bright and neon thats also really cool looking. In-Game asset. 2d. High contrast. No shadows
- Shape: a glowing neon **circle or hexagon** - Color: bright **red or magenta** - Inner symbol: a small white or yellow **skull**, **explosion**, or **⚠️ warning icon** in the center - Glow: apply a soft outer glow that pulses slightly - Visual style: match the **synthwave aesthetic** (think neon, arcade, retro-futuristic) - Size: same as the existing note blocks - Animation: gently pulsate or shimmer while falling. In-Game asset. 2d. High contrast. No shadows
a bright laser vertical line retro style. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
a line that is set to the theme of this game that looks like lightsabre. In-Game asset. 2d. High contrast. No shadows
synthwave bright neon glow audiosurf or f-zero like ship In-Game asset. 2d. High contrast. No shadows. facing upright vertical 3d like