/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // FollowCircle: For "follow the path" notes (future extension, not used in MVP) var FollowCircle = Container.expand(function () { var self = Container.call(this); var circle = self.attachAsset('followCircle', { anchorX: 0.5, anchorY: 0.5 }); circle.alpha = 0.8; return self; }); // HitCircle: Tap or Hold circle var HitCircle = Container.expand(function () { var self = Container.call(this); // Attach approach circle (shrinks towards hit time) var approach = self.attachAsset('approachCircle', { anchorX: 0.5, anchorY: 0.5 }); approach.alpha = 0.5; // Generate a random color for this hit circle self.circleColor = Math.floor(Math.random() * 0xFFFFFF); // Attach main hit circle with random color var circle = self.attachAsset('hitCircle', { anchorX: 0.5, anchorY: 0.5, color: self.circleColor }); circle.alpha = 1; // Text for number or timing var label = new Text2('', { size: 80, fill: 0x222222, font: "Lilita One" }); label.anchor.set(0.5, 0.5); self.addChild(label); self.hitTime = 0; // ms self.type = 'tap'; // 'tap' | 'hold' self.holdDuration = 0; // ms, for hold notes self.hit = false; self.held = false; self.completed = false; self.index = 0; // for combo display self.setType = function (type, holdDuration) { self.type = type; if (type === 'hold') { self.holdDuration = holdDuration; } }; self.setLabel = function (txt) { label.setText(txt); }; self.setApproachScale = function (scale) { approach.scaleX = scale; approach.scaleY = scale; }; self.setCircleAlpha = function (a) { circle.alpha = a; }; self.flash = function (color, duration) { LK.effects.flashObject(circle, color, duration); }; self.hideApproach = function () { approach.visible = false; }; self.hideHoldRing = function () { // No-op: hold ring removed }; self.destroySelf = function () { self.destroy(); }; return self; }); // Particle: Simple expanding/fading circle for hit effect var Particle = Container.expand(function () { var self = Container.call(this); var circle = self.attachAsset('hitCircle', { anchorX: 0.5, anchorY: 0.5, color: 0xffffff }); circle.alpha = 0.7; self.life = 350; // ms self.elapsed = 0; self.startScale = 0.5; self.endScale = 1.1; self.startAlpha = 0.7; self.endAlpha = 0.0; self.update = function () { self.elapsed += 1000 / 60; var t = Math.min(1, self.elapsed / self.life); circle.scaleX = circle.scaleY = self.startScale + (self.endScale - self.startScale) * t; circle.alpha = self.startAlpha + (self.endAlpha - self.startAlpha) * t; if (self.elapsed >= self.life) { self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c24 }); /**** * Game Code ****/ // Background image: 2048x2732, id is a placeholder, replace with your actual image asset id if needed // Display new background image behind the current background image (centered, covers full play area) var bgImageBehind = LK.getAsset('bgImageBehind', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(bgImageBehind); var bgImage = LK.getAsset('bgImage', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(bgImage); // Music (placeholder, actual music asset will be loaded by LK) // Sounds (placeholders, actual sound assets will be loaded by LK) // Circles: main hit objects // --- Rhythm Map Data: Generate random hit circles for full song duration --- // Set songDuration to actual song length (LK asset 'song1' end - start in ms) var songDuration = 120000; // 2 minutes in ms var rhythmMap = []; var rpm = 80; var interval = 60000 / rpm; // ms per beat var numCircles = Math.floor(songDuration / interval); for (var i = 0; i < numCircles; i++) { // Random position, but keep inside safe play area (avoid edges) var margin = 250; var x = Math.round(margin + Math.random() * (2048 - 2 * margin)); var y = Math.round(600 + Math.random() * (2000 - 2 * margin)); rhythmMap.push({ time: Math.round(1200 + i * interval), x: x, y: y, type: 'tap' }); } // --- Game State --- var hitObjects = []; // All active hit circles var currentIndex = 0; // Next object to spawn var startTime = 0; // ms, when song started var playing = false; var score = 0; var combo = 0; var maxCombo = 0; var lastTapTime = 0; var lastTapObj = null; var holdActive = null; // Currently held hold note var holdStartTime = 0; var holdBroken = false; // var songDuration = 10000; // ms, for MVP (removed, now set to 2 min above) var approachTime = 900; // ms, time for approach circle to shrink var hitWindow = 320; // ms, allowed timing error for "perfect" (increased) var goodWindow = 500; // ms, allowed for "good" (increased) var missWindow = 700; // ms, after this it's a miss (increased) // --- UI Elements --- var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF, font: "Lilita One" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var comboBg = LK.getAsset('comboTextBg', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 300 }); comboBg.alpha = 0.7; game.addChild(comboBg); var comboTxt = new Text2('', { size: 90, fill: 0x00FF99, font: "Lilita One" }); comboTxt.anchor.set(0.5, 0.5); comboTxt.x = 1024; comboTxt.y = 300; game.addChild(comboTxt); // --- Time Bar UI --- // Bar background var timeBarBg = LK.getAsset('comboTextBg', { anchorX: 0, anchorY: 0.5, x: 120, y: 2732 - 100 }); timeBarBg.width = 1800; timeBarBg.height = 44; timeBarBg.alpha = 0.5; game.addChild(timeBarBg); // Foreground progress bar var timeBarFg = LK.getAsset('comboTextBg', { anchorX: 0, anchorY: 0.5, x: 120, y: 2732 - 100, color: 0x00bfff // blue (change this to your desired color) }); timeBarFg.width = 0; timeBarFg.height = 32; timeBarFg.alpha = 1; game.addChild(timeBarFg); // Countdown text for time bar var timeBarCountdown = new Text2('2:00', { size: 60, fill: 0xffffff, font: "Lilita One" }); timeBarCountdown.anchor.set(1, 0.5); timeBarCountdown.x = timeBarBg.x + timeBarBg.width - 10; timeBarCountdown.y = timeBarBg.y; game.addChild(timeBarCountdown); // --- Start Game --- var loadingBar = null; var loadingText = null; var loadingTween = null; function showLoadingBar(duration, onFinish) { // Remove previous if any if (loadingBar) loadingBar.destroy(); if (loadingText) loadingText.destroy(); // Bar background loadingBar = LK.getAsset('comboTextBg', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); loadingBar.width = 800; loadingBar.height = 120; loadingBar.alpha = 0.85; game.addChild(loadingBar); // Foreground bar (progress) var barFg = LK.getAsset('comboTextBg', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, color: 0x00ff99 }); barFg.width = 0; barFg.height = 80; barFg.alpha = 1; loadingBar.addChild(barFg); // Text loadingText = new Text2('Song coming...', { size: 80, fill: 0xffffff, font: "Lilita One" }); loadingText.anchor.set(0.5, 0.5); loadingText.x = 1024; loadingText.y = 1366; game.addChild(loadingText); // Animate bar var start = Date.now(); function updateBar() { var now = Date.now(); var elapsed = now - start; var frac = Math.min(1, elapsed / duration); barFg.width = 760 * frac; if (frac < 1) { loadingTween = LK.setTimeout(updateBar, 16); } else { if (loadingBar) loadingBar.destroy(); if (loadingText) loadingText.destroy(); loadingBar = null; loadingText = null; if (onFinish) onFinish(); } } updateBar(); } function startGame() { // Reset state for (var i = 0; i < hitObjects.length; i++) hitObjects[i].destroySelf(); hitObjects = []; currentIndex = 0; score = 0; combo = 0; maxCombo = 0; holdActive = null; holdBroken = false; scoreTxt.setText('0'); comboTxt.setText(''); comboBg.visible = false; playing = false; LK.setScore(0); // Show loading bar for 10s, then start hit circles showLoadingBar(10000, function () { startTime = Date.now(); playing = true; }); } startGame(); // --- Spawning Hit Circles --- function spawnHitObject(obj, idx) { var hc = new HitCircle(); hc.x = obj.x; hc.y = obj.y; hc.hitTime = obj.time; hc.index = idx + 1; // Set label to (idx mod 8) + 1, so it loops 1-8 var circleNum = idx % 8 + 1; hc.setLabel(circleNum + ''); // Define a palette of 8 distinct colors (looped) var palette = [0xff5555, // red 0xffc300, // yellow 0x4dd0e1, // cyan 0x81c784, // green 0xba68c8, // purple 0xff8a65, // orange 0x90caf9, // blue 0xf06292 // pink ]; var colorIdx = (circleNum - 1) % palette.length; var color = palette[colorIdx]; // Set the hit circle's color to match its number if (hc.children && hc.children.length > 1) { hc.children[1].tint = color; } hc.circleColor = color; if (obj.type === 'hold') { hc.setType('hold', obj.hold); } // Start approach circle at a larger scale for more dramatic appearance hc.setApproachScale(1.5); game.addChild(hc); hitObjects.push(hc); } // --- Scoring --- function addScore(val) { score += val; LK.setScore(score); scoreTxt.setText(score); } function setCombo(val) { combo = val; if (combo > maxCombo) maxCombo = combo; if (combo > 1) { comboTxt.setText(combo + 'x'); comboBg.visible = true; } else { comboTxt.setText(''); comboBg.visible = false; } } // --- Judgement Feedback --- function showJudgement(hc, type) { var txt = ''; var color = 0xffffff; if (type === 'perfect') { txt = 'Perfect!'; color = 0x00ff99; } else if (type === 'good') { txt = 'Good'; color = 0xffe066; } else if (type === 'miss') { txt = 'Miss'; color = 0xff3333; } var judge = new Text2(txt, { size: 90, fill: 0xFFFFFF, font: "Lilita One" }); judge.anchor.set(0.5, 0.5); judge.x = hc.x; judge.y = hc.y - 120; game.addChild(judge); tween(judge, { alpha: 0, y: hc.y - 220 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { judge.destroy(); } }); // Hit effect: flash the main circle (not the container) for more visual feedback if (hc.children && hc.children.length > 1) { LK.effects.flashObject(hc.children[1], color, 200); } else { LK.effects.flashObject(hc, color, 200); } // Flash all four screen edges for hit feedback var edges = [ // Top edge { x: 0, y: 0, anchorX: 0, anchorY: 0, width: 2048, height: 80 }, // Bottom edge { x: 0, y: 2732, anchorX: 0, anchorY: 1, width: 2048, height: 80 }, // Left edge { x: 0, y: 0, anchorX: 0, anchorY: 0, width: 80, height: 2732 }, // Right edge { x: 2048, y: 0, anchorX: 1, anchorY: 0, width: 80, height: 2732 }]; var edgeFlashColor = hc && hc.circleColor ? hc.circleColor : color; for (var i = 0; i < edges.length; i++) { var e = edges[i]; var flashRect = LK.getAsset('comboTextBg', { anchorX: e.anchorX, anchorY: e.anchorY, x: e.x, y: e.y }); flashRect.width = e.width; flashRect.height = e.height; flashRect.alpha = 0.7; flashRect.tint = edgeFlashColor; game.addChild(flashRect); tween(flashRect, { alpha: 0 }, { duration: 220, onFinish: function (rect) { return function () { rect.destroy(); }; }(flashRect) }); } // Particle effect at hit location var particle = new Particle(); particle.x = hc.x; particle.y = hc.y; if (hc.circleColor) { if (particle.children && particle.children.length > 0) { particle.children[0].tint = hc.circleColor; } } game.addChild(particle); // Extra: spawn a burst of small particles for more impact for (var i = 0; i < 6; i++) { var p = new Particle(); p.x = hc.x; p.y = hc.y; if (hc.circleColor && p.children && p.children.length > 0) { p.children[0].tint = hc.circleColor; } // Give each particle a random direction and speed var angle = Math.random() * Math.PI * 2; var speed = 8 + Math.random() * 10; p.vx = Math.cos(angle) * speed; p.vy = Math.sin(angle) * speed; // Override update to move outward and fade (function (p) { var baseUpdate = p.update; p.update = function () { p.x += p.vx; p.y += p.vy; // Slow down p.vx *= 0.92; p.vy *= 0.92; baseUpdate.call(p); }; })(p); game.addChild(p); } } // --- Input Handling --- game.down = function (x, y, obj) { if (!playing) return; var now = Date.now(); var found = false; for (var i = 0; i < hitObjects.length; i++) { var hc = hitObjects[i]; if (hc.hit) continue; // Only allow tap if within approach window var dt = now - startTime - hc.hitTime; if (Math.abs(dt) > missWindow + 200) continue; // Check if tap is inside circle var dx = x - hc.x; var dy = y - hc.y; var r = hc.width / 2; if (dx * dx + dy * dy < r * r) { found = true; if (hc.type === 'tap') { judgeTap(hc, dt); } else if (hc.type === 'hold') { // Start hold if (Math.abs(dt) <= hitWindow) { holdActive = hc; holdStartTime = now; holdBroken = false; hc.held = true; hc.hideApproach(); } else { judgeTap(hc, dt); // treat as tap if too early/late } } break; } } if (!found) { // Miss: tap not on any circle setCombo(0); } }; game.up = function (x, y, obj) { if (holdActive && holdActive.held && !holdActive.completed) { var now = Date.now(); var heldTime = now - holdStartTime; if (heldTime >= holdActive.holdDuration - 120) { // Success judgeHold(holdActive, heldTime); } else { // Released too early holdBroken = true; judgeHold(holdActive, heldTime); } holdActive.held = false; holdActive = null; } }; // --- Judgement Logic --- function judgeTap(hc, dt) { if (hc.hit) return; var absdt = Math.abs(dt); if (absdt <= hitWindow) { // Perfect addScore(300); setCombo(combo + 1); showJudgement(hc, 'perfect'); } else if (absdt <= goodWindow) { addScore(100); setCombo(combo + 1); showJudgement(hc, 'good'); } else if (absdt <= missWindow) { addScore(0); setCombo(0); showJudgement(hc, 'miss'); } else { return; // too early/late, ignore } hc.hit = true; hc.setCircleAlpha(0.3); hc.hideApproach(); tween(hc, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { hc.destroySelf(); } }); } function judgeHold(hc, heldTime) { if (hc.hit) return; if (holdBroken || heldTime < hc.holdDuration - 120) { // Miss addScore(0); setCombo(0); showJudgement(hc, 'miss'); } else { addScore(400); setCombo(combo + 1); showJudgement(hc, 'perfect'); } hc.hit = true; hc.completed = true; hc.setCircleAlpha(0.3); hc.hideHoldRing(); tween(hc, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { hc.destroySelf(); } }); } // --- Main Update Loop --- game.update = function () { if (!playing) return; var now = Date.now(); var songPos = now - startTime; // Spawn new hit objects while (currentIndex < rhythmMap.length && rhythmMap[currentIndex].time - approachTime <= songPos) { spawnHitObject(rhythmMap[currentIndex], currentIndex); currentIndex++; } // Update hit objects for (var i = hitObjects.length - 1; i >= 0; i--) { var hc = hitObjects[i]; if (hc.hit) continue; var dt = songPos - hc.hitTime; // Approach circle shrinks var approachScale = Math.max(0.2, 1.0 - (dt + approachTime) / approachTime); hc.setApproachScale(approachScale); // Missed? if (dt > missWindow && !hc.hit) { // Miss addScore(0); setCombo(0); showJudgement(hc, 'miss'); hc.hit = true; hc.setCircleAlpha(0.2); hc.hideApproach(); tween(hc, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { hc.destroySelf(); } }); } // For hold notes: if held, show progress if (hc.type === 'hold' && hc.held && !hc.completed) { var heldTime = now - holdStartTime; if (heldTime >= hc.holdDuration) { judgeHold(hc, heldTime); holdActive = null; } } } // Update time bar if (typeof timeBarFg !== "undefined" && typeof timeBarBg !== "undefined") { // Always set the left edge and width, so the bar grows from left to right var frac = Math.max(0, Math.min(1, songPos / songDuration)); timeBarFg.x = timeBarBg.x; // align left edge timeBarFg.y = timeBarBg.y; timeBarFg.width = timeBarBg.width * frac; timeBarFg.height = timeBarBg.height - 12; // Update countdown text if (typeof timeBarCountdown !== "undefined") { var remaining = Math.max(0, songDuration - songPos); var sec = Math.floor(remaining / 1000); var min = Math.floor(sec / 60); sec = sec % 60; var timeStr = min + ":" + (sec < 10 ? "0" : "") + sec; timeBarCountdown.setText(timeStr); } } // End of song if (songPos > songDuration + 1000) { playing = false; LK.showYouWin(); } }; // --- Music Start --- // (Music is started in startGame) // --- Game Over/Win Handling (handled by LK) --- // --- Touchscreen: No keyboard controls needed --- // --- Prevent elements in top left 100x100 px --- comboBg.x = 1024; comboBg.y = 300; comboTxt.x = 1024; comboTxt.y = 300; // --- (Optional) Restart on game over/win --- /* LK.on('gameover', function(){ startGame(); }); LK.on('youwin', function(){ startGame(); }); */
===================================================================
--- original.js
+++ change.js
@@ -115,10 +115,10 @@
/****
* Game Code
****/
-// Display new background image behind the current background image (centered, covers full play area)
// Background image: 2048x2732, id is a placeholder, replace with your actual image asset id if needed
+// Display new background image behind the current background image (centered, covers full play area)
var bgImageBehind = LK.getAsset('bgImageBehind', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
@@ -305,11 +305,8 @@
comboTxt.setText('');
comboBg.visible = false;
playing = false;
LK.setScore(0);
- LK.stopMusic();
- // Start music before loading bar, so music plays during the loading bar wait
- LK.playMusic('song1');
// Show loading bar for 10s, then start hit circles
showLoadingBar(10000, function () {
startTime = Date.now();
playing = true;
@@ -541,9 +538,8 @@
holdStartTime = now;
holdBroken = false;
hc.held = true;
hc.hideApproach();
- LK.getSound('hold').play();
} else {
judgeTap(hc, dt); // treat as tap if too early/late
}
}
@@ -552,9 +548,8 @@
}
if (!found) {
// Miss: tap not on any circle
setCombo(0);
- LK.getSound('miss').play();
}
};
game.up = function (x, y, obj) {
if (holdActive && holdActive.held && !holdActive.completed) {
@@ -580,19 +575,16 @@
// Perfect
addScore(300);
setCombo(combo + 1);
showJudgement(hc, 'perfect');
- LK.getSound('hit').play();
} else if (absdt <= goodWindow) {
addScore(100);
setCombo(combo + 1);
showJudgement(hc, 'good');
- LK.getSound('hit').play();
} else if (absdt <= missWindow) {
addScore(0);
setCombo(0);
showJudgement(hc, 'miss');
- LK.getSound('miss').play();
} else {
return; // too early/late, ignore
}
hc.hit = true;
@@ -613,14 +605,12 @@
// Miss
addScore(0);
setCombo(0);
showJudgement(hc, 'miss');
- LK.getSound('miss').play();
} else {
addScore(400);
setCombo(combo + 1);
showJudgement(hc, 'perfect');
- LK.getSound('hit').play();
}
hc.hit = true;
hc.completed = true;
hc.setCircleAlpha(0.3);
@@ -657,9 +647,8 @@
// Miss
addScore(0);
setCombo(0);
showJudgement(hc, 'miss');
- LK.getSound('miss').play();
hc.hit = true;
hc.setCircleAlpha(0.2);
hc.hideApproach();
tween(hc, {