Code edit (1 edits merged)
Please save this source code
User prompt
increase vertical space between menutiles because they overlap
Code edit (8 edits merged)
Please save this source code
User prompt
in menu tile align the name on the left and add a playbutton asset on the right
User prompt
Create a MenuManager class that creates a list of menuTile per Song
User prompt
add a menuTile asset and a text in MenuTile class
Code edit (3 edits merged)
Please save this source code
User prompt
move startBtn's onTap logic in startBtn down() function
Code edit (3 edits merged)
Please save this source code
User prompt
play start sound when start is pressed
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
make start button entering and exit longer ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
animate the start button exit ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (2 edits merged)
Please save this source code
User prompt
in startButton add a big nodeDot asset before the StartText asset
User prompt
hide the scoretxt and show it only in startGame()
User prompt
hide the targets and show them only in startGame()
User prompt
hide the lanes and show them in startGame
User prompt
now call startGame() only when start button is tapped
User prompt
Create a StartButton class
User prompt
Calculate points based on the absolute distance between bestNote.y and the hitLineY : if (distance/(bestNote.height/2) <= 0.1 => 10 points ... if (distance/(bestNote.height/2) > 0.9 => 1 points
Code edit (6 edits merged)
Please save this source code
User prompt
play cheers sound at the end of the notes, before YouWin
User prompt
play cheers sound 1 sec after the last note is destroyed
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BackgroundManager class: manages the game background
var BackgroundManager = Container.expand(function () {
var self = Container.call(this);
// Attach the background00 asset, anchored at top-left
var bg = self.attachAsset('background00', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
return self;
});
// ComboText class: animated combo display
var ComboText = Container.expand(function () {
var self = Container.call(this);
// Internal Text2 for display
self.textObj = new Text2('', {
size: 80,
fill: 0xFFFFFF,
dropShadow: true
});
self.textObj.anchor.set(0.5, 0);
self.addChild(self.textObj);
// Set text and animate
self.setText = function (txt) {
self.textObj.setText(txt);
// Animate: pop if not empty, fade out if empty
tween.stop(self.textObj, {
scaleX: true,
scaleY: true,
alpha: true
});
if (txt && txt.length > 0) {
self.textObj.alpha = 1;
self.textObj.scaleX = 1.0;
self.textObj.scaleY = 1.0;
tween(self.textObj, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.textObj, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
} else {
// Fade out if empty
tween(self.textObj, {
alpha: 0
}, {
duration: 180
});
}
};
return self;
});
// MenuTile class: a menu tile with an image and a text label
var MenuTile = Container.expand(function () {
var self = Container.call(this);
// Attach the menuTile asset, centered
self.tileImg = self.attachAsset('menuTile', {
anchorX: 0.5,
anchorY: 0.5
});
// Add a text label centered on the tile
self.textObj = new Text2('', {
size: 100,
fill: 0x222222,
dropShadow: true
});
self.textObj.anchor.set(0.5, 0.5);
self.textObj.x = 0;
self.textObj.y = 0;
self.addChild(self.textObj);
// Public method to set the text
self.setText = function (txt) {
self.textObj.setText(txt);
};
return self;
});
// Note class: a falling dot in a lane, with timing and tap state
var Note = Container.expand(function () {
var self = Container.call(this);
// Attach the note dot asset, centered
self.noteBall = self.attachAsset('noteDot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
self.noteSign = self.attachAsset('noteSign', {
anchorX: 0.5,
anchorY: 0.5
});
// Lane index (0,1,2)
self.lane = 0;
// Time (in ms) when this note should be hit
self.hitTime = 0;
// Whether this note has been tapped
self.tapped = false;
// Whether this note has been missed
self.missed = false;
// For tap feedback
self.showTapFeedback = function () {
var feedback = self.attachAsset('tapFeedback', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
// Spawn sparkles at note position
var sparkles = new Sparkles();
sparkles.x = self.x;
sparkles.y = self.y;
if (self.parent) {
self.parent.addChild(sparkles);
}
// Animate noteSign scale up to 4 and back to 1
self.noteSign.scaleX = 1;
self.noteSign.scaleY = 1;
tween(self.noteSign, {
scaleX: 3,
scaleY: 3
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
// tween(self.noteSign, {
// scaleX: 1,
// scaleY: 1
// }, {
// duration: 180,
// easing: tween.cubicIn
// });
}
});
tween(feedback, {
alpha: 0
}, {
duration: 250,
onFinish: function onFinish() {
feedback.destroy();
}
});
};
// Called every tick
self.update = function () {
// Calculate songElapsed and t for this note
if (!gameActive) {
return;
}
var now = Date.now();
var songElapsed = now - songStartTime;
// Calculate progress: 0 (spawn) to 1 (at hit line), allow t > 1 for movement past hit line
var t = (songElapsed - (self.hitTime - noteTravelTime)) / noteTravelTime;
if (t < 0) {
t = 0;
}
// Remove the clamp for t > 1, so notes keep moving past hit line
self.y = noteStartY + (hitLineY - noteStartY) * t + (t > 1 ? (t - 1) * (2732 - hitLineY) : 0);
// Miss detection: if note passes hit line and not tapped
if (!self.tapped && !self.missed && songElapsed > self.hitTime + 220) {
self.missed = true;
self.alpha = 0.3;
combo = 0;
// Show and animate 'Missed!' on comboTxt in red
comboTxt.tint = 0xff2222; // set to red
comboTxt.setText('Missed!');
tween.stop(comboTxt, {
scaleX: true,
scaleY: true,
alpha: true
});
comboTxt.scaleX = 1.0;
comboTxt.scaleY = 1.0;
comboTxt.alpha = 1.0;
tween(comboTxt, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0.0
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
comboTxt.setText('');
comboTxt.tint = 0x3A8EE6; // restore to blue after
comboTxt.scaleX = 1.0;
comboTxt.scaleY = 1.0;
comboTxt.alpha = 1.0;
}
});
// Animate noteSign to flash red
tween(self.noteSign, {
tint: 0xff0000
}, {
duration: 60,
onFinish: function onFinish() {
tween(self.noteSign, {
tint: 0xffffff
}, {
duration: 180
});
}
});
// Play tapMiss sound when missed
LK.getSound('tapMiss').play();
}
};
// Play the corresponding key sound for this note
self.down = function () {
// Find the original song note for this Note instance
// We'll use the lane and hitTime to match to songNotesRaw
for (var i = 0; i < songNotesRaw.length; i++) {
var sn = songNotesRaw[i];
var lane = 0;
if (keyToLane.hasOwnProperty(sn.key)) {
lane = keyToLane[sn.key];
} else {
lane = i % 3;
}
if (lane === self.lane && sn.time === self.hitTime) {
if (typeof sn.key === "string") {
var keySoundName = sn.key.toLowerCase();
var keySound = LK.getSound(keySoundName);
if (keySound) {
keySound.play();
}
}
break;
}
}
};
return self;
});
// NoteManager class: handles spawning and management of notes
var NoteManager = Container.expand(function () {
var self = Container.call(this);
// Notes in play
self.notes = [];
// Index of next note to spawn
self.nextNoteIdx = 0;
// Reset all state
self.reset = function () {
for (var i = 0; i < self.notes.length; i++) {
self.notes[i].destroy();
}
self.notes = [];
self.nextNoteIdx = 0;
};
// Spawn notes as their time approaches
self.spawnNotes = function (songNotes, songElapsed, noteTravelTime, laneX, noteStartY) {
while (self.nextNoteIdx < songNotes.length && songNotes[self.nextNoteIdx].time - noteTravelTime <= songElapsed) {
var noteData = songNotes[self.nextNoteIdx];
var note = new Note();
note.lane = noteData.lane;
note.hitTime = noteData.time;
note.x = laneX[note.lane];
note.y = noteStartY;
self.notes.push(note);
game.addChild(note);
self.nextNoteIdx++;
}
};
// Remove notes that are far past the hit line
self.cleanupNotes = function (songElapsed) {
for (var i = self.notes.length - 1; i >= 0; i--) {
var note = self.notes[i];
// Remove note if it has moved past the bottom of the screen
if (note.y > 2732 + 100) {
note.destroy();
self.notes.splice(i, 1);
}
}
};
// Remove a specific note from the manager
self.removeNote = function (note) {
for (var i = 0; i < self.notes.length; i++) {
if (self.notes[i] === note) {
self.notes.splice(i, 1);
break;
}
}
};
// Get all notes in play
self.getNotes = function () {
return self.notes;
};
// Get the index of the next note to spawn
self.getNextNoteIdx = function () {
return self.nextNoteIdx;
};
// Set the index of the next note to spawn
self.setNextNoteIdx = function (idx) {
self.nextNoteIdx = idx;
};
return self;
});
// ScoreText class: animated score display (mirrors ComboText)
var ScoreText = Container.expand(function () {
var self = Container.call(this);
// Internal Text2 for display
self.textObj = new Text2('', {
size: 120,
fill: 0xFFFFFF,
dropShadow: true
});
self.textObj.anchor.set(0.5, 0);
self.addChild(self.textObj);
// Set text and animate
self.setText = function (txt) {
self.textObj.setText(txt);
// Animate: pop if not empty, fade out if empty
tween.stop(self.textObj, {
scaleX: true,
scaleY: true,
alpha: true
});
if (txt && txt.length > 0) {
self.textObj.alpha = 1;
self.textObj.scaleX = 1.0;
self.textObj.scaleY = 1.0;
tween(self.textObj, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.textObj, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
} else {
// Fade out if empty
tween(self.textObj, {
alpha: 0
}, {
duration: 180
});
}
};
return self;
});
// Sparkles class: creates and animates a small particle explosion
var Sparkles = Container.expand(function () {
var self = Container.call(this);
// Configurable parameters
var particleCount = 12;
var minSpeed = 160;
var maxSpeed = 360;
var minScale = 0.5;
var maxScale = 1.2;
var minAlpha = 0.7;
var maxAlpha = 1.0;
var minDuration = 320;
var maxDuration = 1520;
// Particle colors: only white to blue shades
var colors = [0xffffff,
// white
0xe0f7fa,
// very light blue
0xb3e5fc,
// light blue
0x81d4fa,
// sky blue
0x4fc3f7,
// lighter blue
0x29b6f6,
// blue
0x039be5,
// vivid blue
0x0288d1,
// deep blue
0x0277bd,
// darker blue
0x01579b,
// navy blue
0x3a8ee6 // main game blue
];
// Create particles
for (var i = 0; i < particleCount; i++) {
var angle = Math.PI * 2 * (i / particleCount); // + Math.random() * 0.3;
var speed = minSpeed + Math.random() * (maxSpeed - minSpeed);
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
var scale = minScale + Math.random() * (maxScale - minScale);
var alpha = minAlpha + Math.random() * (maxAlpha - minAlpha);
var color = colors[Math.floor(Math.random() * colors.length)];
var duration = minDuration + Math.random() * (maxDuration - minDuration);
var targetAngle = Math.PI * 2 * Math.random();
// Use a small circle as the sparkle
var sparkle = self.attachAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale,
alpha: alpha,
tint: color
});
// Animate outward movement, fade and shrink
(function (sparkle, vx, vy, duration) {
var startX = 0,
startY = 0;
// Calculate the final position based on velocity and duration (assuming 60fps, so duration/1000 seconds)
var seconds = duration / 1000;
var finalX = startX + vx * seconds;
var finalY = startY + vy * seconds;
tween(sparkle, {
x: finalX,
y: finalY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: targetAngle
}, {
duration: duration,
easing: tween.cubicOut,
onFinish: function onFinish() {
sparkle.destroy();
}
});
})(sparkle, vx, vy, duration);
}
// Destroy the container after all particles are done
LK.setTimeout(function () {
self.destroy();
}, maxDuration + 40);
return self;
});
// StartButton class: a tappable start button with image and animation
var StartButton = Container.expand(function () {
var self = Container.call(this);
// Attach a big noteDot asset, centered, before the startText asset
var dot = self.attachAsset('noteDot', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 5,
alpha: 0.85
});
// Attach the startText asset, centered, above the dot
var btn = self.attachAsset('startText', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1,
alpha: 1
});
// Animate pop-in (longer duration)
self.scaleX = 0.1;
self.scaleY = 0.1;
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 240,
easing: tween.cubicIn
});
}
});
// Optional: pulse animation
var pulseTween;
function startPulse() {
pulseTween = tween(self, {
scaleX: 1.08,
scaleY: 1.08
}, {
duration: 1000,
easing: tween.cubicInOut,
yoyo: true,
repeat: Infinity
});
}
startPulse();
// Public: set callback for tap
self.onTap = null;
// Handle tap
self.down = function (x, y, obj) {
// Animate tap feedback
tween.stop(self, {
scaleX: true,
scaleY: true
});
self.scaleX = 1.0;
self.scaleY = 1.0;
// --- Moved start logic here ---
// Play start sound when start is pressed
LK.getSound('startSound').play();
// Animate exit: scale up and fade out, then destroy and start game (longer duration)
tween(self, {
scaleX: 100,
scaleY: 100,
alpha: 0
}, {
duration: 1200,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.destroy();
self = null;
startGame();
}
});
};
// Clean up pulse tween on destroy
var origDestroy = self.destroy;
self.destroy = function () {
if (pulseTween) {
tween.stop(self, {
scaleX: true,
scaleY: true
});
}
origDestroy.call(self);
};
return self;
});
// Target class: represents the target area for each lane
var Target = Container.expand(function () {
var self = Container.call(this);
// Attach the target asset, centered, with blue tint
var targetAsset = self.attachAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x3a8ee6,
alpha: 0.8
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Music track (Ode to Joy, assumed loaded as 'odeToJoy')
// Sound for miss
// Sound for correct tap
// Lane highlight (subtle gray)
// Tap feedback (blue highlight)
// Falling note (white dot)
// Sound assets for key0 to key14
var SONGS = [{
"name": "Ode to Joy",
"bpm": 220,
"pitchLevel": 0,
"bitsPerPage": 16,
"isComposed": false,
"songNotes": [{
"time": 1432,
"key": "Key6"
}, {
"time": 1855,
"key": "Key6"
}, {
"time": 2305,
"key": "Key7"
}, {
"time": 2788,
"key": "Key8"
}, {
"time": 3216,
"key": "Key8"
}, {
"time": 3666,
"key": "Key7"
}, {
"time": 4122,
"key": "Key6"
}, {
"time": 4567,
"key": "Key5"
}, {
"time": 5027,
"key": "Key4"
}, {
"time": 5479,
"key": "Key4"
}, {
"time": 5937,
"key": "Key5"
}, {
"time": 6397,
"key": "Key6"
}, {
"time": 6864,
"key": "Key6"
}, {
"time": 7583,
"key": "Key5"
}, {
"time": 7820,
"key": "Key5"
}, {
"time": 8816,
"key": "Key6"
}, {
"time": 9289,
"key": "Key6"
}, {
"time": 9778,
"key": "Key7"
}, {
"time": 10205,
"key": "Key8"
}, {
"time": 10672,
"key": "Key8"
}, {
"time": 11108,
"key": "Key7"
}, {
"time": 11564,
"key": "Key6"
}, {
"time": 12000,
"key": "Key5"
}, {
"time": 12455,
"key": "Key4"
}, {
"time": 12911,
"key": "Key4"
}, {
"time": 13339,
"key": "Key5"
}, {
"time": 13785,
"key": "Key6"
}, {
"time": 14370,
"key": "Key5"
}, {
"time": 15131,
"key": "Key4"
}, {
"time": 15341,
"key": "Key4"
}, {
"time": 16318,
"key": "Key5"
}, {
"time": 16760,
"key": "Key5"
}, {
"time": 17243,
"key": "Key6"
}, {
"time": 17711,
"key": "Key4"
}, {
"time": 18164,
"key": "Key5"
}, {
"time": 18607,
"key": "Key6"
}, {
"time": 18840,
"key": "Key7"
}, {
"time": 19107,
"key": "Key6"
}, {
"time": 19556,
"key": "Key4"
}, {
"time": 20007,
"key": "Key5"
}, {
"time": 20428,
"key": "Key6"
}, {
"time": 20634,
"key": "Key7"
}, {
"time": 20915,
"key": "Key6"
}, {
"time": 21375,
"key": "Key5"
}, {
"time": 21859,
"key": "Key4"
}, {
"time": 22325,
"key": "Key5"
}, {
"time": 22818,
"key": "Key1"
}, {
"time": 23809,
"key": "Key6"
}, {
"time": 24259,
"key": "Key6"
}, {
"time": 24725,
"key": "Key7"
}, {
"time": 25156,
"key": "Key8"
}, {
"time": 25597,
"key": "Key8"
}, {
"time": 26039,
"key": "Key7"
}, {
"time": 26496,
"key": "Key6"
}, {
"time": 26950,
"key": "Key5"
}, {
"time": 27413,
"key": "Key4"
}, {
"time": 27882,
"key": "Key4"
}, {
"time": 28309,
"key": "Key5"
}, {
"time": 28830,
"key": "Key6"
}, {
"time": 29319,
"key": "Key5"
}, {
"time": 30092,
"key": "Key4"
}, {
"time": 30343,
"key": "Key4"
}],
"fromLibrary": true
}];
;
// --- Song Data: Use SONGS[0] ---
// Each note: {lane: 0|1|2, time: ms from song start}
// Lane 0: left, 1: center, 2: right
// We'll map keys to lanes below.
var songNotesRaw = SONGS[0].songNotes;
var keyToLane = {
"Key0": 0,
"Key1": 1,
"Key2": 2,
"Key3": 0,
"Key4": 1,
"Key5": 2,
"Key6": 0,
"Key7": 1,
"Key8": 2,
"Key9": 0,
"Key10": 1,
"Key11": 2,
"Key12": 0,
"Key13": 1,
"Key14": 2
};
var songNotes = [];
// Song duration (ms)
var songDuration;
// --- Lane positions (3 columns) ---
var laneCount = 3;
var laneWidth = 600;
var laneOffset = 150;
var laneSpacing = 2048 / laneCount;
var laneX = [laneSpacing * 0.5 + laneOffset,
// left
laneSpacing * 1.5,
// center
laneSpacing * 2.5 - laneOffset // right
];
// --- Visual: show hit line (subtle, not interactive) ---
var hitLine;
// --- Hit line (invisible, for reference) ---
var hitLineYDisplay;
// --- Instantiate 3 targets, one in each column at hitLineY ---
var targets = [];
// --- Note fall parameters ---
var speedMultiplier = 1.0; //1.0; // Global speed multiplier (1.0 = normal, >1 = faster, <1 = slower)
var baseNoteTravelTime = 2000; //8000; // ms: base time from spawn (top) to hit line (bottom)
var noteTravelTime = baseNoteTravelTime / speedMultiplier; // effective travel time, updated if speedMultiplier changes
var hitLineY = 2000; // y position where notes should be tapped
var noteStartY = -100; // spawn just above the screen
// --- State ---
// Declare all global game objects as variables (no instantiation here)
var noteManager;
var songStartTime;
var gameActive;
var score;
var combo;
var maxCombo;
var lastTapTime;
// --- GUI Elements ---
var scoreTxt;
var comboTxt;
// --- Instantiate and add background manager ---
var bgManager;
// --- Lane highlights (for visual feedback) ---
var laneHighlights;
function startGame() {
// Reset state
if (!noteManager) {
noteManager = new NoteManager();
}
noteManager.reset();
score = 0;
combo = 0;
maxCombo = 0;
if (!scoreTxt) {
scoreTxt = new ScoreText();
scoreTxt.y = 0;
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = true;
scoreTxt.setText('0');
if (!comboTxt) {
comboTxt = new ComboText();
comboTxt.y = 130;
comboTxt.tint = 0x3A8EE6;
LK.gui.top.addChild(comboTxt);
}
comboTxt.setText('');
lastTapTime = 0;
// Show lane highlights when game starts
if (laneHighlights) {
for (var i = 0; i < laneHighlights.length; i++) {
laneHighlights[i].visible = true;
}
}
// Show targets when game starts
if (targets) {
for (var i = 0; i < targets.length; i++) {
targets[i].visible = true;
}
}
gameActive = true;
songStartTime = Date.now();
}
// --- Main game update loop ---
game.update = function () {
// Update noteTravelTime in case speedMultiplier has changed
noteTravelTime = baseNoteTravelTime / speedMultiplier;
if (!gameActive) {
return;
}
var now = Date.now();
var songElapsed = now - songStartTime;
// --- Spawn notes as their time approaches ---
noteManager.spawnNotes(songNotes, songElapsed, noteTravelTime, laneX, noteStartY);
// --- Update notes: remove notes that are far past the hit line ---
noteManager.cleanupNotes(songElapsed);
// --- Win condition: song finished and all notes handled ---
if (songElapsed > songDuration + 1000 && noteManager.getNotes().length === 0 && gameActive) {
gameActive = false;
// Play cheers sound before YouWin
LK.getSound('cheers').play();
LK.setTimeout(function () {
LK.showYouWin();
}, 2000);
}
};
// --- Tap input handling ---
// Convert x to lane index (0,1,2)
function getLaneFromX(x) {
// Each lane is laneSpacing wide, centered at laneX[i]
for (var i = 0; i < laneCount; i++) {
var left = laneX[i] - laneWidth / 2;
var right = laneX[i] + laneWidth / 2;
if (x >= left && x <= right) {
return i;
}
}
// Out of bounds
return -1;
}
// On tap (down) anywhere in game
game.down = function (x, y, obj) {
if (!gameActive) {
return;
}
// Only accept taps near the hit line (±220px)
if (y < hitLineY - 220 || y > hitLineY + 220) {
return;
}
var lane = getLaneFromX(x);
if (lane < 0 || lane >= laneCount) {
return;
}
// Find the earliest untapped note in this lane within hit window
var now = Date.now();
var songElapsed = now - songStartTime;
var bestNote = null;
var bestDelta = 9999;
var notesInPlay = noteManager.getNotes();
for (var i = 0; i < notesInPlay.length; i++) {
var note = notesInPlay[i];
if (note.lane !== lane) {
continue;
}
if (note.tapped || note.missed) {
continue;
}
var delta = Math.abs(songElapsed - note.hitTime);
if (delta < 320 && delta < bestDelta) {
// 320ms window
bestNote = note;
bestDelta = delta;
}
}
if (bestNote) {
// Correct tap!
bestNote.tapped = true;
bestNote.showTapFeedback();
LK.getSound('tapGood').play();
// Play the corresponding key sound for this note (moved to Note.down)
if (typeof bestNote.down === "function") {
bestNote.down();
}
// Calculate points based on the distance to the hitLineY
var distance = Math.abs(bestNote.y - hitLineY);
var norm = distance / (bestNote.height / 2);
var points = 1;
if (norm <= 0.1) {
points = 10;
} else if (norm <= 0.2) {
points = 9;
} else if (norm <= 0.3) {
points = 8;
} else if (norm <= 0.4) {
points = 7;
} else if (norm <= 0.5) {
points = 6;
} else if (norm <= 0.6) {
points = 5;
} else if (norm <= 0.7) {
points = 4;
} else if (norm <= 0.8) {
points = 3;
} else if (norm <= 0.9) {
points = 2;
} else {
points = 1;
}
combo += 1;
score += combo * points;
if (combo > maxCombo) {
maxCombo = combo;
}
scoreTxt.setText(score + '');
// Animate scoreTxt scale pop
tween.stop(scoreTxt, {
scaleX: true,
scaleY: true
});
scoreTxt.scaleX = 1.0;
scoreTxt.scaleY = 1.0;
tween(scoreTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 160,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(scoreTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
if (combo > 1) {
comboTxt.setText('Combo x' + combo + '!');
// Animate comboTxt scale pop
tween.stop(comboTxt, {
scaleX: true,
scaleY: true
});
comboTxt.scaleX = 1.0;
comboTxt.scaleY = 1.0;
tween(comboTxt, {
scaleX: 1.6,
scaleY: 1.6
}, {
duration: 160,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(comboTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
} else {
comboTxt.setText('');
}
// Flash lane blue
LK.effects.flashObject(laneHighlights[lane], 0x3a8ee6, 180);
// Animate corresponding target scale
var tappedTarget = targets[lane];
if (tappedTarget) {
tappedTarget.scaleX = 1;
tappedTarget.scaleY = 1;
tween(tappedTarget, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 90,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(tappedTarget, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.cubicIn
});
}
});
}
// Remove note visually
tween(bestNote, {
alpha: 0
}, {
duration: 180,
onFinish: function onFinish() {
bestNote.destroy();
}
});
// Remove from notes array after fade
noteManager.removeNote(bestNote);
} else {
// Miss! (Tapped with no note in window)
combo = 0;
comboTxt.setText('');
LK.getSound('tapMiss').play();
LK.effects.flashObject(laneHighlights[lane], 0xff0000, 300);
//LK.effects.flashScreen(0xff0000, 100);
/*
gameActive = false;
LK.setTimeout(function () {
LK.showGameOver();
}, 600);
*/
}
};
// --- Reset game on game over or win ---
LK.on('gameover', function () {
LK.stopMusic();
startGame();
});
LK.on('youwin', function () {
LK.stopMusic();
startGame();
});
// Game initialization
function initializeGame() {
// Instantiate and initialize all global objects here
// --- Instantiate and add background manager ---
bgManager = new BackgroundManager();
game.addChild(bgManager);
// If the song uses only Key6/Key7/Key8, map them to 0/1/2, else fallback to a round-robin
for (var i = 0; i < songNotesRaw.length; i++) {
var note = songNotesRaw[i];
var lane = 0;
if (keyToLane.hasOwnProperty(note.key)) {
lane = keyToLane[note.key];
} else {
lane = i % 3;
}
songNotes.push({
lane: lane,
time: note.time
});
}
// Song duration (ms)
songDuration = songNotes.length > 0 ? songNotes[songNotes.length - 1].time + 1000 : 9000;
hitLine = LK.getAsset('laneHighlight', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 8,
color: 0x3a8ee6,
alpha: 0.18,
x: 2048 / 2,
y: hitLineY
});
game.addChild(hitLine);
for (var i = 0; i < laneCount; i++) {
var target = new Target();
target.x = laneX[i];
target.y = hitLineY;
target.visible = false; // hide initially
game.addChild(target);
targets.push(target);
}
// --- State ---
noteManager = new NoteManager(); // Handles all notes and spawning
songStartTime = 0; // Date.now() when song started
gameActive = false; // True if game is running
score = 0;
combo = 0;
maxCombo = 0;
lastTapTime = 0;
// --- GUI Elements ---
scoreTxt = new ScoreText();
scoreTxt.setText('0');
scoreTxt.y = 0;
scoreTxt.visible = false; // hide initially
LK.gui.top.addChild(scoreTxt);
comboTxt = new ComboText();
comboTxt.y = 130;
comboTxt.tint = 0x3A8EE6;
LK.gui.top.addChild(comboTxt);
// --- Lane highlights (for visual feedback) ---
laneHighlights = [];
for (var i = 0; i < laneCount; i++) {
var laneHL = LK.getAsset('laneHighlight', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.07,
x: laneX[i],
y: 0,
height: 2732,
visible: false // hide initially
});
game.addChild(laneHL);
laneHighlights.push(laneHL);
}
// --- Hit line (invisible, for reference) ---
hitLineYDisplay = hitLineY;
// Show StartButton and call startGame only when tapped
if (typeof startBtn !== "undefined" && startBtn) {
startBtn.destroy();
startBtn = null;
}
startBtn = new StartButton();
startBtn.x = 2048 / 2;
startBtn.y = 1200;
game.addChild(startBtn);
}
initializeGame(); ===================================================================
--- original.js
+++ change.js
@@ -68,8 +68,32 @@
}
};
return self;
});
+// MenuTile class: a menu tile with an image and a text label
+var MenuTile = Container.expand(function () {
+ var self = Container.call(this);
+ // Attach the menuTile asset, centered
+ self.tileImg = self.attachAsset('menuTile', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Add a text label centered on the tile
+ self.textObj = new Text2('', {
+ size: 100,
+ fill: 0x222222,
+ dropShadow: true
+ });
+ self.textObj.anchor.set(0.5, 0.5);
+ self.textObj.x = 0;
+ self.textObj.y = 0;
+ self.addChild(self.textObj);
+ // Public method to set the text
+ self.setText = function (txt) {
+ self.textObj.setText(txt);
+ };
+ return self;
+});
// Note class: a falling dot in a lane, with timing and tap state
var Note = Container.expand(function () {
var self = Container.call(this);
// Attach the note dot asset, centered
@@ -483,43 +507,25 @@
scaleY: true
});
self.scaleX = 1.0;
self.scaleY = 1.0;
- // tween(self, {
- // scaleX: 10,
- // scaleY: 10
- // }, {
- // duration: 1800,
- // easing: tween.easeOut,
- // onFinish: function onFinish() {
- // // (optional: could reset scale here)
- // }
- // });
// --- Moved start logic here ---
// Play start sound when start is pressed
LK.getSound('startSound').play();
- if (typeof startBtn !== "undefined" && startBtn) {
- // Animate exit: scale up and fade out, then destroy and start game (longer duration)
- tween(startBtn, {
- scaleX: 10,
- scaleY: 10,
- alpha: 0
- }, {
- duration: 1800,
- easing: tween.cubicIn,
- onFinish: function onFinish() {
- startBtn.destroy();
- startBtn = null;
- startGame();
- }
- });
- } else {
- startGame();
- }
- // If onTap is set, call it for compatibility
- if (typeof self.onTap === "function") {
- self.onTap();
- }
+ // Animate exit: scale up and fade out, then destroy and start game (longer duration)
+ tween(self, {
+ scaleX: 100,
+ scaleY: 100,
+ alpha: 0
+ }, {
+ duration: 1200,
+ easing: tween.cubicIn,
+ onFinish: function onFinish() {
+ self.destroy();
+ self = null;
+ startGame();
+ }
+ });
};
// Clean up pulse tween on destroy
var origDestroy = self.destroy;
self.destroy = function () {
key0
Sound effect
key1
Sound effect
key2
Sound effect
key3
Sound effect
key4
Sound effect
key5
Sound effect
key6
Sound effect
key7
Sound effect
key8
Sound effect
key10
Sound effect
key11
Sound effect
key12
Sound effect
key13
Sound effect
key14
Sound effect
key9
Sound effect
tapMiss
Sound effect
cheers
Sound effect
startSound
Sound effect
click
Sound effect
menuSpawn
Sound effect
jeers
Sound effect