User prompt
show life with hearts start with 5 life and each time remove one image
User prompt
remove last change
User prompt
instead of showing life with text show with music note shape images vertical order
User prompt
make win state 50 score
User prompt
add a tutorial to start say and show left tilt and right tilt
User prompt
size up particle effect 3x
User prompt
add particle effect to center when i hit note โช๐ก Consider importing and using the following plugins: @upit/tween.v1
User prompt
add color to that ui
User prompt
add a nice ui to score and combo
User prompt
add 5 more bgmusic and each level start choose random
User prompt
add 5 more music not image for each side and choose random image each time
Code edit (1 edits merged)
Please save this source code
User prompt
remove rightpathline convert left to center
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'rightPathLine is not defined' in or related to this line: 'rightPathLine.rotation = Math.PI; // horizontal, flipped' Line Number: 170
Code edit (1 edits merged)
Please save this source code
User prompt
add a reference slot to note lines
User prompt
add asset reference to that line
User prompt
add a transparent line for notes way
User prompt
when miss note stop music when hit resume it
User prompt
remove last change
User prompt
if miss rewind the note and play again
User prompt
Please fix the bug: 'TypeError: LK.setMusicVolume is not a function' in or related to this line: 'LK.setMusicVolume(0.1, 100);' Line Number: 340
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ // Note class var Note = Container.expand(function () { var self = Container.call(this); self.direction = 'up'; // will be set after creation self.speed = 12; // pixels per frame, reduced for easier gameplay self.hit = false; self.active = true; // Attach asset self.noteAsset = null; // will be set after direction is set // For hit animation self.flashTween = null; // Called every tick self.update = function () { if (!self.active) { return; } var vec = getNoteTravelVec(self.direction); self.x += vec.x * self.speed; self.y += vec.y * self.speed; }; // Animate on hit self.animateHit = function () { self.active = false; if (self.flashTween) { tween.stop(self, { alpha: true, scaleX: true, scaleY: true }); } tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { self.destroy(); } }); }; // Animate on miss self.animateMiss = function () { self.active = false; if (self.flashTween) { tween.stop(self, { alpha: true, scaleX: true, scaleY: true }); } tween(self, { alpha: 0.2 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Particle effect for center hit var ParticleEffect = Container.expand(function () { var self = Container.call(this); self.particles = []; self.duration = 350; self.numParticles = 18; self.radius = 240; // 3x original (was 80) self.createParticles = function (x, y) { for (var i = 0; i < self.numParticles; i++) { var angle = 2 * Math.PI * i / self.numParticles; var px = x + Math.cos(angle) * 30; // 3x original (was 10) var py = y + Math.sin(angle) * 30; // 3x original (was 10) var color = i % 2 === 0 ? 0xfff176 : 0xff8a65; var part = LK.getAsset('centerTarget', { anchorX: 0.5, anchorY: 0.5, x: px, y: py, scaleX: (0.18 + Math.random() * 0.12) * 3, // 3x original scaleY: (0.18 + Math.random() * 0.12) * 3, // 3x original alpha: 0.85, tint: color }); self.addChild(part); // Animate outward and fade var tx = x + Math.cos(angle) * (self.radius + Math.random() * 90); // 3x original (was 30) var ty = y + Math.sin(angle) * (self.radius + Math.random() * 90); // 3x original (was 30) tween(part, { x: tx, y: ty, alpha: 0, scaleX: part.scaleX * 1.5, scaleY: part.scaleY * 1.5 }, { duration: self.duration, easing: tween.cubicOut, onFinish: function (p) { return function () { if (p.parent) p.parent.removeChild(p); }; }(part) }); self.particles.push(part); } // Remove effect container after animation tween(self, {}, { duration: self.duration + 10, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c20 }); /**** * Game Code ****/ // Heart image for life display // Center of play area // Four note shapes for each direction // Sounds for hit and miss // Music (placeholder, actual music asset id will be auto-loaded) // Note directions // Left note images (6 total) // Right note images (6 total) var NOTE_DIRECTIONS = ['left', 'right']; var NOTE_ASSET = { left: ['noteLeft', 'noteLeft2', 'noteLeft3', 'noteLeft4', 'noteLeft5', 'noteLeft6'], right: ['noteRight', 'noteRight2', 'noteRight3', 'noteRight4', 'noteRight5', 'noteRight6'] }; // Note spawn positions (offscreen, moving toward center) function getNoteSpawnPos(direction) { var centerX = 2048 / 2, centerY = 2732 / 2; var offset = 900; // How far from center to spawn if (direction === 'left') { return { x: centerX - offset, y: centerY }; } if (direction === 'right') { return { x: centerX + offset, y: centerY }; } return { x: centerX, y: centerY }; } // Note travel vector (unit vector toward center) function getNoteTravelVec(direction) { if (direction === 'left') { return { x: 1, y: 0 }; } if (direction === 'right') { return { x: -1, y: 0 }; } return { x: 0, y: 0 }; } var centerX = 2048 / 2, centerY = 2732 / 2; // Add transparent center note path line (asset: centerLine) var pathLineAlpha = 0.25; var pathLineWidth = 300; var pathLineLength = 2000; var pathLineColor = 0xffffff; // Center path line (asset: centerLine) var centerPathLine = LK.getAsset('centerLine', { anchorX: 0.5, anchorY: 0.5, width: pathLineLength, height: pathLineWidth, x: centerX, y: centerY, alpha: pathLineAlpha, reference: 'centerPathLine' // asset reference slot }); centerPathLine.rotation = 0; // horizontal game.addChild(centerPathLine); // Add center target var centerTarget = LK.getAsset('centerTarget', { anchorX: 0.5, anchorY: 0.5, x: centerX, y: centerY, scaleX: 1, scaleY: 1 }); game.addChild(centerTarget); // Score display var score = 0; var scoreTxt = new Text2('0', { size: 170, fill: { type: 'linear', colors: ['#ffecb3', '#ffd740', '#ff6f00'], stops: [0, 0.5, 1] }, stroke: '#ffb300', strokeThickness: 12, dropShadow: true, dropShadowColor: '#ff6f00', dropShadowDistance: 6, dropShadowAngle: Math.PI / 2, dropShadowAlpha: 0.7, fontWeight: 'bold' }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Combo display var combo = 0; var comboTxt = new Text2('', { size: 110, fill: { type: 'linear', colors: ['#b2ff59', '#00e676', '#00bfae'], stops: [0, 0.5, 1] }, stroke: '#00bfae', strokeThickness: 10, dropShadow: true, dropShadowColor: '#00bfae', dropShadowDistance: 5, dropShadowAngle: Math.PI / 2, dropShadowAlpha: 0.6, fontWeight: 'bold' }); comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(comboTxt); comboTxt.y = 150; // Remaining life display (top right) - as heart images var maxHearts = 5; var heartImages = []; var heartSpacing = 18; var heartStartY = 30; var heartStartX = LK.gui.topRight.width - 30; var maxMisses = maxHearts; // always 5 function updateLifeNotes() { // Remove all old hearts for (var i = 0; i < heartImages.length; i++) { if (heartImages[i].parent) { heartImages[i].parent.removeChild(heartImages[i]); } } heartImages = []; // Add hearts for remaining life var remaining = maxHearts - misses; for (var i = 0; i < remaining; i++) { var heart = LK.getAsset('heart', { anchorX: 1, anchorY: 0, x: heartStartX, y: heartStartY + i * (120 + heartSpacing), scaleX: 1, scaleY: 1, alpha: 1 }); LK.gui.topRight.addChild(heart); heartImages.push(heart); } } updateLifeNotes(); // Miss feedback var missTxt = new Text2('', { size: 100, fill: '#e57373' }); missTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(missTxt); // Notes array var notes = []; // Timing var noteInterval = 38; // frames between notes (about 1.5 notes/sec at 60fps) var noteTimer = 0; // Game state var isGameOver = false; var isYouWin = false; var maxMisses = 5; var misses = 0; var targetScore = 50; // Tutorial state var isTutorial = true; var tutorialStep = 0; // 0: show left, 1: wait left, 2: show right, 3: wait right, 4: done var tutorialTxt = new Text2('', { size: 120, fill: { type: 'linear', colors: ['#fffde7', '#ffd54f', '#ff7043'], stops: [0, 0.5, 1] }, stroke: '#ffb300', strokeThickness: 10, dropShadow: true, dropShadowColor: '#ff6f00', dropShadowDistance: 6, dropShadowAngle: Math.PI / 2, dropShadowAlpha: 0.7, fontWeight: 'bold' }); tutorialTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(tutorialTxt); tutorialTxt.x = LK.gui.center.width / 2; tutorialTxt.y = LK.gui.center.height / 2 - 200; tutorialTxt.visible = true; // Show left arrow image for tutorial var tutLeftImg = LK.getAsset('noteLeft', { anchorX: 0.5, anchorY: 0.5, x: LK.gui.center.width / 2 - 300, y: LK.gui.center.height / 2 - 200, scaleX: 1.2, scaleY: 1.2, alpha: 0.9 }); tutLeftImg.rotation = -Math.PI / 2; LK.gui.center.addChild(tutLeftImg); tutLeftImg.visible = false; // Show right arrow image for tutorial var tutRightImg = LK.getAsset('noteRight', { anchorX: 0.5, anchorY: 0.5, x: LK.gui.center.width / 2 + 300, y: LK.gui.center.height / 2 - 200, scaleX: 1.2, scaleY: 1.2, alpha: 0.9 }); tutRightImg.rotation = Math.PI / 2; LK.gui.center.addChild(tutRightImg); tutRightImg.visible = false; // Face direction detection function getFaceDirection() { // Use noseTip and chin to estimate tilt // If facekit is not ready, return null if (!facekit.noseTip || !facekit.chin || !facekit.leftEye || !facekit.rightEye) { return null; } var dx = facekit.rightEye.x - facekit.leftEye.x; var dy = facekit.rightEye.y - facekit.leftEye.y; var angle = Math.atan2(dy, dx) * 180 / Math.PI; // horizontal head tilt // Left/right: compare noseTip.x to center var horizontal = facekit.noseTip.x - centerX; // Heuristics: prioritize strong tilts if (horizontal < -80) { return 'left'; } if (horizontal > 80) { return 'right'; } // If head is turned, use angle if (angle < -25) { return 'right'; } if (angle > 25) { return 'left'; } return null; } // Show combo function showCombo() { if (combo > 1) { comboTxt.setText(combo + 'x Combo!'); // Animate combo pop comboTxt.scale.set(1.3, 1.3); tween(comboTxt, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.cubicOut }); } else { comboTxt.setText(''); } } // Show miss function showMiss() { missTxt.setText('Miss!'); tween(missTxt, { alpha: 0 }, { duration: 600, onFinish: function onFinish() { missTxt.setText(''); missTxt.alpha = 1; } }); } // Spawn a note function spawnNote() { var dir = NOTE_DIRECTIONS[Math.floor(Math.random() * NOTE_DIRECTIONS.length)]; var note = new Note(); note.direction = dir; var spawn = getNoteSpawnPos(dir); note.x = spawn.x; note.y = spawn.y; // Randomly select a note image for this direction var assetList = NOTE_ASSET[dir]; var assetId = assetList[Math.floor(Math.random() * assetList.length)]; note.noteAsset = note.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Rotate asset to point toward center if (dir === 'left') { note.noteAsset.rotation = -Math.PI / 2; } if (dir === 'right') { note.noteAsset.rotation = Math.PI / 2; } notes.push(note); game.addChild(note); } // Check if note is in hit window (distance to center) function isNoteHittable(note) { var dx = note.x - centerX; var dy = note.y - centerY; var dist = Math.sqrt(dx * dx + dy * dy); return dist < 180; // hit window radius (increased for easier hits) } // Check if note is missed (passed center) function isNoteMissed(note) { var dx = note.x - centerX; var dy = note.y - centerY; var dist = Math.sqrt(dx * dx + dy * dy); return dist < 20; // too close, missed (smaller, so player has more time to hit) } // Main update loop game.update = function () { if (isGameOver || isYouWin) { return; } // Tutorial logic if (isTutorial) { var faceDir = getFaceDirection(); // Step 0: Show "Tilt your face LEFT" and left arrow if (tutorialStep === 0) { tutorialTxt.setText('Tilt your face LEFT'); tutLeftImg.visible = true; tutRightImg.visible = false; // Wait for left tilt if (faceDir === 'left') { tutorialStep = 1; // Animate arrow and text tween(tutLeftImg, { scaleX: 1.7, scaleY: 1.7, alpha: 0.2 }, { duration: 350, onFinish: function onFinish() { tutLeftImg.visible = false; tutLeftImg.scaleX = 1.2; tutLeftImg.scaleY = 1.2; tutLeftImg.alpha = 0.9; } }); tween(tutorialTxt, { scaleX: 1.2, scaleY: 1.2 }, { duration: 180, yoyo: true, repeat: 1, onFinish: function onFinish() { tutorialTxt.scaleX = 1; tutorialTxt.scaleY = 1; } }); // Short delay before next step LK.setTimeout(function () { tutorialStep = 2; }, 400); } return; } // Step 2: Show "Tilt your face RIGHT" and right arrow if (tutorialStep === 2) { tutorialTxt.setText('Tilt your face RIGHT'); tutLeftImg.visible = false; tutRightImg.visible = true; if (faceDir === 'right') { tutorialStep = 3; tween(tutRightImg, { scaleX: 1.7, scaleY: 1.7, alpha: 0.2 }, { duration: 350, onFinish: function onFinish() { tutRightImg.visible = false; tutRightImg.scaleX = 1.2; tutRightImg.scaleY = 1.2; tutRightImg.alpha = 0.9; } }); tween(tutorialTxt, { scaleX: 1.2, scaleY: 1.2 }, { duration: 180, yoyo: true, repeat: 1, onFinish: function onFinish() { tutorialTxt.scaleX = 1; tutorialTxt.scaleY = 1; } }); LK.setTimeout(function () { tutorialStep = 4; }, 400); } return; } // Step 4: Done, hide tutorial and start game if (tutorialStep === 4) { tutorialTxt.setText('Great! Get ready...'); tutLeftImg.visible = false; tutRightImg.visible = false; LK.setTimeout(function () { tutorialTxt.visible = false; isTutorial = false; }, 700); tutorialStep = 5; return; } // During tutorial, do not run game logic return; } // Spawn notes noteTimer++; if (noteTimer >= noteInterval) { spawnNote(); noteTimer = 0; } // Get current face direction var faceDir = getFaceDirection(); // For each note: move, check for hit/miss for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; note.update(); if (!note.active) { notes.splice(i, 1); continue; } // If note is in hit window and faceDir matches, hit! if (!note.hit && isNoteHittable(note) && faceDir === note.direction) { note.hit = true; note.animateHit(); // Particle effect at center var effect = new ParticleEffect(); effect.createParticles(centerX, centerY); game.addChild(effect); // On hit, resume music LK.playMusic(currentMusicId); score++; combo++; showCombo(); scoreTxt.setText(score); if (score >= targetScore) { isYouWin = true; LK.showYouWin(); return; } continue; } // If note passes center and not hit, miss // Only trigger miss the moment the note crosses the miss threshold (not repeatedly) if (!note.hit && note.lastMissed !== true && isNoteMissed(note)) { note.hit = false; note.lastMissed = true; note.animateMiss(); // On miss, stop music LK.stopMusic(); misses++; combo = 0; showCombo(); showMiss(); updateLifeNotes(); if (misses >= maxMisses) { isGameOver = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } continue; } else if (!note.hit && note.lastMissed !== true) { // Track that note is not yet missed note.lastMissed = false; } } }; // Music pool var BGMUSIC_IDS = ['bgmusic', 'bgmusic2', 'bgmusic3', 'bgmusic4', 'bgmusic5', 'bgmusic6']; var currentMusicId = BGMUSIC_IDS[Math.floor(Math.random() * BGMUSIC_IDS.length)]; LK.playMusic(currentMusicId, { fade: { start: 0.1, end: 1, duration: 100 } }); // Reset state on game restart game.on('reset', function () { score = 0; combo = 0; misses = 0; isGameOver = false; isYouWin = false; notes = []; scoreTxt.setText('0'); comboTxt.setText(''); missTxt.setText(''); noteTimer = 0; updateLifeNotes(); // Reset tutorial state isTutorial = true; tutorialStep = 0; tutorialTxt.visible = true; tutorialTxt.setText(''); tutLeftImg.visible = false; tutRightImg.visible = false; // Pick a new random music for the next level currentMusicId = BGMUSIC_IDS[Math.floor(Math.random() * BGMUSIC_IDS.length)]; LK.playMusic(currentMusicId, { fade: { start: 0.1, end: 1, duration: 100 } }); }); // No touch controls needed; game is face-only // Make sure all elements are visible and not in top left 100x100 centerTarget.x = centerX; centerTarget.y = centerY; scoreTxt.x = LK.gui.top.width / 2; scoreTxt.y = 30; comboTxt.x = LK.gui.top.width / 2; comboTxt.y = scoreTxt.y + scoreTxt.height + 10; missTxt.x = LK.gui.center.width / 2; missTxt.y = LK.gui.center.height / 2; ;
===================================================================
--- original.js
+++ change.js
@@ -137,15 +137,16 @@
/****
* Game Code
****/
-// Right note images (6 total)
-// Left note images (6 total)
-// Note directions
-// Music (placeholder, actual music asset id will be auto-loaded)
-// Sounds for hit and miss
-// Four note shapes for each direction
+// Heart image for life display
// Center of play area
+// Four note shapes for each direction
+// Sounds for hit and miss
+// Music (placeholder, actual music asset id will be auto-loaded)
+// Note directions
+// Left note images (6 total)
+// Right note images (6 total)
var NOTE_DIRECTIONS = ['left', 'right'];
var NOTE_ASSET = {
left: ['noteLeft', 'noteLeft2', 'noteLeft3', 'noteLeft4', 'noteLeft5', 'noteLeft6'],
right: ['noteRight', 'noteRight2', 'noteRight3', 'noteRight4', 'noteRight5', 'noteRight6']
@@ -261,25 +262,38 @@
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.y = 150;
-// Remaining life display (top right) - as text
-var lifeTxt = new Text2('', {
- size: 110,
- fill: '#fffde7',
- stroke: '#ffb300',
- strokeThickness: 8,
- dropShadow: true,
- dropShadowColor: '#ff6f00',
- dropShadowDistance: 4,
- dropShadowAngle: Math.PI / 2,
- dropShadowAlpha: 0.7,
- fontWeight: 'bold'
-});
-lifeTxt.anchor.set(1, 0);
-LK.gui.topRight.addChild(lifeTxt);
+// Remaining life display (top right) - as heart images
+var maxHearts = 5;
+var heartImages = [];
+var heartSpacing = 18;
+var heartStartY = 30;
+var heartStartX = LK.gui.topRight.width - 30;
+var maxMisses = maxHearts; // always 5
function updateLifeNotes() {
- lifeTxt.setText('Life: ' + (maxMisses - misses));
+ // Remove all old hearts
+ for (var i = 0; i < heartImages.length; i++) {
+ if (heartImages[i].parent) {
+ heartImages[i].parent.removeChild(heartImages[i]);
+ }
+ }
+ heartImages = [];
+ // Add hearts for remaining life
+ var remaining = maxHearts - misses;
+ for (var i = 0; i < remaining; i++) {
+ var heart = LK.getAsset('heart', {
+ anchorX: 1,
+ anchorY: 0,
+ x: heartStartX,
+ y: heartStartY + i * (120 + heartSpacing),
+ scaleX: 1,
+ scaleY: 1,
+ alpha: 1
+ });
+ LK.gui.topRight.addChild(heart);
+ heartImages.push(heart);
+ }
}
updateLifeNotes();
// Miss feedback
var missTxt = new Text2('', {
@@ -295,9 +309,9 @@
var noteTimer = 0;
// Game state
var isGameOver = false;
var isYouWin = false;
-var maxMisses = 8;
+var maxMisses = 5;
var misses = 0;
var targetScore = 50;
// Tutorial state
var isTutorial = true;