User prompt
Prevent note generation when key animation is not playing.
User prompt
Prevent note spawning in the top 4 square when animation is not playing.
User prompt
Notes spawn only when the animation plays in the top 4 square. Instantly spawn notes when the animation plays.
User prompt
Make all difficulty levels a little easier
User prompt
Spawn notes when animation plays in top 4 frames. Limit the maximum number of notes that can be on screen at the same time.
User prompt
Remove all note effects.
User prompt
Move the top 4 squares down a little. When an animation is played in these frames, a note will appear. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Move the bottom 4 squares very high.
User prompt
İ want to create a key animation that is synchronized with the beats of the background music. Every time there's a beat in the music, a key (either a virtual or on-secreen key) should visually animate – as if it's being pressed. The animation should follow the BPM (beats per minute) of the music. At each beat, a visual effect (like a scale change, color flash or glow) should be triggered. Remove all note animations. Then recreate button and note animations accordingly. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: setTimeout is not a function' in or related to this line: 'setTimeout(function () {' Line Number: 1453
User prompt
Please fix the bug: 'ReferenceError: notes is not defined' in or related to this line: 'for (var t = 0; t < notes.length; t++) {' Line Number: 1662
User prompt
İ want to create a key animation that is synchronized with the beats of the background music. Every time there's a beat in the music, a key (either a virtual or on-secreen key) should visually animate – as if it's being pressed. The animation should follow the BPM (beats per minute) of the music. At each beat, a visual effect (like a scale change, color flash or glow) should be triggered. Remove all note animations. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
İ want to create a key animation that is synchronized with the beats of the background music. Every time there's a beat in the music, a key (either a virtual or on-secreen key) should visually animate – as if it's being pressed. The animation should follow the BPM (beats per minute) of the music. At each beat, a visual effect (like a scale change, color flash or glow) should be triggered. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Remix started
Copy Rhythm Rush
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BackgroundParticle = Container.expand(function () {
var self = Container.call(this);
// Create particle using small note asset
self.particleGraphics = self.attachAsset('chordNote2', {
anchorX: 0.5,
anchorY: 0.5
});
// Random properties
self.speed = 1 + Math.random() * 3;
self.rotationSpeed = (Math.random() - 0.5) * 0.05;
self.floatAmplitude = 20 + Math.random() * 30;
self.floatSpeed = 0.02 + Math.random() * 0.03;
self.initialY = self.y;
// Set random position
self.x = Math.random() * 2048;
self.y = Math.random() * 2732;
self.initialY = self.y;
// Set random size and transparency
var scale = 0.1 + Math.random() * 0.3;
self.particleGraphics.scaleX = scale;
self.particleGraphics.scaleY = scale;
self.particleGraphics.alpha = 0.1 + Math.random() * 0.3;
// Random color tint
var colors = [0x8800ff, 0xff00ff, 0x00ffff, 0xffffff];
self.particleGraphics.tint = colors[Math.floor(Math.random() * colors.length)];
self.update = function () {
// Slow upward movement
self.y -= self.speed;
// Floating motion
self.x += Math.sin(LK.ticks * self.floatSpeed) * 0.5;
// Gentle rotation
self.particleGraphics.rotation += self.rotationSpeed;
// Music-reactive pulsing during gameplay
var basePulse = 1 + Math.sin(LK.ticks * 0.05) * 0.1;
var musicPulse = 1;
if (gameState === 'playing' && beatIntensity > 0) {
musicPulse = 1 + beatIntensity * 0.3;
beatIntensity *= 0.95; // Decay beat intensity
}
var finalPulse = basePulse * musicPulse;
self.particleGraphics.scaleX = scale * finalPulse;
self.particleGraphics.scaleY = scale * finalPulse;
// Music-reactive color shifting
if (gameState === 'playing') {
var beatColor = Math.sin(musicBeat * 0.1) * 0.5 + 0.5;
var musicColorShift = Math.floor(beatColor * 100);
self.particleGraphics.tint = self.particleGraphics.tint & 0xFF00FF | musicColorShift << 8;
}
// Reset particle when it goes off screen
if (self.y < -100) {
self.y = 2732 + 100;
self.x = Math.random() * 2048;
}
};
return self;
});
var BackgroundWave = Container.expand(function () {
var self = Container.call(this);
// Create wave using lane asset
self.waveGraphics = self.attachAsset('lane', {
anchorX: 0.5,
anchorY: 0.5
});
// Wave properties
self.amplitude = 50 + Math.random() * 100;
self.frequency = 0.01 + Math.random() * 0.02;
self.speed = 0.5 + Math.random() * 1.5;
self.initialX = Math.random() * 2048;
self.direction = Math.random() > 0.5 ? 1 : -1;
// Set position and appearance
self.x = self.initialX;
self.y = Math.random() * 2732;
self.waveGraphics.alpha = 0.05 + Math.random() * 0.1;
self.waveGraphics.scaleX = 0.5 + Math.random() * 1.5;
self.waveGraphics.scaleY = 0.1 + Math.random() * 0.3;
// Random color tint
var colors = [0x8800ff, 0xff00ff, 0x00ffff];
self.waveGraphics.tint = colors[Math.floor(Math.random() * colors.length)];
self.update = function () {
// Wave movement
self.x += self.speed * self.direction;
// Sine wave motion
self.y += Math.sin(LK.ticks * self.frequency) * 0.5;
// Gentle rotation
self.waveGraphics.rotation = Math.sin(LK.ticks * 0.02) * 0.2;
// Reset wave when it goes off screen
if (self.x < -200 || self.x > 2248) {
self.x = self.direction > 0 ? -200 : 2248;
self.y = Math.random() * 2732;
}
};
return self;
});
var Button = Container.expand(function (lane) {
var self = Container.call(this);
self.lane = lane;
self.isPressed = false;
self.buttonGraphics = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
// Position at bottom of lane
self.x = lane * (2048 / 3) + 2048 / 6;
self.y = 2732 - 200;
self.down = function (x, y, obj) {
self.press();
};
self.press = function () {
if (self.isPressed) return;
self.isPressed = true;
// Create ripple wave effect
var ripple = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
ripple.x = self.x;
ripple.y = self.y;
ripple.alpha = 0.8;
ripple.scaleX = 0.8;
ripple.scaleY = 0.8;
ripple.tint = 0x00FFFF;
// Animate ripple expanding outward
tween(ripple, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (ripple && ripple.parent) {
ripple.destroy();
}
}
});
// Enhanced press animation with bounce
tween(self.buttonGraphics, {
alpha: 0.6,
scaleX: 0.85,
scaleY: 0.85,
rotation: Math.PI * 0.1
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
// Quick bounce back
tween(self.buttonGraphics, {
scaleX: 1.1,
scaleY: 1.1,
rotation: -Math.PI * 0.05
}, {
duration: 60,
easing: tween.bounceOut
});
}
});
// Check for notes in this lane
var hitNote = null;
var bestDistance = Infinity;
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.lane === self.lane && !note.hasBeenHit && note.y >= timingLineY) {
var distance = Math.abs(note.y - self.y);
if (distance < bestDistance) {
bestDistance = distance;
hitNote = note;
}
}
}
if (hitNote) {
hitNote.hasBeenHit = true;
// Calculate points based on timing
var points = 0;
var feedbackText = '';
if (bestDistance <= 150) {
// Perfect hit - note is on button
points = 100;
feedbackText = 'PERFECT';
} else if (bestDistance <= 250) {
// Great hit - note is between timing line and button
points = 50;
feedbackText = 'GREAT';
}
if (points > 0 && gameState === 'playing') {
LK.setScore(LK.getScore() + points);
scoreTxt.setText(LK.getScore());
LK.getSound('hit').play();
// Update combo (hit)
updateCombo(true);
// Visual feedback
LK.effects.flashObject(hitNote, 0xffffff, 200);
// Create explosion effect at note position
var explosionColor = feedbackText === 'PERFECT' ? 0xFFD700 : 0x00FFFF;
createExplosion(hitNote.x, hitNote.y, explosionColor);
// Show feedback text
feedbackTxt.setText(feedbackText);
feedbackTxt.alpha = 1;
tween(feedbackTxt, {
alpha: 0
}, {
duration: 800
});
// Create floating score animation
var scoreText = new Text2('+' + points, {
size: 50,
fill: explosionColor
});
scoreText.anchor.set(0.5, 0.5);
scoreText.x = hitNote.x;
scoreText.y = hitNote.y;
scoreText.alpha = 1;
game.addChild(scoreText);
// Animate floating score
tween(scoreText, {
y: scoreText.y - 150,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (scoreText && scoreText.parent) {
scoreText.destroy();
}
}
});
// Special animations for GREAT and PERFECT
if (feedbackText === 'PERFECT') {
// Create sparkle particles around button
for (var s = 0; s < 12; s++) {
var sparkle = game.addChild(LK.getAsset('chordNote2', {
anchorX: 0.5,
anchorY: 0.5
}));
sparkle.x = self.x + (Math.random() - 0.5) * 200;
sparkle.y = self.y + (Math.random() - 0.5) * 200;
sparkle.scaleX = 0.2 + Math.random() * 0.3;
sparkle.scaleY = sparkle.scaleX;
sparkle.alpha = 0.8;
sparkle.tint = 0xFFD700;
// Animate sparkles floating upward and fading
tween(sparkle, {
y: sparkle.y - 150 - Math.random() * 100,
x: sparkle.x + (Math.random() - 0.5) * 100,
alpha: 0,
rotation: Math.PI * 2 * (Math.random() > 0.5 ? 1 : -1),
scaleX: sparkle.scaleX * 0.5,
scaleY: sparkle.scaleY * 0.5
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
delay: Math.random() * 200,
onFinish: function onFinish() {
if (sparkle && sparkle.parent) {
sparkle.destroy();
}
}
});
}
// Perfect animation: scale up and spin with color change
var originalScale = self.buttonGraphics.scaleX;
var originalTint = self.buttonGraphics.tint;
self.buttonGraphics.tint = 0xFFD700; // Gold color
tween(self.buttonGraphics, {
scaleX: originalScale * 1.5,
scaleY: originalScale * 1.5,
rotation: Math.PI * 2
}, {
duration: 600,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(self.buttonGraphics, {
scaleX: originalScale,
scaleY: originalScale,
rotation: 0,
tint: originalTint
}, {
duration: 200
});
}
});
// Create perfect ring animation around button
var perfectRing = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
perfectRing.x = self.x;
perfectRing.y = self.y;
perfectRing.tint = 0xFFD700;
perfectRing.alpha = 0.8;
perfectRing.scaleX = 0.5;
perfectRing.scaleY = 0.5;
// Animate ring expanding outward while fading
tween(perfectRing, {
scaleX: 3.0,
scaleY: 3.0,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (perfectRing && perfectRing.parent) {
perfectRing.destroy();
}
}
});
} else if (feedbackText === 'GREAT') {
// Create energy trail effect
for (var t = 0; t < 6; t++) {
var trail = game.addChild(LK.getAsset('chordNote3', {
anchorX: 0.5,
anchorY: 0.5
}));
trail.x = self.x;
trail.y = self.y;
trail.scaleX = 0.4;
trail.scaleY = 0.4;
trail.alpha = 0.7;
trail.tint = 0x00FFFF;
// Animate trails in different directions
var angle = t / 6 * Math.PI * 2;
var distance = 120 + Math.random() * 80;
var targetX = self.x + Math.cos(angle) * distance;
var targetY = self.y + Math.sin(angle) * distance;
tween(trail, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.PI
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
delay: t * 50,
onFinish: function onFinish() {
if (trail && trail.parent) {
trail.destroy();
}
}
});
}
// Great animation: pulse and glow effect
var originalScale = self.buttonGraphics.scaleX;
var originalTint = self.buttonGraphics.tint;
self.buttonGraphics.tint = 0x00FFFF; // Cyan color
tween(self.buttonGraphics, {
scaleX: originalScale * 1.3,
scaleY: originalScale * 1.3
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(self.buttonGraphics, {
scaleX: originalScale,
scaleY: originalScale,
tint: originalTint
}, {
duration: 300,
easing: tween.easeOut
});
}
});
}
// Remove the note immediately
hitNote.destroy();
var noteIndex = notes.indexOf(hitNote);
if (noteIndex !== -1) {
notes.splice(noteIndex, 1);
}
}
}
// Reset button after short delay with smooth animation
LK.setTimeout(function () {
self.isPressed = false;
// Smooth release animation
tween(self.buttonGraphics, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.elasticOut
});
}, 100);
};
return self;
});
var Note = Container.expand(function (lane, type) {
var self = Container.call(this);
self.lane = lane;
self.type = type;
self.hasBeenHit = false;
// Get speed multiplier from current difficulty
var speedMultiplier = currentDifficulty !== -1 ? difficulties[currentDifficulty].noteSpeedMultiplier : 1.0;
// Set speed and asset based on type
if (type === 'slow') {
self.speed = 8 * speedMultiplier;
self.noteGraphics = self.attachAsset('slowNote', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'medium') {
self.speed = 12 * speedMultiplier;
self.noteGraphics = self.attachAsset('mediumNote', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'fast') {
self.speed = 16 * speedMultiplier;
self.noteGraphics = self.attachAsset('fastNote', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Position in lane
self.x = lane * (2048 / 3) + 2048 / 6;
self.y = 0;
// Add idle breathing animation to buttons
self.update = function () {
// Move note down the screen
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'playing', or 'gameOver'
// Background effects
var backgroundParticles = [];
var backgroundWaves = [];
var startButton;
var gameOverButton;
var difficultyButtons = [];
var currentDifficulty = -1; // -1=None selected, 0=Easy, 1=Medium, 2=Hard, 3=Expert
var difficultySelected = false;
var musicButtons = [];
var currentMusic = -1; // -1=None selected, 0=Music1, 1=Music2, 2=Music3
var musicTracks = [{
name: '1',
id: 'GameMusic1',
color: 0x8800ff,
bpm: 120
}, {
name: '2',
id: 'GameMusic2',
color: 0xff8800,
bpm: 140
}, {
name: '3',
id: 'GameMusic3',
color: 0x0088ff,
bpm: 128
}, {
name: '4',
id: 'GameMusic4',
color: 0x00ff88,
bpm: 110
}, {
name: '5',
id: 'GameMusic5',
color: 0xff0088,
bpm: 135
}, {
name: '6',
id: 'GameMusic6',
color: 0x88ff00,
bpm: 125
}];
var difficulties = [{
name: 'EASY',
spawnInterval: 120,
maxNotes: 4,
timeLimit: 60,
noteSpeedMultiplier: 0.8,
color: 0x00FF00
}, {
name: 'MEDIUM',
spawnInterval: 80,
maxNotes: 6,
timeLimit: 60,
noteSpeedMultiplier: 1.2,
color: 0xFFFF00
}, {
name: 'HARD',
spawnInterval: 50,
maxNotes: 8,
timeLimit: 60,
noteSpeedMultiplier: 1.6,
color: 0xFF8800
}, {
name: 'EXPERT',
spawnInterval: 30,
maxNotes: 12,
timeLimit: 60,
noteSpeedMultiplier: 2.0,
color: 0xFF0000
}];
// Game variables
var notes = [];
var buttons = [];
var maxNotes = 5;
var spawnTimer = 0;
var spawnInterval = 120; // frames between spawns
var timingLineY = 2732 - 500;
var bottomLineY = 2732 - 2; // Position miss line at bottom edge of screen
// Music synchronization variables
var musicBeat = 0;
var beatInterval = 30; // frames per beat (adjustable based on music tempo)
var beatIntensity = 0;
var musicPulseElements = [];
// BPM-based beat detection and key animation variables
var bpm = 120; // Default BPM - will be set based on selected music
var beatsPerSecond = bpm / 60;
var framesPerBeat = Math.floor(60 / beatsPerSecond); // 60 FPS assumed
var beatTimer = 0;
var lastBeatTime = 0;
var beatAccuracy = 0.1; // Timing accuracy for beat detection
// Virtual key animation variables
var virtualKey = null;
var keyAnimationActive = false;
// Create lane separators
var lane1 = game.addChild(LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0
}));
lane1.x = 2048 / 3;
lane1.y = 0;
lane1.visible = false; // Hide initially
var lane2 = game.addChild(LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0
}));
lane2.x = 2048 / 3 * 2;
lane2.y = 0;
lane2.visible = false; // Hide initially
// Create timing line
var timingLine = game.addChild(LK.getAsset('timingLine', {
anchorX: 0,
anchorY: 0.5
}));
timingLine.x = 0;
timingLine.y = timingLineY;
timingLine.visible = false; // Hide initially
// Create bottom line
var bottomLine = game.addChild(LK.getAsset('bottomLine', {
anchorX: 0,
anchorY: 0.5
}));
bottomLine.x = 0;
bottomLine.y = bottomLineY;
bottomLine.visible = false; // Hide initially
// Create start menu elements
var titleTxt = new Text2('RHYTHM GAME', {
size: 120,
fill: 0xFFFFFF
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.x = 2048 / 2;
titleTxt.y = 2732 / 2 - 200;
titleTxt.alpha = 0;
titleTxt.scaleX = 0.5;
titleTxt.scaleY = 0.5;
game.addChild(titleTxt);
// Animate title appearing
tween(titleTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 1000,
easing: tween.elasticOut
});
// Add continuous floating animation
function animateTitleFloat() {
tween(titleTxt, {
y: titleTxt.y - 20
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(titleTxt, {
y: titleTxt.y + 20
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: animateTitleFloat
});
}
});
}
LK.setTimeout(animateTitleFloat, 1500);
// Add breathing effect to background elements
function createBreathingEffect() {
// Animate lane separators with subtle breathing
tween(lane1, {
alpha: 0.8,
scaleY: 1.05
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lane1, {
alpha: 0.5,
scaleY: 1.0
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: createBreathingEffect
});
}
});
// Offset timing for lane2
LK.setTimeout(function () {
tween(lane2, {
alpha: 0.8,
scaleY: 1.05
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(lane2, {
alpha: 0.5,
scaleY: 1.0
}, {
duration: 3000,
easing: tween.easeInOut
});
}
});
}, 1500);
}
// Start breathing effect when game elements are visible
var breathingEffectStarted = false;
var startButtonShape = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
startButtonShape.x = 2048 / 2;
startButtonShape.y = 2732 / 2 + 100;
startButtonShape.tint = 0x00AA00;
startButtonShape.alpha = 0;
startButtonShape.scaleX = 0;
startButtonShape.scaleY = 0;
// Animate start button appearing
tween(startButtonShape, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut,
delay: 1200
});
// Add pulsing glow effect to start button
function animateStartButtonPulse() {
tween(startButtonShape, {
scaleX: 1.1,
scaleY: 1.1,
tint: 0x00FF00
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(startButtonShape, {
scaleX: 1,
scaleY: 1,
tint: 0x00AA00
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: animateStartButtonPulse
});
}
});
}
LK.setTimeout(animateStartButtonPulse, 2000);
var startButtonTxt = new Text2('START', {
size: 60,
fill: 0xFFFFFF
});
startButtonTxt.anchor.set(0.5, 0.5);
startButtonTxt.x = 2048 / 2 + 300;
startButtonTxt.y = 2732 / 2 + 100;
startButtonTxt.alpha = 0;
game.addChild(startButtonTxt);
// Animate start button text sliding in
tween(startButtonTxt, {
x: 2048 / 2,
alpha: 1
}, {
duration: 600,
easing: tween.easeOut,
delay: 1400
});
// Create difficulty selection text
var difficultyTitleTxt = new Text2('SELECT DIFFICULTY', {
size: 80,
fill: 0xFFFFFF
});
difficultyTitleTxt.anchor.set(0.5, 0.5);
difficultyTitleTxt.x = 2048 / 2;
difficultyTitleTxt.y = 2732 / 2 + 300;
game.addChild(difficultyTitleTxt);
// Create difficulty selection indicator text
var difficultyIndicatorTxt = new Text2('None selected', {
size: 60,
fill: 0xFF0000
});
difficultyIndicatorTxt.anchor.set(0.5, 0.5);
difficultyIndicatorTxt.x = 2048 / 2;
difficultyIndicatorTxt.y = 2732 / 2 + 500;
difficultyIndicatorTxt.alpha = 0;
game.addChild(difficultyIndicatorTxt);
// Create music selection text
var musicTitleTxt = new Text2('SELECT MUSIC', {
size: 70,
fill: 0xFFFFFF
});
musicTitleTxt.anchor.set(0.5, 0.5);
musicTitleTxt.x = 2048 / 2;
musicTitleTxt.y = 2732 / 2 + 650;
game.addChild(musicTitleTxt);
// Create music selection indicator text
var musicIndicatorTxt = new Text2('None selected', {
size: 50,
fill: 0xFF0000
});
musicIndicatorTxt.anchor.set(0.5, 0.5);
musicIndicatorTxt.x = 2048 / 2;
musicIndicatorTxt.y = 2732 / 2 + 1130;
musicIndicatorTxt.alpha = 0;
game.addChild(musicIndicatorTxt);
// Create difficulty buttons
for (var d = 0; d < difficulties.length; d++) {
var diffButton = {
index: d,
text: new Text2(difficulties[d].name, {
size: 50,
fill: difficulties[d].color
})
};
diffButton.text.anchor.set(0.5, 0.5);
diffButton.text.x = d * 400 + 424; // Spread across screen
diffButton.text.y = 2732 / 2 + 400; // Below the "select difficulty" text
diffButton.text.scaleX = 0;
diffButton.text.scaleY = 0;
diffButton.text.alpha = 0;
game.addChild(diffButton.text);
difficultyButtons.push(diffButton);
// Animate text appearing with staggered timing
tween(diffButton.text, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 600,
easing: tween.elasticOut,
delay: d * 150
});
}
// Animate difficulty title appearing
difficultyTitleTxt.alpha = 0;
difficultyTitleTxt.scaleY = 0;
tween(difficultyTitleTxt, {
alpha: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut,
delay: 200
});
// Animate difficulty indicator appearing
tween(difficultyIndicatorTxt, {
alpha: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut,
delay: 800
});
// Create music selection buttons
for (var m = 0; m < musicTracks.length; m++) {
var musicButton = {
index: m,
shape: game.addChild(LK.getAsset('musicButton', {
anchorX: 0.5,
anchorY: 0.5
})),
text: new Text2(musicTracks[m].name, {
size: 40,
fill: 0xFFFFFF
})
};
// Position buttons in 2 rows of 3
var row = Math.floor(m / 3);
var col = m % 3;
musicButton.shape.x = col * 300 + 724; // Spread across screen
musicButton.shape.y = 2732 / 2 + 800 + row * 200;
musicButton.shape.tint = musicTracks[m].color;
musicButton.shape.scaleX = 0;
musicButton.shape.scaleY = 0;
musicButton.shape.alpha = 0;
musicButton.text.anchor.set(0.5, 0.5);
musicButton.text.x = musicButton.shape.x;
musicButton.text.y = musicButton.shape.y;
musicButton.text.scaleX = 0;
musicButton.text.scaleY = 0;
musicButton.text.alpha = 0;
game.addChild(musicButton.text);
musicButtons.push(musicButton);
// Animate buttons appearing with staggered timing
tween(musicButton.shape, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 600,
easing: tween.elasticOut,
delay: 1000 + m * 100
});
tween(musicButton.text, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 600,
easing: tween.elasticOut,
delay: 1200 + m * 100
});
}
// Animate music title appearing
musicTitleTxt.alpha = 0;
musicTitleTxt.scaleY = 0;
tween(musicTitleTxt, {
alpha: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut,
delay: 400
});
// Animate music indicator appearing
tween(musicIndicatorTxt, {
alpha: 1
}, {
duration: 800,
easing: tween.bounceOut,
delay: 1000
});
// No initial music selection - all buttons start at normal size
// Initialize background effects
function createBackgroundEffects() {
// Create floating particles
for (var p = 0; p < 15; p++) {
var particle = new BackgroundParticle();
backgroundParticles.push(particle);
game.addChildAt(particle, 0); // Add behind other elements
}
// Create background waves
for (var w = 0; w < 5; w++) {
var wave = new BackgroundWave();
backgroundWaves.push(wave);
game.addChildAt(wave, 0); // Add behind other elements
}
}
// Start background effects
createBackgroundEffects();
// Add dynamic background color transitions
var backgroundColors = [0x001122, 0x220011, 0x112200, 0x001100];
var currentBgColorIndex = 0;
var bgColorTransitionTimer = 0;
function updateBackgroundColor() {
bgColorTransitionTimer++;
// Music-reactive beat pulsing
if (gameState === 'playing') {
musicBeat++;
if (musicBeat >= beatInterval) {
musicBeat = 0;
beatIntensity = 1.0;
// Flash background on beat
var currentBgColor = backgroundColors[currentBgColorIndex];
var brightColor = {
r: Math.min(255, (currentBgColor >> 16 & 0xFF) + 30),
g: Math.min(255, (currentBgColor >> 8 & 0xFF) + 30),
b: Math.min(255, (currentBgColor & 0xFF) + 30)
};
var flashColor = brightColor.r << 16 | brightColor.g << 8 | brightColor.b;
game.setBackgroundColor(flashColor);
// Fade back to normal
tween({
intensity: 1
}, {
intensity: 0
}, {
duration: beatInterval * 16,
easing: tween.easeOut,
onUpdate: function onUpdate() {
var fade = this.intensity;
var newR = Math.floor((currentBgColor >> 16 & 0xFF) + (brightColor.r - (currentBgColor >> 16 & 0xFF)) * fade);
var newG = Math.floor((currentBgColor >> 8 & 0xFF) + (brightColor.g - (currentBgColor >> 8 & 0xFF)) * fade);
var newB = Math.floor((currentBgColor & 0xFF) + (brightColor.b - (currentBgColor & 0xFF)) * fade);
game.setBackgroundColor(newR << 16 | newG << 8 | newB);
}
});
}
}
if (bgColorTransitionTimer >= 600) {
// Change every 10 seconds
bgColorTransitionTimer = 0;
var nextColorIndex = (currentBgColorIndex + 1) % backgroundColors.length;
// Smooth color transition using tween
var currentColor = backgroundColors[currentBgColorIndex];
var nextColor = backgroundColors[nextColorIndex];
// Create temporary object to animate color values
var colorTransition = {
r: currentColor >> 16 & 0xFF,
g: currentColor >> 8 & 0xFF,
b: currentColor & 0xFF
};
var targetR = nextColor >> 16 & 0xFF;
var targetG = nextColor >> 8 & 0xFF;
var targetB = nextColor & 0xFF;
tween(colorTransition, {
r: targetR,
g: targetG,
b: targetB
}, {
duration: 3000,
easing: tween.easeInOut,
onUpdate: function onUpdate() {
var newColor = Math.floor(colorTransition.r) << 16 | Math.floor(colorTransition.g) << 8 | Math.floor(colorTransition.b);
game.setBackgroundColor(newColor);
},
onFinish: function onFinish() {
currentBgColorIndex = nextColorIndex;
}
});
}
}
// No initial difficulty highlighting since none is selected
// Create start button handler
startButton = {
shape: startButtonShape,
text: startButtonTxt,
down: function down(x, y, obj) {
this.startGame();
},
startGame: function startGame() {
// Check if difficulty is selected
if (currentDifficulty === -1) {
// Flash difficulty title to indicate selection is required
tween(difficultyTitleTxt, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF0000
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(difficultyTitleTxt, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
return; // Don't start the game
}
// Check if music is selected
if (currentMusic === -1) {
// Flash music title to indicate selection is required
tween(musicTitleTxt, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF0000
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(musicTitleTxt, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
return; // Don't start the game
}
// Stop all music for 3 seconds and play start sound
LK.stopMusic();
LK.getSound('Gameisstart').play();
// Create enhanced zoom animation sequence with pulsing and glow effects
// Initial button press feedback - quick scale down
tween(this.shape, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Phase 1: Zoom out with rotation and color change
tween(startButton.shape, {
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.7,
rotation: Math.PI * 2,
tint: 0x00FFFF
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
// Phase 2: Explosive zoom in with multiple pulses
tween(startButton.shape, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0,
rotation: Math.PI * 4,
tint: 0xFFFFFF
}, {
duration: 2000,
easing: tween.elasticOut
});
// Create multiple expanding rings for more dramatic effect
for (var r = 0; r < 3; r++) {
var expandingRing = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
expandingRing.x = startButton.shape.x;
expandingRing.y = startButton.shape.y;
expandingRing.tint = r === 0 ? 0x00FF00 : r === 1 ? 0x00FFFF : 0xFFFFFF;
expandingRing.alpha = 0.6;
expandingRing.scaleX = 0.5;
expandingRing.scaleY = 0.5;
// Animate each ring with staggered timing
tween(expandingRing, {
scaleX: 6.0 + r * 2,
scaleY: 6.0 + r * 2,
alpha: 0,
rotation: Math.PI * (2 + r)
}, {
duration: 2500 + r * 200,
easing: tween.easeOut,
delay: r * 150,
onFinish: function onFinish() {
if (expandingRing && expandingRing.parent) {
expandingRing.destroy();
}
}
});
}
}
});
}
});
// Text animation with bounce and glow effect
tween(this.text, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Text zoom out with glow
tween(startButton.text, {
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.8,
tint: 0xFFD700
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
// Text explosive zoom with sparkle effect
tween(startButton.text, {
scaleX: 4.0,
scaleY: 4.0,
alpha: 0,
tint: 0xFFFFFF
}, {
duration: 2000,
easing: tween.bounceOut
});
}
});
}
});
// Hide menu elements immediately
titleTxt.visible = false;
// Hide difficulty selection
difficultyTitleTxt.visible = false;
difficultyIndicatorTxt.visible = false;
for (var d = 0; d < difficultyButtons.length; d++) {
difficultyButtons[d].text.visible = false;
}
// Hide music selection
musicTitleTxt.visible = false;
musicIndicatorTxt.visible = false;
for (var m = 0; m < musicButtons.length; m++) {
musicButtons[m].shape.visible = false;
musicButtons[m].text.visible = false;
}
// Start the actual game after 3 seconds
LK.setTimeout(function () {
// Hide start button elements after animation
startButton.shape.visible = false;
startButton.text.visible = false;
// Show game elements
timerTxt.visible = true;
scoreTxt.visible = true;
timingLine.visible = true;
bottomLine.visible = true;
lane1.visible = true;
lane2.visible = true;
// Create game buttons
for (var i = 0; i < 3; i++) {
var button = new Button(i);
buttons.push(button);
game.addChild(button);
}
// Play selected music
LK.playMusic(musicTracks[currentMusic].id);
// Set BPM for beat detection based on selected music
bpm = musicTracks[currentMusic].bpm;
beatsPerSecond = bpm / 60;
framesPerBeat = Math.floor(60 / beatsPerSecond);
beatTimer = 0;
lastBeatTime = 0;
// Show virtual key during gameplay
virtualKey.visible = true;
if (keyLabel) keyLabel.visible = true;
// Apply difficulty settings
var difficulty = difficulties[currentDifficulty];
maxNotes = difficulty.maxNotes;
spawnInterval = difficulty.spawnInterval;
timeRemaining = difficulty.timeLimit;
// Start the game
gameState = 'playing';
LK.setScore(0);
scoreTxt.setText('0');
timerTxt.setText(timeRemaining.toString());
}, 3000);
}
};
// Initially hide game elements
var gameElements = [];
// Create countdown timer
var timeRemaining = 60;
// Create timer display above score
var timerTxt = new Text2('60', {
size: 80,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0.5);
timerTxt.x = 2048 / 2;
timerTxt.y = 2732 / 2 - 300;
timerTxt.visible = false; // Hide initially
game.addChild(timerTxt);
// Create score display in center of game area
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0.5);
scoreTxt.x = 2048 / 2;
scoreTxt.y = 2732 / 2 - 200;
scoreTxt.visible = false; // Hide initially
game.addChild(scoreTxt);
// Create virtual key for beat synchronization
virtualKey = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
virtualKey.x = 2048 / 2;
virtualKey.y = 150; // Top center of screen
virtualKey.scaleX = 0.8;
virtualKey.scaleY = 0.8;
virtualKey.tint = 0xFFD700; // Gold color
virtualKey.alpha = 0.7;
virtualKey.visible = false; // Hide initially
// Create key label
var keyLabel = new Text2('♪ BEAT KEY ♪', {
size: 40,
fill: 0xFFFFFF
});
keyLabel.anchor.set(0.5, 0.5);
keyLabel.x = virtualKey.x;
keyLabel.y = virtualKey.y + 80;
keyLabel.visible = false;
game.addChild(keyLabel);
// Create feedback text display
var feedbackTxt = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
feedbackTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(feedbackTxt);
feedbackTxt.alpha = 0;
// Create game over menu elements
var gameOverTxt = new Text2('GAME OVER', {
size: 120,
fill: 0xFF0000
});
gameOverTxt.anchor.set(0.5, 0.5);
gameOverTxt.x = 2048 / 2;
gameOverTxt.y = 2732 / 2 - 300;
gameOverTxt.visible = false;
game.addChild(gameOverTxt);
var finalScoreTxt = new Text2('', {
size: 80,
fill: 0xFFFFFF
});
finalScoreTxt.anchor.set(0.5, 0.5);
finalScoreTxt.x = 2048 / 2;
finalScoreTxt.y = 2732 / 2 - 150;
finalScoreTxt.visible = false;
game.addChild(finalScoreTxt);
var restartButtonShape = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
restartButtonShape.x = 2048 / 2;
restartButtonShape.y = 2732 / 2 + 100;
restartButtonShape.tint = 0x0066CC;
restartButtonShape.visible = false;
var restartButtonTxt = new Text2('RESTART', {
size: 60,
fill: 0xFFFFFF
});
restartButtonTxt.anchor.set(0.5, 0.5);
restartButtonTxt.x = 2048 / 2;
restartButtonTxt.y = 2732 / 2 + 100;
restartButtonTxt.visible = false;
game.addChild(restartButtonTxt);
// Create restart button handler
gameOverButton = {
shape: restartButtonShape,
text: restartButtonTxt,
down: function down(x, y, obj) {
this.restartGame();
},
restartGame: function restartGame() {
// Stop all music when restart is pressed
LK.stopMusic();
// Hide game over elements
gameOverTxt.visible = false;
finalScoreTxt.visible = false;
this.shape.visible = false;
this.text.visible = false;
// Clear existing notes and buttons
for (var i = notes.length - 1; i >= 0; i--) {
notes[i].destroy();
notes.splice(i, 1);
}
for (var i = buttons.length - 1; i >= 0; i--) {
buttons[i].destroy();
buttons.splice(i, 1);
}
// Show menu elements with smooth animations
titleTxt.visible = true;
titleTxt.alpha = 0;
titleTxt.scaleX = 0.8;
titleTxt.scaleY = 0.8;
startButton.shape.visible = true;
startButton.shape.alpha = 0;
startButton.shape.scaleX = 0;
startButton.shape.scaleY = 0;
startButton.text.visible = true;
startButton.text.alpha = 0;
startButton.text.x = startButton.text.x + 200;
difficultyTitleTxt.visible = true;
difficultyTitleTxt.alpha = 0;
difficultyTitleTxt.scaleY = 0;
difficultyIndicatorTxt.visible = true;
difficultyIndicatorTxt.alpha = 0;
musicTitleTxt.visible = true;
musicTitleTxt.alpha = 0;
musicTitleTxt.scaleY = 0;
musicIndicatorTxt.visible = true;
musicIndicatorTxt.alpha = 0;
// Animate title appearing
tween(titleTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.elasticOut
});
// Animate start button
tween(startButton.shape, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.bounceOut,
delay: 400
});
// Animate start button text sliding in
tween(startButton.text, {
alpha: 1,
x: startButton.text.x - 200
}, {
duration: 500,
easing: tween.easeOut,
delay: 600
});
// Animate difficulty title
tween(difficultyTitleTxt, {
alpha: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.bounceOut,
delay: 200
});
// Animate difficulty indicator
tween(difficultyIndicatorTxt, {
alpha: 1
}, {
duration: 400,
delay: 800
});
// Animate difficulty buttons
for (var d = 0; d < difficultyButtons.length; d++) {
difficultyButtons[d].text.visible = true;
difficultyButtons[d].text.alpha = 0;
difficultyButtons[d].text.scaleX = 0;
difficultyButtons[d].text.scaleY = 0;
// Staggered animation for each button
tween(difficultyButtons[d].text, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.elasticOut,
delay: 600 + d * 100
});
}
// Animate music title
tween(musicTitleTxt, {
alpha: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.bounceOut,
delay: 400
});
// Animate music indicator
tween(musicIndicatorTxt, {
alpha: 1
}, {
duration: 400,
delay: 1000
});
// Animate music buttons
for (var m = 0; m < musicButtons.length; m++) {
musicButtons[m].shape.visible = true;
musicButtons[m].shape.alpha = 0;
musicButtons[m].shape.scaleX = currentMusic === m && currentMusic !== -1 ? 1.2 : 0;
musicButtons[m].shape.scaleY = currentMusic === m && currentMusic !== -1 ? 1.2 : 0;
musicButtons[m].text.visible = true;
musicButtons[m].text.alpha = 0;
musicButtons[m].text.scaleX = 0;
musicButtons[m].text.scaleY = 0;
// Staggered animation for each button
tween(musicButtons[m].shape, {
alpha: 1,
scaleX: currentMusic === m && currentMusic !== -1 ? 1.2 : 1,
scaleY: currentMusic === m && currentMusic !== -1 ? 1.2 : 1
}, {
duration: 500,
easing: tween.elasticOut,
delay: 800 + m * 100
});
tween(musicButtons[m].text, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.elasticOut,
delay: 1000 + m * 100
});
}
// Hide game elements
timerTxt.visible = false;
scoreTxt.visible = false;
timingLine.visible = false;
bottomLine.visible = false;
lane1.visible = false;
lane2.visible = false;
// Hide virtual key
virtualKey.visible = false;
if (keyLabel) keyLabel.visible = false;
keyAnimationActive = false;
// Reset game state to menu
gameState = 'menu';
spawnTimer = 0;
LK.setScore(0);
}
};
// Create beat-synchronized key animation
function triggerBeatKeyAnimation() {
if (!virtualKey || !virtualKey.visible || keyAnimationActive) return;
keyAnimationActive = true;
// Key press animation - scale down quickly
tween(virtualKey, {
scaleX: 0.6,
scaleY: 0.6,
alpha: 1.0,
tint: 0xFFFFFF
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Key release animation - scale back up with glow
tween(virtualKey, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.7,
tint: 0xFFD700
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
keyAnimationActive = false;
}
});
}
});
// Create ripple effect around key
var ripple = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
ripple.x = virtualKey.x;
ripple.y = virtualKey.y;
ripple.alpha = 0.5;
ripple.scaleX = 0.8;
ripple.scaleY = 0.8;
ripple.tint = 0x00FFFF;
// Animate ripple expanding
tween(ripple, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (ripple && ripple.parent) {
ripple.destroy();
}
}
});
// Create sparkle particles around key
for (var s = 0; s < 6; s++) {
var sparkle = game.addChild(LK.getAsset('chordNote2', {
anchorX: 0.5,
anchorY: 0.5
}));
var angle = s / 6 * Math.PI * 2;
var distance = 60 + Math.random() * 40;
sparkle.x = virtualKey.x + Math.cos(angle) * distance;
sparkle.y = virtualKey.y + Math.sin(angle) * distance;
sparkle.scaleX = 0.3;
sparkle.scaleY = 0.3;
sparkle.alpha = 0.8;
sparkle.tint = 0xFFD700;
// Animate sparkles
tween(sparkle, {
y: sparkle.y - 50 - Math.random() * 50,
x: sparkle.x + (Math.random() - 0.5) * 80,
alpha: 0,
rotation: Math.PI * 2 * (Math.random() > 0.5 ? 1 : -1),
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400 + Math.random() * 300,
easing: tween.easeOut,
delay: Math.random() * 100,
onFinish: function onFinish() {
if (sparkle && sparkle.parent) {
sparkle.destroy();
}
}
});
}
}
// Create explosion animation function
function createExplosion(x, y, color) {
var particleCount = 8;
var particles = [];
for (var i = 0; i < particleCount; i++) {
// Create particle using small ellipse shape
var particle = game.addChild(LK.getAsset('fastNote', {
anchorX: 0.5,
anchorY: 0.5
}));
particle.x = x;
particle.y = y;
particle.tint = color || 0xFFFFFF;
particle.scaleX = 0.3;
particle.scaleY = 0.3;
particles.push(particle);
// Calculate random direction for each particle
var angle = i / particleCount * Math.PI * 2 + (Math.random() - 0.5) * 0.5;
var distance = 100 + Math.random() * 50;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particle outward and fade out
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 600 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.parent) {
particle.destroy();
}
}
});
}
}
// Create screen shake effect
function shakeScreen(intensity, duration) {
var originalX = game.x;
var originalY = game.y;
var shakeStartTime = LK.ticks;
var shakeDuration = duration || 300;
var shakeIntensity = intensity || 20;
// Create shake animation
function shakeUpdate() {
var elapsed = LK.ticks - shakeStartTime;
if (elapsed < shakeDuration) {
var progress = elapsed / shakeDuration;
var currentIntensity = shakeIntensity * (1 - progress);
game.x = originalX + (Math.random() - 0.5) * currentIntensity * 2;
game.y = originalY + (Math.random() - 0.5) * currentIntensity * 2;
LK.setTimeout(shakeUpdate, 16); // ~60fps
} else {
game.x = originalX;
game.y = originalY;
}
}
shakeUpdate();
}
// Create combo streak effects
var comboCount = 0;
var lastHitTime = 0;
function updateCombo(isHit) {
if (isHit) {
var currentTime = LK.ticks;
if (currentTime - lastHitTime < 180) {
// Within 3 seconds
comboCount++;
} else {
comboCount = 1;
}
lastHitTime = currentTime;
// Show combo effects for streaks
if (comboCount >= 5 && comboCount % 5 === 0) {
// Create combo text
var comboText = new Text2('COMBO x' + comboCount, {
size: 60,
fill: 0xFFD700
});
comboText.anchor.set(0.5, 0.5);
comboText.x = 2048 / 2;
comboText.y = 2732 / 2 + 100;
comboText.alpha = 0;
game.addChild(comboText);
// Animate combo text
tween(comboText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(comboText, {
alpha: 0,
y: comboText.y - 100
}, {
duration: 500,
onFinish: function onFinish() {
if (comboText && comboText.parent) {
comboText.destroy();
}
}
});
}
});
}
} else {
comboCount = 0;
}
}
// Spawn note function with music synchronization
function spawnNote() {
if (notes.length >= maxNotes) return;
var lane = Math.floor(Math.random() * 3);
var noteTypes = ['slow', 'medium', 'fast'];
var type = noteTypes[Math.floor(Math.random() * noteTypes.length)];
var note = new Note(lane, type);
// Music-reactive entrance animation
var musicSpawnScale = 1;
var musicSpawnColor = 0xFFFFFF;
if (beatIntensity > 0.7) {
// Spawn with extra flair on strong beats
musicSpawnScale = 1.3;
musicSpawnColor = 0xFFD700; // Gold color for beat spawns
note.noteGraphics.tint = musicSpawnColor;
}
// Add entrance animation - start small and grow with music sync
note.noteGraphics.scaleX = 0;
note.noteGraphics.scaleY = 0;
note.noteGraphics.alpha = 0;
tween(note.noteGraphics, {
scaleX: musicSpawnScale,
scaleY: musicSpawnScale,
alpha: 1
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Return to normal scale after spawn
if (musicSpawnScale > 1) {
tween(note.noteGraphics, {
scaleX: 1,
scaleY: 1,
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
}
});
// Add music-reactive spawn effects
if (beatIntensity > 0.5) {
// Create spawn burst effect
createExplosion(note.x, note.y - 100, 0x00FFFF);
}
notes.push(note);
game.addChild(note);
}
// Handle clicks for start button
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Check if click is on start button
var dx = x - startButton.shape.x;
var dy = y - startButton.shape.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 150) {
// Button radius
startButton.down(x, y, obj);
}
// Check if click is on difficulty buttons
for (var d = 0; d < difficultyButtons.length; d++) {
var diffButton = difficultyButtons[d];
var ddx = x - diffButton.text.x;
var ddy = y - diffButton.text.y;
var ddistance = Math.sqrt(ddx * ddx + ddy * ddy);
if (ddistance <= 100) {
// Text button hit area
// Reset previous selection if any
if (currentDifficulty !== -1) {
difficultyButtons[currentDifficulty].text.scaleX = 1.0;
difficultyButtons[currentDifficulty].text.scaleY = 1.0;
}
// Set new selection
currentDifficulty = d;
difficultySelected = true;
diffButton.text.scaleX = 1.0;
diffButton.text.scaleY = 1.0;
// Update indicator text to show selected difficulty
if (d === 0) {
// Easy mode - show in red
difficultyIndicatorTxt.setText('Easy mode selected');
difficultyIndicatorTxt.fill = 0xFF0000;
difficultyIndicatorTxt.tint = 0xFF0000;
} else {
difficultyIndicatorTxt.setText(difficulties[d].name.toLowerCase() + ' mode selected');
difficultyIndicatorTxt.fill = difficulties[d].color;
difficultyIndicatorTxt.tint = difficulties[d].color;
}
// Add selection effect
tween(diffButton.text, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(diffButton.text, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
// Animate the indicator text to show selection
tween(difficultyIndicatorTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(difficultyIndicatorTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
break;
}
}
// Check if click is on music buttons
for (var m = 0; m < musicButtons.length; m++) {
var musicButton = musicButtons[m];
var mdx = x - musicButton.shape.x;
var mdy = y - musicButton.shape.y;
var mdistance = Math.sqrt(mdx * mdx + mdy * mdy);
if (mdistance <= 90) {
// Button radius
// Reset previous selection
if (currentMusic !== -1) {
musicButtons[currentMusic].shape.scaleX = 1.0;
musicButtons[currentMusic].shape.scaleY = 1.0;
}
// Set new selection
currentMusic = m;
musicButton.shape.scaleX = 1.0;
musicButton.shape.scaleY = 1.0;
// Update indicator text
if (m === 2) {
musicIndicatorTxt.setText('3 selected');
} else if (m === 3) {
musicIndicatorTxt.setText('4 selected');
} else {
musicIndicatorTxt.setText(musicTracks[m].name + ' selected');
}
musicIndicatorTxt.fill = musicTracks[m].color;
musicIndicatorTxt.tint = musicTracks[m].color;
// Add selection effect
tween(musicButton.shape, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.bounceOut
});
// Animate the indicator text
tween(musicIndicatorTxt, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(musicIndicatorTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200
});
}
});
// Play the selected music
LK.stopMusic();
LK.playMusic(musicTracks[m].id);
break;
}
}
} else if (gameState === 'gameOver') {
// Check if click is on restart button
var dx = x - gameOverButton.shape.x;
var dy = y - gameOverButton.shape.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 150) {
// Button radius
gameOverButton.down(x, y, obj);
}
}
};
game.update = function () {
// Update background effects regardless of game state
updateBackgroundColor();
// Update background particles
for (var p = 0; p < backgroundParticles.length; p++) {
if (backgroundParticles[p] && backgroundParticles[p].update) {
backgroundParticles[p].update();
}
}
// Update background waves
for (var w = 0; w < backgroundWaves.length; w++) {
if (backgroundWaves[w] && backgroundWaves[w].update) {
backgroundWaves[w].update();
}
}
// Start breathing effect when appropriate
if (!breathingEffectStarted && gameState === 'playing') {
breathingEffectStarted = true;
createBreathingEffect();
}
// Add music-synchronized button effects during menu
if (gameState === 'menu' && currentMusic !== -1) {
// Create gentle music-reactive effects for menu buttons
var menuMusicBeat = Math.sin(LK.ticks * 0.1) * 0.5 + 0.5;
// Animate start button with music
if (startButton && startButton.shape && startButton.shape.visible) {
var startPulse = 1 + menuMusicBeat * 0.05;
startButton.shape.scaleX = startPulse;
startButton.shape.scaleY = startPulse;
// Subtle color shift
var startColorShift = Math.floor(menuMusicBeat * 50);
startButton.shape.tint = 0x00AA00 + (startColorShift << 8);
}
// Animate music selection buttons with staggered timing
for (var mb = 0; mb < musicButtons.length; mb++) {
if (musicButtons[mb] && musicButtons[mb].shape && musicButtons[mb].shape.visible) {
var staggeredBeat = Math.sin(LK.ticks * 0.08 + mb * 0.8) * 0.5 + 0.5;
var buttonPulse = 1 + staggeredBeat * 0.03;
// Scale animation
if (currentMusic !== mb) {
musicButtons[mb].shape.scaleX = buttonPulse;
musicButtons[mb].shape.scaleY = buttonPulse;
}
// Color breathing effect
var colorIntensity = staggeredBeat * 0.3 + 0.7;
var originalColor = musicTracks[mb].color;
var r = Math.floor((originalColor >> 16 & 0xFF) * colorIntensity);
var g = Math.floor((originalColor >> 8 & 0xFF) * colorIntensity);
var b = Math.floor((originalColor & 0xFF) * colorIntensity);
musicButtons[mb].shape.tint = r << 16 | g << 8 | b;
}
}
}
// Only update game logic when playing
if (gameState !== 'playing') {
return;
}
// BPM-based beat detection and key animation
if (virtualKey && virtualKey.visible) {
beatTimer++;
// Check if it's time for a beat based on BPM
if (beatTimer >= framesPerBeat) {
beatTimer = 0;
lastBeatTime = LK.ticks;
// Trigger beat key animation
triggerBeatKeyAnimation();
// Update beat intensity for other systems
beatIntensity = 1.0;
musicBeat = 0;
}
// Add continuous subtle breathing animation to virtual key
if (!keyAnimationActive) {
var breathingScale = 0.8 + Math.sin(LK.ticks * 0.05) * 0.1;
virtualKey.scaleX = breathingScale;
virtualKey.scaleY = breathingScale;
// Subtle color shifting
var colorCycle = Math.sin(LK.ticks * 0.03) * 0.5 + 0.5;
var colorShift = Math.floor(colorCycle * 50);
virtualKey.tint = 0xFFD700 + (colorShift << 8);
}
// Update key label position to follow virtual key
if (keyLabel) {
keyLabel.x = virtualKey.x;
keyLabel.y = virtualKey.y + 80;
}
}
// Add fluid animation to timing line with music synchronization
if (timingLine.visible) {
// Pulsing effect based on notes approaching
var nearbyNotes = 0;
for (var t = 0; t < notes.length; t++) {
var distanceToLine = Math.abs(notes[t].y - timingLineY);
if (distanceToLine < 200) {
nearbyNotes++;
}
}
var pulseIntensity = 1 + nearbyNotes * 0.1;
var basePulse = 1 + Math.sin(LK.ticks * 0.2) * 0.1 * pulseIntensity;
// Add music beat synchronization
var musicPulse = 1;
if (beatIntensity > 0) {
musicPulse = 1 + beatIntensity * 0.4;
// Create beat wave along timing line
timingLine.scaleX = 1 + beatIntensity * 0.1;
}
timingLine.scaleY = basePulse * musicPulse;
timingLine.alpha = 0.8 + Math.sin(LK.ticks * 0.15) * 0.2;
// Music-reactive color cycling
var musicColorCycle = Math.sin(musicBeat * 0.05) * 0.5 + 0.5;
var baseColor = 0xFFFFFF;
if (nearbyNotes > 0) {
var colorShift = Math.sin(LK.ticks * 0.3) * 0.5 + 0.5;
baseColor = 0xFFFFFF + (Math.floor(colorShift * 50) << 16);
}
// Add music-reactive blue/cyan tint
var musicTint = Math.floor(musicColorCycle * 255);
timingLine.tint = baseColor & 0xFF0000 | musicTint << 8 | 0xFF;
}
// Add music-synchronized glow effect to buttons
for (var b = 0; b < buttons.length; b++) {
if (buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
var baseGlow = Math.sin(LK.ticks * 0.08 + b) * 0.1 + 0.9;
var musicGlow = 1;
// Music beat detection for synchronized effects
var isOnBeat = beatIntensity > 0.7;
var isMediumBeat = beatIntensity > 0.4;
if (beatIntensity > 0) {
musicGlow = 1 + beatIntensity * 0.3;
// Enhanced beat-reactive scaling with smooth transitions
var targetScaleX = 1 + beatIntensity * 0.15;
var targetScaleY = 1 + beatIntensity * 0.15;
// Smooth scaling animation to target with additional null check
if (buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
tween(buttons[b].buttonGraphics, {
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 100,
easing: tween.easeOut
});
}
// Strong beat effects
if (isOnBeat && buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
// Pulse ring effect around button
var beatRing = game.addChild(LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
}));
beatRing.x = buttons[b].x;
beatRing.y = buttons[b].y;
beatRing.alpha = 0.6;
beatRing.scaleX = 0.8;
beatRing.scaleY = 0.8;
beatRing.tint = 0x00FFFF;
// Animate ring expanding and fading
tween(beatRing, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (beatRing && beatRing.parent) {
beatRing.destroy();
}
}
});
// Color flash effect on strong beats
if (buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
var originalTint = buttons[b].buttonGraphics.tint;
buttons[b].buttonGraphics.tint = 0xFFFFFF;
tween(buttons[b].buttonGraphics, {
tint: originalTint
}, {
duration: 200,
easing: tween.easeOut
});
}
}
// Medium beat effects
if (isMediumBeat && !isOnBeat && buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
// Subtle glow pulse
var originalAlpha = buttons[b].buttonGraphics.alpha;
tween(buttons[b].buttonGraphics, {
alpha: Math.min(1.0, originalAlpha * 1.3)
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
if (buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
tween(buttons[b].buttonGraphics, {
alpha: originalAlpha
}, {
duration: 150,
easing: tween.easeOut
});
}
}
});
}
}
// Continuous music-reactive color cycling
var musicColorCycle = Math.sin(musicBeat * 0.1 + b * 1.2) * 0.5 + 0.5;
var baseColor = 0x00FF00; // Default green
// Apply music-reactive color shifts
if (beatIntensity > 0.2) {
var beatColorR = Math.floor((musicColorCycle + beatIntensity * 0.5) * 150) % 255;
var beatColorG = Math.floor(musicColorCycle * 255);
var beatColorB = Math.floor((1 - musicColorCycle + beatIntensity * 0.3) * 200) % 255;
baseColor = beatColorR << 16 | beatColorG << 8 | beatColorB;
}
// Apply subtle rotation on beats
if (beatIntensity > 0.5 && buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
var rotationAmount = beatIntensity * 0.1 * Math.sin(LK.ticks * 0.2 + b);
buttons[b].buttonGraphics.rotation = rotationAmount;
} else if (buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
// Return to neutral rotation smoothly
if (Math.abs(buttons[b].buttonGraphics.rotation) > 0.01) {
tween(buttons[b].buttonGraphics, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Apply final alpha and color
if (buttons[b] && buttons[b].buttonGraphics && buttons[b].buttonGraphics.parent) {
buttons[b].buttonGraphics.alpha = buttons[b].isPressed ? 0.7 : baseGlow * musicGlow;
if (!buttons[b].isPressed) {
buttons[b].buttonGraphics.tint = baseColor;
}
}
}
}
// Add music-synchronized flowing effects to lane separators
if (lane1.visible && lane2.visible) {
// Flowing light effect down the lanes with music sync
var baseFlow = Math.sin(LK.ticks * 0.1) * 0.3 + 0.7;
var musicFlow = 1;
if (beatIntensity > 0) {
musicFlow = 1 + beatIntensity * 0.5;
}
lane1.alpha = baseFlow * musicFlow;
lane2.alpha = baseFlow * musicFlow;
// Music-reactive width pulsing
var baseWidthPulse = 1 + Math.sin(LK.ticks * 0.05) * 0.2;
var musicWidthPulse = 1;
if (beatIntensity > 0) {
musicWidthPulse = 1 + beatIntensity * 0.3;
}
lane1.scaleX = baseWidthPulse * musicWidthPulse;
lane2.scaleX = baseWidthPulse * musicWidthPulse;
// Music-reactive color shifting
var tempo = Math.sin(LK.ticks * 0.2) * 0.5 + 0.5;
var musicTempo = Math.sin(musicBeat * 0.1) * 0.5 + 0.5;
var colorR = Math.floor((tempo + musicTempo) * 100) % 255;
var colorG = Math.floor(musicTempo * 150) % 255;
var colorTint = 0xFF0000 | colorG << 8 | 0xFF;
lane1.tint = colorTint;
lane2.tint = colorTint;
}
// Update timer - countdown every second (60 frames)
if (LK.ticks % 60 === 0 && timeRemaining > 0) {
timeRemaining--;
timerTxt.setText(timeRemaining.toString());
}
// Check for game over when timer reaches 0
if (timeRemaining <= 0 && gameState === 'playing') {
gameState = 'gameOver';
// Clean up all remaining notes without awarding points
for (var n = notes.length - 1; n >= 0; n--) {
var note = notes[n];
if (!note.hasBeenHit) {
note.hasBeenHit = true;
// Create visual effects for remaining notes without scoring
LK.effects.flashObject(note, 0xFFD700, 200);
createExplosion(note.x, note.y, 0xFFD700);
// Remove the note
note.destroy();
notes.splice(n, 1);
}
}
// Update final score display
scoreTxt.setText(LK.getScore());
// Hide game elements
timerTxt.visible = false;
scoreTxt.visible = false;
timingLine.visible = false;
bottomLine.visible = false;
lane1.visible = false;
lane2.visible = false;
// Hide virtual key
virtualKey.visible = false;
if (keyLabel) keyLabel.visible = false;
keyAnimationActive = false;
// Hide buttons
for (var i = 0; i < buttons.length; i++) {
buttons[i].visible = false;
}
// Stop all music when game over screen appears
LK.stopMusic();
// Play game ending music after a brief pause
LK.setTimeout(function () {
LK.playMusic('End', {
loop: true
});
}, 500);
// Show game over menu with smooth animations
gameOverTxt.visible = true;
gameOverTxt.alpha = 0;
gameOverTxt.scaleX = 0.5;
gameOverTxt.scaleY = 0.5;
finalScoreTxt.setText('Final Score: ' + LK.getScore() + '\nDifficulty: ' + difficulties[currentDifficulty].name);
finalScoreTxt.visible = true;
finalScoreTxt.alpha = 0;
finalScoreTxt.y = finalScoreTxt.y + 50;
gameOverButton.shape.visible = true;
gameOverButton.shape.alpha = 0;
gameOverButton.shape.scaleX = 0;
gameOverButton.shape.scaleY = 0;
gameOverButton.text.visible = true;
gameOverButton.text.alpha = 0;
// Animate game over text
tween(gameOverTxt, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.bounceOut
});
// Animate final score text sliding up
tween(finalScoreTxt, {
alpha: 1,
y: finalScoreTxt.y - 50
}, {
duration: 600,
easing: tween.easeOut,
delay: 400
});
// Animate restart button
tween(gameOverButton.shape, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.elasticOut,
delay: 800
});
tween(gameOverButton.text, {
alpha: 1
}, {
duration: 400,
delay: 1000
});
}
// Spawn notes
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnNote();
spawnTimer = 0;
// Gradually increase spawn rate
if (spawnInterval > 60) {
spawnInterval = Math.max(60, spawnInterval - 1);
}
}
// Update and check notes with music synchronization
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Add music-reactive pulsing effect to notes as they approach timing line
var distanceToTimingLine = Math.abs(note.y - timingLineY);
if (distanceToTimingLine < 300) {
var proximityPulse = 1 + (300 - distanceToTimingLine) / 300 * 0.3;
var musicNotePulse = 1;
if (beatIntensity > 0) {
musicNotePulse = 1 + beatIntensity * 0.2;
// Add music-reactive glow
note.noteGraphics.alpha = 1 + beatIntensity * 0.3;
}
note.noteGraphics.scaleX = proximityPulse * musicNotePulse;
note.noteGraphics.scaleY = proximityPulse * musicNotePulse;
// Music-reactive color shifting for approaching notes
if (distanceToTimingLine < 150) {
var beatColor = Math.sin(musicBeat * 0.2) * 0.5 + 0.5;
var colorShift = Math.floor(beatColor * 100);
note.noteGraphics.tint = 0xFFFFFF + (colorShift << 16) + (colorShift << 8);
}
}
// Check if note reached bottom line (missed)
if (note.y >= bottomLineY && !note.hasBeenHit) {
LK.getSound('miss').play();
// Create red explosion effect for missed notes
createExplosion(note.x, note.y, 0xFF0000);
// Add screen shake for missed notes
shakeScreen(5, 100);
// Update combo (miss)
updateCombo(false);
// Show missed feedback text
feedbackTxt.setText('MISSED');
feedbackTxt.alpha = 1;
tween(feedbackTxt, {
alpha: 0
}, {
duration: 800
});
note.destroy();
notes.splice(i, 1);
continue;
}
// Remove notes that went off screen or were hit
if (note.y > 2732 + 100 || note.hasBeenHit) {
if (note.hasBeenHit) {
// Small delay before removing hit notes for visual feedback
LK.setTimeout(function (noteToRemove, index) {
return function () {
if (noteToRemove && noteToRemove.parent) {
noteToRemove.destroy();
}
var noteIndex = notes.indexOf(noteToRemove);
if (noteIndex !== -1) {
notes.splice(noteIndex, 1);
}
};
}(note, i), 200);
} else {
note.destroy();
notes.splice(i, 1);
}
}
}
}; ===================================================================
--- original.js
+++ change.js
@@ -457,29 +457,35 @@
var currentMusic = -1; // -1=None selected, 0=Music1, 1=Music2, 2=Music3
var musicTracks = [{
name: '1',
id: 'GameMusic1',
- color: 0x8800ff
+ color: 0x8800ff,
+ bpm: 120
}, {
name: '2',
id: 'GameMusic2',
- color: 0xff8800
+ color: 0xff8800,
+ bpm: 140
}, {
name: '3',
id: 'GameMusic3',
- color: 0x0088ff
+ color: 0x0088ff,
+ bpm: 128
}, {
name: '4',
id: 'GameMusic4',
- color: 0x00ff88
+ color: 0x00ff88,
+ bpm: 110
}, {
name: '5',
id: 'GameMusic5',
- color: 0xff0088
+ color: 0xff0088,
+ bpm: 135
}, {
name: '6',
id: 'GameMusic6',
- color: 0x88ff00
+ color: 0x88ff00,
+ bpm: 125
}];
var difficulties = [{
name: 'EASY',
spawnInterval: 120,
@@ -521,8 +527,18 @@
var musicBeat = 0;
var beatInterval = 30; // frames per beat (adjustable based on music tempo)
var beatIntensity = 0;
var musicPulseElements = [];
+// BPM-based beat detection and key animation variables
+var bpm = 120; // Default BPM - will be set based on selected music
+var beatsPerSecond = bpm / 60;
+var framesPerBeat = Math.floor(60 / beatsPerSecond); // 60 FPS assumed
+var beatTimer = 0;
+var lastBeatTime = 0;
+var beatAccuracy = 0.1; // Timing accuracy for beat detection
+// Virtual key animation variables
+var virtualKey = null;
+var keyAnimationActive = false;
// Create lane separators
var lane1 = game.addChild(LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0
@@ -1127,8 +1143,17 @@
game.addChild(button);
}
// Play selected music
LK.playMusic(musicTracks[currentMusic].id);
+ // Set BPM for beat detection based on selected music
+ bpm = musicTracks[currentMusic].bpm;
+ beatsPerSecond = bpm / 60;
+ framesPerBeat = Math.floor(60 / beatsPerSecond);
+ beatTimer = 0;
+ lastBeatTime = 0;
+ // Show virtual key during gameplay
+ virtualKey.visible = true;
+ if (keyLabel) keyLabel.visible = true;
// Apply difficulty settings
var difficulty = difficulties[currentDifficulty];
maxNotes = difficulty.maxNotes;
spawnInterval = difficulty.spawnInterval;
@@ -1164,8 +1189,30 @@
scoreTxt.x = 2048 / 2;
scoreTxt.y = 2732 / 2 - 200;
scoreTxt.visible = false; // Hide initially
game.addChild(scoreTxt);
+// Create virtual key for beat synchronization
+virtualKey = game.addChild(LK.getAsset('button', {
+ anchorX: 0.5,
+ anchorY: 0.5
+}));
+virtualKey.x = 2048 / 2;
+virtualKey.y = 150; // Top center of screen
+virtualKey.scaleX = 0.8;
+virtualKey.scaleY = 0.8;
+virtualKey.tint = 0xFFD700; // Gold color
+virtualKey.alpha = 0.7;
+virtualKey.visible = false; // Hide initially
+// Create key label
+var keyLabel = new Text2('♪ BEAT KEY ♪', {
+ size: 40,
+ fill: 0xFFFFFF
+});
+keyLabel.anchor.set(0.5, 0.5);
+keyLabel.x = virtualKey.x;
+keyLabel.y = virtualKey.y + 80;
+keyLabel.visible = false;
+game.addChild(keyLabel);
// Create feedback text display
var feedbackTxt = new Text2('', {
size: 80,
fill: 0xFFFFFF
@@ -1368,14 +1415,106 @@
timingLine.visible = false;
bottomLine.visible = false;
lane1.visible = false;
lane2.visible = false;
+ // Hide virtual key
+ virtualKey.visible = false;
+ if (keyLabel) keyLabel.visible = false;
+ keyAnimationActive = false;
// Reset game state to menu
gameState = 'menu';
spawnTimer = 0;
LK.setScore(0);
}
};
+// Create beat-synchronized key animation
+function triggerBeatKeyAnimation() {
+ if (!virtualKey || !virtualKey.visible || keyAnimationActive) return;
+ keyAnimationActive = true;
+ // Key press animation - scale down quickly
+ tween(virtualKey, {
+ scaleX: 0.6,
+ scaleY: 0.6,
+ alpha: 1.0,
+ tint: 0xFFFFFF
+ }, {
+ duration: 100,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ // Key release animation - scale back up with glow
+ tween(virtualKey, {
+ scaleX: 1.0,
+ scaleY: 1.0,
+ alpha: 0.7,
+ tint: 0xFFD700
+ }, {
+ duration: 300,
+ easing: tween.elasticOut,
+ onFinish: function onFinish() {
+ keyAnimationActive = false;
+ }
+ });
+ }
+ });
+ // Create ripple effect around key
+ var ripple = game.addChild(LK.getAsset('button', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }));
+ ripple.x = virtualKey.x;
+ ripple.y = virtualKey.y;
+ ripple.alpha = 0.5;
+ ripple.scaleX = 0.8;
+ ripple.scaleY = 0.8;
+ ripple.tint = 0x00FFFF;
+ // Animate ripple expanding
+ tween(ripple, {
+ scaleX: 2.5,
+ scaleY: 2.5,
+ alpha: 0
+ }, {
+ duration: 600,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (ripple && ripple.parent) {
+ ripple.destroy();
+ }
+ }
+ });
+ // Create sparkle particles around key
+ for (var s = 0; s < 6; s++) {
+ var sparkle = game.addChild(LK.getAsset('chordNote2', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }));
+ var angle = s / 6 * Math.PI * 2;
+ var distance = 60 + Math.random() * 40;
+ sparkle.x = virtualKey.x + Math.cos(angle) * distance;
+ sparkle.y = virtualKey.y + Math.sin(angle) * distance;
+ sparkle.scaleX = 0.3;
+ sparkle.scaleY = 0.3;
+ sparkle.alpha = 0.8;
+ sparkle.tint = 0xFFD700;
+ // Animate sparkles
+ tween(sparkle, {
+ y: sparkle.y - 50 - Math.random() * 50,
+ x: sparkle.x + (Math.random() - 0.5) * 80,
+ alpha: 0,
+ rotation: Math.PI * 2 * (Math.random() > 0.5 ? 1 : -1),
+ scaleX: 0.1,
+ scaleY: 0.1
+ }, {
+ duration: 400 + Math.random() * 300,
+ easing: tween.easeOut,
+ delay: Math.random() * 100,
+ onFinish: function onFinish() {
+ if (sparkle && sparkle.parent) {
+ sparkle.destroy();
+ }
+ }
+ });
+ }
+}
// Create explosion animation function
function createExplosion(x, y, color) {
var particleCount = 8;
var particles = [];
@@ -1737,8 +1876,37 @@
// Only update game logic when playing
if (gameState !== 'playing') {
return;
}
+ // BPM-based beat detection and key animation
+ if (virtualKey && virtualKey.visible) {
+ beatTimer++;
+ // Check if it's time for a beat based on BPM
+ if (beatTimer >= framesPerBeat) {
+ beatTimer = 0;
+ lastBeatTime = LK.ticks;
+ // Trigger beat key animation
+ triggerBeatKeyAnimation();
+ // Update beat intensity for other systems
+ beatIntensity = 1.0;
+ musicBeat = 0;
+ }
+ // Add continuous subtle breathing animation to virtual key
+ if (!keyAnimationActive) {
+ var breathingScale = 0.8 + Math.sin(LK.ticks * 0.05) * 0.1;
+ virtualKey.scaleX = breathingScale;
+ virtualKey.scaleY = breathingScale;
+ // Subtle color shifting
+ var colorCycle = Math.sin(LK.ticks * 0.03) * 0.5 + 0.5;
+ var colorShift = Math.floor(colorCycle * 50);
+ virtualKey.tint = 0xFFD700 + (colorShift << 8);
+ }
+ // Update key label position to follow virtual key
+ if (keyLabel) {
+ keyLabel.x = virtualKey.x;
+ keyLabel.y = virtualKey.y + 80;
+ }
+ }
// Add fluid animation to timing line with music synchronization
if (timingLine.visible) {
// Pulsing effect based on notes approaching
var nearbyNotes = 0;
@@ -1944,8 +2112,12 @@
timingLine.visible = false;
bottomLine.visible = false;
lane1.visible = false;
lane2.visible = false;
+ // Hide virtual key
+ virtualKey.visible = false;
+ if (keyLabel) keyLabel.visible = false;
+ keyAnimationActive = false;
// Hide buttons
for (var i = 0; i < buttons.length; i++) {
buttons[i].visible = false;
}