Code edit (2 edits merged)
Please save this source code
User prompt
Ok, now make final orb scale twice bigger
User prompt
now orb have the correct orientation, but they don't move and grow like gates. Please step back and analyse then fix orbs behaviour to match gate's one
User prompt
adjust orbs spawning and update so that they look like gates in term of position and movement
Code edit (1 edits merged)
Please save this source code
User prompt
Now make orbs behave like gates after they are spawned, orbs should follow a similar trajectory
User prompt
in GateManager.spawnGateAtTime(), call in OrbManager to spawn an Orb at the 'free angle' when spawning 2 gates.
User prompt
in OrbManager and Orb, use the same technique as GateManager.spawnGateAtTime() and Gate.updateScale() to spawn orbs and make them move ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
enhance orbs move : clone the system used for gates
User prompt
add an obrManager to spawn orbs; they should spanw from center with small scale and increase scale like the system used for gates ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'scaleX')' in or related to this line: 'var currentScale = orb.orbAsset.scaleX;' Line Number: 622
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'rotation')' in or related to this line: 'orb.orbAsset.rotation = orbAngle; // Assuming orbAsset exists and needs rotation' Line Number: 589
User prompt
Please fix the bug: 'TypeError: orb.setColor is not a function' in or related to this line: 'orb.setColor(orbColor); // Assuming Orb class has a setColor method' Line Number: 562
User prompt
Please fix the bug: 'ReferenceError: orbManager is not defined' in or related to this line: 'lastTick = now;' Line Number: 3174
User prompt
Please fix the bug: 'ReferenceError: orbManager is not defined' in or related to this line: 'if (orbManager) {' Line Number: 3176
User prompt
add an obrManager to spawn orbs like gates
User prompt
Ok now add and orb class but don't use it. add an anim using assets orb0,orb1,orb2,orb3,orb4 and orb5 as frames and changing their alpha in sequence ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
ok, add some randomness in WorldManager target's selection : don't only go to left border of left BG then right border of right BG but selected a random target between the two and animate to it then choose another random target ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ok, add some randomness in WorldManager target's selection
Code edit (1 edits merged)
Please save this source code
User prompt
in WorldManager make BGs shift very slowly during song play to left the right ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add a new worldManager to display 2 worldBackground side by side (one with scaleX = -1)
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ /***********************************************************************************/ /******************************* UTILITY FUNCTIONS *********************************/ /***********************************************************************************/ var BackgroundManager = Container.expand(function () { var self = Container.call(this); // Create three background instances for smoother tunnel effect self.bg0 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 2.4, scaleY: 2.4, alpha: 1 }); self.bg1 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.1, scaleY: 1.1, alpha: 1 }); self.bg2 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.5, scaleY: 0.5, alpha: 1 }); self.bg3 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.22, scaleY: 0.22, alpha: 1 }); self.bg4 = self.attachAsset('background01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.11, scaleY: 0.11, alpha: 1 }); self.tore0 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.2, scaleY: 1.2, alpha: 0 }); self.tore1 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.6, scaleY: 0.6, alpha: 0 }); self.tore2 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.26, scaleY: 0.26, alpha: 0 }); self.tore3 = self.attachAsset('tore', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.12, scaleY: 0.12, alpha: 0 }); // Apply different tints in debug mode if (isDebug) { self.bg1.tint = 0xFF0000; // Red tint for first background self.tore1.tint = 0x00FF00; // Green tint for first tore self.bg2.tint = 0x00FFFF; // Cyan tint for second background self.tore2.tint = 0xFF00FF; // Magenta tint for second tore self.bg3.tint = 0xfff200; // Yellow tint for third background } // Animation properties self.bgAnimationSpeed = globalSpeed / 1000; //0.002; self.bgAnimationAcceleration = 2; // Add tore assets between backgrounds for animation self.backgrounds = [self.bg0, self.tore0, self.bg1, self.tore1, self.bg2, self.tore2, self.bg3, self.tore3, self.bg4]; //self.backgrounds = [self.bg0, self.bg1, self.bg2, self.bg3]; // Define initial scale for each background/torus (tore: 0.26, bg: 0.22) //self.bgInitialScales = [0.22, 0.22, 0.22, 0.22, 0.22]; //self.bgInitialScales = [0, 0, 0, 0, 0]; //self.bgInitialScales = [0, 0, 0, 0, 0, 0, 0, 0, 0]; // Animation state: single startTime for all backgrounds/torus self.bgAnimStartTime = Date.now(); // Update method - handle background/torus scale animation self.update = function () { // Don't update if song hasn't started if (!songStarted) { return; } var now = Date.now(); var elapsed = now - self.bgAnimStartTime; var resetTriggered = false; for (var i = 0; i < self.backgrounds.length; i++) { var bg = self.backgrounds[i]; // Make the scale speed increase as the scale increases (e.g. exponential or quadratic growth) //var scaleMultiplier = bg.scaleX * self.bgAnimationAcceleration; //bg.scaleX += self.bgAnimationSpeed * scaleMultiplier; bg.scaleX += self.bgAnimationSpeed * bg.scaleX; bg.scaleY = bg.scaleX; if (bg.scaleX > 3.0) { bg.scaleX = 0.12; //self.bgInitialScales[i]; bg.scaleY = bg.scaleX; } //bg.alpha = Math.min(1, bg.scaleX + 0.66); bg.tint = 0x1697b8; // 0x33FF33; } }; return self; }); // Initialize the game; /***********************************************************************************/ /********************************** BALL CLASS *************************************/ /***********************************************************************************/ var Ball = Container.expand(function () { var self = Container.call(this); // Create and attach ball asset var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, tint: currentColor, alpha: 1 }); // Initialize ball properties self.speedX = 0; self.speedY = 0; // Track last intersecting state for each gate self.lastIntersectingGates = {}; // Update method to follow runner's position self.update = function () { // Don't process gates until song has started if (!songStarted) { return; } // Make ball follow runner's exact position if (runner) { self.x = runner.x; self.y = runner.y; } // Check for collisions with gates if (gateManager && gateManager.gates) { // Iterate backwards to avoid index shifting issues when removing gates for (var i = gateManager.gates.length - 1; i >= 0; i--) { var gate = gateManager.gates[i]; var gateId = gate.gateId; // Initialize tracking if needed if (self.lastIntersectingGates[gateId] === undefined) { self.lastIntersectingGates[gateId] = false; } // Check intersection with bounding box instead of gate asset, but ignore if gate scale > 1.0 var currentIntersecting = gate.gateAsset.scaleX >= minDetectionScale && gate.gateAsset.scaleX <= maxDetectionScale && self.intersects(gate.boundingBox); // Detect transition from not intersecting to intersecting if (!self.lastIntersectingGates[gateId] && currentIntersecting) { // Play hit sound playHitSound(); // Mark gate as being destroyed to prevent multiple triggers if (!gate.isDestroying) { gate.isDestroying = true; // Animate scale down tween(gate, { scaleX: 0, scaleY: 0 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { // Request destruction from gate manager gateManager.destroyGate(gate); } }); } } // Update last intersecting state self.lastIntersectingGates[gateId] = currentIntersecting; } // No cleanup needed - gate IDs are unique and won't be reused } }; return self; }); /***********************************************************************************/ /********************************** GATE CLASS *************************************/ /***********************************************************************************/ var Gate = Container.expand(function () { var self = Container.call(this); // Create gate asset with initial properties self.gateAsset = self.attachAsset('gate', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 0.26, // Start at same scale as tore2 scaleY: 0.26, alpha: 1, visible: false }); // Store direction angle for this gate self.directionAngle = 0; // Add bounding box for collision detection self.boundingBox = self.attachAsset('boundingBox', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2450, alpha: 1 // Invisible by default }); /* width: 200, heigh: 100, */ // Store the color for this gate self.gateColor = 0xFFFFFF; // Default white, will be set by manager // Store unique ID for this gate self.gateId = null; // Will be set by manager // Set the tint to match the gate color self.setColor = function (color) { self.gateColor = color; self.gateAsset.tint = color; // Show bounding box in debug mode //if (isDebug) { self.boundingBox.alpha = 0.8; //self.boundingBox.tint = color; } }; // Update scale to match background animation self.updateScale = function (newScale) { /* self.scaleX = newScale; self.scaleY = newScale; self.alpha = Math.min(1, newScale + 0.66); */ self.gateAsset.scaleX = newScale; self.gateAsset.scaleY = newScale; self.gateAsset.alpha = Math.min(1, newScale + 0.66); // Scale bounding box proportionally self.boundingBox.scaleX = newScale; // 3 * newScale / 0.26; // Maintain 300px width relative to gate scale self.boundingBox.scaleY = newScale; //0.3 * newScale / 0.26; // Maintain 30px height relative to gate scale // Calculate boundingBox position using directionAngle and scale // Calculate distance from center based on scale var distance = (2450 - 1366) * newScale; // Use directionAngle to position boundingBox self.boundingBox.x = centerX + distance * Math.cos(self.directionAngle + Math.PI * 0.5); self.boundingBox.y = centerY + distance * Math.sin(self.directionAngle + Math.PI * 0.5); self.boundingBox.rotation = self.directionAngle; }; return self; }); /***********************************************************************************/ /********************************** GATE MANAGER CLASS *****************************/ /***********************************************************************************/ var GateManager = Container.expand(function () { var self = Container.call(this); // Array to hold gates self.gates = []; // Animation timing self.gateAnimStartTime = Date.now(); self.gateAnimationSpeed = globalSpeed / 1000; // Same as background // Song timing properties self.currentSong = songListV3[0]; self.songStartTime = Date.now(); self.currentNoteIndex = 0; self.noteSpawnScale = 0.12; // Initial scale for new gates matching smallest tore self.lastGateAngle = null; // Track last gate angle for path continuity self.lastProcessedBeat = -1; // Track last processed beat for speaker synchronization // Spawn a single gate at current time self.spawnGateAtTime = function () { // Get the current beat value var beatValue = null; if (self.currentNoteIndex < self.currentSong.songBeats.length) { beatValue = self.currentSong.songBeats[self.currentNoteIndex].beat; } // Map beat to key for color selection var keyNumber = parseInt(beatValue, 10) || 1; // Default to 1 if parse fails var noteKey = 'Key' + keyNumber; var keyColor = keyColorMap[noteKey] || currentColor; // Default to currentColor if key not found // --- Calculate gate travel time so it reaches the player at the correct beat time --- var startScale = self.noteSpawnScale; var endScale = 1.0; // The scale at which the gate should reach the player (runner) var speed = self.gateAnimationSpeed; // This is the per-tick scale growth factor var timeToReachPlayer = Math.log(endScale / startScale) / speed; // ms // Determine beat angle (where the safe path is) var beatAngle = centerAngle; if (beatValue === "1") { beatAngle = rightAngle; } else if (beatValue === "2") { beatAngle = leftAngle; } else { // Beat values 0 and 3 go to center beatAngle = centerAngle; } // Store this angle for the next gate self.lastGateAngle = beatAngle; // Create array of all possible angles var allAngles = [leftAngle, centerAngle, rightAngle]; var gateAngles = []; // Filter out the beat angle to get the non-beat angles for (var i = 0; i < allAngles.length; i++) { if (allAngles[i] !== beatAngle) { gateAngles.push(allAngles[i]); } } // Calculate timing var beatTime = self.currentSong.songBeats[self.currentNoteIndex].time; var now = Date.now(); var songElapsed = now - self.songStartTime; var spawnTime = beatTime - timeToReachPlayer; // Create gates at non-beat positions for (var j = 0; j < gateAngles.length; j++) { var gate = new Gate(); gate.setColor(keyColor); gate.updateScale(self.noteSpawnScale); // Store spawn time for tracking gate.spawnTime = Date.now() + 200 / globalSpeed; gate.colorIndex = 0; // Assign unique ID to gate gate.gateId = getNextGateId(); // Store the note key for this gate gate.noteKey = noteKey; // Set the direction angle for this gate gate.directionAngle = gateAngles[j]; // Apply rotation to gate asset gate.gateAsset.rotation = gateAngles[j]; // Spawn or schedule the gate if (songElapsed >= spawnTime) { // Spawn now self.gates.push(gate); self.addChild(gate); } else { // Schedule spawn for later (function (g) { LK.setTimeout(function () { self.gates.push(g); self.addChild(g); }, spawnTime - songElapsed); })(gate); } } }; // Update gates animation self.update = function () { // Don't update if song hasn't started if (!songStarted) { return; } var now = Date.now(); var songElapsed = now - self.songStartTime; // Check if we need to spawn a new gate based on song timing if (self.currentNoteIndex < self.currentSong.songBeats.length) { var nextBeat = self.currentSong.songBeats[self.currentNoteIndex]; if (songElapsed >= nextBeat.time) { // Check if enough time has passed since last beat if (now - lastBeatTime >= skipBeatDelay) { // Spawn a new gate for this beat self.spawnGateAtTime(); lastBeatTime = now; } self.currentNoteIndex++; } } // Animate existing gates for (var i = self.gates.length - 1; i >= 0; i--) { var gate = self.gates[i]; var currentScale = gate.gateAsset.scaleX; // Increase scale with acceleration var newScale = currentScale + self.gateAnimationSpeed * currentScale; // Remove gate when too large if (newScale > 3.0) { gate.destroy(); self.gates.splice(i, 1); } else { gate.updateScale(newScale); } } // Check if song has ended and needs restart self.checkSongEnd(); }; // Reset song when it ends self.resetSong = function () { self.songStartTime = Date.now(); self.currentNoteIndex = 0; self.lastGateAngle = null; // Reset angle tracking for new song }; // Check if song has ended and restart self.checkSongEnd = function () { if (self.currentNoteIndex >= self.currentSong.songBeats.length) { // All beats have been spawned, check if we should restart var lastBeatTime = self.currentSong.songBeats[self.currentSong.songBeats.length - 1].time; var songElapsed = Date.now() - self.songStartTime; // Wait a bit after the last beat before restarting if (songElapsed > lastBeatTime + 5000) { self.resetSong(); } } }; // Destroy a specific gate self.destroyGate = function (gate) { var index = self.gates.indexOf(gate); if (index > -1) { self.gates.splice(index, 1); gate.destroy(); } }; return self; }); var Note = Container.expand(function () { var self = Container.call(this); // Create and attach note asset var noteGraphics = self.attachAsset('note', { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); // Note properties self.velocity = { x: 0, y: -5 }; // Default upward movement self.lifetime = 1500; // 1.5 seconds (reduced from 3 seconds) self.spawnTime = Date.now(); self.noteType = 'note'; // Can be 'note' or 'note2' self.noteColor = 0xFFFFFF; // Default white // Set note type and appearance self.setNoteType = function (type) { self.noteType = type; // Remove current graphics noteGraphics.destroy(); // Create new graphics with appropriate asset noteGraphics = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); noteGraphics.tint = self.noteColor; }; // Set note color self.setColor = function (color) { self.noteColor = color; noteGraphics.tint = color; }; // Set velocity self.setVelocity = function (vx, vy) { self.velocity.x = vx; self.velocity.y = vy; }; // Update method self.update = function () { // Move based on velocity self.x += self.velocity.x; self.y += self.velocity.y; // Check lifetime var elapsed = Date.now() - self.spawnTime; if (elapsed > self.lifetime) { self.alpha = Math.max(0, 1 - (elapsed - self.lifetime) / 500); // Fade out over 500ms } // Destroy if too old or out of bounds if (elapsed > self.lifetime + 500 || self.y < -100 || self.y > 2832 || self.x < -100 || self.x > 2148) { self.shouldDestroy = true; } }; return self; }); var NoteSparks = Container.expand(function () { var self = Container.call(this); // Array to track active notes self.notes = []; // Spawn configuration self.spawnRate = 500; // Milliseconds between spawns self.lastSpawnTime = 0; self.spawnEnabled = false; // Default spawn properties self.spawnX = 1024; self.spawnY = 1366; self.spawnVelocityRange = { x: { min: -2, max: 2 }, y: { min: -8, max: -4 } }; self.noteColors = [0xFF073A, 0x39FF14, 0x00FFFF, 0xF3F315, 0xFF61F6]; // Set spawn position self.setSpawnPosition = function (x, y) { self.spawnX = x; self.spawnY = y; }; // Enable/disable spawning self.setSpawnEnabled = function (enabled) { self.spawnEnabled = enabled; }; // Set spawn rate self.setSpawnRate = function (rate) { self.spawnRate = rate; }; // Spawn a single note self.spawnNote = function () { var note = new Note(); // Set random position around spawn point note.x = self.spawnX + (Math.random() - 0.5) * 50; note.y = self.spawnY; // Set random velocity var vx = self.spawnVelocityRange.x.min + Math.random() * (self.spawnVelocityRange.x.max - self.spawnVelocityRange.x.min); var vy = self.spawnVelocityRange.y.min + Math.random() * (self.spawnVelocityRange.y.max - self.spawnVelocityRange.y.min); note.setVelocity(vx, vy); // Set random note type note.setNoteType(Math.random() > 0.5 ? 'note' : 'note2'); // Set random color var color = self.noteColors[Math.floor(Math.random() * self.noteColors.length)]; note.setColor(color); // Add slight rotation note.rotation = (Math.random() - 0.5) * 0.5; // Add to arrays and stage self.notes.push(note); self.addChild(note); }; // Spawn multiple notes at once (burst) self.spawnBurst = function (count) { for (var i = 0; i < count; i++) { self.spawnNote(); } }; // Update method self.update = function () { var now = Date.now(); // Spawn new notes if enabled if (self.spawnEnabled && now - self.lastSpawnTime >= self.spawnRate) { self.spawnNote(); self.lastSpawnTime = now; } // Update and clean up notes for (var i = self.notes.length - 1; i >= 0; i--) { var note = self.notes[i]; if (note.shouldDestroy) { note.destroy(); self.notes.splice(i, 1); } } }; return self; }); /***********************************************************************************/ /********************************** RUNNER CLASS ***********************************/ /***********************************************************************************/ var Runner = Container.expand(function () { var self = Container.call(this); // Create and attach runner asset var runnerGraphics = self.attachAsset('runnerDir4_001', { anchorX: 0.5, anchorY: 0.5, tint: 0xFFFFFF, //currentColor, alpha: 1 }); // Initialize runner properties self.speedX = 0; self.speedY = 0; // Track last intersecting state for each gate self.lastIntersectingGates = {}; // Add tick counter for scale flipping self.tickCounter = 0; self.update = function () { // Don't update if song hasn't started if (!songStarted) { return; } // Calculate angle based on runner's position relative to center var angle = Math.atan2(self.y - centerY, self.x - centerX); // Apply rotation to runner self.rotation = angle - Math.PI * 0.5; // Add PI/2 to orient correctly // Increment tick counter and flip scale only every 60 ticks self.tickCounter++; if (self.tickCounter >= 5) { self.scaleX *= -1; self.tickCounter = 0; // Reset counter } }; return self; }); var Speaker = Container.expand(function () { var self = Container.call(this); // Create and attach speaker asset var speakerGraphics = self.attachAsset('speaker', { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); // Initialize speaker properties self.volume = 1.0; self.isPlaying = false; // Method to set speaker volume self.setVolume = function (vol) { self.volume = Math.max(0, Math.min(1, vol)); }; // Method to toggle speaker state self.toggleState = function () { self.isPlaying = !self.isPlaying; speakerGraphics.alpha = self.isPlaying ? 1.0 : 0.5; }; return self; }); var SpeakerManager = Container.expand(function () { var self = Container.call(this); // Array to hold 3 speakers self.speakers = []; self.lastBeatTime = 0; self.beatCooldown = 200; // Minimum time between beat animations // Properties for tracking speakersBeat data self.speakerSongData = speakersBeat[0]; // Use first song from speakersBeat self.currentBeatIndex = 0; self.songStartTime = 0; // Will be set when song starts // Initialize 3 speakers self.initializeSpeakers = function () { var speakerPositions = [{ x: 1024 - 400, y: 512 }, // Left speaker { x: 1024, y: 512 }, // Center speaker { x: 1024 + 400, y: 512 } // Right speaker ]; for (var i = 0; i < 3; i++) { var speaker = new Speaker(); speaker.x = speakerPositions[i].x; speaker.y = speakerPositions[i].y; speaker.scaleX = 0.8; speaker.scaleY = 0.8; speaker.isBigBumping = false; // Track if big bump animation is running self.speakers.push(speaker); self.addChild(speaker); } }; // Animate specific speaker based on beat value self.onBeat = function (beatValue) { var now = Date.now(); if (now - self.lastBeatTime >= self.beatCooldown) { // Determine which speaker to animate based on beat value var speakerIndex = -1; if (beatValue === "1") { speakerIndex = 0; // Left speaker } else if (beatValue === "2") { speakerIndex = 2; // Right speaker } else { speakerIndex = 1; // Center speaker for beats 0 and 3 } // Animate speaker based on whether it's the main speaker for this beat if (speakerIndex >= 0 && speakerIndex < self.speakers.length) { var mainSpeaker = self.speakers[speakerIndex]; // Set flag to indicate big bump is running mainSpeaker.isBigBumping = true; // Spawn note sparks around the speaker self.spawnNoteSparks(mainSpeaker); // Big bump for the main speaker tween(mainSpeaker, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { // Return to base scale (will be overridden by continuous animation) tween(mainSpeaker, { scaleX: 0.8, scaleY: 0.8 }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { // Clear flag when big bump is complete mainSpeaker.isBigBumping = false; } }); } }); } self.lastBeatTime = now; } }; // Update method to check for beats and add swing animation self.update = function () { // Add continuous twist (rotation) and small bump (scale) animations when song is playing if (songStarted) { for (var i = 0; i < self.speakers.length; i++) { var speaker = self.speakers[i]; var time = Date.now() * 0.003; // Time scale for animations // Twist animation (rotation) - always apply var twistAngle = Math.sin(time + i * 0.5) * 0.15; // Rotation with phase offset speaker.rotation = twistAngle; // Small bump animation (scale) - only apply if big bump is not running if (!speaker.isBigBumping) { var scaleValue = 0.8 + Math.sin(time * 2 + i * 0.3) * 0.05; // Small scale oscillation speaker.scaleX = scaleValue; speaker.scaleY = scaleValue; } } } // Check for beat events if (!songStarted) { return; } // Check if a new beat has occurred using speakersBeat data var now = Date.now(); var songElapsed = now - self.songStartTime; // Process beats from speakersBeat data if (self.currentBeatIndex < self.speakerSongData.songBeats.length) { var nextBeat = self.speakerSongData.songBeats[self.currentBeatIndex]; if (songElapsed >= nextBeat.time) { // Trigger beat self.onBeat(nextBeat.beat); self.currentBeatIndex++; } } else { // Check if song has ended and needs restart var lastBeatTime = self.speakerSongData.songBeats[self.speakerSongData.songBeats.length - 1].time; // Wait a bit after the last beat before restarting if (songElapsed > lastBeatTime + 5000) { self.songStartTime = Date.now(); self.currentBeatIndex = 0; } } }; // Method to spawn note sparks around a speaker self.spawnNoteSparks = function (speaker) { // Spawn 3-5 notes around the speaker var noteCount = 3 + Math.floor(Math.random() * 3); for (var i = 0; i < noteCount; i++) { var note = new Note(); // Position notes randomly around the speaker var angle = Math.random() * Math.PI * 2; var distance = 50 + Math.random() * 100; note.x = speaker.x + Math.cos(angle) * distance; note.y = speaker.y + Math.sin(angle) * distance; // Set random velocity outward from speaker var speed = 3 + Math.random() * 5; note.setVelocity(Math.cos(angle) * speed, Math.sin(angle) * speed - 2); // Randomly choose note or note2 note.setNoteType(Math.random() > 0.5 ? 'note' : 'note2'); // Use only futuristic violet and blue colors var futuristicColors = [0xe77d82, 0x48dae9, 0x0b4aa4, 0x9d2c44]; // Violet and Blue var color = futuristicColors[Math.floor(Math.random() * futuristicColors.length)]; note.setColor(color); // Add slight rotation note.rotation = (Math.random() - 0.5) * 0.5; // Add to noteSparks manager if it exists if (noteSparks) { noteSparks.notes.push(note); noteSparks.addChild(note); } } }; // Initialize speakers when created self.initializeSpeakers(); return self; }); // Music will be started by start button // LK.playMusic('track_02'); // test gate /* var testGate = new Gate(); var newScale = 1; testGate.scaleX = newScale; testGate.scaleY = newScale; testGate.gateAsset.scaleX = newScale; testGate.gateAsset.scaleY = newScale; testGate.boundingBox.scaleX = newScale; testGate.boundingBox.scaleY = newScale; testGate.boundingBox.alpha = 0.3; game.addChild(testGate); */ var StartButton = Container.expand(function () { var self = Container.call(this); // Create button background self.buttonBg = self.attachAsset('start', { anchorX: 0.5, anchorY: 0.5 }); // Position at center of screen self.x = centerX; self.y = centerY; // Handle button press self.down = function () { // Prevent multiple presses if (songStarted) { return; } // Mark song as started songStarted = true; // Start the music LK.playMusic('track_01'); // Reset gate manager timing if (gateManager) { gateManager.songStartTime = Date.now(); gateManager.currentNoteIndex = 0; } // Reset speaker manager timing if (speakerManager) { speakerManager.songStartTime = Date.now(); speakerManager.currentBeatIndex = 0; } // Enable note sparks spawning if (noteSparks) { //noteSparks.setSpawnEnabled(true); } // Fade out and remove button tween(self, { alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); /**** * Initialize Game ****/ // Utility function to draw a polygon using drawLine var game = new LK.Game({ backgroundColor: 0x000c33 // Initialize game with a black background }); /**** * Game Code ****/ // Global center coordinates var centerX = 1024; var centerY = 1366; // Global array of 6 neon colors var neonColors = [0x39FF14, // Neon Green 0xFF073A, // Neon Red 0x00FFFF, // Neon Cyan 0xF3F315, // Neon Yellow 0xFF61F6, // Neon Pink 0xFF9900 // Neon Orange ]; // Map keys to colors - 15 keys (0-14) mapped to neon colors var keyColorMap = { 'Key0': 0x39FF14, // Neon Green 'Key1': 0xFF073A, // Neon Red 'Key2': 0x00FFFF, // Neon Cyan 'Key3': 0xF3F315, // Neon Yellow 'Key4': 0xFF61F6, // Neon Pink 'Key5': 0xFF9900, // Neon Orange 'Key6': 0x39FF14, // Neon Green (repeat) 'Key7': 0xFF073A, // Neon Red (repeat) 'Key8': 0x00FFFF, // Neon Cyan (repeat) 'Key9': 0xF3F315, // Neon Yellow (repeat) 'Key10': 0xFF61F6, // Neon Pink (repeat) 'Key11': 0xFF9900, // Neon Orange (repeat) 'Key12': 0x39FF14, // Neon Green (repeat) 'Key13': 0xFF073A, // Neon Red (repeat) 'Key14': 0x00FFFF // Neon Cyan (repeat) }; // Global currentColor, set to a random neon color var currentColor = neonColors[Math.floor(Math.random() * neonColors.length)]; /***********************************************************************************/ /******************************* UTILITY FUNCTIONS *********************************/ /***********************************************************************************/ function drawPolygon(coordinates, tint) { log("drawPolygon ", coordinates); var lines = []; for (var i = 0; i < coordinates.length; i++) { var startPoint = coordinates[i]; var endPoint = coordinates[(i + 1) % coordinates.length]; // Loop back to the first point var line = drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, tint); lines.push(line); } return lines; } function updatePolygon(lines, newCoordinates, scale) { log("updatePolygon ", lines, scale); // Ensure lines and newCoordinates have the same length if (lines.length !== newCoordinates.length) { error("updatePolygon error: lines and newCoordinates length mismatch"); return lines; } // Update each line with new coordinates for (var i = 0; i < lines.length; i++) { var startPoint = newCoordinates[i]; var endPoint = newCoordinates[(i + 1) % newCoordinates.length]; // Loop back to the first point for the last line updateLine(lines[i], startPoint.x, startPoint.y, endPoint.x, endPoint.y, scale); } return lines; } // Utility function to draw lines between two points function drawLine(x1, y1, x2, y2, tint) { log("drawLine ", x1, y1); var line = LK.getAsset('line', { anchorX: 0.0, anchorY: 0.0, x: x1, y: y1, tint: tint }); line.startX = x1; line.startY = y1; line.endX = x2; line.endY = y2; // Calculate the distance between the two points var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); // Set the width of the line to the distance between the points line.width = distance; // Calculate the angle between the two points var angle = Math.atan2(y2 - y1, x2 - x1); // Correct angle calculation for all quadrants line.rotation = angle; return line; } // Utility function to draw lines between two points function updateLine(line, newX1, newY1, newX2, newY2, scale) { log("updateLine ", line); scale = scale === undefined ? 1 : scale; // Calculate midpoint of the original line var midX = (newX1 + newX2) / 2; var midY = (newY1 + newY2) / 2; // Adjust start and end points based on scale newX1 = midX + (newX1 - midX) * scale; newY1 = midY + (newY1 - midY) * scale; newX2 = midX + (newX2 - midX) * scale; newY2 = midY + (newY2 - midY) * scale; // Update line start and end coordinates after scaling line.x = newX1; line.y = newY1; line.startX = newX1; line.startY = newY1; line.endX = newX2; line.endY = newY2; // Recalculate the distance between the new scaled points var distance = Math.sqrt(Math.pow(newX2 - newX1, 2) + Math.pow(newY2 - newY1, 2)); // Update the width of the line to the new distance line.width = distance; // Recalculate the angle between the new points var angle = Math.atan2(newY2 - newY1, newX2 - newX1); // Update the rotation of the line to the new angle line.rotation = angle; return line; } function log() { if (isDebug) { console.log(arguments); } } /***********************************************************************************/ /******************************* GAME VARIABLES*********************************/ /***********************************************************************************/ var songListV3 = [{ "name": "Words Fly Fast", "songBeats": [{ "time": 651, "beat": "1" }, { "time": 1256, "beat": "1" }, { "time": 1800, "beat": "1" }, { "time": 2364, "beat": "1" }, { "time": 2828, "beat": "1" }, { "time": 3324, "beat": "1" }, { "time": 3708, "beat": "1" }, { "time": 4192, "beat": "2" }, { "time": 4804, "beat": "2" }, { "time": 5320, "beat": "2" }, { "time": 5796, "beat": "2" }, { "time": 6240, "beat": "2" }, { "time": 6692, "beat": "2" }, { "time": 7176, "beat": "2" }, { "time": 7640, "beat": "2" }, { "time": 8092, "beat": "1" }, { "time": 8576, "beat": "2" }, { "time": 9124, "beat": "1" }, { "time": 9580, "beat": "2" }, { "time": 10084, "beat": "1" }, { "time": 10548, "beat": "2" }, { "time": 11072, "beat": "1" }, { "time": 11556, "beat": "2" }, { "time": 12092, "beat": "1" }, { "time": 12564, "beat": "2" }, { "time": 13008, "beat": "1" }, { "time": 13400, "beat": "2" }, { "time": 13916, "beat": "1" }, { "time": 14460, "beat": "2" }, { "time": 14944, "beat": "1" }, { "time": 15360, "beat": "2" }, { "time": 17340, "beat": "1" }, { "time": 18260, "beat": "2" }, { "time": 19196, "beat": "1" }, { "time": 20064, "beat": "2" }, { "time": 21092, "beat": "1" }, { "time": 22072, "beat": "2" }, { "time": 23100, "beat": "1" }, { "time": 24040, "beat": "2" }, { "time": 24988, "beat": "1" }, { "time": 25884, "beat": "2" }, { "time": 26876, "beat": "1" }, { "time": 27892, "beat": "2" }, { "time": 28820, "beat": "1" }, { "time": 29688, "beat": "2" }, { "time": 30728, "beat": "1" }, { "time": 31696, "beat": "2" }, { "time": 32656, "beat": "1" }, { "time": 33624, "beat": "2" }, { "time": 34673, "beat": "1" }, { "time": 35692, "beat": "2" }, { "time": 36628, "beat": "1" }, { "time": 37536, "beat": "2" }, { "time": 38516, "beat": "1" }, { "time": 39372, "beat": "2" }, { "time": 40524, "beat": "1" }, { "time": 41028, "beat": "1" }, { "time": 43080, "beat": "2" }, { "time": 43564, "beat": "2" }, { "time": 44400, "beat": "1" }, { "time": 44948, "beat": "1" }, { "time": 46888, "beat": "2" }, { "time": 47412, "beat": "2" }, { "time": 48260, "beat": "1" }, { "time": 48818, "beat": "1" }, { "time": 50816, "beat": "2" }, { "time": 51524, "beat": "2" }, { "time": 52168, "beat": "1" }, { "time": 52684, "beat": "1" }, { "time": 54540, "beat": "3" }, { "time": 55488, "beat": "3" }, { "time": 56448, "beat": "3" }, { "time": 57436, "beat": "3" }, { "time": 58412, "beat": "1" }, { "time": 59240, "beat": "2" }, { "time": 59988, "beat": "1" }, { "time": 60808, "beat": "2" }, { "time": 61636, "beat": "1" }, { "time": 63800, "beat": "1" }, { "time": 64688, "beat": "2" }, { "time": 65656, "beat": "1" }, { "time": 66544, "beat": "2" }, { "time": 67492, "beat": "1" }, { "time": 68432, "beat": "2" }, { "time": 69369, "beat": "1" }, { "time": 70336, "beat": "2" }, { "time": 71416, "beat": "1" }, { "time": 72324, "beat": "2" }, { "time": 73408, "beat": "1" }, { "time": 74316, "beat": "2" }, { "time": 75276, "beat": "1" }, { "time": 76204, "beat": "2" }, { "time": 77248, "beat": "2" }, { "time": 78232, "beat": "1" }, { "time": 79168, "beat": "2" }, { "time": 81048, "beat": "1" }, { "time": 81996, "beat": "2" }, { "time": 83096, "beat": "1" }, { "time": 84064, "beat": "2" }, { "time": 85040, "beat": "1" }, { "time": 86008, "beat": "2" }, { "time": 86956, "beat": "1" }, { "time": 87976, "beat": "2" }, { "time": 88832, "beat": "1" }, { "time": 89844, "beat": "2" }, { "time": 90832, "beat": "1" }, { "time": 92672, "beat": "3" }, { "time": 93156, "beat": "3" }, { "time": 93720, "beat": "3" }, { "time": 94329, "beat": "3" }, { "time": 94812, "beat": "3" }, { "time": 95256, "beat": "3" }, { "time": 95720, "beat": "1" }, { "time": 96496, "beat": "2" }, { "time": 97556, "beat": "3" }, { "time": 98112, "beat": "3" }, { "time": 98624, "beat": "3" }, { "time": 99100, "beat": "3" }, { "time": 99604, "beat": "1" }, { "time": 100440, "beat": "2" }, { "time": 101420, "beat": "1" }, { "time": 102388, "beat": "2" }, { "time": 103336, "beat": "1" }, { "time": 104324, "beat": "2" }, { "time": 105172, "beat": "1" }, { "time": 106260, "beat": "3" }, { "time": 106836, "beat": "3" }, { "time": 107392, "beat": "3" }, { "time": 108028, "beat": "2" }, { "time": 108692, "beat": "1" }, { "time": 109540, "beat": "2" }, { "time": 110488, "beat": "1" }, { "time": 111932, "beat": "2" }, { "time": 112888, "beat": "1" }, { "time": 113796, "beat": "2" }, { "time": 114864, "beat": "1" }, { "time": 115904, "beat": "2" }, { "time": 116864, "beat": "2" }, { "time": 117792, "beat": "1" }, { "time": 119184, "beat": "2" }, { "time": 120244, "beat": "1" }, { "time": 121112, "beat": "2" }, { "time": 122121, "beat": "3" }, { "time": 122744, "beat": "3" }, { "time": 123260, "beat": "3" }, { "time": 123784, "beat": "3" }, { "time": 124217, "beat": "3" }, { "time": 125620, "beat": "1" }, { "time": 126668, "beat": "2" }, { "time": 127788, "beat": "1" }, { "time": 128796, "beat": "2" }, { "time": 129716, "beat": "1" }, { "time": 130884, "beat": "2" }, { "time": 131936, "beat": "1" }, { "time": 132932, "beat": "2" }, { "time": 134092, "beat": "1" }, { "time": 135124, "beat": "2" }, { "time": 136160, "beat": "1" }, { "time": 137128, "beat": "2" }, { "time": 139693, "beat": "3" }, { "time": 140208, "beat": "3" }, { "time": 140712, "beat": "3" }, { "time": 141216, "beat": "3" }, { "time": 141700, "beat": "3" }, { "time": 142164, "beat": "3" }, { "time": 142668, "beat": "3" }, { "time": 143164, "beat": "3" }, { "time": 143668, "beat": "1" }, { "time": 144484, "beat": "2" }, { "time": 145412, "beat": "1" }, { "time": 146340, "beat": "2" }, { "time": 147708, "beat": "3" }, { "time": 148304, "beat": "3" }, { "time": 148820, "beat": "3" }, { "time": 149312, "beat": "3" }, { "time": 149908, "beat": "1" }, { "time": 150804, "beat": "2" }, { "time": 151784, "beat": "3" }, { "time": 152328, "beat": "3" }, { "time": 152832, "beat": "3" }, { "time": 153236, "beat": "3" }, { "time": 153680, "beat": "1" }, { "time": 154518, "beat": "2" }] }]; var speakersBeat = [{ "name": "Words Fly Fast", "songBeats": [{ "time": 128, "beat": "1" }, { "time": 443, "beat": "1" }, { "time": 747, "beat": "1" }, { "time": 1232, "beat": "1" }, { "time": 1579, "beat": "1" }, { "time": 1893, "beat": "1" }, { "time": 2224, "beat": "1" }, { "time": 2549, "beat": "1" }, { "time": 2853, "beat": "1" }, { "time": 3269, "beat": "1" }, { "time": 3712, "beat": "1" }, { "time": 4037, "beat": "1" }, { "time": 4352, "beat": "1" }, { "time": 4661, "beat": "1" }, { "time": 5072, "beat": "1" }, { "time": 5392, "beat": "1" }, { "time": 5728, "beat": "1" }, { "time": 6176, "beat": "1" }, { "time": 6587, "beat": "1" }, { "time": 7141, "beat": "1" }, { "time": 7541, "beat": "1" }, { "time": 7856, "beat": "1" }, { "time": 8171, "beat": "3" }, { "time": 8501, "beat": "2" }, { "time": 8816, "beat": "2" }, { "time": 9120, "beat": "3" }, { "time": 9424, "beat": "2" }, { "time": 9728, "beat": "2" }, { "time": 10032, "beat": "3" }, { "time": 10347, "beat": "2" }, { "time": 10651, "beat": "1" }, { "time": 10971, "beat": "2" }, { "time": 11275, "beat": "2" }, { "time": 11600, "beat": "2" }, { "time": 11915, "beat": "1" }, { "time": 12240, "beat": "2" }, { "time": 12560, "beat": "3" }, { "time": 12875, "beat": "2" }, { "time": 13184, "beat": "3" }, { "time": 13493, "beat": "2" }, { "time": 13813, "beat": "3" }, { "time": 14176, "beat": "3" }, { "time": 14517, "beat": "1" }, { "time": 14843, "beat": "2" }, { "time": 15147, "beat": "2" }, { "time": 15472, "beat": "3" }, { "time": 15781, "beat": "2" }, { "time": 16112, "beat": "2" }, { "time": 16432, "beat": "2" }, { "time": 16741, "beat": "2" }, { "time": 17056, "beat": "3" }, { "time": 17360, "beat": "3" }, { "time": 17685, "beat": "2" }, { "time": 17995, "beat": "3" }, { "time": 18299, "beat": "3" }, { "time": 18608, "beat": "3" }, { "time": 18917, "beat": "3" }, { "time": 19232, "beat": "1" }, { "time": 19547, "beat": "2" }, { "time": 19851, "beat": "3" }, { "time": 20171, "beat": "1" }, { "time": 20475, "beat": "3" }, { "time": 20805, "beat": "3" }, { "time": 21120, "beat": "1" }, { "time": 21435, "beat": "2" }, { "time": 21749, "beat": "2" }, { "time": 22059, "beat": "2" }, { "time": 22373, "beat": "2" }, { "time": 22699, "beat": "2" }, { "time": 23019, "beat": "1" }, { "time": 23323, "beat": "3" }, { "time": 23627, "beat": "3" }, { "time": 23931, "beat": "2" }, { "time": 24240, "beat": "2" }, { "time": 24549, "beat": "2" }, { "time": 24869, "beat": "2" }, { "time": 25189, "beat": "3" }, { "time": 25493, "beat": "2" }, { "time": 25819, "beat": "2" }, { "time": 26133, "beat": "1" }, { "time": 26437, "beat": "2" }, { "time": 26757, "beat": "2" }, { "time": 27061, "beat": "2" }, { "time": 27365, "beat": "2" }, { "time": 27669, "beat": "3" }, { "time": 27973, "beat": "3" }, { "time": 28277, "beat": "3" }, { "time": 28581, "beat": "3" }, { "time": 28885, "beat": "3" }, { "time": 29221, "beat": "2" }, { "time": 29557, "beat": "3" }, { "time": 29861, "beat": "2" }, { "time": 30197, "beat": "2" }, { "time": 30507, "beat": "2" }, { "time": 30827, "beat": "2" }, { "time": 31141, "beat": "2" }, { "time": 31483, "beat": "2" }, { "time": 31787, "beat": "2" }, { "time": 32091, "beat": "3" }, { "time": 32395, "beat": "3" }, { "time": 32731, "beat": "1" }, { "time": 33045, "beat": "2" }, { "time": 33349, "beat": "3" }, { "time": 33653, "beat": "2" }, { "time": 33957, "beat": "3" }, { "time": 34261, "beat": "2" }, { "time": 34576, "beat": "2" }, { "time": 34901, "beat": "2" }, { "time": 35211, "beat": "3" }, { "time": 35520, "beat": "2" }, { "time": 35829, "beat": "3" }, { "time": 36149, "beat": "3" }, { "time": 36464, "beat": "2" }, { "time": 36779, "beat": "3" }, { "time": 37083, "beat": "2" }, { "time": 37403, "beat": "2" }, { "time": 37707, "beat": "2" }, { "time": 38037, "beat": "2" }, { "time": 38341, "beat": "3" }, { "time": 38661, "beat": "2" }, { "time": 38976, "beat": "1" }, { "time": 39301, "beat": "1" }, { "time": 39611, "beat": "2" }, { "time": 39941, "beat": "2" }, { "time": 40256, "beat": "1" }, { "time": 40565, "beat": "2" }, { "time": 40885, "beat": "1" }, { "time": 41189, "beat": "1" }, { "time": 41499, "beat": "1" }, { "time": 41851, "beat": "1" }, { "time": 42155, "beat": "1" }, { "time": 42459, "beat": "1" }, { "time": 42976, "beat": "1" }, { "time": 43429, "beat": "1" }, { "time": 43733, "beat": "1" }, { "time": 44043, "beat": "1" }, { "time": 44357, "beat": "2" }, { "time": 44672, "beat": "2" }, { "time": 44987, "beat": "1" }, { "time": 45312, "beat": "1" }, { "time": 45744, "beat": "1" }, { "time": 46059, "beat": "1" }, { "time": 46373, "beat": "1" }, { "time": 46683, "beat": "2" }, { "time": 46992, "beat": "2" }, { "time": 47301, "beat": "1" }, { "time": 47605, "beat": "2" }, { "time": 47920, "beat": "2" }, { "time": 48229, "beat": "1" }, { "time": 48544, "beat": "2" }, { "time": 48859, "beat": "1" }, { "time": 49168, "beat": "1" }, { "time": 49477, "beat": "2" }, { "time": 49845, "beat": "1" }, { "time": 50160, "beat": "1" }, { "time": 50496, "beat": "2" }, { "time": 50805, "beat": "1" }, { "time": 51173, "beat": "1" }, { "time": 51477, "beat": "2" }, { "time": 51792, "beat": "2" }, { "time": 52107, "beat": "2" }, { "time": 52421, "beat": "1" }, { "time": 52869, "beat": "1" }, { "time": 53189, "beat": "1" }, { "time": 53595, "beat": "1" }, { "time": 53979, "beat": "1" }, { "time": 54288, "beat": "1" }, { "time": 54597, "beat": "3" }, { "time": 54907, "beat": "2" }, { "time": 55227, "beat": "3" }, { "time": 55531, "beat": "3" }, { "time": 55851, "beat": "2" }, { "time": 56165, "beat": "3" }, { "time": 56475, "beat": "3" }, { "time": 56789, "beat": "2" }, { "time": 57104, "beat": "2" }, { "time": 57408, "beat": "1" }, { "time": 57728, "beat": "3" }, { "time": 58053, "beat": "2" }, { "time": 58363, "beat": "1" }, { "time": 58667, "beat": "3" }, { "time": 58976, "beat": "3" }, { "time": 59307, "beat": "1" }, { "time": 59621, "beat": "3" }, { "time": 59947, "beat": "3" }, { "time": 60304, "beat": "2" }, { "time": 60608, "beat": "3" }, { "time": 60971, "beat": "1" }, { "time": 61339, "beat": "2" }, { "time": 61701, "beat": "1" }, { "time": 62032, "beat": "1" }, { "time": 62336, "beat": "3" }, { "time": 62640, "beat": "3" }, { "time": 62955, "beat": "3" }, { "time": 63269, "beat": "3" }, { "time": 63584, "beat": "2" }, { "time": 63904, "beat": "3" }, { "time": 64213, "beat": "3" }, { "time": 64555, "beat": "2" }, { "time": 64864, "beat": "3" }, { "time": 65179, "beat": "2" }, { "time": 65499, "beat": "2" }, { "time": 65824, "beat": "3" }, { "time": 66144, "beat": "2" }, { "time": 66464, "beat": "2" }, { "time": 66768, "beat": "3" }, { "time": 67093, "beat": "1" }, { "time": 67403, "beat": "3" }, { "time": 67707, "beat": "3" }, { "time": 68053, "beat": "2" }, { "time": 68368, "beat": "2" }, { "time": 68709, "beat": "1" }, { "time": 69024, "beat": "1" }, { "time": 69333, "beat": "2" }, { "time": 69648, "beat": "3" }, { "time": 69968, "beat": "2" }, { "time": 70283, "beat": "2" }, { "time": 70587, "beat": "3" }, { "time": 70896, "beat": "3" }, { "time": 71205, "beat": "3" }, { "time": 71525, "beat": "3" }, { "time": 71835, "beat": "3" }, { "time": 72139, "beat": "3" }, { "time": 72448, "beat": "3" }, { "time": 72779, "beat": "3" }, { "time": 73083, "beat": "2" }, { "time": 73387, "beat": "3" }, { "time": 73691, "beat": "1" }, { "time": 74016, "beat": "3" }, { "time": 74336, "beat": "2" }, { "time": 74667, "beat": "3" }, { "time": 74987, "beat": "3" }, { "time": 75296, "beat": "1" }, { "time": 75605, "beat": "3" }, { "time": 75915, "beat": "3" }, { "time": 76224, "beat": "3" }, { "time": 76549, "beat": "3" }, { "time": 76853, "beat": "3" }, { "time": 77157, "beat": "3" }, { "time": 77461, "beat": "3" }, { "time": 77776, "beat": "1" }, { "time": 78107, "beat": "2" }, { "time": 78421, "beat": "2" }, { "time": 78731, "beat": "2" }, { "time": 79045, "beat": "2" }, { "time": 79365, "beat": "2" }, { "time": 79669, "beat": "1" }, { "time": 79995, "beat": "1" }, { "time": 80309, "beat": "1" }, { "time": 80640, "beat": "1" }, { "time": 80965, "beat": "1" }, { "time": 81296, "beat": "1" }, { "time": 81856, "beat": "1" }, { "time": 82165, "beat": "2" }, { "time": 82491, "beat": "2" }, { "time": 82827, "beat": "2" }, { "time": 83136, "beat": "2" }, { "time": 83451, "beat": "2" }, { "time": 83765, "beat": "1" }, { "time": 84080, "beat": "1" }, { "time": 84389, "beat": "1" }, { "time": 84704, "beat": "1" }, { "time": 85040, "beat": "1" }, { "time": 85392, "beat": "2" }, { "time": 85696, "beat": "1" }, { "time": 86016, "beat": "1" }, { "time": 86325, "beat": "1" }, { "time": 86688, "beat": "1" }, { "time": 87019, "beat": "2" }, { "time": 87323, "beat": "2" }, { "time": 87643, "beat": "1" }, { "time": 87947, "beat": "1" }, { "time": 88272, "beat": "1" }, { "time": 88587, "beat": "1" }, { "time": 88901, "beat": "1" }, { "time": 89216, "beat": "1" }, { "time": 89520, "beat": "1" }, { "time": 89872, "beat": "1" }, { "time": 90176, "beat": "2" }, { "time": 90485, "beat": "2" }, { "time": 90800, "beat": "2" }, { "time": 91115, "beat": "1" }, { "time": 91424, "beat": "1" }, { "time": 91819, "beat": "1" }, { "time": 92304, "beat": "1" }, { "time": 92667, "beat": "1" }, { "time": 92992, "beat": "2" }, { "time": 93301, "beat": "3" }, { "time": 93659, "beat": "1" }, { "time": 93968, "beat": "3" }, { "time": 94272, "beat": "3" }, { "time": 94581, "beat": "2" }, { "time": 94885, "beat": "3" }, { "time": 95205, "beat": "3" }, { "time": 95525, "beat": "2" }, { "time": 95845, "beat": "3" }, { "time": 96155, "beat": "1" }, { "time": 96485, "beat": "2" }, { "time": 96811, "beat": "3" }, { "time": 97125, "beat": "3" }, { "time": 97451, "beat": "2" }, { "time": 97755, "beat": "3" }, { "time": 98069, "beat": "3" }, { "time": 98411, "beat": "2" }, { "time": 98725, "beat": "3" }, { "time": 99035, "beat": "3" }, { "time": 99349, "beat": "3" }, { "time": 99680, "beat": "1" }, { "time": 100000, "beat": "2" }, { "time": 100304, "beat": "3" }, { "time": 100624, "beat": "3" }, { "time": 100928, "beat": "3" }, { "time": 101232, "beat": "3" }, { "time": 101557, "beat": "3" }, { "time": 101888, "beat": "2" }, { "time": 102192, "beat": "3" }, { "time": 102507, "beat": "3" }, { "time": 102853, "beat": "3" }, { "time": 103157, "beat": "3" }, { "time": 103461, "beat": "3" }, { "time": 103765, "beat": "2" }, { "time": 104085, "beat": "1" }, { "time": 104395, "beat": "1" }, { "time": 104709, "beat": "2" }, { "time": 105013, "beat": "3" }, { "time": 105317, "beat": "2" }, { "time": 105627, "beat": "1" }, { "time": 105931, "beat": "3" }, { "time": 106240, "beat": "2" }, { "time": 106555, "beat": "3" }, { "time": 106864, "beat": "3" }, { "time": 107168, "beat": "3" }, { "time": 107472, "beat": "1" }, { "time": 107781, "beat": "2" }, { "time": 108091, "beat": "3" }, { "time": 108416, "beat": "2" }, { "time": 108731, "beat": "2" }, { "time": 109045, "beat": "2" }, { "time": 109349, "beat": "3" }, { "time": 109669, "beat": "2" }, { "time": 109979, "beat": "2" }, { "time": 110293, "beat": "3" }, { "time": 110597, "beat": "3" }, { "time": 110917, "beat": "3" }, { "time": 111221, "beat": "3" }, { "time": 111525, "beat": "3" }, { "time": 111840, "beat": "2" }, { "time": 112144, "beat": "3" }, { "time": 112459, "beat": "3" }, { "time": 112773, "beat": "3" }, { "time": 113093, "beat": "3" }, { "time": 113419, "beat": "3" }, { "time": 113723, "beat": "3" }, { "time": 114032, "beat": "2" }, { "time": 114336, "beat": "2" }, { "time": 114651, "beat": "3" }, { "time": 114965, "beat": "1" }, { "time": 115280, "beat": "2" }, { "time": 115584, "beat": "3" }, { "time": 115888, "beat": "3" }, { "time": 116208, "beat": "2" }, { "time": 116523, "beat": "3" }, { "time": 116837, "beat": "2" }, { "time": 117147, "beat": "3" }, { "time": 117451, "beat": "3" }, { "time": 117787, "beat": "3" }, { "time": 118101, "beat": "3" }, { "time": 118405, "beat": "3" }, { "time": 118741, "beat": "2" }, { "time": 119051, "beat": "2" }, { "time": 119360, "beat": "1" }, { "time": 119664, "beat": "2" }, { "time": 119973, "beat": "3" }, { "time": 120283, "beat": "2" }, { "time": 120603, "beat": "3" }, { "time": 120912, "beat": "3" }, { "time": 121216, "beat": "3" }, { "time": 121525, "beat": "3" }, { "time": 121829, "beat": "3" }, { "time": 122293, "beat": "1" }, { "time": 122779, "beat": "1" }, { "time": 123099, "beat": "1" }, { "time": 123504, "beat": "1" }, { "time": 123845, "beat": "1" }, { "time": 124352, "beat": "1" }, { "time": 124699, "beat": "1" }, { "time": 125024, "beat": "1" }, { "time": 125333, "beat": "1" }, { "time": 125648, "beat": "1" }, { "time": 125979, "beat": "1" }, { "time": 126325, "beat": "1" }, { "time": 126635, "beat": "1" }, { "time": 126960, "beat": "1" }, { "time": 127275, "beat": "1" }, { "time": 127589, "beat": "1" }, { "time": 128059, "beat": "1" }, { "time": 128523, "beat": "1" }, { "time": 128837, "beat": "2" }, { "time": 129173, "beat": "1" }, { "time": 129483, "beat": "2" }, { "time": 129813, "beat": "2" }, { "time": 130133, "beat": "1" }, { "time": 130437, "beat": "1" }, { "time": 130741, "beat": "1" }, { "time": 131109, "beat": "1" }, { "time": 131413, "beat": "1" }, { "time": 131739, "beat": "2" }, { "time": 132080, "beat": "2" }, { "time": 132384, "beat": "2" }, { "time": 132784, "beat": "1" }, { "time": 133093, "beat": "1" }, { "time": 133408, "beat": "2" }, { "time": 133739, "beat": "1" }, { "time": 134069, "beat": "1" }, { "time": 134389, "beat": "1" }, { "time": 134709, "beat": "1" }, { "time": 135019, "beat": "1" }, { "time": 135355, "beat": "1" }, { "time": 135733, "beat": "1" }, { "time": 136064, "beat": "2" }, { "time": 136384, "beat": "3" }, { "time": 136693, "beat": "1" }, { "time": 137024, "beat": "2" }, { "time": 137339, "beat": "2" }, { "time": 137776, "beat": "1" }, { "time": 138261, "beat": "1" }, { "time": 138741, "beat": "1" }, { "time": 139109, "beat": "1" }, { "time": 139435, "beat": "1" }, { "time": 139744, "beat": "2" }, { "time": 140048, "beat": "2" }, { "time": 140352, "beat": "3" }, { "time": 140656, "beat": "3" }, { "time": 140987, "beat": "2" }, { "time": 141291, "beat": "3" }, { "time": 141605, "beat": "3" }, { "time": 141909, "beat": "3" }, { "time": 142251, "beat": "1" }, { "time": 142555, "beat": "1" }, { "time": 142859, "beat": "3" }, { "time": 143184, "beat": "3" }, { "time": 143504, "beat": "3" }, { "time": 143813, "beat": "2" }, { "time": 144123, "beat": "3" }, { "time": 144427, "beat": "2" }, { "time": 144741, "beat": "3" }, { "time": 145061, "beat": "3" }, { "time": 145365, "beat": "3" }, { "time": 145685, "beat": "3" }, { "time": 146000, "beat": "3" }, { "time": 146309, "beat": "2" }, { "time": 146624, "beat": "3" }, { "time": 146928, "beat": "2" }, { "time": 147237, "beat": "2" }, { "time": 147547, "beat": "3" }, { "time": 147851, "beat": "2" }, { "time": 148165, "beat": "3" }, { "time": 148480, "beat": "3" }, { "time": 148784, "beat": "2" }, { "time": 149104, "beat": "3" }, { "time": 149413, "beat": "3" }, { "time": 149717, "beat": "3" }, { "time": 150032, "beat": "2" }, { "time": 150336, "beat": "2" }, { "time": 150651, "beat": "2" }, { "time": 150971, "beat": "3" }, { "time": 151285, "beat": "2" }, { "time": 151600, "beat": "3" }, { "time": 151904, "beat": "3" }, { "time": 152208, "beat": "3" }, { "time": 152517, "beat": "3" }, { "time": 152821, "beat": "3" }, { "time": 153248, "beat": "1" }, { "time": 153728, "beat": "1" }, { "time": 154208, "beat": "1" }, { "time": 154576, "beat": "1" }] }]; var isDebug = false; var globalSpeed = 20; var currentRotationAngle = 0; var fullLog = []; var fpsText; var lastTick; var frameCount; var debugText; // Removed drag-related variables - using tap controls now var backgroundManager; var gateManager; var targetManager; var ball; var runner; var speakerManager; var noteSparks; var minDetectionScale = 0.4; var maxDetectionScale = 0.6; var borderLimitAngle = Math.PI * 0.08; var gateLimitAngle = Math.PI * 0.2; var gateUniqueId = 0; var songStarted = false; // Define three fixed positions: left, center, right var leftAngle = -Math.PI * 0.5 + gateLimitAngle; var centerAngle = -Math.PI * 0.5 + Math.PI / 2; var rightAngle = -Math.PI * 0.5 + Math.PI - gateLimitAngle; // Function to get next gate ID function getNextGateId() { return gateUniqueId++; } // Function to play hit sound with protection against multiple simultaneous plays var lastHitSoundTime = 0; var hitSoundCooldown = 100; // Minimum time between hit sounds in milliseconds var skipBeatDelay = 900; var lastBeatTime = 0; function playHitSound() { var now = Date.now(); if (now - lastHitSoundTime >= hitSoundCooldown) { LK.getSound('hit').play(); lastHitSoundTime = now; } } /***********************************************************************************/ /***************************** GAME INITIALIZATION *********************************/ /***********************************************************************************/ function gameInitialize() { // Add world01 as background image var worldBackground = LK.getAsset('world01', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.0, scaleY: 1.0 }); game.addChild(worldBackground); // Initialize background manager first (so it's behind other elements) backgroundManager = new BackgroundManager(); game.addChild(backgroundManager); // Initialize runner at center position //updateSnapPosition(snapPositions.center); // Initialize gate manager gateManager = new GateManager(); game.addChild(gateManager); // Initialize speaker manager speakerManager = new SpeakerManager(); game.addChild(speakerManager); // Initialize note sparks (but don't enable spawning yet) noteSparks = new NoteSparks(); game.addChild(noteSparks); // Create and position ball ball = new Ball(); ball.x = 1024; ball.y = 2000; ball.alpha = true; game.addChild(ball); runner = new Runner(); runner.x = 1024; runner.y = 2000; game.addChild(runner); // Create start button if song hasn't started if (!songStarted) { var startButton = new StartButton(); game.addChild(startButton); } if (isDebug) { var debugMarker = LK.getAsset('debugMarker', { anchorX: 0.5, anchorY: 0.5, x: 2048 * 0.5, y: 2732 / 2 }); game.addChild(debugMarker); fpsText = new Text2('FPS: 0', { size: 50, fill: 0xFFFFFF }); // Position FPS text at the bottom-right corner fpsText.anchor.set(1, 1); // Anchor to the bottom-right LK.gui.bottomRight.addChild(fpsText); // Update FPS display every second lastTick = Date.now(); frameCount = 0; debugText = new Text2('Debug Info', { size: 50, fill: 0xFFFFFF }); debugText.anchor.set(0.5, 0); // Anchor to the bottom-right LK.gui.top.addChild(debugText); // Create sound test button var soundTestButton = new Container(); var buttonBg = LK.getAsset('line', { anchorX: 0.5, anchorY: 0.5, scaleX: 50, scaleY: 15, tint: 0x333333 }); soundTestButton.addChild(buttonBg); var buttonText = new Text2('SOUND TEST', { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); soundTestButton.addChild(buttonText); // Position at top right soundTestButton.x = -100; soundTestButton.y = 50; LK.gui.topRight.addChild(soundTestButton); // Add click handler soundTestButton.down = function () { // Play all key sounds with 600ms delay for (var i = 0; i <= 14; i++) { (function (index) { LK.setTimeout(function () { LK.getSound('key' + index).play(); }, index * 600); })(i); } }; } } /***********************************************************************************/ /******************************** MAIN GAME LOOP ***********************************/ /***********************************************************************************/ game.update = function () { if (isDebug) { // FPS var now = Date.now(); frameCount++; if (now - lastTick >= 1000) { // Update every second fpsText.setText('FPS: ' + frameCount); frameCount = 0; lastTick = now; } } }; // Define magnetic snap positions var snapPositions = { left: 0, center: 1, right: 2 }; // Current snap position (start at center) var currentSnapPosition = snapPositions.center; // Track last mouse position to detect transitions var lastMouseZone = 1; // Start at center zone // Function to update runner position based on snap function updateSnapPosition(snapPos) { currentSnapPosition = snapPos; // Use global angle definitions var targetAngle = centerAngle; if (snapPos === snapPositions.left) { targetAngle = leftAngle; } else if (snapPos === snapPositions.center) { targetAngle = centerAngle; } else if (snapPos === snapPositions.right) { targetAngle = rightAngle; } // Calculate position on the ellipse path var radiusX = 924; var radiusY = 634; // Calculate new position runner.x = centerX + radiusX * Math.cos(targetAngle + Math.PI * 0.5); runner.y = centerY + radiusY * Math.sin(targetAngle + Math.PI * 0.5); // Apply rotation based on position var rotationMap = { 0: -0.5, // left 1: 0, // center 2: 0.5 // right }; currentRotationAngle = rotationMap[snapPos] * Math.PI * 0.5; } // Function to animate runner to new snap position function animateToSnapPosition(snapPos, jump) { // Check if we need to pass through center (moving from left to right or right to left) var needsIntermediateStep = false; if (currentSnapPosition === snapPositions.left && snapPos === snapPositions.right || currentSnapPosition === snapPositions.right && snapPos === snapPositions.left) { needsIntermediateStep = true; } // If we need intermediate step, first move to center if (needsIntermediateStep && !jump) { // Use global angle definitions // Calculate position on the ellipse path var radiusX = 924; var radiusY = 634; // Calculate center position (intermediate step) var centerPosX = centerX + radiusX * Math.cos(centerAngle + Math.PI * 0.5); var centerPosY = centerY + radiusY * Math.sin(centerAngle + Math.PI * 0.5); // Animate to center first tween(runner, { x: centerPosX, y: centerPosY }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { // After reaching center, continue to final destination performSnapAnimation(snapPos, jump); } }); // Update rotation for center position currentRotationAngle = 0; } else { // Direct movement (no intermediate step needed) performSnapAnimation(snapPos, jump); } } // Helper function to perform the actual snap animation function performSnapAnimation(snapPos, jump) { currentSnapPosition = snapPos; // Use global angle definitions var targetAngle = centerAngle; if (snapPos === snapPositions.left) { targetAngle = rightAngle; } else if (snapPos === snapPositions.center) { targetAngle = centerAngle; } else if (snapPos === snapPositions.right) { targetAngle = leftAngle; } // Calculate position on the ellipse path var radiusX = 924; var radiusY = 634; // Calculate target position var targetX = centerX + radiusX * Math.cos(targetAngle + Math.PI * 0.5); var targetY = centerY + radiusY * Math.sin(targetAngle + Math.PI * 0.5); // Apply rotation based on position var rotationMap = { 0: -0.5, // left 1: 0, // center 2: 0.5 // right }; var targetRotation = rotationMap[snapPos] * Math.PI * 0.5; // Check if jump is requested if (jump) { // Jump to y=1200 first, then return to normal position tween(runner, { y: 1200 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // After jump, animate back to target position tween(runner, { x: targetX, y: targetY }, { duration: 200, easing: tween.easeIn }); } }); } else { // Normal animation without jump tween(runner, { x: targetX, y: targetY }, { duration: 300, easing: tween.easeInOut }); } // Update rotation angle for continuous rotation currentRotationAngle = targetRotation; } // Add game event handlers for runner control game.down = function (x, y, obj) { // Don't process input if song hasn't started if (!songStarted) { return; } // Divide screen into thirds var screenWidth = 2048; var thirdWidth = screenWidth / 3; // Determine which zone was tapped var tapZone; if (x < thirdWidth) { tapZone = 0; // Left third } else if (x < thirdWidth * 2) { tapZone = 1; // Center third } else { tapZone = 2; // Right third } // Animate to the corresponding snap position based on tap if (tapZone === 0) { animateToSnapPosition(snapPositions.left); } else if (tapZone === 1) { animateToSnapPosition(snapPositions.center); } else { animateToSnapPosition(snapPositions.right); } // Update lastMouseZone to match the tapped zone lastMouseZone = tapZone; }; game.move = function (x, y, obj) { // Don't process input if song hasn't started if (!songStarted) { return; } // Divide screen into thirds var screenWidth = 2048; var thirdWidth = screenWidth / 3; // Determine which zone the mouse is in var currentZone; if (x < thirdWidth) { currentZone = 0; // Left third } else if (x < thirdWidth * 2) { currentZone = 1; // Center third } else { currentZone = 2; // Right third } // Only update position if we've moved to a different zone if (currentZone !== lastMouseZone) { // Animate to the corresponding snap position if (currentZone === 0) { animateToSnapPosition(snapPositions.left); } else if (currentZone === 1) { animateToSnapPosition(snapPositions.center); } else { animateToSnapPosition(snapPositions.right); } lastMouseZone = currentZone; } }; game.up = function (x, y, obj) { // Empty - no longer needed for mouse-based controls }; gameInitialize();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
/***********************************************************************************/
/******************************* UTILITY FUNCTIONS *********************************/
/***********************************************************************************/
var BackgroundManager = Container.expand(function () {
var self = Container.call(this);
// Create three background instances for smoother tunnel effect
self.bg0 = self.attachAsset('background01', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2.4,
scaleY: 2.4,
alpha: 1
});
self.bg1 = self.attachAsset('background01', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.1,
scaleY: 1.1,
alpha: 1
});
self.bg2 = self.attachAsset('background01', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.5,
scaleY: 0.5,
alpha: 1
});
self.bg3 = self.attachAsset('background01', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.22,
scaleY: 0.22,
alpha: 1
});
self.bg4 = self.attachAsset('background01', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.11,
scaleY: 0.11,
alpha: 1
});
self.tore0 = self.attachAsset('tore', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.2,
scaleY: 1.2,
alpha: 0
});
self.tore1 = self.attachAsset('tore', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.6,
scaleY: 0.6,
alpha: 0
});
self.tore2 = self.attachAsset('tore', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.26,
scaleY: 0.26,
alpha: 0
});
self.tore3 = self.attachAsset('tore', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.12,
scaleY: 0.12,
alpha: 0
});
// Apply different tints in debug mode
if (isDebug) {
self.bg1.tint = 0xFF0000; // Red tint for first background
self.tore1.tint = 0x00FF00; // Green tint for first tore
self.bg2.tint = 0x00FFFF; // Cyan tint for second background
self.tore2.tint = 0xFF00FF; // Magenta tint for second tore
self.bg3.tint = 0xfff200; // Yellow tint for third background
}
// Animation properties
self.bgAnimationSpeed = globalSpeed / 1000; //0.002;
self.bgAnimationAcceleration = 2;
// Add tore assets between backgrounds for animation
self.backgrounds = [self.bg0, self.tore0, self.bg1, self.tore1, self.bg2, self.tore2, self.bg3, self.tore3, self.bg4];
//self.backgrounds = [self.bg0, self.bg1, self.bg2, self.bg3];
// Define initial scale for each background/torus (tore: 0.26, bg: 0.22)
//self.bgInitialScales = [0.22, 0.22, 0.22, 0.22, 0.22];
//self.bgInitialScales = [0, 0, 0, 0, 0];
//self.bgInitialScales = [0, 0, 0, 0, 0, 0, 0, 0, 0];
// Animation state: single startTime for all backgrounds/torus
self.bgAnimStartTime = Date.now();
// Update method - handle background/torus scale animation
self.update = function () {
// Don't update if song hasn't started
if (!songStarted) {
return;
}
var now = Date.now();
var elapsed = now - self.bgAnimStartTime;
var resetTriggered = false;
for (var i = 0; i < self.backgrounds.length; i++) {
var bg = self.backgrounds[i];
// Make the scale speed increase as the scale increases (e.g. exponential or quadratic growth)
//var scaleMultiplier = bg.scaleX * self.bgAnimationAcceleration;
//bg.scaleX += self.bgAnimationSpeed * scaleMultiplier;
bg.scaleX += self.bgAnimationSpeed * bg.scaleX;
bg.scaleY = bg.scaleX;
if (bg.scaleX > 3.0) {
bg.scaleX = 0.12; //self.bgInitialScales[i];
bg.scaleY = bg.scaleX;
}
//bg.alpha = Math.min(1, bg.scaleX + 0.66);
bg.tint = 0x1697b8; // 0x33FF33;
}
};
return self;
});
// Initialize the game;
/***********************************************************************************/
/********************************** BALL CLASS *************************************/
/***********************************************************************************/
var Ball = Container.expand(function () {
var self = Container.call(this);
// Create and attach ball asset
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
tint: currentColor,
alpha: 1
});
// Initialize ball properties
self.speedX = 0;
self.speedY = 0;
// Track last intersecting state for each gate
self.lastIntersectingGates = {};
// Update method to follow runner's position
self.update = function () {
// Don't process gates until song has started
if (!songStarted) {
return;
}
// Make ball follow runner's exact position
if (runner) {
self.x = runner.x;
self.y = runner.y;
}
// Check for collisions with gates
if (gateManager && gateManager.gates) {
// Iterate backwards to avoid index shifting issues when removing gates
for (var i = gateManager.gates.length - 1; i >= 0; i--) {
var gate = gateManager.gates[i];
var gateId = gate.gateId;
// Initialize tracking if needed
if (self.lastIntersectingGates[gateId] === undefined) {
self.lastIntersectingGates[gateId] = false;
}
// Check intersection with bounding box instead of gate asset, but ignore if gate scale > 1.0
var currentIntersecting = gate.gateAsset.scaleX >= minDetectionScale && gate.gateAsset.scaleX <= maxDetectionScale && self.intersects(gate.boundingBox);
// Detect transition from not intersecting to intersecting
if (!self.lastIntersectingGates[gateId] && currentIntersecting) {
// Play hit sound
playHitSound();
// Mark gate as being destroyed to prevent multiple triggers
if (!gate.isDestroying) {
gate.isDestroying = true;
// Animate scale down
tween(gate, {
scaleX: 0,
scaleY: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Request destruction from gate manager
gateManager.destroyGate(gate);
}
});
}
}
// Update last intersecting state
self.lastIntersectingGates[gateId] = currentIntersecting;
}
// No cleanup needed - gate IDs are unique and won't be reused
}
};
return self;
});
/***********************************************************************************/
/********************************** GATE CLASS *************************************/
/***********************************************************************************/
var Gate = Container.expand(function () {
var self = Container.call(this);
// Create gate asset with initial properties
self.gateAsset = self.attachAsset('gate', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.26,
// Start at same scale as tore2
scaleY: 0.26,
alpha: 1,
visible: false
});
// Store direction angle for this gate
self.directionAngle = 0;
// Add bounding box for collision detection
self.boundingBox = self.attachAsset('boundingBox', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2450,
alpha: 1 // Invisible by default
});
/*
width: 200,
heigh: 100,
*/
// Store the color for this gate
self.gateColor = 0xFFFFFF; // Default white, will be set by manager
// Store unique ID for this gate
self.gateId = null; // Will be set by manager
// Set the tint to match the gate color
self.setColor = function (color) {
self.gateColor = color;
self.gateAsset.tint = color;
// Show bounding box in debug mode
//if (isDebug)
{
self.boundingBox.alpha = 0.8;
//self.boundingBox.tint = color;
}
};
// Update scale to match background animation
self.updateScale = function (newScale) {
/*
self.scaleX = newScale;
self.scaleY = newScale;
self.alpha = Math.min(1, newScale + 0.66);
*/
self.gateAsset.scaleX = newScale;
self.gateAsset.scaleY = newScale;
self.gateAsset.alpha = Math.min(1, newScale + 0.66);
// Scale bounding box proportionally
self.boundingBox.scaleX = newScale; // 3 * newScale / 0.26; // Maintain 300px width relative to gate scale
self.boundingBox.scaleY = newScale; //0.3 * newScale / 0.26; // Maintain 30px height relative to gate scale
// Calculate boundingBox position using directionAngle and scale
// Calculate distance from center based on scale
var distance = (2450 - 1366) * newScale;
// Use directionAngle to position boundingBox
self.boundingBox.x = centerX + distance * Math.cos(self.directionAngle + Math.PI * 0.5);
self.boundingBox.y = centerY + distance * Math.sin(self.directionAngle + Math.PI * 0.5);
self.boundingBox.rotation = self.directionAngle;
};
return self;
});
/***********************************************************************************/
/********************************** GATE MANAGER CLASS *****************************/
/***********************************************************************************/
var GateManager = Container.expand(function () {
var self = Container.call(this);
// Array to hold gates
self.gates = [];
// Animation timing
self.gateAnimStartTime = Date.now();
self.gateAnimationSpeed = globalSpeed / 1000; // Same as background
// Song timing properties
self.currentSong = songListV3[0];
self.songStartTime = Date.now();
self.currentNoteIndex = 0;
self.noteSpawnScale = 0.12; // Initial scale for new gates matching smallest tore
self.lastGateAngle = null; // Track last gate angle for path continuity
self.lastProcessedBeat = -1; // Track last processed beat for speaker synchronization
// Spawn a single gate at current time
self.spawnGateAtTime = function () {
// Get the current beat value
var beatValue = null;
if (self.currentNoteIndex < self.currentSong.songBeats.length) {
beatValue = self.currentSong.songBeats[self.currentNoteIndex].beat;
}
// Map beat to key for color selection
var keyNumber = parseInt(beatValue, 10) || 1; // Default to 1 if parse fails
var noteKey = 'Key' + keyNumber;
var keyColor = keyColorMap[noteKey] || currentColor; // Default to currentColor if key not found
// --- Calculate gate travel time so it reaches the player at the correct beat time ---
var startScale = self.noteSpawnScale;
var endScale = 1.0; // The scale at which the gate should reach the player (runner)
var speed = self.gateAnimationSpeed; // This is the per-tick scale growth factor
var timeToReachPlayer = Math.log(endScale / startScale) / speed; // ms
// Determine beat angle (where the safe path is)
var beatAngle = centerAngle;
if (beatValue === "1") {
beatAngle = rightAngle;
} else if (beatValue === "2") {
beatAngle = leftAngle;
} else {
// Beat values 0 and 3 go to center
beatAngle = centerAngle;
}
// Store this angle for the next gate
self.lastGateAngle = beatAngle;
// Create array of all possible angles
var allAngles = [leftAngle, centerAngle, rightAngle];
var gateAngles = [];
// Filter out the beat angle to get the non-beat angles
for (var i = 0; i < allAngles.length; i++) {
if (allAngles[i] !== beatAngle) {
gateAngles.push(allAngles[i]);
}
}
// Calculate timing
var beatTime = self.currentSong.songBeats[self.currentNoteIndex].time;
var now = Date.now();
var songElapsed = now - self.songStartTime;
var spawnTime = beatTime - timeToReachPlayer;
// Create gates at non-beat positions
for (var j = 0; j < gateAngles.length; j++) {
var gate = new Gate();
gate.setColor(keyColor);
gate.updateScale(self.noteSpawnScale);
// Store spawn time for tracking
gate.spawnTime = Date.now() + 200 / globalSpeed;
gate.colorIndex = 0;
// Assign unique ID to gate
gate.gateId = getNextGateId();
// Store the note key for this gate
gate.noteKey = noteKey;
// Set the direction angle for this gate
gate.directionAngle = gateAngles[j];
// Apply rotation to gate asset
gate.gateAsset.rotation = gateAngles[j];
// Spawn or schedule the gate
if (songElapsed >= spawnTime) {
// Spawn now
self.gates.push(gate);
self.addChild(gate);
} else {
// Schedule spawn for later
(function (g) {
LK.setTimeout(function () {
self.gates.push(g);
self.addChild(g);
}, spawnTime - songElapsed);
})(gate);
}
}
};
// Update gates animation
self.update = function () {
// Don't update if song hasn't started
if (!songStarted) {
return;
}
var now = Date.now();
var songElapsed = now - self.songStartTime;
// Check if we need to spawn a new gate based on song timing
if (self.currentNoteIndex < self.currentSong.songBeats.length) {
var nextBeat = self.currentSong.songBeats[self.currentNoteIndex];
if (songElapsed >= nextBeat.time) {
// Check if enough time has passed since last beat
if (now - lastBeatTime >= skipBeatDelay) {
// Spawn a new gate for this beat
self.spawnGateAtTime();
lastBeatTime = now;
}
self.currentNoteIndex++;
}
}
// Animate existing gates
for (var i = self.gates.length - 1; i >= 0; i--) {
var gate = self.gates[i];
var currentScale = gate.gateAsset.scaleX;
// Increase scale with acceleration
var newScale = currentScale + self.gateAnimationSpeed * currentScale;
// Remove gate when too large
if (newScale > 3.0) {
gate.destroy();
self.gates.splice(i, 1);
} else {
gate.updateScale(newScale);
}
}
// Check if song has ended and needs restart
self.checkSongEnd();
};
// Reset song when it ends
self.resetSong = function () {
self.songStartTime = Date.now();
self.currentNoteIndex = 0;
self.lastGateAngle = null; // Reset angle tracking for new song
};
// Check if song has ended and restart
self.checkSongEnd = function () {
if (self.currentNoteIndex >= self.currentSong.songBeats.length) {
// All beats have been spawned, check if we should restart
var lastBeatTime = self.currentSong.songBeats[self.currentSong.songBeats.length - 1].time;
var songElapsed = Date.now() - self.songStartTime;
// Wait a bit after the last beat before restarting
if (songElapsed > lastBeatTime + 5000) {
self.resetSong();
}
}
};
// Destroy a specific gate
self.destroyGate = function (gate) {
var index = self.gates.indexOf(gate);
if (index > -1) {
self.gates.splice(index, 1);
gate.destroy();
}
};
return self;
});
var Note = Container.expand(function () {
var self = Container.call(this);
// Create and attach note asset
var noteGraphics = self.attachAsset('note', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1
});
// Note properties
self.velocity = {
x: 0,
y: -5
}; // Default upward movement
self.lifetime = 1500; // 1.5 seconds (reduced from 3 seconds)
self.spawnTime = Date.now();
self.noteType = 'note'; // Can be 'note' or 'note2'
self.noteColor = 0xFFFFFF; // Default white
// Set note type and appearance
self.setNoteType = function (type) {
self.noteType = type;
// Remove current graphics
noteGraphics.destroy();
// Create new graphics with appropriate asset
noteGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1
});
noteGraphics.tint = self.noteColor;
};
// Set note color
self.setColor = function (color) {
self.noteColor = color;
noteGraphics.tint = color;
};
// Set velocity
self.setVelocity = function (vx, vy) {
self.velocity.x = vx;
self.velocity.y = vy;
};
// Update method
self.update = function () {
// Move based on velocity
self.x += self.velocity.x;
self.y += self.velocity.y;
// Check lifetime
var elapsed = Date.now() - self.spawnTime;
if (elapsed > self.lifetime) {
self.alpha = Math.max(0, 1 - (elapsed - self.lifetime) / 500); // Fade out over 500ms
}
// Destroy if too old or out of bounds
if (elapsed > self.lifetime + 500 || self.y < -100 || self.y > 2832 || self.x < -100 || self.x > 2148) {
self.shouldDestroy = true;
}
};
return self;
});
var NoteSparks = Container.expand(function () {
var self = Container.call(this);
// Array to track active notes
self.notes = [];
// Spawn configuration
self.spawnRate = 500; // Milliseconds between spawns
self.lastSpawnTime = 0;
self.spawnEnabled = false;
// Default spawn properties
self.spawnX = 1024;
self.spawnY = 1366;
self.spawnVelocityRange = {
x: {
min: -2,
max: 2
},
y: {
min: -8,
max: -4
}
};
self.noteColors = [0xFF073A, 0x39FF14, 0x00FFFF, 0xF3F315, 0xFF61F6];
// Set spawn position
self.setSpawnPosition = function (x, y) {
self.spawnX = x;
self.spawnY = y;
};
// Enable/disable spawning
self.setSpawnEnabled = function (enabled) {
self.spawnEnabled = enabled;
};
// Set spawn rate
self.setSpawnRate = function (rate) {
self.spawnRate = rate;
};
// Spawn a single note
self.spawnNote = function () {
var note = new Note();
// Set random position around spawn point
note.x = self.spawnX + (Math.random() - 0.5) * 50;
note.y = self.spawnY;
// Set random velocity
var vx = self.spawnVelocityRange.x.min + Math.random() * (self.spawnVelocityRange.x.max - self.spawnVelocityRange.x.min);
var vy = self.spawnVelocityRange.y.min + Math.random() * (self.spawnVelocityRange.y.max - self.spawnVelocityRange.y.min);
note.setVelocity(vx, vy);
// Set random note type
note.setNoteType(Math.random() > 0.5 ? 'note' : 'note2');
// Set random color
var color = self.noteColors[Math.floor(Math.random() * self.noteColors.length)];
note.setColor(color);
// Add slight rotation
note.rotation = (Math.random() - 0.5) * 0.5;
// Add to arrays and stage
self.notes.push(note);
self.addChild(note);
};
// Spawn multiple notes at once (burst)
self.spawnBurst = function (count) {
for (var i = 0; i < count; i++) {
self.spawnNote();
}
};
// Update method
self.update = function () {
var now = Date.now();
// Spawn new notes if enabled
if (self.spawnEnabled && now - self.lastSpawnTime >= self.spawnRate) {
self.spawnNote();
self.lastSpawnTime = now;
}
// Update and clean up notes
for (var i = self.notes.length - 1; i >= 0; i--) {
var note = self.notes[i];
if (note.shouldDestroy) {
note.destroy();
self.notes.splice(i, 1);
}
}
};
return self;
});
/***********************************************************************************/
/********************************** RUNNER CLASS ***********************************/
/***********************************************************************************/
var Runner = Container.expand(function () {
var self = Container.call(this);
// Create and attach runner asset
var runnerGraphics = self.attachAsset('runnerDir4_001', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF,
//currentColor,
alpha: 1
});
// Initialize runner properties
self.speedX = 0;
self.speedY = 0;
// Track last intersecting state for each gate
self.lastIntersectingGates = {};
// Add tick counter for scale flipping
self.tickCounter = 0;
self.update = function () {
// Don't update if song hasn't started
if (!songStarted) {
return;
}
// Calculate angle based on runner's position relative to center
var angle = Math.atan2(self.y - centerY, self.x - centerX);
// Apply rotation to runner
self.rotation = angle - Math.PI * 0.5; // Add PI/2 to orient correctly
// Increment tick counter and flip scale only every 60 ticks
self.tickCounter++;
if (self.tickCounter >= 5) {
self.scaleX *= -1;
self.tickCounter = 0; // Reset counter
}
};
return self;
});
var Speaker = Container.expand(function () {
var self = Container.call(this);
// Create and attach speaker asset
var speakerGraphics = self.attachAsset('speaker', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1
});
// Initialize speaker properties
self.volume = 1.0;
self.isPlaying = false;
// Method to set speaker volume
self.setVolume = function (vol) {
self.volume = Math.max(0, Math.min(1, vol));
};
// Method to toggle speaker state
self.toggleState = function () {
self.isPlaying = !self.isPlaying;
speakerGraphics.alpha = self.isPlaying ? 1.0 : 0.5;
};
return self;
});
var SpeakerManager = Container.expand(function () {
var self = Container.call(this);
// Array to hold 3 speakers
self.speakers = [];
self.lastBeatTime = 0;
self.beatCooldown = 200; // Minimum time between beat animations
// Properties for tracking speakersBeat data
self.speakerSongData = speakersBeat[0]; // Use first song from speakersBeat
self.currentBeatIndex = 0;
self.songStartTime = 0; // Will be set when song starts
// Initialize 3 speakers
self.initializeSpeakers = function () {
var speakerPositions = [{
x: 1024 - 400,
y: 512
},
// Left speaker
{
x: 1024,
y: 512
},
// Center speaker
{
x: 1024 + 400,
y: 512
} // Right speaker
];
for (var i = 0; i < 3; i++) {
var speaker = new Speaker();
speaker.x = speakerPositions[i].x;
speaker.y = speakerPositions[i].y;
speaker.scaleX = 0.8;
speaker.scaleY = 0.8;
speaker.isBigBumping = false; // Track if big bump animation is running
self.speakers.push(speaker);
self.addChild(speaker);
}
};
// Animate specific speaker based on beat value
self.onBeat = function (beatValue) {
var now = Date.now();
if (now - self.lastBeatTime >= self.beatCooldown) {
// Determine which speaker to animate based on beat value
var speakerIndex = -1;
if (beatValue === "1") {
speakerIndex = 0; // Left speaker
} else if (beatValue === "2") {
speakerIndex = 2; // Right speaker
} else {
speakerIndex = 1; // Center speaker for beats 0 and 3
}
// Animate speaker based on whether it's the main speaker for this beat
if (speakerIndex >= 0 && speakerIndex < self.speakers.length) {
var mainSpeaker = self.speakers[speakerIndex];
// Set flag to indicate big bump is running
mainSpeaker.isBigBumping = true;
// Spawn note sparks around the speaker
self.spawnNoteSparks(mainSpeaker);
// Big bump for the main speaker
tween(mainSpeaker, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to base scale (will be overridden by continuous animation)
tween(mainSpeaker, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
// Clear flag when big bump is complete
mainSpeaker.isBigBumping = false;
}
});
}
});
}
self.lastBeatTime = now;
}
};
// Update method to check for beats and add swing animation
self.update = function () {
// Add continuous twist (rotation) and small bump (scale) animations when song is playing
if (songStarted) {
for (var i = 0; i < self.speakers.length; i++) {
var speaker = self.speakers[i];
var time = Date.now() * 0.003; // Time scale for animations
// Twist animation (rotation) - always apply
var twistAngle = Math.sin(time + i * 0.5) * 0.15; // Rotation with phase offset
speaker.rotation = twistAngle;
// Small bump animation (scale) - only apply if big bump is not running
if (!speaker.isBigBumping) {
var scaleValue = 0.8 + Math.sin(time * 2 + i * 0.3) * 0.05; // Small scale oscillation
speaker.scaleX = scaleValue;
speaker.scaleY = scaleValue;
}
}
}
// Check for beat events
if (!songStarted) {
return;
}
// Check if a new beat has occurred using speakersBeat data
var now = Date.now();
var songElapsed = now - self.songStartTime;
// Process beats from speakersBeat data
if (self.currentBeatIndex < self.speakerSongData.songBeats.length) {
var nextBeat = self.speakerSongData.songBeats[self.currentBeatIndex];
if (songElapsed >= nextBeat.time) {
// Trigger beat
self.onBeat(nextBeat.beat);
self.currentBeatIndex++;
}
} else {
// Check if song has ended and needs restart
var lastBeatTime = self.speakerSongData.songBeats[self.speakerSongData.songBeats.length - 1].time;
// Wait a bit after the last beat before restarting
if (songElapsed > lastBeatTime + 5000) {
self.songStartTime = Date.now();
self.currentBeatIndex = 0;
}
}
};
// Method to spawn note sparks around a speaker
self.spawnNoteSparks = function (speaker) {
// Spawn 3-5 notes around the speaker
var noteCount = 3 + Math.floor(Math.random() * 3);
for (var i = 0; i < noteCount; i++) {
var note = new Note();
// Position notes randomly around the speaker
var angle = Math.random() * Math.PI * 2;
var distance = 50 + Math.random() * 100;
note.x = speaker.x + Math.cos(angle) * distance;
note.y = speaker.y + Math.sin(angle) * distance;
// Set random velocity outward from speaker
var speed = 3 + Math.random() * 5;
note.setVelocity(Math.cos(angle) * speed, Math.sin(angle) * speed - 2);
// Randomly choose note or note2
note.setNoteType(Math.random() > 0.5 ? 'note' : 'note2');
// Use only futuristic violet and blue colors
var futuristicColors = [0xe77d82, 0x48dae9, 0x0b4aa4, 0x9d2c44]; // Violet and Blue
var color = futuristicColors[Math.floor(Math.random() * futuristicColors.length)];
note.setColor(color);
// Add slight rotation
note.rotation = (Math.random() - 0.5) * 0.5;
// Add to noteSparks manager if it exists
if (noteSparks) {
noteSparks.notes.push(note);
noteSparks.addChild(note);
}
}
};
// Initialize speakers when created
self.initializeSpeakers();
return self;
});
// Music will be started by start button
// LK.playMusic('track_02');
// test gate
/*
var testGate = new Gate();
var newScale = 1;
testGate.scaleX = newScale;
testGate.scaleY = newScale;
testGate.gateAsset.scaleX = newScale;
testGate.gateAsset.scaleY = newScale;
testGate.boundingBox.scaleX = newScale;
testGate.boundingBox.scaleY = newScale;
testGate.boundingBox.alpha = 0.3;
game.addChild(testGate);
*/
var StartButton = Container.expand(function () {
var self = Container.call(this);
// Create button background
self.buttonBg = self.attachAsset('start', {
anchorX: 0.5,
anchorY: 0.5
});
// Position at center of screen
self.x = centerX;
self.y = centerY;
// Handle button press
self.down = function () {
// Prevent multiple presses
if (songStarted) {
return;
}
// Mark song as started
songStarted = true;
// Start the music
LK.playMusic('track_01');
// Reset gate manager timing
if (gateManager) {
gateManager.songStartTime = Date.now();
gateManager.currentNoteIndex = 0;
}
// Reset speaker manager timing
if (speakerManager) {
speakerManager.songStartTime = Date.now();
speakerManager.currentBeatIndex = 0;
}
// Enable note sparks spawning
if (noteSparks) {
//noteSparks.setSpawnEnabled(true);
}
// Fade out and remove button
tween(self, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
// Utility function to draw a polygon using drawLine
var game = new LK.Game({
backgroundColor: 0x000c33 // Initialize game with a black background
});
/****
* Game Code
****/
// Global center coordinates
var centerX = 1024;
var centerY = 1366;
// Global array of 6 neon colors
var neonColors = [0x39FF14,
// Neon Green
0xFF073A,
// Neon Red
0x00FFFF,
// Neon Cyan
0xF3F315,
// Neon Yellow
0xFF61F6,
// Neon Pink
0xFF9900 // Neon Orange
];
// Map keys to colors - 15 keys (0-14) mapped to neon colors
var keyColorMap = {
'Key0': 0x39FF14,
// Neon Green
'Key1': 0xFF073A,
// Neon Red
'Key2': 0x00FFFF,
// Neon Cyan
'Key3': 0xF3F315,
// Neon Yellow
'Key4': 0xFF61F6,
// Neon Pink
'Key5': 0xFF9900,
// Neon Orange
'Key6': 0x39FF14,
// Neon Green (repeat)
'Key7': 0xFF073A,
// Neon Red (repeat)
'Key8': 0x00FFFF,
// Neon Cyan (repeat)
'Key9': 0xF3F315,
// Neon Yellow (repeat)
'Key10': 0xFF61F6,
// Neon Pink (repeat)
'Key11': 0xFF9900,
// Neon Orange (repeat)
'Key12': 0x39FF14,
// Neon Green (repeat)
'Key13': 0xFF073A,
// Neon Red (repeat)
'Key14': 0x00FFFF // Neon Cyan (repeat)
};
// Global currentColor, set to a random neon color
var currentColor = neonColors[Math.floor(Math.random() * neonColors.length)];
/***********************************************************************************/
/******************************* UTILITY FUNCTIONS *********************************/
/***********************************************************************************/
function drawPolygon(coordinates, tint) {
log("drawPolygon ", coordinates);
var lines = [];
for (var i = 0; i < coordinates.length; i++) {
var startPoint = coordinates[i];
var endPoint = coordinates[(i + 1) % coordinates.length]; // Loop back to the first point
var line = drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, tint);
lines.push(line);
}
return lines;
}
function updatePolygon(lines, newCoordinates, scale) {
log("updatePolygon ", lines, scale);
// Ensure lines and newCoordinates have the same length
if (lines.length !== newCoordinates.length) {
error("updatePolygon error: lines and newCoordinates length mismatch");
return lines;
}
// Update each line with new coordinates
for (var i = 0; i < lines.length; i++) {
var startPoint = newCoordinates[i];
var endPoint = newCoordinates[(i + 1) % newCoordinates.length]; // Loop back to the first point for the last line
updateLine(lines[i], startPoint.x, startPoint.y, endPoint.x, endPoint.y, scale);
}
return lines;
}
// Utility function to draw lines between two points
function drawLine(x1, y1, x2, y2, tint) {
log("drawLine ", x1, y1);
var line = LK.getAsset('line', {
anchorX: 0.0,
anchorY: 0.0,
x: x1,
y: y1,
tint: tint
});
line.startX = x1;
line.startY = y1;
line.endX = x2;
line.endY = y2;
// Calculate the distance between the two points
var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
// Set the width of the line to the distance between the points
line.width = distance;
// Calculate the angle between the two points
var angle = Math.atan2(y2 - y1, x2 - x1);
// Correct angle calculation for all quadrants
line.rotation = angle;
return line;
}
// Utility function to draw lines between two points
function updateLine(line, newX1, newY1, newX2, newY2, scale) {
log("updateLine ", line);
scale = scale === undefined ? 1 : scale;
// Calculate midpoint of the original line
var midX = (newX1 + newX2) / 2;
var midY = (newY1 + newY2) / 2;
// Adjust start and end points based on scale
newX1 = midX + (newX1 - midX) * scale;
newY1 = midY + (newY1 - midY) * scale;
newX2 = midX + (newX2 - midX) * scale;
newY2 = midY + (newY2 - midY) * scale;
// Update line start and end coordinates after scaling
line.x = newX1;
line.y = newY1;
line.startX = newX1;
line.startY = newY1;
line.endX = newX2;
line.endY = newY2;
// Recalculate the distance between the new scaled points
var distance = Math.sqrt(Math.pow(newX2 - newX1, 2) + Math.pow(newY2 - newY1, 2));
// Update the width of the line to the new distance
line.width = distance;
// Recalculate the angle between the new points
var angle = Math.atan2(newY2 - newY1, newX2 - newX1);
// Update the rotation of the line to the new angle
line.rotation = angle;
return line;
}
function log() {
if (isDebug) {
console.log(arguments);
}
}
/***********************************************************************************/
/******************************* GAME VARIABLES*********************************/
/***********************************************************************************/
var songListV3 = [{
"name": "Words Fly Fast",
"songBeats": [{
"time": 651,
"beat": "1"
}, {
"time": 1256,
"beat": "1"
}, {
"time": 1800,
"beat": "1"
}, {
"time": 2364,
"beat": "1"
}, {
"time": 2828,
"beat": "1"
}, {
"time": 3324,
"beat": "1"
}, {
"time": 3708,
"beat": "1"
}, {
"time": 4192,
"beat": "2"
}, {
"time": 4804,
"beat": "2"
}, {
"time": 5320,
"beat": "2"
}, {
"time": 5796,
"beat": "2"
}, {
"time": 6240,
"beat": "2"
}, {
"time": 6692,
"beat": "2"
}, {
"time": 7176,
"beat": "2"
}, {
"time": 7640,
"beat": "2"
}, {
"time": 8092,
"beat": "1"
}, {
"time": 8576,
"beat": "2"
}, {
"time": 9124,
"beat": "1"
}, {
"time": 9580,
"beat": "2"
}, {
"time": 10084,
"beat": "1"
}, {
"time": 10548,
"beat": "2"
}, {
"time": 11072,
"beat": "1"
}, {
"time": 11556,
"beat": "2"
}, {
"time": 12092,
"beat": "1"
}, {
"time": 12564,
"beat": "2"
}, {
"time": 13008,
"beat": "1"
}, {
"time": 13400,
"beat": "2"
}, {
"time": 13916,
"beat": "1"
}, {
"time": 14460,
"beat": "2"
}, {
"time": 14944,
"beat": "1"
}, {
"time": 15360,
"beat": "2"
}, {
"time": 17340,
"beat": "1"
}, {
"time": 18260,
"beat": "2"
}, {
"time": 19196,
"beat": "1"
}, {
"time": 20064,
"beat": "2"
}, {
"time": 21092,
"beat": "1"
}, {
"time": 22072,
"beat": "2"
}, {
"time": 23100,
"beat": "1"
}, {
"time": 24040,
"beat": "2"
}, {
"time": 24988,
"beat": "1"
}, {
"time": 25884,
"beat": "2"
}, {
"time": 26876,
"beat": "1"
}, {
"time": 27892,
"beat": "2"
}, {
"time": 28820,
"beat": "1"
}, {
"time": 29688,
"beat": "2"
}, {
"time": 30728,
"beat": "1"
}, {
"time": 31696,
"beat": "2"
}, {
"time": 32656,
"beat": "1"
}, {
"time": 33624,
"beat": "2"
}, {
"time": 34673,
"beat": "1"
}, {
"time": 35692,
"beat": "2"
}, {
"time": 36628,
"beat": "1"
}, {
"time": 37536,
"beat": "2"
}, {
"time": 38516,
"beat": "1"
}, {
"time": 39372,
"beat": "2"
}, {
"time": 40524,
"beat": "1"
}, {
"time": 41028,
"beat": "1"
}, {
"time": 43080,
"beat": "2"
}, {
"time": 43564,
"beat": "2"
}, {
"time": 44400,
"beat": "1"
}, {
"time": 44948,
"beat": "1"
}, {
"time": 46888,
"beat": "2"
}, {
"time": 47412,
"beat": "2"
}, {
"time": 48260,
"beat": "1"
}, {
"time": 48818,
"beat": "1"
}, {
"time": 50816,
"beat": "2"
}, {
"time": 51524,
"beat": "2"
}, {
"time": 52168,
"beat": "1"
}, {
"time": 52684,
"beat": "1"
}, {
"time": 54540,
"beat": "3"
}, {
"time": 55488,
"beat": "3"
}, {
"time": 56448,
"beat": "3"
}, {
"time": 57436,
"beat": "3"
}, {
"time": 58412,
"beat": "1"
}, {
"time": 59240,
"beat": "2"
}, {
"time": 59988,
"beat": "1"
}, {
"time": 60808,
"beat": "2"
}, {
"time": 61636,
"beat": "1"
}, {
"time": 63800,
"beat": "1"
}, {
"time": 64688,
"beat": "2"
}, {
"time": 65656,
"beat": "1"
}, {
"time": 66544,
"beat": "2"
}, {
"time": 67492,
"beat": "1"
}, {
"time": 68432,
"beat": "2"
}, {
"time": 69369,
"beat": "1"
}, {
"time": 70336,
"beat": "2"
}, {
"time": 71416,
"beat": "1"
}, {
"time": 72324,
"beat": "2"
}, {
"time": 73408,
"beat": "1"
}, {
"time": 74316,
"beat": "2"
}, {
"time": 75276,
"beat": "1"
}, {
"time": 76204,
"beat": "2"
}, {
"time": 77248,
"beat": "2"
}, {
"time": 78232,
"beat": "1"
}, {
"time": 79168,
"beat": "2"
}, {
"time": 81048,
"beat": "1"
}, {
"time": 81996,
"beat": "2"
}, {
"time": 83096,
"beat": "1"
}, {
"time": 84064,
"beat": "2"
}, {
"time": 85040,
"beat": "1"
}, {
"time": 86008,
"beat": "2"
}, {
"time": 86956,
"beat": "1"
}, {
"time": 87976,
"beat": "2"
}, {
"time": 88832,
"beat": "1"
}, {
"time": 89844,
"beat": "2"
}, {
"time": 90832,
"beat": "1"
}, {
"time": 92672,
"beat": "3"
}, {
"time": 93156,
"beat": "3"
}, {
"time": 93720,
"beat": "3"
}, {
"time": 94329,
"beat": "3"
}, {
"time": 94812,
"beat": "3"
}, {
"time": 95256,
"beat": "3"
}, {
"time": 95720,
"beat": "1"
}, {
"time": 96496,
"beat": "2"
}, {
"time": 97556,
"beat": "3"
}, {
"time": 98112,
"beat": "3"
}, {
"time": 98624,
"beat": "3"
}, {
"time": 99100,
"beat": "3"
}, {
"time": 99604,
"beat": "1"
}, {
"time": 100440,
"beat": "2"
}, {
"time": 101420,
"beat": "1"
}, {
"time": 102388,
"beat": "2"
}, {
"time": 103336,
"beat": "1"
}, {
"time": 104324,
"beat": "2"
}, {
"time": 105172,
"beat": "1"
}, {
"time": 106260,
"beat": "3"
}, {
"time": 106836,
"beat": "3"
}, {
"time": 107392,
"beat": "3"
}, {
"time": 108028,
"beat": "2"
}, {
"time": 108692,
"beat": "1"
}, {
"time": 109540,
"beat": "2"
}, {
"time": 110488,
"beat": "1"
}, {
"time": 111932,
"beat": "2"
}, {
"time": 112888,
"beat": "1"
}, {
"time": 113796,
"beat": "2"
}, {
"time": 114864,
"beat": "1"
}, {
"time": 115904,
"beat": "2"
}, {
"time": 116864,
"beat": "2"
}, {
"time": 117792,
"beat": "1"
}, {
"time": 119184,
"beat": "2"
}, {
"time": 120244,
"beat": "1"
}, {
"time": 121112,
"beat": "2"
}, {
"time": 122121,
"beat": "3"
}, {
"time": 122744,
"beat": "3"
}, {
"time": 123260,
"beat": "3"
}, {
"time": 123784,
"beat": "3"
}, {
"time": 124217,
"beat": "3"
}, {
"time": 125620,
"beat": "1"
}, {
"time": 126668,
"beat": "2"
}, {
"time": 127788,
"beat": "1"
}, {
"time": 128796,
"beat": "2"
}, {
"time": 129716,
"beat": "1"
}, {
"time": 130884,
"beat": "2"
}, {
"time": 131936,
"beat": "1"
}, {
"time": 132932,
"beat": "2"
}, {
"time": 134092,
"beat": "1"
}, {
"time": 135124,
"beat": "2"
}, {
"time": 136160,
"beat": "1"
}, {
"time": 137128,
"beat": "2"
}, {
"time": 139693,
"beat": "3"
}, {
"time": 140208,
"beat": "3"
}, {
"time": 140712,
"beat": "3"
}, {
"time": 141216,
"beat": "3"
}, {
"time": 141700,
"beat": "3"
}, {
"time": 142164,
"beat": "3"
}, {
"time": 142668,
"beat": "3"
}, {
"time": 143164,
"beat": "3"
}, {
"time": 143668,
"beat": "1"
}, {
"time": 144484,
"beat": "2"
}, {
"time": 145412,
"beat": "1"
}, {
"time": 146340,
"beat": "2"
}, {
"time": 147708,
"beat": "3"
}, {
"time": 148304,
"beat": "3"
}, {
"time": 148820,
"beat": "3"
}, {
"time": 149312,
"beat": "3"
}, {
"time": 149908,
"beat": "1"
}, {
"time": 150804,
"beat": "2"
}, {
"time": 151784,
"beat": "3"
}, {
"time": 152328,
"beat": "3"
}, {
"time": 152832,
"beat": "3"
}, {
"time": 153236,
"beat": "3"
}, {
"time": 153680,
"beat": "1"
}, {
"time": 154518,
"beat": "2"
}]
}];
var speakersBeat = [{
"name": "Words Fly Fast",
"songBeats": [{
"time": 128,
"beat": "1"
}, {
"time": 443,
"beat": "1"
}, {
"time": 747,
"beat": "1"
}, {
"time": 1232,
"beat": "1"
}, {
"time": 1579,
"beat": "1"
}, {
"time": 1893,
"beat": "1"
}, {
"time": 2224,
"beat": "1"
}, {
"time": 2549,
"beat": "1"
}, {
"time": 2853,
"beat": "1"
}, {
"time": 3269,
"beat": "1"
}, {
"time": 3712,
"beat": "1"
}, {
"time": 4037,
"beat": "1"
}, {
"time": 4352,
"beat": "1"
}, {
"time": 4661,
"beat": "1"
}, {
"time": 5072,
"beat": "1"
}, {
"time": 5392,
"beat": "1"
}, {
"time": 5728,
"beat": "1"
}, {
"time": 6176,
"beat": "1"
}, {
"time": 6587,
"beat": "1"
}, {
"time": 7141,
"beat": "1"
}, {
"time": 7541,
"beat": "1"
}, {
"time": 7856,
"beat": "1"
}, {
"time": 8171,
"beat": "3"
}, {
"time": 8501,
"beat": "2"
}, {
"time": 8816,
"beat": "2"
}, {
"time": 9120,
"beat": "3"
}, {
"time": 9424,
"beat": "2"
}, {
"time": 9728,
"beat": "2"
}, {
"time": 10032,
"beat": "3"
}, {
"time": 10347,
"beat": "2"
}, {
"time": 10651,
"beat": "1"
}, {
"time": 10971,
"beat": "2"
}, {
"time": 11275,
"beat": "2"
}, {
"time": 11600,
"beat": "2"
}, {
"time": 11915,
"beat": "1"
}, {
"time": 12240,
"beat": "2"
}, {
"time": 12560,
"beat": "3"
}, {
"time": 12875,
"beat": "2"
}, {
"time": 13184,
"beat": "3"
}, {
"time": 13493,
"beat": "2"
}, {
"time": 13813,
"beat": "3"
}, {
"time": 14176,
"beat": "3"
}, {
"time": 14517,
"beat": "1"
}, {
"time": 14843,
"beat": "2"
}, {
"time": 15147,
"beat": "2"
}, {
"time": 15472,
"beat": "3"
}, {
"time": 15781,
"beat": "2"
}, {
"time": 16112,
"beat": "2"
}, {
"time": 16432,
"beat": "2"
}, {
"time": 16741,
"beat": "2"
}, {
"time": 17056,
"beat": "3"
}, {
"time": 17360,
"beat": "3"
}, {
"time": 17685,
"beat": "2"
}, {
"time": 17995,
"beat": "3"
}, {
"time": 18299,
"beat": "3"
}, {
"time": 18608,
"beat": "3"
}, {
"time": 18917,
"beat": "3"
}, {
"time": 19232,
"beat": "1"
}, {
"time": 19547,
"beat": "2"
}, {
"time": 19851,
"beat": "3"
}, {
"time": 20171,
"beat": "1"
}, {
"time": 20475,
"beat": "3"
}, {
"time": 20805,
"beat": "3"
}, {
"time": 21120,
"beat": "1"
}, {
"time": 21435,
"beat": "2"
}, {
"time": 21749,
"beat": "2"
}, {
"time": 22059,
"beat": "2"
}, {
"time": 22373,
"beat": "2"
}, {
"time": 22699,
"beat": "2"
}, {
"time": 23019,
"beat": "1"
}, {
"time": 23323,
"beat": "3"
}, {
"time": 23627,
"beat": "3"
}, {
"time": 23931,
"beat": "2"
}, {
"time": 24240,
"beat": "2"
}, {
"time": 24549,
"beat": "2"
}, {
"time": 24869,
"beat": "2"
}, {
"time": 25189,
"beat": "3"
}, {
"time": 25493,
"beat": "2"
}, {
"time": 25819,
"beat": "2"
}, {
"time": 26133,
"beat": "1"
}, {
"time": 26437,
"beat": "2"
}, {
"time": 26757,
"beat": "2"
}, {
"time": 27061,
"beat": "2"
}, {
"time": 27365,
"beat": "2"
}, {
"time": 27669,
"beat": "3"
}, {
"time": 27973,
"beat": "3"
}, {
"time": 28277,
"beat": "3"
}, {
"time": 28581,
"beat": "3"
}, {
"time": 28885,
"beat": "3"
}, {
"time": 29221,
"beat": "2"
}, {
"time": 29557,
"beat": "3"
}, {
"time": 29861,
"beat": "2"
}, {
"time": 30197,
"beat": "2"
}, {
"time": 30507,
"beat": "2"
}, {
"time": 30827,
"beat": "2"
}, {
"time": 31141,
"beat": "2"
}, {
"time": 31483,
"beat": "2"
}, {
"time": 31787,
"beat": "2"
}, {
"time": 32091,
"beat": "3"
}, {
"time": 32395,
"beat": "3"
}, {
"time": 32731,
"beat": "1"
}, {
"time": 33045,
"beat": "2"
}, {
"time": 33349,
"beat": "3"
}, {
"time": 33653,
"beat": "2"
}, {
"time": 33957,
"beat": "3"
}, {
"time": 34261,
"beat": "2"
}, {
"time": 34576,
"beat": "2"
}, {
"time": 34901,
"beat": "2"
}, {
"time": 35211,
"beat": "3"
}, {
"time": 35520,
"beat": "2"
}, {
"time": 35829,
"beat": "3"
}, {
"time": 36149,
"beat": "3"
}, {
"time": 36464,
"beat": "2"
}, {
"time": 36779,
"beat": "3"
}, {
"time": 37083,
"beat": "2"
}, {
"time": 37403,
"beat": "2"
}, {
"time": 37707,
"beat": "2"
}, {
"time": 38037,
"beat": "2"
}, {
"time": 38341,
"beat": "3"
}, {
"time": 38661,
"beat": "2"
}, {
"time": 38976,
"beat": "1"
}, {
"time": 39301,
"beat": "1"
}, {
"time": 39611,
"beat": "2"
}, {
"time": 39941,
"beat": "2"
}, {
"time": 40256,
"beat": "1"
}, {
"time": 40565,
"beat": "2"
}, {
"time": 40885,
"beat": "1"
}, {
"time": 41189,
"beat": "1"
}, {
"time": 41499,
"beat": "1"
}, {
"time": 41851,
"beat": "1"
}, {
"time": 42155,
"beat": "1"
}, {
"time": 42459,
"beat": "1"
}, {
"time": 42976,
"beat": "1"
}, {
"time": 43429,
"beat": "1"
}, {
"time": 43733,
"beat": "1"
}, {
"time": 44043,
"beat": "1"
}, {
"time": 44357,
"beat": "2"
}, {
"time": 44672,
"beat": "2"
}, {
"time": 44987,
"beat": "1"
}, {
"time": 45312,
"beat": "1"
}, {
"time": 45744,
"beat": "1"
}, {
"time": 46059,
"beat": "1"
}, {
"time": 46373,
"beat": "1"
}, {
"time": 46683,
"beat": "2"
}, {
"time": 46992,
"beat": "2"
}, {
"time": 47301,
"beat": "1"
}, {
"time": 47605,
"beat": "2"
}, {
"time": 47920,
"beat": "2"
}, {
"time": 48229,
"beat": "1"
}, {
"time": 48544,
"beat": "2"
}, {
"time": 48859,
"beat": "1"
}, {
"time": 49168,
"beat": "1"
}, {
"time": 49477,
"beat": "2"
}, {
"time": 49845,
"beat": "1"
}, {
"time": 50160,
"beat": "1"
}, {
"time": 50496,
"beat": "2"
}, {
"time": 50805,
"beat": "1"
}, {
"time": 51173,
"beat": "1"
}, {
"time": 51477,
"beat": "2"
}, {
"time": 51792,
"beat": "2"
}, {
"time": 52107,
"beat": "2"
}, {
"time": 52421,
"beat": "1"
}, {
"time": 52869,
"beat": "1"
}, {
"time": 53189,
"beat": "1"
}, {
"time": 53595,
"beat": "1"
}, {
"time": 53979,
"beat": "1"
}, {
"time": 54288,
"beat": "1"
}, {
"time": 54597,
"beat": "3"
}, {
"time": 54907,
"beat": "2"
}, {
"time": 55227,
"beat": "3"
}, {
"time": 55531,
"beat": "3"
}, {
"time": 55851,
"beat": "2"
}, {
"time": 56165,
"beat": "3"
}, {
"time": 56475,
"beat": "3"
}, {
"time": 56789,
"beat": "2"
}, {
"time": 57104,
"beat": "2"
}, {
"time": 57408,
"beat": "1"
}, {
"time": 57728,
"beat": "3"
}, {
"time": 58053,
"beat": "2"
}, {
"time": 58363,
"beat": "1"
}, {
"time": 58667,
"beat": "3"
}, {
"time": 58976,
"beat": "3"
}, {
"time": 59307,
"beat": "1"
}, {
"time": 59621,
"beat": "3"
}, {
"time": 59947,
"beat": "3"
}, {
"time": 60304,
"beat": "2"
}, {
"time": 60608,
"beat": "3"
}, {
"time": 60971,
"beat": "1"
}, {
"time": 61339,
"beat": "2"
}, {
"time": 61701,
"beat": "1"
}, {
"time": 62032,
"beat": "1"
}, {
"time": 62336,
"beat": "3"
}, {
"time": 62640,
"beat": "3"
}, {
"time": 62955,
"beat": "3"
}, {
"time": 63269,
"beat": "3"
}, {
"time": 63584,
"beat": "2"
}, {
"time": 63904,
"beat": "3"
}, {
"time": 64213,
"beat": "3"
}, {
"time": 64555,
"beat": "2"
}, {
"time": 64864,
"beat": "3"
}, {
"time": 65179,
"beat": "2"
}, {
"time": 65499,
"beat": "2"
}, {
"time": 65824,
"beat": "3"
}, {
"time": 66144,
"beat": "2"
}, {
"time": 66464,
"beat": "2"
}, {
"time": 66768,
"beat": "3"
}, {
"time": 67093,
"beat": "1"
}, {
"time": 67403,
"beat": "3"
}, {
"time": 67707,
"beat": "3"
}, {
"time": 68053,
"beat": "2"
}, {
"time": 68368,
"beat": "2"
}, {
"time": 68709,
"beat": "1"
}, {
"time": 69024,
"beat": "1"
}, {
"time": 69333,
"beat": "2"
}, {
"time": 69648,
"beat": "3"
}, {
"time": 69968,
"beat": "2"
}, {
"time": 70283,
"beat": "2"
}, {
"time": 70587,
"beat": "3"
}, {
"time": 70896,
"beat": "3"
}, {
"time": 71205,
"beat": "3"
}, {
"time": 71525,
"beat": "3"
}, {
"time": 71835,
"beat": "3"
}, {
"time": 72139,
"beat": "3"
}, {
"time": 72448,
"beat": "3"
}, {
"time": 72779,
"beat": "3"
}, {
"time": 73083,
"beat": "2"
}, {
"time": 73387,
"beat": "3"
}, {
"time": 73691,
"beat": "1"
}, {
"time": 74016,
"beat": "3"
}, {
"time": 74336,
"beat": "2"
}, {
"time": 74667,
"beat": "3"
}, {
"time": 74987,
"beat": "3"
}, {
"time": 75296,
"beat": "1"
}, {
"time": 75605,
"beat": "3"
}, {
"time": 75915,
"beat": "3"
}, {
"time": 76224,
"beat": "3"
}, {
"time": 76549,
"beat": "3"
}, {
"time": 76853,
"beat": "3"
}, {
"time": 77157,
"beat": "3"
}, {
"time": 77461,
"beat": "3"
}, {
"time": 77776,
"beat": "1"
}, {
"time": 78107,
"beat": "2"
}, {
"time": 78421,
"beat": "2"
}, {
"time": 78731,
"beat": "2"
}, {
"time": 79045,
"beat": "2"
}, {
"time": 79365,
"beat": "2"
}, {
"time": 79669,
"beat": "1"
}, {
"time": 79995,
"beat": "1"
}, {
"time": 80309,
"beat": "1"
}, {
"time": 80640,
"beat": "1"
}, {
"time": 80965,
"beat": "1"
}, {
"time": 81296,
"beat": "1"
}, {
"time": 81856,
"beat": "1"
}, {
"time": 82165,
"beat": "2"
}, {
"time": 82491,
"beat": "2"
}, {
"time": 82827,
"beat": "2"
}, {
"time": 83136,
"beat": "2"
}, {
"time": 83451,
"beat": "2"
}, {
"time": 83765,
"beat": "1"
}, {
"time": 84080,
"beat": "1"
}, {
"time": 84389,
"beat": "1"
}, {
"time": 84704,
"beat": "1"
}, {
"time": 85040,
"beat": "1"
}, {
"time": 85392,
"beat": "2"
}, {
"time": 85696,
"beat": "1"
}, {
"time": 86016,
"beat": "1"
}, {
"time": 86325,
"beat": "1"
}, {
"time": 86688,
"beat": "1"
}, {
"time": 87019,
"beat": "2"
}, {
"time": 87323,
"beat": "2"
}, {
"time": 87643,
"beat": "1"
}, {
"time": 87947,
"beat": "1"
}, {
"time": 88272,
"beat": "1"
}, {
"time": 88587,
"beat": "1"
}, {
"time": 88901,
"beat": "1"
}, {
"time": 89216,
"beat": "1"
}, {
"time": 89520,
"beat": "1"
}, {
"time": 89872,
"beat": "1"
}, {
"time": 90176,
"beat": "2"
}, {
"time": 90485,
"beat": "2"
}, {
"time": 90800,
"beat": "2"
}, {
"time": 91115,
"beat": "1"
}, {
"time": 91424,
"beat": "1"
}, {
"time": 91819,
"beat": "1"
}, {
"time": 92304,
"beat": "1"
}, {
"time": 92667,
"beat": "1"
}, {
"time": 92992,
"beat": "2"
}, {
"time": 93301,
"beat": "3"
}, {
"time": 93659,
"beat": "1"
}, {
"time": 93968,
"beat": "3"
}, {
"time": 94272,
"beat": "3"
}, {
"time": 94581,
"beat": "2"
}, {
"time": 94885,
"beat": "3"
}, {
"time": 95205,
"beat": "3"
}, {
"time": 95525,
"beat": "2"
}, {
"time": 95845,
"beat": "3"
}, {
"time": 96155,
"beat": "1"
}, {
"time": 96485,
"beat": "2"
}, {
"time": 96811,
"beat": "3"
}, {
"time": 97125,
"beat": "3"
}, {
"time": 97451,
"beat": "2"
}, {
"time": 97755,
"beat": "3"
}, {
"time": 98069,
"beat": "3"
}, {
"time": 98411,
"beat": "2"
}, {
"time": 98725,
"beat": "3"
}, {
"time": 99035,
"beat": "3"
}, {
"time": 99349,
"beat": "3"
}, {
"time": 99680,
"beat": "1"
}, {
"time": 100000,
"beat": "2"
}, {
"time": 100304,
"beat": "3"
}, {
"time": 100624,
"beat": "3"
}, {
"time": 100928,
"beat": "3"
}, {
"time": 101232,
"beat": "3"
}, {
"time": 101557,
"beat": "3"
}, {
"time": 101888,
"beat": "2"
}, {
"time": 102192,
"beat": "3"
}, {
"time": 102507,
"beat": "3"
}, {
"time": 102853,
"beat": "3"
}, {
"time": 103157,
"beat": "3"
}, {
"time": 103461,
"beat": "3"
}, {
"time": 103765,
"beat": "2"
}, {
"time": 104085,
"beat": "1"
}, {
"time": 104395,
"beat": "1"
}, {
"time": 104709,
"beat": "2"
}, {
"time": 105013,
"beat": "3"
}, {
"time": 105317,
"beat": "2"
}, {
"time": 105627,
"beat": "1"
}, {
"time": 105931,
"beat": "3"
}, {
"time": 106240,
"beat": "2"
}, {
"time": 106555,
"beat": "3"
}, {
"time": 106864,
"beat": "3"
}, {
"time": 107168,
"beat": "3"
}, {
"time": 107472,
"beat": "1"
}, {
"time": 107781,
"beat": "2"
}, {
"time": 108091,
"beat": "3"
}, {
"time": 108416,
"beat": "2"
}, {
"time": 108731,
"beat": "2"
}, {
"time": 109045,
"beat": "2"
}, {
"time": 109349,
"beat": "3"
}, {
"time": 109669,
"beat": "2"
}, {
"time": 109979,
"beat": "2"
}, {
"time": 110293,
"beat": "3"
}, {
"time": 110597,
"beat": "3"
}, {
"time": 110917,
"beat": "3"
}, {
"time": 111221,
"beat": "3"
}, {
"time": 111525,
"beat": "3"
}, {
"time": 111840,
"beat": "2"
}, {
"time": 112144,
"beat": "3"
}, {
"time": 112459,
"beat": "3"
}, {
"time": 112773,
"beat": "3"
}, {
"time": 113093,
"beat": "3"
}, {
"time": 113419,
"beat": "3"
}, {
"time": 113723,
"beat": "3"
}, {
"time": 114032,
"beat": "2"
}, {
"time": 114336,
"beat": "2"
}, {
"time": 114651,
"beat": "3"
}, {
"time": 114965,
"beat": "1"
}, {
"time": 115280,
"beat": "2"
}, {
"time": 115584,
"beat": "3"
}, {
"time": 115888,
"beat": "3"
}, {
"time": 116208,
"beat": "2"
}, {
"time": 116523,
"beat": "3"
}, {
"time": 116837,
"beat": "2"
}, {
"time": 117147,
"beat": "3"
}, {
"time": 117451,
"beat": "3"
}, {
"time": 117787,
"beat": "3"
}, {
"time": 118101,
"beat": "3"
}, {
"time": 118405,
"beat": "3"
}, {
"time": 118741,
"beat": "2"
}, {
"time": 119051,
"beat": "2"
}, {
"time": 119360,
"beat": "1"
}, {
"time": 119664,
"beat": "2"
}, {
"time": 119973,
"beat": "3"
}, {
"time": 120283,
"beat": "2"
}, {
"time": 120603,
"beat": "3"
}, {
"time": 120912,
"beat": "3"
}, {
"time": 121216,
"beat": "3"
}, {
"time": 121525,
"beat": "3"
}, {
"time": 121829,
"beat": "3"
}, {
"time": 122293,
"beat": "1"
}, {
"time": 122779,
"beat": "1"
}, {
"time": 123099,
"beat": "1"
}, {
"time": 123504,
"beat": "1"
}, {
"time": 123845,
"beat": "1"
}, {
"time": 124352,
"beat": "1"
}, {
"time": 124699,
"beat": "1"
}, {
"time": 125024,
"beat": "1"
}, {
"time": 125333,
"beat": "1"
}, {
"time": 125648,
"beat": "1"
}, {
"time": 125979,
"beat": "1"
}, {
"time": 126325,
"beat": "1"
}, {
"time": 126635,
"beat": "1"
}, {
"time": 126960,
"beat": "1"
}, {
"time": 127275,
"beat": "1"
}, {
"time": 127589,
"beat": "1"
}, {
"time": 128059,
"beat": "1"
}, {
"time": 128523,
"beat": "1"
}, {
"time": 128837,
"beat": "2"
}, {
"time": 129173,
"beat": "1"
}, {
"time": 129483,
"beat": "2"
}, {
"time": 129813,
"beat": "2"
}, {
"time": 130133,
"beat": "1"
}, {
"time": 130437,
"beat": "1"
}, {
"time": 130741,
"beat": "1"
}, {
"time": 131109,
"beat": "1"
}, {
"time": 131413,
"beat": "1"
}, {
"time": 131739,
"beat": "2"
}, {
"time": 132080,
"beat": "2"
}, {
"time": 132384,
"beat": "2"
}, {
"time": 132784,
"beat": "1"
}, {
"time": 133093,
"beat": "1"
}, {
"time": 133408,
"beat": "2"
}, {
"time": 133739,
"beat": "1"
}, {
"time": 134069,
"beat": "1"
}, {
"time": 134389,
"beat": "1"
}, {
"time": 134709,
"beat": "1"
}, {
"time": 135019,
"beat": "1"
}, {
"time": 135355,
"beat": "1"
}, {
"time": 135733,
"beat": "1"
}, {
"time": 136064,
"beat": "2"
}, {
"time": 136384,
"beat": "3"
}, {
"time": 136693,
"beat": "1"
}, {
"time": 137024,
"beat": "2"
}, {
"time": 137339,
"beat": "2"
}, {
"time": 137776,
"beat": "1"
}, {
"time": 138261,
"beat": "1"
}, {
"time": 138741,
"beat": "1"
}, {
"time": 139109,
"beat": "1"
}, {
"time": 139435,
"beat": "1"
}, {
"time": 139744,
"beat": "2"
}, {
"time": 140048,
"beat": "2"
}, {
"time": 140352,
"beat": "3"
}, {
"time": 140656,
"beat": "3"
}, {
"time": 140987,
"beat": "2"
}, {
"time": 141291,
"beat": "3"
}, {
"time": 141605,
"beat": "3"
}, {
"time": 141909,
"beat": "3"
}, {
"time": 142251,
"beat": "1"
}, {
"time": 142555,
"beat": "1"
}, {
"time": 142859,
"beat": "3"
}, {
"time": 143184,
"beat": "3"
}, {
"time": 143504,
"beat": "3"
}, {
"time": 143813,
"beat": "2"
}, {
"time": 144123,
"beat": "3"
}, {
"time": 144427,
"beat": "2"
}, {
"time": 144741,
"beat": "3"
}, {
"time": 145061,
"beat": "3"
}, {
"time": 145365,
"beat": "3"
}, {
"time": 145685,
"beat": "3"
}, {
"time": 146000,
"beat": "3"
}, {
"time": 146309,
"beat": "2"
}, {
"time": 146624,
"beat": "3"
}, {
"time": 146928,
"beat": "2"
}, {
"time": 147237,
"beat": "2"
}, {
"time": 147547,
"beat": "3"
}, {
"time": 147851,
"beat": "2"
}, {
"time": 148165,
"beat": "3"
}, {
"time": 148480,
"beat": "3"
}, {
"time": 148784,
"beat": "2"
}, {
"time": 149104,
"beat": "3"
}, {
"time": 149413,
"beat": "3"
}, {
"time": 149717,
"beat": "3"
}, {
"time": 150032,
"beat": "2"
}, {
"time": 150336,
"beat": "2"
}, {
"time": 150651,
"beat": "2"
}, {
"time": 150971,
"beat": "3"
}, {
"time": 151285,
"beat": "2"
}, {
"time": 151600,
"beat": "3"
}, {
"time": 151904,
"beat": "3"
}, {
"time": 152208,
"beat": "3"
}, {
"time": 152517,
"beat": "3"
}, {
"time": 152821,
"beat": "3"
}, {
"time": 153248,
"beat": "1"
}, {
"time": 153728,
"beat": "1"
}, {
"time": 154208,
"beat": "1"
}, {
"time": 154576,
"beat": "1"
}]
}];
var isDebug = false;
var globalSpeed = 20;
var currentRotationAngle = 0;
var fullLog = [];
var fpsText;
var lastTick;
var frameCount;
var debugText;
// Removed drag-related variables - using tap controls now
var backgroundManager;
var gateManager;
var targetManager;
var ball;
var runner;
var speakerManager;
var noteSparks;
var minDetectionScale = 0.4;
var maxDetectionScale = 0.6;
var borderLimitAngle = Math.PI * 0.08;
var gateLimitAngle = Math.PI * 0.2;
var gateUniqueId = 0;
var songStarted = false;
// Define three fixed positions: left, center, right
var leftAngle = -Math.PI * 0.5 + gateLimitAngle;
var centerAngle = -Math.PI * 0.5 + Math.PI / 2;
var rightAngle = -Math.PI * 0.5 + Math.PI - gateLimitAngle;
// Function to get next gate ID
function getNextGateId() {
return gateUniqueId++;
}
// Function to play hit sound with protection against multiple simultaneous plays
var lastHitSoundTime = 0;
var hitSoundCooldown = 100; // Minimum time between hit sounds in milliseconds
var skipBeatDelay = 900;
var lastBeatTime = 0;
function playHitSound() {
var now = Date.now();
if (now - lastHitSoundTime >= hitSoundCooldown) {
LK.getSound('hit').play();
lastHitSoundTime = now;
}
}
/***********************************************************************************/
/***************************** GAME INITIALIZATION *********************************/
/***********************************************************************************/
function gameInitialize() {
// Add world01 as background image
var worldBackground = LK.getAsset('world01', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.0
});
game.addChild(worldBackground);
// Initialize background manager first (so it's behind other elements)
backgroundManager = new BackgroundManager();
game.addChild(backgroundManager);
// Initialize runner at center position
//updateSnapPosition(snapPositions.center);
// Initialize gate manager
gateManager = new GateManager();
game.addChild(gateManager);
// Initialize speaker manager
speakerManager = new SpeakerManager();
game.addChild(speakerManager);
// Initialize note sparks (but don't enable spawning yet)
noteSparks = new NoteSparks();
game.addChild(noteSparks);
// Create and position ball
ball = new Ball();
ball.x = 1024;
ball.y = 2000;
ball.alpha = true;
game.addChild(ball);
runner = new Runner();
runner.x = 1024;
runner.y = 2000;
game.addChild(runner);
// Create start button if song hasn't started
if (!songStarted) {
var startButton = new StartButton();
game.addChild(startButton);
}
if (isDebug) {
var debugMarker = LK.getAsset('debugMarker', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 * 0.5,
y: 2732 / 2
});
game.addChild(debugMarker);
fpsText = new Text2('FPS: 0', {
size: 50,
fill: 0xFFFFFF
});
// Position FPS text at the bottom-right corner
fpsText.anchor.set(1, 1); // Anchor to the bottom-right
LK.gui.bottomRight.addChild(fpsText);
// Update FPS display every second
lastTick = Date.now();
frameCount = 0;
debugText = new Text2('Debug Info', {
size: 50,
fill: 0xFFFFFF
});
debugText.anchor.set(0.5, 0); // Anchor to the bottom-right
LK.gui.top.addChild(debugText);
// Create sound test button
var soundTestButton = new Container();
var buttonBg = LK.getAsset('line', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 50,
scaleY: 15,
tint: 0x333333
});
soundTestButton.addChild(buttonBg);
var buttonText = new Text2('SOUND TEST', {
size: 40,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
soundTestButton.addChild(buttonText);
// Position at top right
soundTestButton.x = -100;
soundTestButton.y = 50;
LK.gui.topRight.addChild(soundTestButton);
// Add click handler
soundTestButton.down = function () {
// Play all key sounds with 600ms delay
for (var i = 0; i <= 14; i++) {
(function (index) {
LK.setTimeout(function () {
LK.getSound('key' + index).play();
}, index * 600);
})(i);
}
};
}
}
/***********************************************************************************/
/******************************** MAIN GAME LOOP ***********************************/
/***********************************************************************************/
game.update = function () {
if (isDebug) {
// FPS
var now = Date.now();
frameCount++;
if (now - lastTick >= 1000) {
// Update every second
fpsText.setText('FPS: ' + frameCount);
frameCount = 0;
lastTick = now;
}
}
};
// Define magnetic snap positions
var snapPositions = {
left: 0,
center: 1,
right: 2
};
// Current snap position (start at center)
var currentSnapPosition = snapPositions.center;
// Track last mouse position to detect transitions
var lastMouseZone = 1; // Start at center zone
// Function to update runner position based on snap
function updateSnapPosition(snapPos) {
currentSnapPosition = snapPos;
// Use global angle definitions
var targetAngle = centerAngle;
if (snapPos === snapPositions.left) {
targetAngle = leftAngle;
} else if (snapPos === snapPositions.center) {
targetAngle = centerAngle;
} else if (snapPos === snapPositions.right) {
targetAngle = rightAngle;
}
// Calculate position on the ellipse path
var radiusX = 924;
var radiusY = 634;
// Calculate new position
runner.x = centerX + radiusX * Math.cos(targetAngle + Math.PI * 0.5);
runner.y = centerY + radiusY * Math.sin(targetAngle + Math.PI * 0.5);
// Apply rotation based on position
var rotationMap = {
0: -0.5,
// left
1: 0,
// center
2: 0.5 // right
};
currentRotationAngle = rotationMap[snapPos] * Math.PI * 0.5;
}
// Function to animate runner to new snap position
function animateToSnapPosition(snapPos, jump) {
// Check if we need to pass through center (moving from left to right or right to left)
var needsIntermediateStep = false;
if (currentSnapPosition === snapPositions.left && snapPos === snapPositions.right || currentSnapPosition === snapPositions.right && snapPos === snapPositions.left) {
needsIntermediateStep = true;
}
// If we need intermediate step, first move to center
if (needsIntermediateStep && !jump) {
// Use global angle definitions
// Calculate position on the ellipse path
var radiusX = 924;
var radiusY = 634;
// Calculate center position (intermediate step)
var centerPosX = centerX + radiusX * Math.cos(centerAngle + Math.PI * 0.5);
var centerPosY = centerY + radiusY * Math.sin(centerAngle + Math.PI * 0.5);
// Animate to center first
tween(runner, {
x: centerPosX,
y: centerPosY
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
// After reaching center, continue to final destination
performSnapAnimation(snapPos, jump);
}
});
// Update rotation for center position
currentRotationAngle = 0;
} else {
// Direct movement (no intermediate step needed)
performSnapAnimation(snapPos, jump);
}
}
// Helper function to perform the actual snap animation
function performSnapAnimation(snapPos, jump) {
currentSnapPosition = snapPos;
// Use global angle definitions
var targetAngle = centerAngle;
if (snapPos === snapPositions.left) {
targetAngle = rightAngle;
} else if (snapPos === snapPositions.center) {
targetAngle = centerAngle;
} else if (snapPos === snapPositions.right) {
targetAngle = leftAngle;
}
// Calculate position on the ellipse path
var radiusX = 924;
var radiusY = 634;
// Calculate target position
var targetX = centerX + radiusX * Math.cos(targetAngle + Math.PI * 0.5);
var targetY = centerY + radiusY * Math.sin(targetAngle + Math.PI * 0.5);
// Apply rotation based on position
var rotationMap = {
0: -0.5,
// left
1: 0,
// center
2: 0.5 // right
};
var targetRotation = rotationMap[snapPos] * Math.PI * 0.5;
// Check if jump is requested
if (jump) {
// Jump to y=1200 first, then return to normal position
tween(runner, {
y: 1200
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// After jump, animate back to target position
tween(runner, {
x: targetX,
y: targetY
}, {
duration: 200,
easing: tween.easeIn
});
}
});
} else {
// Normal animation without jump
tween(runner, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeInOut
});
}
// Update rotation angle for continuous rotation
currentRotationAngle = targetRotation;
}
// Add game event handlers for runner control
game.down = function (x, y, obj) {
// Don't process input if song hasn't started
if (!songStarted) {
return;
}
// Divide screen into thirds
var screenWidth = 2048;
var thirdWidth = screenWidth / 3;
// Determine which zone was tapped
var tapZone;
if (x < thirdWidth) {
tapZone = 0; // Left third
} else if (x < thirdWidth * 2) {
tapZone = 1; // Center third
} else {
tapZone = 2; // Right third
}
// Animate to the corresponding snap position based on tap
if (tapZone === 0) {
animateToSnapPosition(snapPositions.left);
} else if (tapZone === 1) {
animateToSnapPosition(snapPositions.center);
} else {
animateToSnapPosition(snapPositions.right);
}
// Update lastMouseZone to match the tapped zone
lastMouseZone = tapZone;
};
game.move = function (x, y, obj) {
// Don't process input if song hasn't started
if (!songStarted) {
return;
}
// Divide screen into thirds
var screenWidth = 2048;
var thirdWidth = screenWidth / 3;
// Determine which zone the mouse is in
var currentZone;
if (x < thirdWidth) {
currentZone = 0; // Left third
} else if (x < thirdWidth * 2) {
currentZone = 1; // Center third
} else {
currentZone = 2; // Right third
}
// Only update position if we've moved to a different zone
if (currentZone !== lastMouseZone) {
// Animate to the corresponding snap position
if (currentZone === 0) {
animateToSnapPosition(snapPositions.left);
} else if (currentZone === 1) {
animateToSnapPosition(snapPositions.center);
} else {
animateToSnapPosition(snapPositions.right);
}
lastMouseZone = currentZone;
}
};
game.up = function (x, y, obj) {
// Empty - no longer needed for mouse-based controls
};
gameInitialize();
remove background
remove background
Futuristic speaker in the shape of a white orb. Face view
white video camera icon
landscape of a furturistic world by night
a white music note
white sparkles emiting from the center. back background
clean red-violet beam from above
button in the shape of a protorealistic holographic futuristc Rectangle . Front view.
above the clouds by a bright night, no visible moon Photorealistic
White Clef de sol
A 20 nodes straight metalic lock chain. High definition. In-Game asset. 2d. High contrast. No shadows
a closed metalic padlock. No visible key hole.
white menu icon
in white