User prompt
could we also add a booster block along with the others for the player to collect which will increase the players speed and increase the tempo of the music. โช๐ก Consider importing and using the following plugins: @upit/tween.v1
User prompt
make it so!
User prompt
make it so!
User prompt
sounds good make it so!
User prompt
sick! make it so please!
User prompt
can you move the 2 buttons a little higher
User prompt
make it so!
User prompt
cool! love this! make it so! โช๐ก Consider importing and using the following plugins: @upit/facekit.v1
User prompt
try again โช๐ก Consider importing and using the following plugins: @upit/facekit.v1
User prompt
For the background image can you Add a moving neon grid or subtle scrolling starfield for synthwave effect Use a linear gradient (top to bottom) in dark purples/blues โช๐ก Consider importing and using the following plugins: @upit/tween.v1
User prompt
make it so!
User prompt
cool sounds good make it so !
Code edit (1 edits merged)
Please save this source code
User prompt
Sound Surfer
Initial prompt
Create a simple rhythm game called "Sound Surfer" with the following features: A main menu that will be within a black and neon box with two options: option 1 will include use of the facekit which will use the mic for recording the music and beats that the player decides to use. Option 2 will NOT use the facekit and mic for beats/music and instead will use a randomly generated beat/music that you will create for the player. The game has three vertical lanes: left, center, and right. The player is a small glowing cool looking ship like from F-zero games at the bottom of the screen that can switch between lanes by clicking. Clicking on the left half of the screen moves the player one lane to the left. Clicking on the right half of the screen moves the player one lane to the right. The player cannot move outside the 3 lanes. Colored blocks spawn at the top of the screen in a random lane and fall down at a constant speed. When a block reaches the playerโs lane and collides with the player, the block disappears, and: +10 points are added to the score. A combo counter increases. If the block passes the player without a collision, the combo resets. Display the current "score" and "combo" in the top corner of the screen. Use a minimal neon/synthwave visual style: Dark background Bright glowing blocks and player Simple grid or lines to separate the lanes Use simple logic for now: Spawn a block every 1 second using a timer.
/**** * 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