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");
var facekit = LK.import("@upit/facekit.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
var currentIntersecting = self.intersects(gate.boundingBox);
// Detect transition from not intersecting to intersecting
if (!self.lastIntersectingGates[gateId] && currentIntersecting) {
// 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);
}
});
}
// Play the sound based on the gate's assigned key
if (false && gate.noteKey) {
// Extract the number from the key (e.g., "Key6" -> "6")
var keyNumber = gate.noteKey.replace('Key', '');
var soundKey = 'key' + keyNumber;
// Play the corresponding sound
LK.getSound(soundKey).play();
}
}
// 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 two gates at current time (inverted system - spawn gates in non-beat angles)
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
// The scale grows as: scale = startScale * Math.exp(speed * t)
// But in our code, scale increases as: scale += speed * scale per frame (exponential growth)
// So, scale(t) = startScale * Math.exp(speed * t)
// We want to solve for t: endScale = startScale * Math.exp(speed * t)
// => t = ln(endScale/startScale) / speed
// But our speed is per ms, so t is in ms
var timeToReachPlayer = Math.log(endScale / startScale) / speed; // ms
// Now, we want the gate to reach the player at the beat time, so we need to spawn it early
// The current song time is (Date.now() - self.songStartTime)
// The beat time is self.currentSong.songBeats[self.currentNoteIndex].time
// So, we need to spawn the gate at (beatTime - timeToReachPlayer)
// If we're late, spawn immediately
// Determine which angle to spawn gate at (the beat angle, not the other angles)
var gatesToSpawn = [];
if (beatValue === "1") {
// Beat 1 = spawn gate at right
gatesToSpawn = [rightAngle];
} else if (beatValue === "2") {
// Beat 2 = spawn gate at left
gatesToSpawn = [leftAngle];
} else {
// Beat values 0 and 3 = spawn gate at center
gatesToSpawn = [centerAngle];
}
// Spawn gates for each angle
for (var i = 0; i < gatesToSpawn.length; i++) {
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 = gatesToSpawn[i];
// Apply rotation to gate asset
gate.gateAsset.rotation = gatesToSpawn[i];
// Instead of adding the gate immediately, schedule it if needed
var beatTime = self.currentSong.songBeats[self.currentNoteIndex].time;
var now = Date.now();
var songElapsed = now - self.songStartTime;
var spawnTime = beatTime - timeToReachPlayer;
if (songElapsed >= spawnTime) {
// Spawn now
self.gates.push(gate);
self.addChild(gate);
} else {
// Schedule spawn for later
LK.setTimeout(function () {
self.gates.push(gate);
self.addChild(gate);
}, spawnTime - songElapsed);
}
}
};
// 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) {
// Spawn a new gate for this beat
self.spawnGateAtTime();
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++;
}
/***********************************************************************************/
/***************************** 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;
}
}
// Mouth open jump detection
if (songStarted && facekit && facekit.mouthOpen) {
// Only trigger jump if not already in jump (debounce)
if (!game._mouthWasOpen) {
// Simulate a jump by moving runner to center lane (or you can define your own jump logic)
animateToSnapPosition(snapPositions.center, true);
game._mouthWasOpen = true;
}
} else {
game._mouthWasOpen = false;
}
};
// 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
@@ -327,19 +327,19 @@
// The current song time is (Date.now() - self.songStartTime)
// The beat time is self.currentSong.songBeats[self.currentNoteIndex].time
// So, we need to spawn the gate at (beatTime - timeToReachPlayer)
// If we're late, spawn immediately
- // Determine which angles to spawn gates at (the 2 OTHER angles, not the beat angle)
+ // Determine which angle to spawn gate at (the beat angle, not the other angles)
var gatesToSpawn = [];
if (beatValue === "1") {
- // Beat 1 = right is free, spawn gates at left and center
- gatesToSpawn = [leftAngle, centerAngle];
+ // Beat 1 = spawn gate at right
+ gatesToSpawn = [rightAngle];
} else if (beatValue === "2") {
- // Beat 2 = left is free, spawn gates at right and center
- gatesToSpawn = [rightAngle, centerAngle];
+ // Beat 2 = spawn gate at left
+ gatesToSpawn = [leftAngle];
} else {
- // Beat values 0 and 3 = center is free, spawn gates at left and right
- gatesToSpawn = [leftAngle, rightAngle];
+ // Beat values 0 and 3 = spawn gate at center
+ gatesToSpawn = [centerAngle];
}
// Spawn gates for each angle
for (var i = 0; i < gatesToSpawn.length; i++) {
var gate = new Gate();
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