Code edit (18 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Increase the size of the 'Combo' text slightly. Implement an animation that triggers every time the combo count hits multiples of 10, causing the text to smoothly scale up and then return to its original size ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add a combo counter that is always visible in the top-right corner of the screen.
Code edit (1 edits merged)
Please save this source code
User prompt
Fix comboDisplay HUD: ▪ Always create (or update) a single text object named comboDisplay ▪ Anchor: top‐right corner – set position to (screenWidth ‐ 140, 40) every frame ▪ Layer: bringToFront / zIndex = 100 so it stays above all HUD elements ▪ When comboCounter ≥ 2 → comboDisplay.visible = true, text = "Combo x" + comboCounter ▪ When comboCounter < 2 → comboDisplay.visible = false Color rule: ▪ Default color = #FFD700 (gold) ▪ If comboCounter % 10 == 0 AND comboCounter > 0: – Temporarily change comboDisplay color to #FF4444 (or any bright color) for 600 ms – Optionally scale text 1.2× then ease back to 1.0 over that period ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Bring 'comboDisplay' above 'scoreDisplay' in render order
User prompt
Update 'comboDisplay' positioning and behavior: - Always show 'comboDisplay' when comboCounter ≥ 2 - Set position to top-right corner: (screenWidth - 140, 40) - Set font size to 28px, bold, and color #FFD700 - Ensure it appears above all HUD elements (set high z-index or bringToFront) - If comboCounter < 2, hide the element
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Column = Container.expand(function () { var self = Container.call(this); var columnGraphics = self.attachAsset('column', { anchorX: 0.5, anchorY: 0, alpha: 0.3 }); var scoreZone = self.attachAsset('scoreZone', { anchorX: 0.5, anchorY: 0.5, y: 2732 - 200, alpha: 0.4 }); self.scoreZoneY = 2732 - 200; return self; }); var Tile = Container.expand(function () { var self = Container.call(this); var tileGraphics = self.attachAsset('tile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 6; self.columnIndex = 0; self.scored = false; self.missed = false; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ var columns = []; var tiles = []; var score = 0; var gameStarted = false; var musicDuration = 22000; // 22 seconds var gameStartTime = 0; var perfectZone = 50; var goodZone = 100; var pianoTones = ['noteA3', 'noteC4', 'noteE4', 'noteG4']; var columnDelays = [0, 50, 100, 150]; // Stagger effects by 50ms per column var lastBeatTime = 0; var beatCount = 0; var baseAnimationIntensity = 1.0; var comboCount = 0; var comboCounter = 0; // Create background overlay for rhythmic effects var backgroundOverlay = LK.getAsset('backgroundOverlay', { anchorX: 0.5, anchorY: 0.5, alpha: 0.05, x: 2048 / 2, y: 2732 / 2 }); game.addChild(backgroundOverlay); // Create back gray overlay for beat animation var backgrayoverlay = LK.getAsset('backgrayoverlay', { anchorX: 0.5, anchorY: 0.5, alpha: 0, x: 2048 / 2, y: 2732 / 2 }); game.addChild(backgrayoverlay); // Create pulse flash overlay at center, behind falling tiles var pulseFlashFull = LK.getAsset('pulseFlash1', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 2048 / 100, // Scale to screen width (2048px / 100px asset) scaleY: 2732 / 100, // Scale to screen height (2732px / 100px asset) x: 2048 / 2, y: 2732 / 2 }); game.addChild(pulseFlashFull); // Create UI var scoreTxt = new Text2('Score: 0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Create combo display var comboDisplay = new Text2('', { size: 28, fill: 0xFFD700, fontWeight: 'bold' }); comboDisplay.anchor.set(1, 0); comboDisplay.x = 2048 - 140; comboDisplay.y = 40; comboDisplay.visible = false; comboDisplay.zIndex = 100; LK.gui.addChild(comboDisplay); // Create columns var columnWidth = 2048 / 4; for (var i = 0; i < 4; i++) { var column = new Column(); column.x = i * columnWidth + columnWidth / 2; column.y = 0; columns.push(column); game.addChild(column); } // Multiple predefined melody patterns for variety var melodyPatterns = [[1, 1, 2, 2, 3, 3, 2, 2, 1], // Pattern A: C4, C4, E4, E4, G4, G4, E4, E4, C4 [3, 2, 1, 1, 0, 0, 1, 2], // Pattern B: G4, E4, C4, C4, A3, A3, C4, E4 [0, 2, 1, 3, 0, 2, 1, 3], // Pattern C: A3, E4, C4, G4, A3, E4, C4, G4 [3, 1, 3, 1, 2, 2, 0, 0] // Pattern D: G4, C4, G4, C4, E4, E4, A3, A3 ]; var currentPatternIndex = 0; var melodyPattern = melodyPatterns[currentPatternIndex]; var spawnPattern = []; var beatInterval = 500; // 500ms per beat for steady rhythm var patternRepeatDuration = melodyPattern.length * beatInterval; // 9 beats = 4500ms per pattern // Generate spawn pattern by repeating the melody pattern throughout the game duration var currentTime = 500; // Start after 500ms var patternPosition = 0; while (currentTime < musicDuration) { spawnPattern.push({ time: currentTime, columns: [melodyPattern[patternPosition]] }); currentTime += beatInterval; patternPosition = (patternPosition + 1) % melodyPattern.length; } var patternIndex = 0; function spawnTile(columnIndex) { var tile = new Tile(); tile.columnIndex = columnIndex; tile.x = columns[columnIndex].x; tile.y = -60; tiles.push(tile); game.addChild(tile); } function calculateScore(distance) { if (distance <= perfectZone) { return 100; } else if (distance <= goodZone) { return 50; } return 0; } function updateScore(points) { score += points; scoreTxt.setText('Score: ' + score); } function triggerBeatAnimation() { beatCount++; // Calculate animation intensity based on score milestones var scoreMultiplier = 1.0; if (score >= 5000) scoreMultiplier = 2.0;else if (score >= 2500) scoreMultiplier = 1.7;else if (score >= 1000) scoreMultiplier = 1.4;else if (score >= 500) scoreMultiplier = 1.2; var animationIntensity = baseAnimationIntensity * scoreMultiplier; // Background pulse - scale and brightness var pulseScale = 1.0 + 0.02 * animationIntensity; // 2% scale increase max var pulseAlpha = 0.05 + 0.01 * animationIntensity; // 1% brightness increase max tween(backgroundOverlay, { scaleX: pulseScale, scaleY: pulseScale, alpha: pulseAlpha }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(backgroundOverlay, { scaleX: 1.0, scaleY: 1.0, alpha: 0.05 }, { duration: 200, easing: tween.easeIn }); } }); // Pulse flash animation - fade in then out on every beat tween(pulseFlashFull, { alpha: 0.35 }, { duration: 0, onFinish: function onFinish() { tween(pulseFlashFull, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } }); // Subtle rotation effect that increases with score var rotationAmount = beatCount % 8 * 0.01 * animationIntensity; // Very subtle rotation tween(backgroundOverlay, { rotation: rotationAmount }, { duration: 300, easing: tween.easeInOut }); // Back gray overlay beat animation - fade in to 0.35 then out over 300ms tween(backgrayoverlay, { alpha: 0.35 }, { duration: 0, onFinish: function onFinish() { tween(backgrayoverlay, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } }); } game.down = function (x, y, obj) { if (!gameStarted) { gameStarted = true; gameStartTime = LK.ticks * (1000 / 60); LK.playMusic('gamesound3_backing', { loop: true }); return; } // Determine which column was tapped var columnIndex = Math.floor(x / columnWidth); if (columnIndex < 0) columnIndex = 0; if (columnIndex > 3) columnIndex = 3; var hitTile = null; var bestDistance = Infinity; // Find the closest tile in the tapped column within scoring range for (var i = 0; i < tiles.length; i++) { var tile = tiles[i]; if (tile.columnIndex === columnIndex && !tile.scored && !tile.missed) { var scoreZoneY = columns[columnIndex].scoreZoneY; var distance = Math.abs(tile.y - scoreZoneY); if (distance <= goodZone && distance < bestDistance) { bestDistance = distance; hitTile = tile; } } } if (hitTile) { hitTile.scored = true; var points = calculateScore(bestDistance); updateScore(points); // Increment combo count comboCount++; comboCounter++; // Update combo display comboDisplay.x = 2048 - 140; comboDisplay.y = 40; if (comboCounter < 2) { comboDisplay.visible = false; } else { comboDisplay.visible = true; comboDisplay.setText('Combo x' + comboCounter); // Check for color change and scaling on multiples of 10 if (comboCounter % 10 === 0 && comboCounter > 0) { // Change color to bright red temporarily comboDisplay.tint = 0xFF4444; // Scale animation tween(comboDisplay, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(comboDisplay, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeIn }); } }); // Reset color after 600ms LK.setTimeout(function () { comboDisplay.tint = 0xFFD700; }, 600); } } // Check for 10-hit combo streak if (comboCount % 10 === 0) { // Spawn 'comboGlow' behind tiles with opacity 0.5 and fade it out over 400ms var comboGlow = LK.getAsset('comboGlow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(comboGlow); // Fade out comboGlow over 400ms tween(comboGlow, { alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { comboGlow.destroy(); } }); // Spawn 'comboText' at (screen center X, 100px from top) var comboText = new Text2('Combo x' + comboCount + '!', { size: 80, fill: 0xFFD700 }); comboText.anchor.set(0.5, 0.5); comboText.x = 2048 / 2; comboText.y = 100; // 100px from top comboText.alpha = 0; comboText.scaleX = 0.8; comboText.scaleY = 0.8; game.addChild(comboText); // Fade in over 150ms with scaling popping effect tween(comboText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Hold for 400ms, then fade out over 300ms LK.setTimeout(function () { tween(comboText, { alpha: 0, scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { comboText.destroy(); } }); }, 400); } }); } // Create color burst effect with bright, saturated colors for each column var burstColors = [0x9932CC, 0x0000FF, 0xFFFF00, 0xFF0000]; // Purple, Blue, Yellow, Red var burstColor = burstColors[columnIndex]; var columnDelay = columnDelays[columnIndex]; // Create column light-up effect LK.setTimeout(function () { var columnOverlay = LK.getAsset('columnOverlay', { anchorX: 0.5, anchorY: 0, tint: burstColor, alpha: 0.4, x: columns[columnIndex].x, y: 0 }); game.addChild(columnOverlay); // Animate the column overlay with pulse effect tween(columnOverlay, { alpha: 0 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { columnOverlay.destroy(); } }); }, columnDelay); // Spawn 'burst1' at the tile's position - limited to 1.5x tile size with vivid colors LK.setTimeout(function () { var burst1 = LK.getAsset('brust1', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6, tint: burstColor, alpha: 0.9, x: hitTile.x, y: hitTile.y }); game.addChild(burst1); tween(burst1, { scaleX: 1.8, // Limited to 1.5x tile size (400px * 1.5 = 600px, so scale ~1.8 for 100px asset) scaleY: 1.8, alpha: 0 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { burst1.destroy(); } }); }, columnDelay); // Overlay 'glowring1' with matching column color and radial gradient effect LK.setTimeout(function () { var glowRing = LK.getAsset('glowring1', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: burstColor, alpha: 0.8, x: hitTile.x, y: hitTile.y }); game.addChild(glowRing); tween(glowRing, { scaleX: 1.8, // Limited to 1.5x tile size scaleY: 1.8, alpha: 0 }, { duration: 380, easing: tween.easeInOut, onFinish: function onFinish() { glowRing.destroy(); } }); }, columnDelay + 25); // Create spark line animation near the tile LK.setTimeout(function () { for (var s = 0; s < 4; s++) { var sparkLine = LK.getAsset('sparkline', { anchorX: 0.5, anchorY: 0.5, tint: burstColor, alpha: 0.9, x: hitTile.x + (Math.random() - 0.5) * 100, y: hitTile.y + (Math.random() - 0.5) * 100, rotation: Math.random() * Math.PI * 2 }); game.addChild(sparkLine); tween(sparkLine, { scaleX: 2, scaleY: 0.2, alpha: 0, rotation: sparkLine.rotation + Math.PI }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { sparkLine.destroy(); } }); } }, columnDelay + 50); // Emit 5-6 small 'trail1' particles radiating outward with small movement radius LK.setTimeout(function () { var numTrails = 5 + Math.floor(Math.random() * 2); // 5-6 particles for (var t = 0; t < numTrails; t++) { var trail = LK.getAsset('trail1', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: burstColor, alpha: 0.8, x: hitTile.x, y: hitTile.y }); game.addChild(trail); var angle = t / numTrails * Math.PI * 2 + Math.random() * 0.3; var distance = 40 + Math.random() * 40; // Small movement radius within 80px var targetX = hitTile.x + Math.cos(angle) * distance; var targetY = hitTile.y + Math.sin(angle) * distance; tween(trail, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { trail.destroy(); } }); } }, columnDelay + 75); // Spawn columnFlash overlay at tile position with upward then downward animation var columnFlashAssets = ['columnFlash1', 'columnFlash2', 'columnFlash3', 'columnFlash4']; var originalY = hitTile.y; var columnFlash = LK.getAsset(columnFlashAssets[columnIndex], { anchorX: 0.5, anchorY: 0.5, alpha: 1.0, x: hitTile.x, y: originalY }); game.addChild(columnFlash); // Animate upward movement over 150ms with ease-out tween(columnFlash, { y: originalY - 100 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Animate downward movement back to original position over 150ms with ease-in tween(columnFlash, { y: originalY }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { columnFlash.destroy(); } }); } }); // Fade out overlay over full 300ms duration tween(columnFlash, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); // Visual feedback for scoring if (points === 100) { LK.effects.flashObject(hitTile, 0x00ff00, 200); } else if (points === 50) { LK.effects.flashObject(hitTile, 0xffff00, 200); } LK.getSound(pianoTones[columnIndex]).play(); // Remove tile hitTile.destroy(); for (var j = tiles.length - 1; j >= 0; j--) { if (tiles[j] === hitTile) { tiles.splice(j, 1); break; } } } }; game.update = function () { if (!gameStarted) { return; } var currentTime = LK.ticks * (1000 / 60) - gameStartTime; // Beat detection for background animation var currentBeatTime = Math.floor(currentTime / beatInterval) * beatInterval; if (currentBeatTime !== lastBeatTime && currentTime >= 500) { lastBeatTime = currentBeatTime; triggerBeatAnimation(); } // Spawn tiles according to endless repeating pattern with pattern cycling and controlled randomness var patternTime = currentTime % patternRepeatDuration; // Loop the pattern timing var shouldSpawn = false; // Check if we need to switch to the next pattern var patternCycleTime = Math.floor(currentTime / patternRepeatDuration); var newPatternIndex = patternCycleTime % melodyPatterns.length; if (newPatternIndex !== currentPatternIndex) { currentPatternIndex = newPatternIndex; melodyPattern = melodyPatterns[currentPatternIndex]; } for (var p = 0; p < melodyPattern.length; p++) { var spawnTime = p * beatInterval + 500; // Add initial 500ms offset if (Math.abs(patternTime - spawnTime) < 16) { // 16ms tolerance for 60fps var targetColumn = melodyPattern[p]; var finalColumn = targetColumn; // Add controlled randomness - 20% chance to vary the column if (Math.random() < 0.2) { // Define harmonic variations for each base column var harmonicVariations = [[0, 1], // A3 can vary to C4 [1, 2], // C4 can vary to E4 [2, 3, 1], // E4 can vary to G4 or back to C4 [3, 2] // G4 can vary to E4 ]; var variations = harmonicVariations[targetColumn]; if (variations && variations.length > 0) { finalColumn = variations[Math.floor(Math.random() * variations.length)]; } } spawnTile(finalColumn); shouldSpawn = true; break; } } // Update tiles and check for misses for (var i = tiles.length - 1; i >= 0; i--) { var tile = tiles[i]; // Check if tile passed the scoring zone without being hit if (!tile.scored && !tile.missed && tile.y > columns[tile.columnIndex].scoreZoneY + goodZone) { tile.missed = true; comboCount = 0; // Reset combo on miss comboCounter = 0; // Reset combo counter on miss // Update combo display visibility and reset properties comboDisplay.visible = false; comboDisplay.tint = 0xFFD700; comboDisplay.scaleX = 1.0; comboDisplay.scaleY = 1.0; LK.effects.flashObject(tile, 0xff0000, 300); LK.getSound('miss').play(); } // Remove tiles that are off screen if (tile.y > 2732 + 100) { tile.destroy(); tiles.splice(i, 1); } } };
===================================================================
--- original.js
+++ change.js
@@ -110,8 +110,9 @@
comboDisplay.anchor.set(1, 0);
comboDisplay.x = 2048 - 140;
comboDisplay.y = 40;
comboDisplay.visible = false;
+comboDisplay.zIndex = 100;
LK.gui.addChild(comboDisplay);
// Create columns
var columnWidth = 2048 / 4;
for (var i = 0; i < 4; i++) {
@@ -265,13 +266,41 @@
// Increment combo count
comboCount++;
comboCounter++;
// Update combo display
+ comboDisplay.x = 2048 - 140;
+ comboDisplay.y = 40;
if (comboCounter < 2) {
comboDisplay.visible = false;
} else {
comboDisplay.visible = true;
comboDisplay.setText('Combo x' + comboCounter);
+ // Check for color change and scaling on multiples of 10
+ if (comboCounter % 10 === 0 && comboCounter > 0) {
+ // Change color to bright red temporarily
+ comboDisplay.tint = 0xFF4444;
+ // Scale animation
+ tween(comboDisplay, {
+ scaleX: 1.2,
+ scaleY: 1.2
+ }, {
+ duration: 300,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ tween(comboDisplay, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 300,
+ easing: tween.easeIn
+ });
+ }
+ });
+ // Reset color after 600ms
+ LK.setTimeout(function () {
+ comboDisplay.tint = 0xFFD700;
+ }, 600);
+ }
}
// Check for 10-hit combo streak
if (comboCount % 10 === 0) {
// Spawn 'comboGlow' behind tiles with opacity 0.5 and fade it out over 400ms
@@ -579,10 +608,13 @@
if (!tile.scored && !tile.missed && tile.y > columns[tile.columnIndex].scoreZoneY + goodZone) {
tile.missed = true;
comboCount = 0; // Reset combo on miss
comboCounter = 0; // Reset combo counter on miss
- // Update combo display visibility
+ // Update combo display visibility and reset properties
comboDisplay.visible = false;
+ comboDisplay.tint = 0xFFD700;
+ comboDisplay.scaleX = 1.0;
+ comboDisplay.scaleY = 1.0;
LK.effects.flashObject(tile, 0xff0000, 300);
LK.getSound('miss').play();
}
// Remove tiles that are off screen