Code edit (3 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Graphics is not a constructor' in or related to this line: 'var hitZoneLine = new Graphics();' Line Number: 242
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: LK.getTime is not a function' in or related to this line: 'var now = LK.getTime();' Line Number: 535
User prompt
Please fix the bug: 'LK.getTime is not a function' in or related to this line: 'gameStartTime = LK.getTime();' Line Number: 549
Code edit (1 edits merged)
Please save this source code
User prompt
Beat Swipe: Rhythm Saber
Initial prompt
Create a 2D rhythm game framework inspired by Beat Saber. Game mechanics: - Notes (circles or shapes) appear far away and grow in size as they "approach" the player, simulating a forward motion toward the screen. - There are 3 types of notes: 1. Tap notes — the player must click exactly when the note reaches maximum size. 2. Swipe notes — the player must perform a quick swipe with the mouse through the note in a given direction (left, right, up, down). 3. Trap notes — these must be avoided entirely. Clicking or swiping them results in a combo break. - Notes appear at specific times and types defined in an array. Controls: - Mouse only. Recognize: - Clicks (onMouseDown + release timing). - Swipes (track onMouseDown → onMouseUp direction and distance). Scoring: - Perfect timing (±0.1 sec) gives max points. - Good timing (±0.3 sec) gives half points. - Missed or incorrect interaction resets combo. - Trap interaction resets combo too. Other features: - Notes scale up as they approach to simulate perspective. - Show combo counter and score. - Simple placeholder graphics and dark background are fine. - Music and visuals will be added manually later. Code structure must clearly separate: - Note spawn logic. - Input detection (click + swipe). - Timing window checks. - Scoring and combo logic. Use simple assets for now (colored shapes). Add comments to explain functions.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Note = Container.expand(function (noteType, swipeDir, targetHitTimeFull) {
var self = Container.call(this);
self.noteType = noteType || 'tap';
self.swipeDir = swipeDir || null;
self.targetHitTime = targetHitTimeFull;
self.visualSpawnTime = self.targetHitTime - noteTravelTime;
self.hit = false;
self.judged = false;
self.scaleStart = 0.3;
self.scaleEnd = 1.2;
self.centerX = 2048 / 2;
self.centerY = 1800;
self.startY = 600;
self.noteAsset = null;
if (self.noteType === 'tap') {
self.noteAsset = self.attachAsset('tapNote', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.noteType === 'swipe') {
self.noteAsset = self.attachAsset('swipeNote', {
// Will use the square definition
anchorX: 0.5,
anchorY: 0.5
});
if (self.swipeDir) {
var arrow = new Text2('', {
size: 80,
// Arrow size
fill: 0xFFFFFF
});
arrow.anchor.set(0.5, 0.5);
if (self.swipeDir === 'left') {
arrow.setText('←');
} else if (self.swipeDir === 'right') {
arrow.setText('→');
} else if (self.swipeDir === 'up') {
arrow.setText('↑');
} else if (self.swipeDir === 'down') {
arrow.setText('↓');
}
self.addChild(arrow);
self.arrow = arrow;
}
} else if (self.noteType === 'trap') {
self.noteAsset = self.attachAsset('trapNote', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.alpha = 0;
self.showHitFeedback = function (result) {
var feedback = LK.getAsset('hitFeedback', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 0.7,
scaleY: 0.7,
alpha: 0.7
});
if (result === 'perfect') {
feedback.tint = 0xffff00;
} else if (result === 'good') {
feedback.tint = 0x00ff00;
} else {
feedback.tint = 0xff0000;
}
self.addChild(feedback);
tween(feedback, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
if (feedback.parent) {
feedback.destroy();
}
}
});
};
self.update = function () {
var now = Date.now();
if (now < self.visualSpawnTime) {
self.alpha = 0;
return;
}
if (self.alpha === 0 && !self.judged) {
self.alpha = 1;
}
var elapsedTimeSinceSpawn = now - self.visualSpawnTime;
var progress = elapsedTimeSinceSpawn / noteTravelTime;
if (progress < 0) {
progress = 0;
}
if (progress > 1) {
progress = 1;
}
var scale = self.scaleStart + (self.scaleEnd - self.scaleStart) * progress;
self.scale.x = scale;
self.scale.y = scale;
self.x = self.centerX;
self.y = self.startY + (self.centerY - self.startY) * progress;
if (!self.judged && now > self.targetHitTime + hitWindowGood) {
self.judged = true;
if (self.noteType !== 'trap') {
game.onNoteMiss(self);
}
}
};
self.isInHitWindow = function () {
var now = Date.now();
var dt = Math.abs(now - self.targetHitTime);
return dt <= hitWindowGood;
};
self.getHitAccuracy = function () {
var now = Date.now();
var dt = Math.abs(now - self.targetHitTime);
if (dt <= hitWindowPerfect) {
return 'perfect';
}
if (dt <= hitWindowGood) {
return 'good';
}
return 'miss';
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181828
});
/****
* Game Code
****/
// Changed to square
var rhythmMap = [{
time: 1000,
type: 'tap'
}, {
time: 1800,
type: 'tap'
}, {
time: 2600,
type: 'swipe',
swipeDir: 'left'
}, {
time: 3400,
type: 'tap'
}, {
time: 4200,
type: 'swipe',
swipeDir: 'right'
}, {
time: 5000,
type: 'trap'
}, {
time: 5800,
type: 'tap'
}, {
time: 6600,
type: 'swipe',
swipeDir: 'up'
}, {
time: 7400,
type: 'tap'
}, {
time: 8200,
type: 'trap'
}, {
time: 9000,
type: 'swipe',
swipeDir: 'down'
}, {
time: 9800,
type: 'tap'
}, {
time: 10600,
type: 'tap'
}, {
time: 11400,
type: 'swipe',
swipeDir: 'left'
}, {
time: 12200,
type: 'trap'
}, {
time: 13000,
type: 'tap'
}];
var noteTravelTime = 1200;
var hitWindowPerfect = 120;
var hitWindowGood = 260;
var MIN_SWIPE_DISTANCE = 60; // Minimum distance for a swipe to be registered
var notes = [];
var nextNoteIdx = 0;
var gameStartTime = 0;
var score = 0;
var combo = 0;
var maxCombo = 0;
var swipeStart = null;
var inputLocked = false;
var scoreTxt = new Text2('Score: 0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.x = 2048 / 2;
scoreTxt.y = 20;
LK.gui.top.addChild(scoreTxt);
var comboTxt = new Text2('Combo: 0', {
size: 80,
fill: 0xFFFF00
});
comboTxt.anchor.set(0.5, 0);
comboTxt.x = 2048 / 2;
comboTxt.y = 130;
LK.gui.top.addChild(comboTxt);
var hitZoneY = 1800; // Matches Note's centerY
// Use a Container and a rectangle asset to visualize the hit zone line
var hitZoneWidth = 400; // Visual width of the hit zone line
var hitZoneLine = new Container();
var hitZoneRect = LK.getAsset('hitZoneRect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
hitZoneLine.addChild(hitZoneRect);
hitZoneLine.x = 2048 / 2;
hitZoneLine.y = hitZoneY;
game.addChild(hitZoneLine);
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
function resetGameState() {
notes.forEach(function (n) {
if (n && n.parent) n.destroy();
});
notes = [];
nextNoteIdx = 0;
score = 0;
combo = 0;
maxCombo = 0;
swipeStart = null;
inputLocked = false;
scoreTxt.setText('Score: 0');
comboTxt.setText('Combo: 0');
}
function spawnNotes() {
var now = Date.now();
while (nextNoteIdx < rhythmMap.length) {
var noteData = rhythmMap[nextNoteIdx];
var noteTargetHitTime = gameStartTime + noteData.time;
var noteVisualSpawnTime = noteTargetHitTime - noteTravelTime;
if (noteVisualSpawnTime <= now) {
var n = new Note(noteData.type, noteData.swipeDir, noteTargetHitTime);
n.x = n.centerX;
n.y = n.startY;
n.scale.x = n.scaleStart;
n.scale.y = n.scaleStart;
n.alpha = 0;
notes.push(n);
game.addChild(n);
nextNoteIdx++;
} else {
break;
}
}
}
function removeOldNotes() {
var now = Date.now();
for (var i = notes.length - 1; i >= 0; i--) {
var n = notes[i];
if (n.judged && now > n.targetHitTime + hitWindowGood + 400) {
if (n.parent) {
n.destroy();
}
notes.splice(i, 1);
} else if (!n.judged && now > n.targetHitTime + noteTravelTime + 500) {
if (n.parent) {
n.destroy();
}
notes.splice(i, 1);
}
}
}
function findNoteAt(x, y, typeToFind) {
var now = Date.now();
var bestNote = null;
var smallestTimeDiff = hitWindowGood + 1;
for (var i = 0; i < notes.length; i++) {
var n = notes[i];
if (n.judged || n.noteType !== typeToFind) {
continue;
}
var timeDiff = Math.abs(now - n.targetHitTime);
if (timeDiff > hitWindowGood) {
continue;
}
var currentNoteWidth = n.noteAsset.width * n.scale.x;
var currentNoteHeight = n.noteAsset.height * n.scale.y;
var dx = x - n.x; // Distance from input to note's center
var dy = y - n.y;
// Check for point within note's bounding box
if (Math.abs(dx) <= currentNoteWidth / 2 && Math.abs(dy) <= currentNoteHeight / 2) {
if (timeDiff < smallestTimeDiff) {
bestNote = n;
smallestTimeDiff = timeDiff;
}
}
}
return bestNote;
}
function addScore(result) {
if (result === 'perfect') {
score += 100;
} else if (result === 'good') {
score += 50;
}
scoreTxt.setText('Score: ' + score);
}
function addCombo() {
combo += 1;
if (combo > maxCombo) {
maxCombo = combo;
}
comboTxt.setText('Combo: ' + combo);
}
function resetCombo() {
combo = 0;
comboTxt.setText('Combo: ' + combo);
}
function checkGameEnd() {
if (nextNoteIdx >= rhythmMap.length && notes.length === 0) {
LK.setTimeout(function () {
if (nextNoteIdx >= rhythmMap.length && notes.length === 0) {
LK.showYouWin();
}
}, 1000);
}
}
game.onNoteMiss = function (note) {
if (!note || note.judged) return;
note.judged = true;
note.showHitFeedback('miss');
resetCombo();
if (note.parent) {
// Check if note still exists before flashing
LK.effects.flashObject(note, 0xff0000, 300);
}
};
game.down = function (x, y, obj) {
if (inputLocked) {
return;
}
swipeStart = {
x: x,
y: y,
time: Date.now()
};
var trap = findNoteAt(x, y, 'trap');
if (trap && !trap.judged && trap.isInHitWindow()) {
trap.judged = true;
trap.showHitFeedback('miss');
resetCombo();
LK.effects.flashScreen(0xff0000, 400);
inputLocked = true;
LK.setTimeout(function () {
inputLocked = false;
}, 200);
return;
}
var tap = findNoteAt(x, y, 'tap');
if (tap && !tap.judged && tap.isInHitWindow()) {
var result = tap.getHitAccuracy();
tap.judged = true;
tap.showHitFeedback(result);
if (result !== 'miss') {
addScore(result);
addCombo();
} else {
resetCombo();
}
inputLocked = true;
LK.setTimeout(function () {
inputLocked = false;
}, 120);
return;
}
};
game.up = function (x, y, obj) {
if (inputLocked || !swipeStart) {
swipeStart = null;
return;
}
var swipeEndX = x;
var swipeEndY = y;
var swipeEndTime = Date.now();
var dx = swipeEndX - swipeStart.x;
var dy = swipeEndY - swipeStart.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var potentialSwipe = dist >= MIN_SWIPE_DISTANCE;
var swipedNoteSuccessfully = false;
if (potentialSwipe) {
var detectedDir = null;
if (Math.abs(dx) > Math.abs(dy)) {
detectedDir = dx > 0 ? 'right' : 'left';
} else {
detectedDir = dy > 0 ? 'down' : 'up';
}
var swipeBoundingBox = {
x: Math.min(swipeStart.x, swipeEndX),
y: Math.min(swipeStart.y, swipeEndY),
width: Math.abs(dx),
height: Math.abs(dy)
};
var bestSwipedNote = null;
var bestSwipedNoteTimeDiff = hitWindowGood + 1;
for (var i = 0; i < notes.length; i++) {
var n = notes[i];
if (n.judged || n.noteType !== 'swipe') {
continue;
}
// Check if the swipe *action time* is within the note's hit window
var timeDiffWithSwipeEnd = Math.abs(swipeEndTime - n.targetHitTime);
if (timeDiffWithSwipeEnd > hitWindowGood) {
// Check also with swipe start time for very fast notes / slow swipes
var timeDiffWithSwipeStart = Math.abs(swipeStart.time - n.targetHitTime);
if (timeDiffWithSwipeStart > hitWindowGood && (swipeStart.time > n.targetHitTime + hitWindowGood || swipeEndTime < n.targetHitTime - hitWindowGood)) {
continue;
}
}
// Check if the note itself is visually near the hit line at the moment of swipe evaluation
// This can be tricky because the note is moving. We rely more on its targetHitTime.
// However, we should only consider notes that are "active" (alpha=1)
if (n.alpha === 0) continue;
var noteCurrentWidth = n.noteAsset.width * n.scale.x;
var noteCurrentHeight = n.noteAsset.height * n.scale.y;
var noteBoundingBox = {
x: n.x - noteCurrentWidth / 2,
y: n.y - noteCurrentHeight / 2,
width: noteCurrentWidth,
height: noteCurrentHeight
};
if (rectsIntersect(swipeBoundingBox, noteBoundingBox)) {
if (detectedDir === n.swipeDir) {
// If multiple notes intersect, prefer the one closest to its targetHitTime
var currentNoteTimeDiff = Math.abs(swipeEndTime - n.targetHitTime); // Or average swipe time
if (bestSwipedNote === null || currentNoteTimeDiff < bestSwipedNoteTimeDiff) {
bestSwipedNote = n;
bestSwipedNoteTimeDiff = currentNoteTimeDiff;
}
}
}
}
if (bestSwipedNote) {
var noteToJudge = bestSwipedNote;
var result = noteToJudge.getHitAccuracy(); // Uses note's targetHitTime vs now (swipeEndTime)
noteToJudge.judged = true;
noteToJudge.showHitFeedback(result);
if (result !== 'miss') {
addScore(result);
addCombo();
} else {
resetCombo();
}
swipedNoteSuccessfully = true;
}
}
if (potentialSwipe && !swipedNoteSuccessfully) {
// Optional: If it was a swipe but didn't hit anything relevant,
// one might still want to reset combo if that's the desired game feel.
// For now, only a judged miss on a note resets combo.
}
inputLocked = true;
LK.setTimeout(function () {
inputLocked = false;
}, 120); // Short lock to prevent accidental double inputs from one action
swipeStart = null;
};
game.move = function (x, y, obj) {
// Not used for now
};
game.update = function () {
spawnNotes();
for (var i = 0; i < notes.length; i++) {
if (notes[i] && notes[i].update) {
notes[i].update();
}
}
removeOldNotes();
checkGameEnd();
};
resetGameState();
gameStartTime = Date.now(); ===================================================================
--- original.js
+++ change.js
@@ -26,14 +26,16 @@
anchorY: 0.5
});
} else if (self.noteType === 'swipe') {
self.noteAsset = self.attachAsset('swipeNote', {
+ // Will use the square definition
anchorX: 0.5,
anchorY: 0.5
});
if (self.swipeDir) {
var arrow = new Text2('', {
size: 80,
+ // Arrow size
fill: 0xFFFFFF
});
arrow.anchor.set(0.5, 0.5);
if (self.swipeDir === 'left') {
@@ -53,9 +55,9 @@
anchorX: 0.5,
anchorY: 0.5
});
}
- self.alpha = 0; // Initially invisible, will become visible when it's time
+ self.alpha = 0;
self.showHitFeedback = function (result) {
var feedback = LK.getAsset('hitFeedback', {
anchorX: 0.5,
anchorY: 0.5,
@@ -89,13 +91,12 @@
};
self.update = function () {
var now = Date.now();
if (now < self.visualSpawnTime) {
- self.alpha = 0; // Ensure it's invisible before visual spawn time
+ self.alpha = 0;
return;
}
- if (self.alpha === 0) {
- // First time it becomes visible
+ if (self.alpha === 0 && !self.judged) {
self.alpha = 1;
}
var elapsedTimeSinceSpawn = now - self.visualSpawnTime;
var progress = elapsedTimeSinceSpawn / noteTravelTime;
@@ -113,10 +114,8 @@
if (!self.judged && now > self.targetHitTime + hitWindowGood) {
self.judged = true;
if (self.noteType !== 'trap') {
game.onNoteMiss(self);
- } else {
- // Trap notes just disappear if not hit, no miss penalty unless clicked
}
}
};
self.isInHitWindow = function () {
@@ -147,8 +146,9 @@
/****
* Game Code
****/
+// Changed to square
var rhythmMap = [{
time: 1000,
type: 'tap'
}, {
@@ -204,8 +204,9 @@
}];
var noteTravelTime = 1200;
var hitWindowPerfect = 120;
var hitWindowGood = 260;
+var MIN_SWIPE_DISTANCE = 60; // Minimum distance for a swipe to be registered
var notes = [];
var nextNoteIdx = 0;
var gameStartTime = 0;
var score = 0;
@@ -228,8 +229,24 @@
comboTxt.anchor.set(0.5, 0);
comboTxt.x = 2048 / 2;
comboTxt.y = 130;
LK.gui.top.addChild(comboTxt);
+var hitZoneY = 1800; // Matches Note's centerY
+// Use a Container and a rectangle asset to visualize the hit zone line
+var hitZoneWidth = 400; // Visual width of the hit zone line
+var hitZoneLine = new Container();
+var hitZoneRect = LK.getAsset('hitZoneRect', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ alpha: 0.6
+});
+hitZoneLine.addChild(hitZoneRect);
+hitZoneLine.x = 2048 / 2;
+hitZoneLine.y = hitZoneY;
+game.addChild(hitZoneLine);
+function rectsIntersect(r1, r2) {
+ return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
+}
function resetGameState() {
notes.forEach(function (n) {
if (n && n.parent) n.destroy();
});
@@ -254,9 +271,9 @@
n.x = n.centerX;
n.y = n.startY;
n.scale.x = n.scaleStart;
n.scale.y = n.scaleStart;
- n.alpha = 0; // Will be set to 1 in its own update when it's time
+ n.alpha = 0;
notes.push(n);
game.addChild(n);
nextNoteIdx++;
} else {
@@ -273,9 +290,8 @@
n.destroy();
}
notes.splice(i, 1);
} else if (!n.judged && now > n.targetHitTime + noteTravelTime + 500) {
- // Failsafe for notes that somehow weren't judged
if (n.parent) {
n.destroy();
}
notes.splice(i, 1);
@@ -294,27 +310,18 @@
var timeDiff = Math.abs(now - n.targetHitTime);
if (timeDiff > hitWindowGood) {
continue;
}
- var dx = x - n.x;
+ var currentNoteWidth = n.noteAsset.width * n.scale.x;
+ var currentNoteHeight = n.noteAsset.height * n.scale.y;
+ var dx = x - n.x; // Distance from input to note's center
var dy = y - n.y;
- var noteRadiusOrWidth = n.noteAsset.width * n.scale.x / 2;
- var noteHeight = n.noteAsset.height * n.scale.y / 2;
- if (typeToFind === 'swipe') {
- if (dx >= -noteRadiusOrWidth && dx <= noteRadiusOrWidth && dy >= -noteHeight && dy <= noteHeight) {
- if (timeDiff < smallestTimeDiff) {
- bestNote = n;
- smallestTimeDiff = timeDiff;
- }
+ // Check for point within note's bounding box
+ if (Math.abs(dx) <= currentNoteWidth / 2 && Math.abs(dy) <= currentNoteHeight / 2) {
+ if (timeDiff < smallestTimeDiff) {
+ bestNote = n;
+ smallestTimeDiff = timeDiff;
}
- } else {
- // tap or trap
- if (dx * dx + dy * dy <= noteRadiusOrWidth * noteRadiusOrWidth) {
- if (timeDiff < smallestTimeDiff) {
- bestNote = n;
- smallestTimeDiff = timeDiff;
- }
- }
}
}
return bestNote;
}
@@ -334,37 +341,37 @@
comboTxt.setText('Combo: ' + combo);
}
function resetCombo() {
combo = 0;
- comboTxt.setText('Combo: 0');
+ comboTxt.setText('Combo: ' + combo);
}
function checkGameEnd() {
if (nextNoteIdx >= rhythmMap.length && notes.length === 0) {
- // A small delay before showing "You Win" to let last feedback animations play
LK.setTimeout(function () {
if (nextNoteIdx >= rhythmMap.length && notes.length === 0) {
- // Double check in case of quick restart
LK.showYouWin();
}
}, 1000);
}
}
game.onNoteMiss = function (note) {
- if (note.judged) return; // Already handled
+ if (!note || note.judged) return;
note.judged = true;
note.showHitFeedback('miss');
resetCombo();
- LK.effects.flashObject(note, 0xff0000, 300);
+ if (note.parent) {
+ // Check if note still exists before flashing
+ LK.effects.flashObject(note, 0xff0000, 300);
+ }
};
game.down = function (x, y, obj) {
if (inputLocked) {
return;
}
swipeStart = {
x: x,
y: y,
- time: Date.now(),
- note: null
+ time: Date.now()
};
var trap = findNoteAt(x, y, 'trap');
if (trap && !trap.judged && trap.isInHitWindow()) {
trap.judged = true;
@@ -393,74 +400,106 @@
inputLocked = false;
}, 120);
return;
}
- // For swipe, we only record the start here if it's over a swipe note.
- // The actual swipe action is judged on game.up
- var swipeNoteUnderCursor = findNoteAt(x, y, 'swipe');
- if (swipeNoteUnderCursor && !swipeNoteUnderCursor.judged && swipeNoteUnderCursor.isInHitWindow()) {
- swipeStart.note = swipeNoteUnderCursor;
- }
};
game.up = function (x, y, obj) {
if (inputLocked || !swipeStart) {
swipeStart = null;
return;
}
- var noteToJudge = swipeStart.note;
- if (!noteToJudge || noteToJudge.judged || noteToJudge.noteType !== 'swipe') {
- swipeStart = null;
- return;
- }
- // Only judge if the swipe ended within a reasonable time window of the note's target time
- if (!noteToJudge.isInHitWindow()) {
- // swipeStart = null; // Optional: could also mark as miss if swipe was too late/early
- return;
- }
- var dx = x - swipeStart.x;
- var dy = y - swipeStart.y;
+ var swipeEndX = x;
+ var swipeEndY = y;
+ var swipeEndTime = Date.now();
+ var dx = swipeEndX - swipeStart.x;
+ var dy = swipeEndY - swipeStart.y;
var dist = Math.sqrt(dx * dx + dy * dy);
- if (dist < 80) {
- swipeStart = null;
- return;
- }
- var detectedDir = null;
- if (Math.abs(dx) > Math.abs(dy)) {
- detectedDir = dx > 0 ? 'right' : 'left';
- } else {
- detectedDir = dy > 0 ? 'down' : 'up';
- }
- if (detectedDir === noteToJudge.swipeDir) {
- var result = noteToJudge.getHitAccuracy();
- noteToJudge.judged = true;
- noteToJudge.showHitFeedback(result);
- if (result !== 'miss') {
- addScore(result);
- addCombo();
+ var potentialSwipe = dist >= MIN_SWIPE_DISTANCE;
+ var swipedNoteSuccessfully = false;
+ if (potentialSwipe) {
+ var detectedDir = null;
+ if (Math.abs(dx) > Math.abs(dy)) {
+ detectedDir = dx > 0 ? 'right' : 'left';
} else {
- resetCombo();
+ detectedDir = dy > 0 ? 'down' : 'up';
}
- } else {
- noteToJudge.judged = true;
- noteToJudge.showHitFeedback('miss');
- resetCombo();
- LK.effects.flashObject(noteToJudge, 0xff0000, 300);
+ var swipeBoundingBox = {
+ x: Math.min(swipeStart.x, swipeEndX),
+ y: Math.min(swipeStart.y, swipeEndY),
+ width: Math.abs(dx),
+ height: Math.abs(dy)
+ };
+ var bestSwipedNote = null;
+ var bestSwipedNoteTimeDiff = hitWindowGood + 1;
+ for (var i = 0; i < notes.length; i++) {
+ var n = notes[i];
+ if (n.judged || n.noteType !== 'swipe') {
+ continue;
+ }
+ // Check if the swipe *action time* is within the note's hit window
+ var timeDiffWithSwipeEnd = Math.abs(swipeEndTime - n.targetHitTime);
+ if (timeDiffWithSwipeEnd > hitWindowGood) {
+ // Check also with swipe start time for very fast notes / slow swipes
+ var timeDiffWithSwipeStart = Math.abs(swipeStart.time - n.targetHitTime);
+ if (timeDiffWithSwipeStart > hitWindowGood && (swipeStart.time > n.targetHitTime + hitWindowGood || swipeEndTime < n.targetHitTime - hitWindowGood)) {
+ continue;
+ }
+ }
+ // Check if the note itself is visually near the hit line at the moment of swipe evaluation
+ // This can be tricky because the note is moving. We rely more on its targetHitTime.
+ // However, we should only consider notes that are "active" (alpha=1)
+ if (n.alpha === 0) continue;
+ var noteCurrentWidth = n.noteAsset.width * n.scale.x;
+ var noteCurrentHeight = n.noteAsset.height * n.scale.y;
+ var noteBoundingBox = {
+ x: n.x - noteCurrentWidth / 2,
+ y: n.y - noteCurrentHeight / 2,
+ width: noteCurrentWidth,
+ height: noteCurrentHeight
+ };
+ if (rectsIntersect(swipeBoundingBox, noteBoundingBox)) {
+ if (detectedDir === n.swipeDir) {
+ // If multiple notes intersect, prefer the one closest to its targetHitTime
+ var currentNoteTimeDiff = Math.abs(swipeEndTime - n.targetHitTime); // Or average swipe time
+ if (bestSwipedNote === null || currentNoteTimeDiff < bestSwipedNoteTimeDiff) {
+ bestSwipedNote = n;
+ bestSwipedNoteTimeDiff = currentNoteTimeDiff;
+ }
+ }
+ }
+ }
+ if (bestSwipedNote) {
+ var noteToJudge = bestSwipedNote;
+ var result = noteToJudge.getHitAccuracy(); // Uses note's targetHitTime vs now (swipeEndTime)
+ noteToJudge.judged = true;
+ noteToJudge.showHitFeedback(result);
+ if (result !== 'miss') {
+ addScore(result);
+ addCombo();
+ } else {
+ resetCombo();
+ }
+ swipedNoteSuccessfully = true;
+ }
}
+ if (potentialSwipe && !swipedNoteSuccessfully) {
+ // Optional: If it was a swipe but didn't hit anything relevant,
+ // one might still want to reset combo if that's the desired game feel.
+ // For now, only a judged miss on a note resets combo.
+ }
inputLocked = true;
LK.setTimeout(function () {
inputLocked = false;
- }, 120);
+ }, 120); // Short lock to prevent accidental double inputs from one action
swipeStart = null;
};
game.move = function (x, y, obj) {
- // Potential drag handling for future, or for a different type of note
+ // Not used for now
};
game.update = function () {
- var now = Date.now();
spawnNotes();
for (var i = 0; i < notes.length; i++) {
- if (notes[i]) {
- // Check if note still exists
+ if (notes[i] && notes[i].update) {
notes[i].update();
}
}
removeOldNotes();