User prompt
add world01 as a background image for the game
User prompt
add a speakerManager that will spawn 3 speakers at the top of the screen (y ~=512) and animate them in rythm with the current beats ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add a Speaker class with speaker asset but don't use it
Code edit (5 edits merged)
Please save this source code
User prompt
add a global skipBeatDelay = 300; ignore beats following within that delay in ms
User prompt
play hit sounf when htting a gate; use a dedicated function that avoid playing the sound multiple times at once
Code edit (1 edits merged)
Please save this source code
User prompt
when gates scale is > 1.0, then ignore intersection with ball
Code edit (1 edits merged)
Please save this source code
User prompt
disable facekit
User prompt
Invert gate spawning logic to spawn 2 gates in non-beat angles instead of 1 gate at beat angle
User prompt
Invert gate spawning logic to spawn 2 gates in non-beat angles instead of 1 gate at beat angle
User prompt
Invert the gate system : instead of spawning 1 gate at a certain angle depending on beat 1,2 or 3, now spawn 2 gates in the 2 other angles at each beat : only the beat gate will stay free
Code edit (3 edits merged)
Please save this source code
User prompt
Lines ``` 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; ``` are duplicated. they should be in global scope. => Adapt the code without breaking anything Please this time be smart and define them AFTER gateLimitAngle declaration
User prompt
Lines ``` 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; ``` are duplicated. they should be in global scope. => Adapt the code without breaking anything
User prompt
``` // 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; ``` is duplicated. it should be in global scope. => Adapt the code
User prompt
I've added the "jump" parameter in animateToSnapPosition, implement it to make player jump to the y=1200 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
when detecting player mouth open, make the runner (and ball) jump ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
show Ey Tilt only in debug mode, and change its fill to yellow
User prompt
angle goes from -3 to 3; it should be more angle between the line made by the two eyes and the horizontal axe
User prompt
add a text with the current eyes tilt angle
User prompt
Ok, add a range for 'zero' angle (center) and invert current left/right tilt angle
User prompt
use eyes line angle to tilt left/right ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
/**** * 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 >= 0.3 && gate.gateAsset.scaleX <= 0.6 && 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 // 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; }); /***********************************************************************************/ /********************************** 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; }); // 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; } // 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 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 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() { // 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); // 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; // Snap threshold for switching positions (percentage of screen width) var snapThreshold = 0.15; // 15% of screen width // 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 halves var screenWidth = 2048; var screenCenter = screenWidth / 2; // Check which half of the screen was tapped if (x < screenCenter) { // Tapped left half - move to left lane animateToSnapPosition(snapPositions.left); } else { // Tapped right half - move to right lane animateToSnapPosition(snapPositions.right); } }; game.move = function (x, y, obj) { // Empty - no longer needed for tap controls }; game.up = function (x, y, obj) { // On release, return to center lane animateToSnapPosition(snapPositions.center); }; gameInitialize();
===================================================================
--- original.js
+++ change.js
@@ -1258,9 +1258,9 @@
}
// 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 = 300;
+var skipBeatDelay = 900;
var lastBeatTime = 0;
function playHitSound() {
var now = Date.now();
if (now - lastHitSoundTime >= hitSoundCooldown) {
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