/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Note = Container.expand(function (trackIndex, isPowerNote) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.isPowerNote = isPowerNote || false;
self.speed = 7;
self.hasBeenTapped = false;
self.hasReducedLife = false; // Flag to track if this note already caused life loss
self.lastY = 0;
var noteGraphics = self.attachAsset(self.isPowerNote ? 'powerNote' : 'note', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply sharpness enhancement to note graphics
if (noteGraphics.filters) {
noteGraphics.filters = [];
} else {
noteGraphics.filters = [];
}
// Enhance contrast for sharper appearance
noteGraphics.alpha = 1.0;
if (self.isPowerNote) {
noteGraphics.tint = 0xFFD93D;
self.scale.set(1.2);
}
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
if (self.lastInTargetZone === undefined) self.lastInTargetZone = false;
// Check if note just entered target zone
var targetY = 1650; // unifiedTargetZone.y
var inTargetZone = Math.abs(self.y - targetY) <= 150; // Expanded target zone range
// Additional check: ensure no other note is currently in target zone
var otherNoteInZone = false;
for (var k = 0; k < notes.length; k++) {
var otherNote = notes[k];
if (otherNote !== self && !otherNote.hasBeenTapped && Math.abs(otherNote.y - targetY) <= 100) {
otherNoteInZone = true;
break;
}
}
if (!self.lastInTargetZone && inTargetZone && !self.hasBeenTapped && !otherNoteInZone) {
// Note just entered target zone and no other note is present - trigger explosion
self.triggerExplosion();
}
// Check if note passed bottom of target zone without being tapped (missed)
var targetZoneBottom = targetY + 150; // Bottom of target zone
if (self.lastY <= targetZoneBottom && self.y > targetZoneBottom && !self.hasBeenTapped && !self.hasReducedLife) {
// Note passed bottom of target zone - play miss sound and reduce lives only once
LK.getSound('miss').play();
updateLives(-1);
self.hasReducedLife = true; // Mark that this note has already caused life loss
}
self.lastY = self.y;
self.lastInTargetZone = inTargetZone;
self.y += self.speed;
};
self.checkTiming = function () {
var targetY = unifiedTargetZone.y;
var distance = Math.abs(self.y - targetY);
if (distance <= 45) {
// Expanded perfect range
return 'perfect';
} else if (distance <= 90) {
// Expanded good range
return 'good';
}
return 'miss';
};
self.triggerExplosion = function () {
if (self.hasBeenTapped) return;
var timing = self.checkTiming();
if (timing !== 'miss') {
self.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(self.x, self.y, self.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(self.x, self.y, 0x87CEEB);
}
updateScore(points);
unifiedTargetZone.flash();
// Trigger explosion animation on corresponding bottom object
var bottomObjects = [leftObject, centerObject, rightObject];
if (bottomObjects[self.trackIndex]) {
var targetObject = bottomObjects[self.trackIndex];
// Flash the bottom object
var originalAlpha = targetObject.alpha;
tween(targetObject, {
alpha: 1.0
}, {
duration: 100,
onFinish: function onFinish() {
tween(targetObject, {
alpha: originalAlpha
}, {
duration: 200
});
}
});
// Scale pulse effect
var originalScale = targetObject.scaleX;
tween(targetObject, {
scaleX: originalScale * 1.1,
scaleY: originalScale * 1.1
}, {
duration: 150,
onFinish: function onFinish() {
tween(targetObject, {
scaleX: originalScale,
scaleY: originalScale
}, {
duration: 150
});
}
});
}
// Explode the note
tween(self, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === self) {
notes.splice(j, 1);
break;
}
}
}
});
}
};
return self;
});
var TargetZone = Container.expand(function (trackIndex) {
var self = Container.call(this);
self.trackIndex = trackIndex;
var zoneGraphics = self.attachAsset('targetZone', {
anchorX: 0.5,
anchorY: 0.5
});
zoneGraphics.alpha = 1.0;
self.flash = function () {
zoneGraphics.alpha = 1.0;
// Remove fade animation to keep target zone fully visible
};
return self;
});
var Track = Container.expand(function (trackIndex) {
var self = Container.call(this);
self.trackIndex = trackIndex;
var trackGraphics = self.attachAsset('track', {
anchorX: 0.5,
anchorY: 0
});
trackGraphics.alpha = 0.1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var notes = [];
var tracks = [];
var targetZones = [];
var numTracks = 3;
var trackWidth = 200; // Reduced from 300 to bring tracks closer
var gameWidth = 2048;
var score = 0;
var lives = 5; // Player starts with 5 lives
var noteSpawnTimer = 0;
var noteSpawnInterval = 90; // Faster baseline spawning
var gameSpeed = 1;
var difficultyTimer = 0;
// Note queue system to prevent simultaneous target zone entry
var noteQueue = [];
var lastTargetZoneEntry = 0;
var minTimeBetweenEntries = 60; // Reduced minimum frames for better rhythm flow
// Vertical spacing system to prevent notes from overlapping
var minVerticalSpacing = 200; // Reduced spacing for more dynamic gameplay
// Bottom objects for explosion effects
var leftObject, centerObject, rightObject;
// Strip line references for sway effects
var leftStripLine, centerStripLine, rightStripLine;
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120; // Position away from platform menu icon
scoreTxt.y = 220; // Move even further down from top
LK.gui.topLeft.addChild(scoreTxt);
// Create array to hold heart graphics for lives display
var heartsArray = [];
var heartsContainer = new Container();
heartsContainer.x = 120; // Same x as score
heartsContainer.y = 380; // Move hearts further down below score text
LK.gui.topLeft.addChild(heartsContainer);
// Initialize 5 hearts
for (var h = 0; h < 5; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = h * 60; // Space hearts 60 pixels apart
heart.y = 0;
heart.alpha = 1.0; // Start with full opacity
heartsArray.push(heart);
heartsContainer.addChild(heart);
}
// Add background image
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
background.x = 0;
background.y = 0;
background.tint = 0x000000;
game.addChild(background);
// Initialize tracks and unified target zone
var startX = (gameWidth - numTracks * trackWidth) / 2 + trackWidth / 2;
for (var i = 0; i < numTracks; i++) {
var track = new Track(i);
track.x = startX + i * trackWidth;
track.y = 0;
tracks.push(track);
game.addChild(track);
}
// Create single unified target zone covering all tracks
var unifiedTargetZone = new TargetZone(0);
unifiedTargetZone.x = gameWidth / 2; // Center of screen
unifiedTargetZone.y = 1650; // Move target zone slightly down
// Scale the target zone to cover all tracks
unifiedTargetZone.scale.x = numTracks * trackWidth / 350; // Scale to cover all track widths with new spacing
unifiedTargetZone.scale.y = 1.5; // Expand target zone height by 50%
// Add sharpness filter for better visual clarity
if (unifiedTargetZone.filters) {
unifiedTargetZone.filters = [];
} else {
unifiedTargetZone.filters = [];
}
// Apply contrast and brightness adjustments for sharper appearance
unifiedTargetZone.alpha = 1.0;
targetZones.push(unifiedTargetZone);
// Add unified background covering entire play area
var unifiedBg = LK.getAsset('unifiedBackground', {
anchorX: 0.5,
anchorY: 0
});
unifiedBg.x = gameWidth / 2; // Center horizontally
unifiedBg.y = 0; // Start from top of screen
// Crop background to fit exactly within game area without extending beyond screen
unifiedBg.width = gameWidth; // Fit exactly to game width (2048px)
unifiedBg.height = 2732; // Fit exactly to game height (2732px)
unifiedBg.alpha = 0.8; // Slightly transparent to show game elements clearly
game.addChild(unifiedBg);
// Re-add strip lines and unified target zone to foreground
for (var i = 0; i < numTracks; i++) {
// Add strip line for each track (moved to foreground) - end at target zone
var stripLine = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0
});
stripLine.x = startX + i * trackWidth;
stripLine.y = 0;
// Set strip line height to end at target zone
stripLine.height = unifiedTargetZone.y + 200 * unifiedTargetZone.scale.y / 2; // End at bottom of target zone
stripLine.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement
if (stripLine.filters) {
stripLine.filters = [];
} else {
stripLine.filters = [];
}
// Store original position for vibration reset
stripLine.originalX = stripLine.x;
game.addChild(stripLine);
// Store references to strip lines
if (i === 0) leftStripLine = stripLine;else if (i === 1) centerStripLine = stripLine;else if (i === 2) rightStripLine = stripLine;
}
// Add unified target zone to foreground
game.addChild(unifiedTargetZone);
// Create rounded border around unified target zone
var borderThickness = 8;
var targetZoneWidth = 350 * unifiedTargetZone.scale.x;
var targetZoneHeight = 200 * unifiedTargetZone.scale.y; // Use scaled height
// Top border
var topBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
topBorder.width = targetZoneWidth - borderThickness;
topBorder.height = borderThickness;
topBorder.x = unifiedTargetZone.x;
topBorder.y = unifiedTargetZone.y - targetZoneHeight / 2 - borderThickness / 2;
topBorder.alpha = 1.0;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
bottomBorder.width = targetZoneWidth - borderThickness;
bottomBorder.height = borderThickness;
bottomBorder.x = unifiedTargetZone.x;
bottomBorder.y = unifiedTargetZone.y + targetZoneHeight / 2 + borderThickness / 2;
bottomBorder.alpha = 1.0;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
leftBorder.width = borderThickness;
leftBorder.height = targetZoneHeight - borderThickness;
leftBorder.x = unifiedTargetZone.x - targetZoneWidth / 2 - borderThickness / 2;
leftBorder.y = unifiedTargetZone.y;
leftBorder.alpha = 1.0;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
rightBorder.width = borderThickness;
rightBorder.height = targetZoneHeight - borderThickness;
rightBorder.x = unifiedTargetZone.x + targetZoneWidth / 2 + borderThickness / 2;
rightBorder.y = unifiedTargetZone.y;
rightBorder.alpha = 1.0;
game.addChild(rightBorder);
// Add rounded corners with ellipses
// Top-left corner
var topLeftCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
topLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2;
topLeftCorner.y = unifiedTargetZone.y - targetZoneHeight / 2;
topLeftCorner.alpha = 1.0;
game.addChild(topLeftCorner);
// Top-right corner
var topRightCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
topRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2;
topRightCorner.y = unifiedTargetZone.y - targetZoneHeight / 2;
topRightCorner.alpha = 1.0;
game.addChild(topRightCorner);
// Bottom-left corner
var bottomLeftCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
bottomLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2;
bottomLeftCorner.y = unifiedTargetZone.y + targetZoneHeight / 2;
bottomLeftCorner.alpha = 1.0;
game.addChild(bottomLeftCorner);
// Bottom-right corner
var bottomRightCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
bottomRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2;
bottomRightCorner.y = unifiedTargetZone.y + targetZoneHeight / 2;
bottomRightCorner.alpha = 1.0;
game.addChild(bottomRightCorner);
// Rhythm pattern system for music-synchronized note spawning
var rhythmPatterns = {
'bgmusic': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0],
// Simple steady beat pattern with occasional variations
'bgmusic2': [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0],
// Upbeat syncopated rhythm with more complexity
'bgmusic3': [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
// Fast-paced pattern with rhythmic variations
'bgmusic4': [1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1],
// Varied mixed pattern with interesting gaps
'bgmusic5': [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0] // Moderate sparse pattern with musical phrasing
};
// Enhanced rhythm synchronization system
var currentBeatIndex = 0;
var beatsPerMinute = {
'bgmusic': 120,
// Corrected BPM values for proper synchronization
'bgmusic2': 128,
'bgmusic3': 130,
'bgmusic4': 125,
'bgmusic5': 115
};
var musicStartFrame = 0;
var beatAccumulator = 0;
var lastSpawnTime = 0;
var framesPerBeat = 0;
var nextBeatFrame = 0;
var rhythmCalibration = {
'bgmusic': 0,
// Removed frame offset calibration - using direct synchronization
'bgmusic2': 0,
'bgmusic3': 0,
'bgmusic4': 0,
'bgmusic5': 0
};
// Music timing variables for precise rhythm synchronization
var musicContext = null;
var musicStartedAt = 0;
var lastMusicTime = 0;
// Helper functions
function spawnNote() {
var trackIndex = Math.floor(Math.random() * numTracks);
var isPowerNote = Math.random() < 0.1;
// Add to queue instead of spawning directly
noteQueue.push({
trackIndex: trackIndex,
isPowerNote: isPowerNote,
readyTime: LK.ticks + Math.max(0, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks)
});
}
function spawnRhythmNote() {
// Only spawn notes if music is playing and we have a rhythm pattern
if (!musicStarted || !currentMusicTrack || !rhythmPatterns[currentMusicTrack]) {
return;
}
// Calculate precise beat timing based on actual music playback time
var currentBPM = beatsPerMinute[currentMusicTrack] || 120;
var beatsPerSecond = currentBPM / 60;
var secondsPerBeat = 1 / beatsPerSecond;
// Use actual music playback time instead of frame counting
var musicElapsedSeconds = (LK.ticks - musicStartFrame) / 60; // Convert frames to seconds (60 FPS)
var currentBeatFloat = musicElapsedSeconds / secondsPerBeat;
var targetBeatIndex = Math.floor(currentBeatFloat);
// Check if we've crossed into a new beat
if (targetBeatIndex > currentBeatIndex) {
currentBeatIndex = targetBeatIndex;
var pattern = rhythmPatterns[currentMusicTrack];
var patternIndex = currentBeatIndex % pattern.length;
var shouldSpawn = pattern[patternIndex];
if (shouldSpawn === 1) {
// Better track distribution with more variation
var trackIndex;
if (currentBeatIndex % 4 === 0) {
trackIndex = 1; // Center track on strong beats
} else {
trackIndex = Math.floor(Math.random() * numTracks);
}
var isPowerNote = currentBeatIndex % 16 === 0 ? true : Math.random() < 0.05; // Power notes less frequent
// Calculate note spawn timing to hit target zone exactly on beat
var noteSpeed = 7; // Fixed note speed
var targetY = 1650; // Target zone Y position
var spawnY = -50; // Spawn Y position
var distanceToTarget = targetY - spawnY;
var framesToTarget = distanceToTarget / noteSpeed;
var secondsToTarget = framesToTarget / 60; // Convert to seconds
// Spawn note early so it arrives at target zone exactly on the musical beat
var nextBeatTime = (targetBeatIndex + 1) * secondsPerBeat;
var spawnDelay = Math.max(0, (nextBeatTime - musicElapsedSeconds - secondsToTarget) * 60); // Convert back to frames
// Add to queue with precise timing for musical synchronization
noteQueue.push({
trackIndex: trackIndex,
isPowerNote: isPowerNote,
readyTime: LK.ticks + Math.floor(spawnDelay) + Math.max(3, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks)
});
lastSpawnTime = LK.ticks;
}
}
}
function processNoteQueue() {
for (var i = noteQueue.length - 1; i >= 0; i--) {
var queuedNote = noteQueue[i];
if (LK.ticks >= queuedNote.readyTime) {
// Check if any note is currently in or approaching target zone
var canSpawn = true;
var targetY = 1650;
var spawnY = -50;
var targetZoneBuffer = 250; // Increased buffer zone for expanded target
for (var j = 0; j < notes.length; j++) {
var existingNote = notes[j];
// Check if note is in extended target zone area (larger buffer for safety)
if (Math.abs(existingNote.y - targetY) <= targetZoneBuffer) {
canSpawn = false;
break;
}
// Check if note will reach target zone soon (prediction based on speed)
var timeToTarget = (targetY - existingNote.y) / existingNote.speed;
if (timeToTarget > 0 && timeToTarget <= 50) {
// Within 50 frames of reaching target
canSpawn = false;
break;
}
// Check vertical spacing collision - prevent notes from being too close vertically
if (Math.abs(existingNote.y - spawnY) < minVerticalSpacing) {
canSpawn = false;
break;
}
}
if (canSpawn) {
// Spawn the note
var note = new Note(queuedNote.trackIndex, queuedNote.isPowerNote);
// Position notes exactly on track center lines
var noteX = startX + queuedNote.trackIndex * trackWidth;
note.x = noteX;
note.y = spawnY;
note.speed = 7; // Fixed speed constant - reduced to 7 for gameplay balance
notes.push(note);
game.addChild(note);
lastTargetZoneEntry = LK.ticks;
noteQueue.splice(i, 1);
} else {
// Delay this note by adding minimum time
queuedNote.readyTime = LK.ticks + minTimeBetweenEntries;
}
}
}
}
function updateScore(points) {
score += points;
scoreTxt.setText('Score: ' + score);
LK.setScore(score);
}
function updateLives(change) {
lives += change;
// Update heart display based on current lives
for (var h = 0; h < heartsArray.length; h++) {
if (h < lives) {
heartsArray[h].alpha = 1.0; // Show heart
} else {
heartsArray[h].alpha = 0; // Make lost heart completely invisible
}
}
if (lives <= 0) {
LK.showGameOver();
}
}
function createParticleEffect(x, y, color) {
// Create enhanced particle effects with increased count
var totalParticles = 80; // Significantly increased particle count
var primaryParticles = 35;
var secondaryParticles = 25;
var trailParticles = 20;
// Primary burst particles - fast moving with enhanced visuals
for (var i = 0; i < primaryParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x;
particle.y = y;
// Enhanced color with gradient-like effect
var colorVariation = 0x303030 * Math.random();
particle.tint = (color || 0xFFD93D) + colorVariation;
particle.alpha = 0.95 + Math.random() * 0.05;
game.addChild(particle);
// Calculate random direction and speed for each particle
var angle = i / primaryParticles * Math.PI * 2 + (Math.random() - 0.5) * 2.2;
var speed = 800 + Math.random() * 700; // Significantly increased speed range
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Enhanced particle scaling with better proportions
particle.scaleX = 3.5 + Math.random() * 3.0; // Larger particle size for better impact
particle.scaleY = particle.scaleX + (Math.random() - 0.5) * 0.8; // More asymmetry for dynamic feel
// Animate particle flying outward with enhanced effects
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
// Smoother scale transition
scaleY: 0.05,
rotation: (Math.random() - 0.5) * Math.PI * 16 // Increased rotation for more dynamic motion
}, {
duration: 1000 + Math.random() * 800,
// Longer duration for better visual impact
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Secondary slower particles for depth with improved visuals
for (var i = 0; i < secondaryParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x + (Math.random() - 0.5) * 60; // Increased initial spread
particle.y = y + (Math.random() - 0.5) * 60;
// Enhanced secondary particle coloring
var secondaryColor = color || 0xFFD93D;
var brightness = 0.8 + Math.random() * 0.2;
particle.tint = secondaryColor * brightness;
particle.alpha = 0.85 + Math.random() * 0.15;
game.addChild(particle);
var angle = Math.random() * Math.PI * 2;
var speed = 450 + Math.random() * 350; // Increased secondary particle speed
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Enhanced secondary particle scaling
particle.scaleX = 2.5 + Math.random() * 2.0; // Larger secondary particles
particle.scaleY = particle.scaleX * (0.7 + Math.random() * 0.6); // More varied proportions
// Delayed animation for staggered effect with enhanced timing
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
// Smoother scale transition
scaleY: 0.1,
rotation: (Math.random() - 0.5) * Math.PI * 14 // Increased rotation
}, {
duration: 1200 + Math.random() * 1000,
// Longer duration for better visual flow
easing: tween.easeInOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Enhanced trail particles that move upward
for (var i = 0; i < trailParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x + (Math.random() - 0.5) * 80; // Increased initial spread
particle.y = y;
// Enhanced trail particle coloring with stronger glow effect
var trailColor = color || 0xFFD93D;
var glowIntensity = 0.9 + Math.random() * 0.1;
particle.tint = trailColor * glowIntensity;
particle.alpha = 0.8 + Math.random() * 0.2;
game.addChild(particle);
// Enhanced trail particle scaling with larger proportions
particle.scaleX = 4.0 + Math.random() * 3.0; // Significantly larger trail particles
particle.scaleY = particle.scaleX * (0.5 + Math.random() * 1.0); // More dramatic shape variations
// Animate upward with enhanced gravity-like effect
tween(particle, {
x: particle.x + (Math.random() - 0.5) * 300,
// Increased horizontal spread
y: y - 500 - Math.random() * 350,
// Significantly enhanced upward movement
alpha: 0,
scaleX: 0.2,
// Smoother scale transition
scaleY: 0.2,
rotation: (Math.random() - 0.5) * Math.PI * 12 // Increased rotation for more dynamic motion
}, {
duration: 1500 + Math.random() * 1200,
// Longer duration for better visual impact
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
function checkNoteInTrack(trackIndex) {
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === trackIndex && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = 0; // Start with first track for consistency
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync
musicRepeatCount = 0;
// Reset rhythm timing for new music with proper initialization
currentBeatIndex = 0; // Start at 0 for immediate beat detection
beatAccumulator = 0; // Reset beat accumulator for precise timing
lastSpawnTime = 0; // Reset last spawn time
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
// Reset rhythm timing for new music with proper initialization
currentBeatIndex = 0; // Start at 0 for immediate beat detection
beatAccumulator = 0; // Reset beat accumulator for precise timing
lastSpawnTime = 0; // Reset last spawn time
}
unifiedTargetZone.flash();
note.destroy();
notes.splice(i, 1);
return true;
}
}
}
return false;
}
// Touch handlers
game.down = function (x, y, obj) {
for (var i = 0; i < numTracks; i++) {
var trackX = startX + i * trackWidth;
if (x >= trackX - trackWidth / 2 && x <= trackX + trackWidth / 2) {
if (!checkNoteInTrack(i)) {
// Removed miss sound and life reduction for missed taps - only notes escaping target zone should reduce lives and play miss sound
}
break;
}
}
};
// Main game loop
game.update = function () {
// Rhythm-based note spawning when music is playing
if (musicStarted && currentMusicTrack) {
// Use precise music synchronization for rhythm-based spawning
spawnRhythmNote();
} else {
// Fallback to timer-based spawning when no music is playing
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
}
}
// Process note queue to ensure no simultaneous target zone entries
processNoteQueue();
// Update difficulty
difficultyTimer++;
if (difficultyTimer >= 1800) {
// Every 30 seconds
gameSpeed += 0.2;
noteSpawnInterval = Math.max(20, noteSpawnInterval - 2);
difficultyTimer = 0;
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Check if note passed beyond target zone - let it pass slightly further before removing
var targetY = 1650; // unifiedTargetZone.y
var targetZoneBottom = targetY + 200 * 1.5 / 2; // Bottom of expanded target zone
var disappearY = targetZoneBottom + 100; // Allow note to pass 100px beyond target zone
if (!note.hasBeenTapped && note.lastY <= disappearY && note.y > disappearY) {
note.destroy();
notes.splice(i, 1);
continue;
}
// Remove notes that are off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
}
// Clean up off-screen particles
var allChildren = game.children.slice(); // Create a copy to avoid modification during iteration
for (var i = allChildren.length - 1; i >= 0; i--) {
var child = allChildren[i];
// Check if this is a particle (has particle-like properties)
if (child && child.x !== undefined && child.y !== undefined && child.width <= 10 && child.height <= 10) {
// Remove particles that are far off-screen or destroyed
if (child.x < -500 || child.x > 2548 || child.y < -500 || child.y > 3232 || child.destroyed) {
if (child.destroy) {
child.destroy();
} else {
game.removeChild(child);
}
}
}
}
// Create continuous particle effects around the three objects
enhanceObjectVisuals();
// Check for music track changes every 60 seconds (1 minute) with fade out
if (musicStarted && LK.ticks % 60 === 0) {
// Check every second
var timeElapsed = (LK.ticks - musicStartTime) / 60; // Convert to seconds since music started
var musicChangeInterval = 60; // 60 seconds (1 minute) per music change
// Start fade out 2 seconds before music ends
if (timeElapsed >= musicChangeInterval - 2 && timeElapsed < musicChangeInterval && !musicFinished) {
// Fade out over 2 seconds
LK.playMusic(currentMusicTrack, {
fade: {
start: 1,
end: 0,
duration: 2000
}
});
}
// Check if current music should have finished
if (timeElapsed >= musicChangeInterval && !musicFinished) {
musicFinished = true;
waitingForNoteAfterMusicEnd = true;
LK.stopMusic(); // Stop current music and wait for note hit
}
}
// Win condition
if (score >= 10000) {
LK.showYouWin();
}
};
// Add 3 objects at bottom of target zone (left, center, right)
var targetBottomY = unifiedTargetZone.y + targetZoneHeight / 2 + 520; // Move objects down very slightly
var objectSpacing = targetZoneWidth / 1.0; // Further increased spacing for better separation between objects
// Left object
leftObject = LK.getAsset('leftObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
leftObject.x = unifiedTargetZone.x - objectSpacing;
leftObject.y = targetBottomY;
// Scale up the left object
leftObject.scaleX = 2.5;
leftObject.scaleY = 2.5;
// Animate color change for left object
tween(leftObject, {
tint: 0xFFD700 // Change to gold color
}, {
duration: 1500,
easing: tween.easeInOut
});
leftObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to left object
if (leftObject.filters) {
leftObject.filters = [];
} else {
leftObject.filters = [];
}
game.addChild(leftObject);
// Center object
centerObject = LK.getAsset('centerObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
centerObject.x = unifiedTargetZone.x;
centerObject.y = targetBottomY;
// Scale up the center object
centerObject.scaleX = 2.5;
centerObject.scaleY = 2.5;
// Apply orange tint to center object
tween(centerObject, {
tint: 0xFF6600 // Change to bright orange color
}, {
duration: 1500,
easing: tween.easeInOut
});
centerObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to center object
if (centerObject.filters) {
centerObject.filters = [];
} else {
centerObject.filters = [];
}
game.addChild(centerObject);
// Right object
rightObject = LK.getAsset('rightObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
rightObject.x = unifiedTargetZone.x + objectSpacing;
rightObject.y = targetBottomY;
// Scale up the right object
rightObject.scaleX = 2.5;
rightObject.scaleY = 2.5;
// Animate color change for right object
tween(rightObject, {
tint: 0x9370DB // Change to medium purple color
}, {
duration: 1500,
easing: tween.easeInOut
});
rightObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to right object
if (rightObject.filters) {
rightObject.filters = [];
} else {
rightObject.filters = [];
}
game.addChild(rightObject);
// Objects are now properly sized - no extreme scaling needed
// Move objects to topmost layer for better visibility
game.removeChild(leftObject);
game.addChild(leftObject);
game.removeChild(centerObject);
game.addChild(centerObject);
game.removeChild(rightObject);
game.addChild(rightObject);
// Add touch handlers to the 3 bottom objects
leftObject.down = function (x, y, obj) {
// Add guitar string vibration effect to left track strip line
if (leftStripLine) {
// Use stored original position instead of current position
var originalX = leftStripLine.originalX;
var _vibrateString = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(leftStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(leftStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString();
}
// Check for notes in left track (track 0) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 0 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
centerObject.down = function (x, y, obj) {
// Add guitar string vibration effect to center track strip line
if (centerStripLine) {
// Use stored original position instead of current position
var originalX = centerStripLine.originalX;
var _vibrateString2 = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(centerStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(centerStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString2();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString2();
}
// Check for notes in center track (track 1) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 1 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
rightObject.down = function (x, y, obj) {
// Add guitar string vibration effect to right track strip line
if (rightStripLine) {
// Use stored original position instead of current position
var originalX = rightStripLine.originalX;
var _vibrateString3 = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(rightStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(rightStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString3();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString3();
}
// Check for notes in right track (track 2) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 2 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
// Music will start when first note is hit
var musicStarted = false;
var musicTracks = ['bgmusic', 'bgmusic2', 'bgmusic3', 'bgmusic4', 'bgmusic5'];
var currentMusicTrack = null;
var musicRepeatCount = 0;
var targetRepeatCount = 3;
var musicStartTime = 0;
var currentMusicIndex = 0;
var musicFinished = false;
var waitingForNoteAfterMusicEnd = false;
// Orbital particle effects removed - variables no longer needed
// Optimized visual effects function
function enhanceObjectVisuals() {
// Orbital particle effects removed
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Note = Container.expand(function (trackIndex, isPowerNote) {
var self = Container.call(this);
self.trackIndex = trackIndex;
self.isPowerNote = isPowerNote || false;
self.speed = 7;
self.hasBeenTapped = false;
self.hasReducedLife = false; // Flag to track if this note already caused life loss
self.lastY = 0;
var noteGraphics = self.attachAsset(self.isPowerNote ? 'powerNote' : 'note', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply sharpness enhancement to note graphics
if (noteGraphics.filters) {
noteGraphics.filters = [];
} else {
noteGraphics.filters = [];
}
// Enhance contrast for sharper appearance
noteGraphics.alpha = 1.0;
if (self.isPowerNote) {
noteGraphics.tint = 0xFFD93D;
self.scale.set(1.2);
}
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
if (self.lastInTargetZone === undefined) self.lastInTargetZone = false;
// Check if note just entered target zone
var targetY = 1650; // unifiedTargetZone.y
var inTargetZone = Math.abs(self.y - targetY) <= 150; // Expanded target zone range
// Additional check: ensure no other note is currently in target zone
var otherNoteInZone = false;
for (var k = 0; k < notes.length; k++) {
var otherNote = notes[k];
if (otherNote !== self && !otherNote.hasBeenTapped && Math.abs(otherNote.y - targetY) <= 100) {
otherNoteInZone = true;
break;
}
}
if (!self.lastInTargetZone && inTargetZone && !self.hasBeenTapped && !otherNoteInZone) {
// Note just entered target zone and no other note is present - trigger explosion
self.triggerExplosion();
}
// Check if note passed bottom of target zone without being tapped (missed)
var targetZoneBottom = targetY + 150; // Bottom of target zone
if (self.lastY <= targetZoneBottom && self.y > targetZoneBottom && !self.hasBeenTapped && !self.hasReducedLife) {
// Note passed bottom of target zone - play miss sound and reduce lives only once
LK.getSound('miss').play();
updateLives(-1);
self.hasReducedLife = true; // Mark that this note has already caused life loss
}
self.lastY = self.y;
self.lastInTargetZone = inTargetZone;
self.y += self.speed;
};
self.checkTiming = function () {
var targetY = unifiedTargetZone.y;
var distance = Math.abs(self.y - targetY);
if (distance <= 45) {
// Expanded perfect range
return 'perfect';
} else if (distance <= 90) {
// Expanded good range
return 'good';
}
return 'miss';
};
self.triggerExplosion = function () {
if (self.hasBeenTapped) return;
var timing = self.checkTiming();
if (timing !== 'miss') {
self.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(self.x, self.y, self.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(self.x, self.y, 0x87CEEB);
}
updateScore(points);
unifiedTargetZone.flash();
// Trigger explosion animation on corresponding bottom object
var bottomObjects = [leftObject, centerObject, rightObject];
if (bottomObjects[self.trackIndex]) {
var targetObject = bottomObjects[self.trackIndex];
// Flash the bottom object
var originalAlpha = targetObject.alpha;
tween(targetObject, {
alpha: 1.0
}, {
duration: 100,
onFinish: function onFinish() {
tween(targetObject, {
alpha: originalAlpha
}, {
duration: 200
});
}
});
// Scale pulse effect
var originalScale = targetObject.scaleX;
tween(targetObject, {
scaleX: originalScale * 1.1,
scaleY: originalScale * 1.1
}, {
duration: 150,
onFinish: function onFinish() {
tween(targetObject, {
scaleX: originalScale,
scaleY: originalScale
}, {
duration: 150
});
}
});
}
// Explode the note
tween(self, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === self) {
notes.splice(j, 1);
break;
}
}
}
});
}
};
return self;
});
var TargetZone = Container.expand(function (trackIndex) {
var self = Container.call(this);
self.trackIndex = trackIndex;
var zoneGraphics = self.attachAsset('targetZone', {
anchorX: 0.5,
anchorY: 0.5
});
zoneGraphics.alpha = 1.0;
self.flash = function () {
zoneGraphics.alpha = 1.0;
// Remove fade animation to keep target zone fully visible
};
return self;
});
var Track = Container.expand(function (trackIndex) {
var self = Container.call(this);
self.trackIndex = trackIndex;
var trackGraphics = self.attachAsset('track', {
anchorX: 0.5,
anchorY: 0
});
trackGraphics.alpha = 0.1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var notes = [];
var tracks = [];
var targetZones = [];
var numTracks = 3;
var trackWidth = 200; // Reduced from 300 to bring tracks closer
var gameWidth = 2048;
var score = 0;
var lives = 5; // Player starts with 5 lives
var noteSpawnTimer = 0;
var noteSpawnInterval = 90; // Faster baseline spawning
var gameSpeed = 1;
var difficultyTimer = 0;
// Note queue system to prevent simultaneous target zone entry
var noteQueue = [];
var lastTargetZoneEntry = 0;
var minTimeBetweenEntries = 60; // Reduced minimum frames for better rhythm flow
// Vertical spacing system to prevent notes from overlapping
var minVerticalSpacing = 200; // Reduced spacing for more dynamic gameplay
// Bottom objects for explosion effects
var leftObject, centerObject, rightObject;
// Strip line references for sway effects
var leftStripLine, centerStripLine, rightStripLine;
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120; // Position away from platform menu icon
scoreTxt.y = 220; // Move even further down from top
LK.gui.topLeft.addChild(scoreTxt);
// Create array to hold heart graphics for lives display
var heartsArray = [];
var heartsContainer = new Container();
heartsContainer.x = 120; // Same x as score
heartsContainer.y = 380; // Move hearts further down below score text
LK.gui.topLeft.addChild(heartsContainer);
// Initialize 5 hearts
for (var h = 0; h < 5; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = h * 60; // Space hearts 60 pixels apart
heart.y = 0;
heart.alpha = 1.0; // Start with full opacity
heartsArray.push(heart);
heartsContainer.addChild(heart);
}
// Add background image
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0
});
background.x = 0;
background.y = 0;
background.tint = 0x000000;
game.addChild(background);
// Initialize tracks and unified target zone
var startX = (gameWidth - numTracks * trackWidth) / 2 + trackWidth / 2;
for (var i = 0; i < numTracks; i++) {
var track = new Track(i);
track.x = startX + i * trackWidth;
track.y = 0;
tracks.push(track);
game.addChild(track);
}
// Create single unified target zone covering all tracks
var unifiedTargetZone = new TargetZone(0);
unifiedTargetZone.x = gameWidth / 2; // Center of screen
unifiedTargetZone.y = 1650; // Move target zone slightly down
// Scale the target zone to cover all tracks
unifiedTargetZone.scale.x = numTracks * trackWidth / 350; // Scale to cover all track widths with new spacing
unifiedTargetZone.scale.y = 1.5; // Expand target zone height by 50%
// Add sharpness filter for better visual clarity
if (unifiedTargetZone.filters) {
unifiedTargetZone.filters = [];
} else {
unifiedTargetZone.filters = [];
}
// Apply contrast and brightness adjustments for sharper appearance
unifiedTargetZone.alpha = 1.0;
targetZones.push(unifiedTargetZone);
// Add unified background covering entire play area
var unifiedBg = LK.getAsset('unifiedBackground', {
anchorX: 0.5,
anchorY: 0
});
unifiedBg.x = gameWidth / 2; // Center horizontally
unifiedBg.y = 0; // Start from top of screen
// Crop background to fit exactly within game area without extending beyond screen
unifiedBg.width = gameWidth; // Fit exactly to game width (2048px)
unifiedBg.height = 2732; // Fit exactly to game height (2732px)
unifiedBg.alpha = 0.8; // Slightly transparent to show game elements clearly
game.addChild(unifiedBg);
// Re-add strip lines and unified target zone to foreground
for (var i = 0; i < numTracks; i++) {
// Add strip line for each track (moved to foreground) - end at target zone
var stripLine = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0
});
stripLine.x = startX + i * trackWidth;
stripLine.y = 0;
// Set strip line height to end at target zone
stripLine.height = unifiedTargetZone.y + 200 * unifiedTargetZone.scale.y / 2; // End at bottom of target zone
stripLine.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement
if (stripLine.filters) {
stripLine.filters = [];
} else {
stripLine.filters = [];
}
// Store original position for vibration reset
stripLine.originalX = stripLine.x;
game.addChild(stripLine);
// Store references to strip lines
if (i === 0) leftStripLine = stripLine;else if (i === 1) centerStripLine = stripLine;else if (i === 2) rightStripLine = stripLine;
}
// Add unified target zone to foreground
game.addChild(unifiedTargetZone);
// Create rounded border around unified target zone
var borderThickness = 8;
var targetZoneWidth = 350 * unifiedTargetZone.scale.x;
var targetZoneHeight = 200 * unifiedTargetZone.scale.y; // Use scaled height
// Top border
var topBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
topBorder.width = targetZoneWidth - borderThickness;
topBorder.height = borderThickness;
topBorder.x = unifiedTargetZone.x;
topBorder.y = unifiedTargetZone.y - targetZoneHeight / 2 - borderThickness / 2;
topBorder.alpha = 1.0;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
bottomBorder.width = targetZoneWidth - borderThickness;
bottomBorder.height = borderThickness;
bottomBorder.x = unifiedTargetZone.x;
bottomBorder.y = unifiedTargetZone.y + targetZoneHeight / 2 + borderThickness / 2;
bottomBorder.alpha = 1.0;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
leftBorder.width = borderThickness;
leftBorder.height = targetZoneHeight - borderThickness;
leftBorder.x = unifiedTargetZone.x - targetZoneWidth / 2 - borderThickness / 2;
leftBorder.y = unifiedTargetZone.y;
leftBorder.alpha = 1.0;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('stripLine', {
anchorX: 0.5,
anchorY: 0.5
});
rightBorder.width = borderThickness;
rightBorder.height = targetZoneHeight - borderThickness;
rightBorder.x = unifiedTargetZone.x + targetZoneWidth / 2 + borderThickness / 2;
rightBorder.y = unifiedTargetZone.y;
rightBorder.alpha = 1.0;
game.addChild(rightBorder);
// Add rounded corners with ellipses
// Top-left corner
var topLeftCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
topLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2;
topLeftCorner.y = unifiedTargetZone.y - targetZoneHeight / 2;
topLeftCorner.alpha = 1.0;
game.addChild(topLeftCorner);
// Top-right corner
var topRightCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
topRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2;
topRightCorner.y = unifiedTargetZone.y - targetZoneHeight / 2;
topRightCorner.alpha = 1.0;
game.addChild(topRightCorner);
// Bottom-left corner
var bottomLeftCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
bottomLeftCorner.x = unifiedTargetZone.x - targetZoneWidth / 2;
bottomLeftCorner.y = unifiedTargetZone.y + targetZoneHeight / 2;
bottomLeftCorner.alpha = 1.0;
game.addChild(bottomLeftCorner);
// Bottom-right corner
var bottomRightCorner = LK.getAsset('cornerEllipse', {
anchorX: 0.5,
anchorY: 0.5
});
bottomRightCorner.x = unifiedTargetZone.x + targetZoneWidth / 2;
bottomRightCorner.y = unifiedTargetZone.y + targetZoneHeight / 2;
bottomRightCorner.alpha = 1.0;
game.addChild(bottomRightCorner);
// Rhythm pattern system for music-synchronized note spawning
var rhythmPatterns = {
'bgmusic': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0],
// Simple steady beat pattern with occasional variations
'bgmusic2': [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0],
// Upbeat syncopated rhythm with more complexity
'bgmusic3': [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
// Fast-paced pattern with rhythmic variations
'bgmusic4': [1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1],
// Varied mixed pattern with interesting gaps
'bgmusic5': [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0] // Moderate sparse pattern with musical phrasing
};
// Enhanced rhythm synchronization system
var currentBeatIndex = 0;
var beatsPerMinute = {
'bgmusic': 120,
// Corrected BPM values for proper synchronization
'bgmusic2': 128,
'bgmusic3': 130,
'bgmusic4': 125,
'bgmusic5': 115
};
var musicStartFrame = 0;
var beatAccumulator = 0;
var lastSpawnTime = 0;
var framesPerBeat = 0;
var nextBeatFrame = 0;
var rhythmCalibration = {
'bgmusic': 0,
// Removed frame offset calibration - using direct synchronization
'bgmusic2': 0,
'bgmusic3': 0,
'bgmusic4': 0,
'bgmusic5': 0
};
// Music timing variables for precise rhythm synchronization
var musicContext = null;
var musicStartedAt = 0;
var lastMusicTime = 0;
// Helper functions
function spawnNote() {
var trackIndex = Math.floor(Math.random() * numTracks);
var isPowerNote = Math.random() < 0.1;
// Add to queue instead of spawning directly
noteQueue.push({
trackIndex: trackIndex,
isPowerNote: isPowerNote,
readyTime: LK.ticks + Math.max(0, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks)
});
}
function spawnRhythmNote() {
// Only spawn notes if music is playing and we have a rhythm pattern
if (!musicStarted || !currentMusicTrack || !rhythmPatterns[currentMusicTrack]) {
return;
}
// Calculate precise beat timing based on actual music playback time
var currentBPM = beatsPerMinute[currentMusicTrack] || 120;
var beatsPerSecond = currentBPM / 60;
var secondsPerBeat = 1 / beatsPerSecond;
// Use actual music playback time instead of frame counting
var musicElapsedSeconds = (LK.ticks - musicStartFrame) / 60; // Convert frames to seconds (60 FPS)
var currentBeatFloat = musicElapsedSeconds / secondsPerBeat;
var targetBeatIndex = Math.floor(currentBeatFloat);
// Check if we've crossed into a new beat
if (targetBeatIndex > currentBeatIndex) {
currentBeatIndex = targetBeatIndex;
var pattern = rhythmPatterns[currentMusicTrack];
var patternIndex = currentBeatIndex % pattern.length;
var shouldSpawn = pattern[patternIndex];
if (shouldSpawn === 1) {
// Better track distribution with more variation
var trackIndex;
if (currentBeatIndex % 4 === 0) {
trackIndex = 1; // Center track on strong beats
} else {
trackIndex = Math.floor(Math.random() * numTracks);
}
var isPowerNote = currentBeatIndex % 16 === 0 ? true : Math.random() < 0.05; // Power notes less frequent
// Calculate note spawn timing to hit target zone exactly on beat
var noteSpeed = 7; // Fixed note speed
var targetY = 1650; // Target zone Y position
var spawnY = -50; // Spawn Y position
var distanceToTarget = targetY - spawnY;
var framesToTarget = distanceToTarget / noteSpeed;
var secondsToTarget = framesToTarget / 60; // Convert to seconds
// Spawn note early so it arrives at target zone exactly on the musical beat
var nextBeatTime = (targetBeatIndex + 1) * secondsPerBeat;
var spawnDelay = Math.max(0, (nextBeatTime - musicElapsedSeconds - secondsToTarget) * 60); // Convert back to frames
// Add to queue with precise timing for musical synchronization
noteQueue.push({
trackIndex: trackIndex,
isPowerNote: isPowerNote,
readyTime: LK.ticks + Math.floor(spawnDelay) + Math.max(3, lastTargetZoneEntry + minTimeBetweenEntries - LK.ticks)
});
lastSpawnTime = LK.ticks;
}
}
}
function processNoteQueue() {
for (var i = noteQueue.length - 1; i >= 0; i--) {
var queuedNote = noteQueue[i];
if (LK.ticks >= queuedNote.readyTime) {
// Check if any note is currently in or approaching target zone
var canSpawn = true;
var targetY = 1650;
var spawnY = -50;
var targetZoneBuffer = 250; // Increased buffer zone for expanded target
for (var j = 0; j < notes.length; j++) {
var existingNote = notes[j];
// Check if note is in extended target zone area (larger buffer for safety)
if (Math.abs(existingNote.y - targetY) <= targetZoneBuffer) {
canSpawn = false;
break;
}
// Check if note will reach target zone soon (prediction based on speed)
var timeToTarget = (targetY - existingNote.y) / existingNote.speed;
if (timeToTarget > 0 && timeToTarget <= 50) {
// Within 50 frames of reaching target
canSpawn = false;
break;
}
// Check vertical spacing collision - prevent notes from being too close vertically
if (Math.abs(existingNote.y - spawnY) < minVerticalSpacing) {
canSpawn = false;
break;
}
}
if (canSpawn) {
// Spawn the note
var note = new Note(queuedNote.trackIndex, queuedNote.isPowerNote);
// Position notes exactly on track center lines
var noteX = startX + queuedNote.trackIndex * trackWidth;
note.x = noteX;
note.y = spawnY;
note.speed = 7; // Fixed speed constant - reduced to 7 for gameplay balance
notes.push(note);
game.addChild(note);
lastTargetZoneEntry = LK.ticks;
noteQueue.splice(i, 1);
} else {
// Delay this note by adding minimum time
queuedNote.readyTime = LK.ticks + minTimeBetweenEntries;
}
}
}
}
function updateScore(points) {
score += points;
scoreTxt.setText('Score: ' + score);
LK.setScore(score);
}
function updateLives(change) {
lives += change;
// Update heart display based on current lives
for (var h = 0; h < heartsArray.length; h++) {
if (h < lives) {
heartsArray[h].alpha = 1.0; // Show heart
} else {
heartsArray[h].alpha = 0; // Make lost heart completely invisible
}
}
if (lives <= 0) {
LK.showGameOver();
}
}
function createParticleEffect(x, y, color) {
// Create enhanced particle effects with increased count
var totalParticles = 80; // Significantly increased particle count
var primaryParticles = 35;
var secondaryParticles = 25;
var trailParticles = 20;
// Primary burst particles - fast moving with enhanced visuals
for (var i = 0; i < primaryParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x;
particle.y = y;
// Enhanced color with gradient-like effect
var colorVariation = 0x303030 * Math.random();
particle.tint = (color || 0xFFD93D) + colorVariation;
particle.alpha = 0.95 + Math.random() * 0.05;
game.addChild(particle);
// Calculate random direction and speed for each particle
var angle = i / primaryParticles * Math.PI * 2 + (Math.random() - 0.5) * 2.2;
var speed = 800 + Math.random() * 700; // Significantly increased speed range
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Enhanced particle scaling with better proportions
particle.scaleX = 3.5 + Math.random() * 3.0; // Larger particle size for better impact
particle.scaleY = particle.scaleX + (Math.random() - 0.5) * 0.8; // More asymmetry for dynamic feel
// Animate particle flying outward with enhanced effects
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
// Smoother scale transition
scaleY: 0.05,
rotation: (Math.random() - 0.5) * Math.PI * 16 // Increased rotation for more dynamic motion
}, {
duration: 1000 + Math.random() * 800,
// Longer duration for better visual impact
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Secondary slower particles for depth with improved visuals
for (var i = 0; i < secondaryParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x + (Math.random() - 0.5) * 60; // Increased initial spread
particle.y = y + (Math.random() - 0.5) * 60;
// Enhanced secondary particle coloring
var secondaryColor = color || 0xFFD93D;
var brightness = 0.8 + Math.random() * 0.2;
particle.tint = secondaryColor * brightness;
particle.alpha = 0.85 + Math.random() * 0.15;
game.addChild(particle);
var angle = Math.random() * Math.PI * 2;
var speed = 450 + Math.random() * 350; // Increased secondary particle speed
var targetX = x + Math.cos(angle) * speed;
var targetY = y + Math.sin(angle) * speed;
// Enhanced secondary particle scaling
particle.scaleX = 2.5 + Math.random() * 2.0; // Larger secondary particles
particle.scaleY = particle.scaleX * (0.7 + Math.random() * 0.6); // More varied proportions
// Delayed animation for staggered effect with enhanced timing
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
// Smoother scale transition
scaleY: 0.1,
rotation: (Math.random() - 0.5) * Math.PI * 14 // Increased rotation
}, {
duration: 1200 + Math.random() * 1000,
// Longer duration for better visual flow
easing: tween.easeInOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Enhanced trail particles that move upward
for (var i = 0; i < trailParticles; i++) {
var particle = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particle.x = x + (Math.random() - 0.5) * 80; // Increased initial spread
particle.y = y;
// Enhanced trail particle coloring with stronger glow effect
var trailColor = color || 0xFFD93D;
var glowIntensity = 0.9 + Math.random() * 0.1;
particle.tint = trailColor * glowIntensity;
particle.alpha = 0.8 + Math.random() * 0.2;
game.addChild(particle);
// Enhanced trail particle scaling with larger proportions
particle.scaleX = 4.0 + Math.random() * 3.0; // Significantly larger trail particles
particle.scaleY = particle.scaleX * (0.5 + Math.random() * 1.0); // More dramatic shape variations
// Animate upward with enhanced gravity-like effect
tween(particle, {
x: particle.x + (Math.random() - 0.5) * 300,
// Increased horizontal spread
y: y - 500 - Math.random() * 350,
// Significantly enhanced upward movement
alpha: 0,
scaleX: 0.2,
// Smoother scale transition
scaleY: 0.2,
rotation: (Math.random() - 0.5) * Math.PI * 12 // Increased rotation for more dynamic motion
}, {
duration: 1500 + Math.random() * 1200,
// Longer duration for better visual impact
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
function checkNoteInTrack(trackIndex) {
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === trackIndex && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = 0; // Start with first track for consistency
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync
musicRepeatCount = 0;
// Reset rhythm timing for new music with proper initialization
currentBeatIndex = 0; // Start at 0 for immediate beat detection
beatAccumulator = 0; // Reset beat accumulator for precise timing
lastSpawnTime = 0; // Reset last spawn time
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicStartFrame = LK.ticks; // Track actual start frame for rhythm sync
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
// Reset rhythm timing for new music with proper initialization
currentBeatIndex = 0; // Start at 0 for immediate beat detection
beatAccumulator = 0; // Reset beat accumulator for precise timing
lastSpawnTime = 0; // Reset last spawn time
}
unifiedTargetZone.flash();
note.destroy();
notes.splice(i, 1);
return true;
}
}
}
return false;
}
// Touch handlers
game.down = function (x, y, obj) {
for (var i = 0; i < numTracks; i++) {
var trackX = startX + i * trackWidth;
if (x >= trackX - trackWidth / 2 && x <= trackX + trackWidth / 2) {
if (!checkNoteInTrack(i)) {
// Removed miss sound and life reduction for missed taps - only notes escaping target zone should reduce lives and play miss sound
}
break;
}
}
};
// Main game loop
game.update = function () {
// Rhythm-based note spawning when music is playing
if (musicStarted && currentMusicTrack) {
// Use precise music synchronization for rhythm-based spawning
spawnRhythmNote();
} else {
// Fallback to timer-based spawning when no music is playing
noteSpawnTimer++;
if (noteSpawnTimer >= noteSpawnInterval) {
spawnNote();
noteSpawnTimer = 0;
}
}
// Process note queue to ensure no simultaneous target zone entries
processNoteQueue();
// Update difficulty
difficultyTimer++;
if (difficultyTimer >= 1800) {
// Every 30 seconds
gameSpeed += 0.2;
noteSpawnInterval = Math.max(20, noteSpawnInterval - 2);
difficultyTimer = 0;
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
// Check if note passed beyond target zone - let it pass slightly further before removing
var targetY = 1650; // unifiedTargetZone.y
var targetZoneBottom = targetY + 200 * 1.5 / 2; // Bottom of expanded target zone
var disappearY = targetZoneBottom + 100; // Allow note to pass 100px beyond target zone
if (!note.hasBeenTapped && note.lastY <= disappearY && note.y > disappearY) {
note.destroy();
notes.splice(i, 1);
continue;
}
// Remove notes that are off screen
if (note.y > 2800) {
note.destroy();
notes.splice(i, 1);
}
}
// Clean up off-screen particles
var allChildren = game.children.slice(); // Create a copy to avoid modification during iteration
for (var i = allChildren.length - 1; i >= 0; i--) {
var child = allChildren[i];
// Check if this is a particle (has particle-like properties)
if (child && child.x !== undefined && child.y !== undefined && child.width <= 10 && child.height <= 10) {
// Remove particles that are far off-screen or destroyed
if (child.x < -500 || child.x > 2548 || child.y < -500 || child.y > 3232 || child.destroyed) {
if (child.destroy) {
child.destroy();
} else {
game.removeChild(child);
}
}
}
}
// Create continuous particle effects around the three objects
enhanceObjectVisuals();
// Check for music track changes every 60 seconds (1 minute) with fade out
if (musicStarted && LK.ticks % 60 === 0) {
// Check every second
var timeElapsed = (LK.ticks - musicStartTime) / 60; // Convert to seconds since music started
var musicChangeInterval = 60; // 60 seconds (1 minute) per music change
// Start fade out 2 seconds before music ends
if (timeElapsed >= musicChangeInterval - 2 && timeElapsed < musicChangeInterval && !musicFinished) {
// Fade out over 2 seconds
LK.playMusic(currentMusicTrack, {
fade: {
start: 1,
end: 0,
duration: 2000
}
});
}
// Check if current music should have finished
if (timeElapsed >= musicChangeInterval && !musicFinished) {
musicFinished = true;
waitingForNoteAfterMusicEnd = true;
LK.stopMusic(); // Stop current music and wait for note hit
}
}
// Win condition
if (score >= 10000) {
LK.showYouWin();
}
};
// Add 3 objects at bottom of target zone (left, center, right)
var targetBottomY = unifiedTargetZone.y + targetZoneHeight / 2 + 520; // Move objects down very slightly
var objectSpacing = targetZoneWidth / 1.0; // Further increased spacing for better separation between objects
// Left object
leftObject = LK.getAsset('leftObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
leftObject.x = unifiedTargetZone.x - objectSpacing;
leftObject.y = targetBottomY;
// Scale up the left object
leftObject.scaleX = 2.5;
leftObject.scaleY = 2.5;
// Animate color change for left object
tween(leftObject, {
tint: 0xFFD700 // Change to gold color
}, {
duration: 1500,
easing: tween.easeInOut
});
leftObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to left object
if (leftObject.filters) {
leftObject.filters = [];
} else {
leftObject.filters = [];
}
game.addChild(leftObject);
// Center object
centerObject = LK.getAsset('centerObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
centerObject.x = unifiedTargetZone.x;
centerObject.y = targetBottomY;
// Scale up the center object
centerObject.scaleX = 2.5;
centerObject.scaleY = 2.5;
// Apply orange tint to center object
tween(centerObject, {
tint: 0xFF6600 // Change to bright orange color
}, {
duration: 1500,
easing: tween.easeInOut
});
centerObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to center object
if (centerObject.filters) {
centerObject.filters = [];
} else {
centerObject.filters = [];
}
game.addChild(centerObject);
// Right object
rightObject = LK.getAsset('rightObjectAsset', {
anchorX: 0.5,
anchorY: 0.5
});
rightObject.x = unifiedTargetZone.x + objectSpacing;
rightObject.y = targetBottomY;
// Scale up the right object
rightObject.scaleX = 2.5;
rightObject.scaleY = 2.5;
// Animate color change for right object
tween(rightObject, {
tint: 0x9370DB // Change to medium purple color
}, {
duration: 1500,
easing: tween.easeInOut
});
rightObject.alpha = 1.0; // Increase alpha for sharper appearance
// Apply sharpness enhancement to right object
if (rightObject.filters) {
rightObject.filters = [];
} else {
rightObject.filters = [];
}
game.addChild(rightObject);
// Objects are now properly sized - no extreme scaling needed
// Move objects to topmost layer for better visibility
game.removeChild(leftObject);
game.addChild(leftObject);
game.removeChild(centerObject);
game.addChild(centerObject);
game.removeChild(rightObject);
game.addChild(rightObject);
// Add touch handlers to the 3 bottom objects
leftObject.down = function (x, y, obj) {
// Add guitar string vibration effect to left track strip line
if (leftStripLine) {
// Use stored original position instead of current position
var originalX = leftStripLine.originalX;
var _vibrateString = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(leftStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(leftStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString();
}
// Check for notes in left track (track 0) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 0 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
centerObject.down = function (x, y, obj) {
// Add guitar string vibration effect to center track strip line
if (centerStripLine) {
// Use stored original position instead of current position
var originalX = centerStripLine.originalX;
var _vibrateString2 = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(centerStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(centerStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString2();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString2();
}
// Check for notes in center track (track 1) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 1 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
rightObject.down = function (x, y, obj) {
// Add guitar string vibration effect to right track strip line
if (rightStripLine) {
// Use stored original position instead of current position
var originalX = rightStripLine.originalX;
var _vibrateString3 = function vibrateString() {
if (vibrationCount >= maxVibrations) {
// Reset to original position when vibration completes
tween(rightStripLine, {
x: originalX
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
var amplitude = baseAmplitude * (1 - vibrationCount / maxVibrations);
var direction = vibrationCount % 2 === 0 ? 1 : -1;
tween(rightStripLine, {
x: originalX + amplitude * direction
}, {
duration: 40,
easing: tween.easeInOut,
onFinish: function onFinish() {
vibrationCount++;
_vibrateString3();
}
});
};
// Guitar string effect - rapid horizontal vibrations that decay
var vibrationCount = 0;
var maxVibrations = 15;
var baseAmplitude = 25;
_vibrateString3();
}
// Check for notes in right track (track 2) within target zone
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
if (note.trackIndex === 2 && !note.hasBeenTapped) {
var timing = note.checkTiming();
if (timing !== 'miss') {
// Explode the note
note.hasBeenTapped = true;
var points = 0;
if (timing === 'perfect') {
points = 1;
LK.getSound('perfect').play();
createParticleEffect(note.x, note.y, note.isPowerNote ? 0xFFD93D : 0x00FF00);
} else if (timing === 'good') {
points = 1;
LK.getSound('tap').play();
createParticleEffect(note.x, note.y, 0x87CEEB);
}
updateScore(points);
// Start background music on first successful hit or after music ends
if (!musicStarted) {
currentMusicIndex = Math.floor(Math.random() * musicTracks.length);
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStarted = true;
musicStartTime = LK.ticks;
musicRepeatCount = 0;
} else if (waitingForNoteAfterMusicEnd) {
// Start next music track after previous one ended
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
currentMusicTrack = musicTracks[currentMusicIndex];
LK.playMusic(currentMusicTrack);
musicStartTime = LK.ticks;
musicFinished = false;
waitingForNoteAfterMusicEnd = false;
}
unifiedTargetZone.flash();
// Scale animation for explosion effect
tween(note, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
note.destroy();
for (var j = 0; j < notes.length; j++) {
if (notes[j] === note) {
notes.splice(j, 1);
break;
}
}
}
});
break;
}
}
}
};
// Music will start when first note is hit
var musicStarted = false;
var musicTracks = ['bgmusic', 'bgmusic2', 'bgmusic3', 'bgmusic4', 'bgmusic5'];
var currentMusicTrack = null;
var musicRepeatCount = 0;
var targetRepeatCount = 3;
var musicStartTime = 0;
var currentMusicIndex = 0;
var musicFinished = false;
var waitingForNoteAfterMusicEnd = false;
// Orbital particle effects removed - variables no longer needed
// Optimized visual effects function
function enhanceObjectVisuals() {
// Orbital particle effects removed
}
Gitar gövde kısmı
Do müzik notası
Mavi pastel renkli daire. In-Game asset. 2d. High contrast. No shadows
Kalp. In-Game asset. 2d. High contrast. No shadows
Gitar aksesuarı turuncu pastel renk. In-Game asset. 2d. High contrast. No shadows
Kırmızı pastel renk gitar çalma aksesuarı. In-Game asset. 2d. High contrast. No shadows