Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'height')' in or related to this line: 'LK.gui.top.add(new Text2('SONG ENDED', {' Line Number: 920
Code edit (11 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'height')' in or related to this line: 'playerHpBarContainer.y = LK.data.height - 80; // Pozycja Y na dole (LK.data.height to wysokość ekranu)' Line Number: 698
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'stringify')' in or related to this line: 'console.log("Swipe BBox:", JSON.stringify(swipeBoundingBox));' Line Number: 497
Code edit (6 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'stringify')' in or related to this line: 'console.log("Swipe BBox:", JSON.stringify(swipeBoundingBox));' Line Number: 497
Code edit (1 edits merged)
Please save this source code
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, centerXVal) {
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 = centerXVal;
self.centerY = 1800;
self.startY = 600;
self.noteAsset = null;
self.isWiderSwipePart = false; // Flag for wider swipe rendering if needed later
// Store original columnIndex if available, for potential debugging or advanced logic
if (this.mapData && this.mapData.originalColumnHint !== undefined) {
self.originalColumnHint = this.mapData.originalColumnHint;
}
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', {
anchorX: 0.5,
anchorY: 0.5
});
if (self.swipeDir) {
var arrow = new Text2('', {
size: 80,
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) {
// Existing circular feedback
var feedbackCircle = LK.getAsset('hitFeedback', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// Relative to the note's center
y: 0,
// Relative to the note's center
scaleX: 0.7,
scaleY: 0.7,
alpha: 0.7
});
var feedbackTextContent = "";
var feedbackTextColor = 0xFFFFFF; // Default white
if (result === 'perfect') {
feedbackCircle.tint = 0xffff00; // Yellow
feedbackTextContent = "Perfect!"; // Or "+100"
feedbackTextColor = 0xffff00;
} else if (result === 'good') {
feedbackCircle.tint = 0x00ff00; // Green
feedbackTextContent = "Good!"; // Or "+50"
feedbackTextColor = 0x00ff00;
} else {
// 'miss'
feedbackCircle.tint = 0xff0000; // Red
feedbackTextContent = "Miss";
feedbackTextColor = 0xff0000;
}
self.addChild(feedbackCircle); // Add circle to the note container
tween(feedbackCircle, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
if (feedbackCircle.parent) {
feedbackCircle.destroy();
}
}
});
// New text feedback
if (feedbackTextContent) {
var scorePopup = new Text2(feedbackTextContent, {
size: 60,
// Możemy dostosować rozmiar
fill: feedbackTextColor,
stroke: 0x000000,
// Czarny kontur dla lepszej czytelności
strokeThickness: 3
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = 0; // W centrum notatki (tam gdzie feedbackCircle)
scorePopup.y = -SWIPE_NOTE_WIDTH / 2 - 30; // Trochę nad notatką (zakładając, że notatka ma środek w 0,0)
// Możemy potrzebować dostosować Y, jeśli anchor notatki jest inny
// Add to the main game layer (or LK.gui.top) so it's not affected by note's alpha/scale changes later
// If added to self (note), it would disappear if note is quickly destroyed.
// Let's add it to the note's parent (which should be 'game')
if (self.parent) {
// Ensure note has a parent
// Position needs to be absolute if added to game layer
scorePopup.x = self.x + 0; // (self.x (Note's absolute X) + relative X (0))
scorePopup.y = self.y - SWIPE_NOTE_WIDTH * self.scale.y / 2 - 30; // (self.y (Note's absolute Y) + relative Y)
self.parent.addChild(scorePopup); // Add to the same layer as the note
}
tween(scorePopup, {
y: scorePopup.y - 80,
// Animacja w górę
alpha: 0
}, {
duration: 700,
// Dłuższy czas trwania animacji
easing: tween.easeOut,
onFinish: function onFinish() {
if (scorePopup.parent) {
scorePopup.destroy();
}
}
});
}
};
self.update = function () {
var now = Date.now();
if (self.judged) {
// If judged (hit or missed), it might still be moving for a bit
if (now > self.targetHitTime + 500) {// Time after hit/miss to be fully removed by removeOldNotes
// This note is done, removeOldNotes will handle it.
// If it was hit, its alpha might be 0 from feedback animation.
// If it was missed and had flash, it's visible.
// We can explicitly hide it here if we want it to disappear sooner than removeOldNotes.
// self.alpha = 0; // Opcjonalnie, aby szybciej zniknęła po ocenie
}
// Allow it to continue moving based on original trajectory if needed for visual effect
// For simplicity, if judged, we mostly let removeOldNotes handle it.
// However, if we want it to continue moving PAST the hit line visibly:
if (this.alpha > 0) {
// Only update position if visible
var elapsedTimeSinceSpawn = now - self.visualSpawnTime;
var currentProgress = elapsedTimeSinceSpawn / noteTravelTime;
// No clamping progress to 1 here, so it can go beyond
self.x = self.centerX;
self.y = self.startY + (self.centerY - self.startY) * currentProgress;
// Keep scale at max after passing hit line
var currentScale = currentProgress >= 1 ? self.scaleEnd : self.scaleStart + (self.scaleEnd - self.scaleStart) * currentProgress;
self.scale.x = Math.max(0.01, currentScale);
self.scale.y = Math.max(0.01, currentScale);
}
return;
}
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;
// Notatka leci dalej, więc progress może być > 1
// Nie ograniczamy już progress do 1 tutaj dla pozycji i skali
// Skala osiąga self.scaleEnd przy progress = 1 i tak pozostaje
var scale;
if (progress >= 1) {
scale = self.scaleEnd;
} else if (progress < 0) {
// Powinno być obsłużone przez visualSpawnTime check, ale dla pewności
scale = self.scaleStart;
progress = 0; // Clamp progress for position if it was somehow negative
} else {
scale = self.scaleStart + (self.scaleEnd - self.scaleStart) * progress;
}
self.scale.x = Math.max(0.01, scale);
self.scale.y = Math.max(0.01, scale);
self.x = self.centerX;
self.y = self.startY + (self.centerY - self.startY) * progress; // Pozwalamy Y iść dalej
// Oznacz jako miss, jeśli nie została oceniona i minął czas + okno tolerancji
if (!self.judged && now > self.targetHitTime + hitWindowGood) {
self.judged = true; // Oznaczona jako oceniona (w tym przypadku jako miss)
if (self.noteType !== 'trap') {
game.onNoteMiss(self); // Wywołaj logikę miss
}
// Notatka będzie dalej widoczna i usuwana przez removeOldNotes
}
};
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
****/
// HP System Variables
var playerMaxHP = 10;
var playerCurrentHP = 10;
var bossMaxHP = 30;
var bossCurrentHP = 30;
var gameOverFlag = false; // To prevent multiple game end triggers
// HP Bar UI Elements
var playerHpBarContainer;
var playerHpBarFill;
var bossHpBarContainer;
var bossHpBarFill;
var hpBarWidth = 400; // Szerokość pasków HP
var hpBarHeight = 30; // Wysokość pasków HP
var gameScreenWidth = 2048;
var playfieldWidth = 1408;
var playfieldStartX = (gameScreenWidth - playfieldWidth) / 2;
var NUM_COLUMNS = 4;
var columnWidth = playfieldWidth / NUM_COLUMNS;
var columnCenterXs = [];
for (var i = 0; i < NUM_COLUMNS; i++) {
columnCenterXs.push(playfieldStartX + i * columnWidth + columnWidth / 2);
}
var SWIPE_NOTE_WIDTH = 160;
// Function to process raw rhythm map data
function processRawRhythmMap(rawMapData, songKeyForLogging) {
console.log("Processing raw map for: " + songKeyForLogging + " with " + rawMapData.length + " initial notes.");
var processedMap = [];
// Manual deep copy for an array of simple objects
var tempMap = [];
for (var k = 0; k < rawMapData.length; k++) {
var originalNote = rawMapData[k];
var copiedNote = {};
for (var key in originalNote) {
if (originalNote.hasOwnProperty(key)) {
copiedNote[key] = originalNote[key];
}
}
tempMap.push(copiedNote);
}
// 1. Convert diagonal swipes and normalize directions
for (var i = 0; i < tempMap.length; i++) {
var note = tempMap[i];
if (note.type === 'swipe' && note.swipeDir) {
var dir = note.swipeDir.toLowerCase();
if (dir.includes('right')) {
note.swipeDir = 'right';
} else if (dir.includes('left')) {
note.swipeDir = 'left';
} else if (dir.includes('up')) {
note.swipeDir = 'up';
} else if (dir.includes('down')) {
note.swipeDir = 'down';
}
}
}
var timeGroupedNotes = {};
tempMap.forEach(function (note) {
if (!timeGroupedNotes[note.time]) {
timeGroupedNotes[note.time] = [];
}
timeGroupedNotes[note.time].push(note);
});
var finalMapNotes = [];
var sortedTimes = Object.keys(timeGroupedNotes).map(Number).sort(function (a, b) {
return a - b;
});
for (var tIdx = 0; tIdx < sortedTimes.length; tIdx++) {
var time = sortedTimes[tIdx];
var notesAtThisTime = timeGroupedNotes[time];
var notesToKeepAtThisTime = [];
var processedForWiderSwipeConversion = []; // Keep track of notes converted from vertical pairs
var colsWithVerticalSwipes = [null, null, null, null];
notesAtThisTime.forEach(function (note) {
if (note.type === 'swipe' && (note.swipeDir === 'up' || note.swipeDir === 'down')) {
if (note.columnIndex >= 0 && note.columnIndex < NUM_COLUMNS) {
// Only add if not already part of a wider swipe conversion that happened
var alreadyConverted = false;
for (var convIdx = 0; convIdx < processedForWiderSwipeConversion.length; convIdx++) {
if (processedForWiderSwipeConversion[convIdx].originalTime === note.time && processedForWiderSwipeConversion[convIdx].originalColumn === note.columnIndex) {
alreadyConverted = true;
break;
}
}
if (!alreadyConverted) {
colsWithVerticalSwipes[note.columnIndex] = note.swipeDir;
}
}
}
});
for (var c = 0; c < NUM_COLUMNS - 1; c++) {
if (colsWithVerticalSwipes[c] && colsWithVerticalSwipes[c + 1] && colsWithVerticalSwipes[c] === colsWithVerticalSwipes[c + 1]) {
var randomHorizontalDir = Math.random() < 0.5 ? 'left' : 'right';
var pairCenterX = (columnCenterXs[c] + columnCenterXs[c + 1]) / 2;
notesToKeepAtThisTime.push({
time: time,
type: 'swipe',
swipeDir: randomHorizontalDir,
partOfWiderSwipe: 'leftHalf',
widerSwipePairCenterX: pairCenterX,
originalColumnHint: c
});
notesToKeepAtThisTime.push({
time: time,
type: 'swipe',
swipeDir: randomHorizontalDir,
partOfWiderSwipe: 'rightHalf',
widerSwipePairCenterX: pairCenterX,
originalColumnHint: c + 1
});
// Add to a temporary list to mark them as "used" by this conversion
processedForWiderSwipeConversion.push({
originalTime: time,
originalColumn: c
});
processedForWiderSwipeConversion.push({
originalTime: time,
originalColumn: c + 1
});
colsWithVerticalSwipes[c] = null;
colsWithVerticalSwipes[c + 1] = null;
c++;
}
}
notesAtThisTime.forEach(function (note) {
var wasConverted = false;
for (var convIdx = 0; convIdx < processedForWiderSwipeConversion.length; convIdx++) {
if (processedForWiderSwipeConversion[convIdx].originalTime === note.time && processedForWiderSwipeConversion[convIdx].originalColumn === note.columnIndex && (note.swipeDir === 'up' || note.swipeDir === 'down')) {
// Check if it was one of the vertical notes
wasConverted = true;
break;
}
}
if (!wasConverted) {
notesToKeepAtThisTime.push(note);
}
});
var horizontalWiderSwipePairs = {};
var notesForFinalProcessing = [];
// Filter out duplicates before further processing this time slot's notes
var uniqueNotesAtThisTime = [];
var seenNotesKeysAtThisTime = {};
notesToKeepAtThisTime.forEach(function (note) {
var key = "" + note.type + "_" + (note.columnIndex !== undefined ? note.columnIndex : note.partOfWiderSwipe ? note.widerSwipePairCenterX + note.partOfWiderSwipe : '') + "_" + (note.swipeDir || '');
if (!seenNotesKeysAtThisTime[key]) {
uniqueNotesAtThisTime.push(note);
seenNotesKeysAtThisTime[key] = true;
}
});
uniqueNotesAtThisTime.sort(function (a, b) {
// Sort by original column hint if available, or column index, or widerSwipePairCenterX
var valA = a.originalColumnHint !== undefined ? a.originalColumnHint : a.columnIndex !== undefined ? a.columnIndex : a.widerSwipePairCenterX || 0;
var valB = b.originalColumnHint !== undefined ? b.originalColumnHint : b.columnIndex !== undefined ? b.columnIndex : b.widerSwipePairCenterX || 0;
return valA - valB;
});
for (var nIdx = 0; nIdx < uniqueNotesAtThisTime.length; nIdx++) {
var note = uniqueNotesAtThisTime[nIdx];
if (note.partOfWiderSwipe) {
notesForFinalProcessing.push(note);
continue;
}
var potentialPartner = null;
if (nIdx + 1 < uniqueNotesAtThisTime.length) {
potentialPartner = uniqueNotesAtThisTime[nIdx + 1];
}
if (note.type === 'swipe' && (note.swipeDir === 'left' || note.swipeDir === 'right') && potentialPartner && potentialPartner.type === 'swipe' && potentialPartner.time === note.time &&
// Ensure same time for partner
potentialPartner.swipeDir === note.swipeDir && potentialPartner.columnIndex === note.columnIndex + 1) {
var pairCenterX = (columnCenterXs[note.columnIndex] + columnCenterXs[potentialPartner.columnIndex]) / 2;
notesForFinalProcessing.push({
time: note.time,
type: 'swipe',
swipeDir: note.swipeDir,
partOfWiderSwipe: 'leftHalf',
widerSwipePairCenterX: pairCenterX,
originalColumnHint: note.columnIndex
});
notesForFinalProcessing.push({
time: potentialPartner.time,
type: 'swipe',
swipeDir: potentialPartner.swipeDir,
partOfWiderSwipe: 'rightHalf',
widerSwipePairCenterX: pairCenterX,
originalColumnHint: potentialPartner.columnIndex
});
nIdx++;
} else {
notesForFinalProcessing.push(note);
}
}
finalMapNotes.push.apply(finalMapNotes, notesForFinalProcessing);
}
var uniqueNotesOverall = [];
var seenNotesOverall = {};
finalMapNotes.sort(function (a, b) {
return a.time - b.time;
});
finalMapNotes.forEach(function (note) {
var cX;
var keyPartForColumn;
if (note.partOfWiderSwipe) {
// For wider swipe parts, their exact X is calculated, so use that for uniqueness.
// Also include which half it is to differentiate parts of the same pair.
cX = note.widerSwipePairCenterX + (note.partOfWiderSwipe === 'leftHalf' ? -(SWIPE_NOTE_WIDTH / 2) : SWIPE_NOTE_WIDTH / 2);
keyPartForColumn = "pC" + note.widerSwipePairCenterX + "h" + (note.partOfWiderSwipe === 'leftHalf' ? 'L' : 'R');
} else if (note.columnIndex !== undefined) {
cX = columnCenterXs[note.columnIndex]; // Should exist if columnIndex is valid
keyPartForColumn = "c" + note.columnIndex;
} else {
// Should not happen if map is well-formed by this stage
cX = gameScreenWidth / 2;
keyPartForColumn = "cX" + cX;
}
var key = "" + note.time + "_" + note.type + "_" + keyPartForColumn + "_" + (note.swipeDir || '');
if (!seenNotesOverall[key]) {
uniqueNotesOverall.push(note);
seenNotesOverall[key] = true;
} else {
// console.log("Filtered final duplicate note: " + key);
}
});
console.log("Processed map for: " + songKeyForLogging + " FINALLY contains " + uniqueNotesOverall.length + " notes.");
return uniqueNotesOverall;
}
var allSongData = {
"defaultTestTrack": {
musicAsset: null,
config: {
// Dodajemy konfigurację HP
playerMaxHP: 10,
bossMaxHP: 20 // Domyślne dla testowego toru
},
rawRhythmMap: [
// Now stores raw map, will be processed by loadSong
{
time: 1000,
type: 'tap',
columnIndex: 0
}, {
time: 1800,
type: 'tap',
columnIndex: 1
}, {
time: 2600,
type: 'swipe',
swipeDir: 'left',
columnIndex: 2
}, {
time: 3400,
type: 'tap',
columnIndex: 3
}, {
time: 4200,
type: 'swipe',
swipeDir: 'right',
columnIndex: 0
}, {
time: 5000,
type: 'trap',
columnIndex: 1
}, {
time: 5800,
type: 'tap',
columnIndex: 2
}, {
time: 6600,
type: 'swipe',
swipeDir: 'right',
// This will become widerSwipe
columnIndex: 1 // Original column for left part of potential pair
}, {
time: 6600,
type: 'swipe',
swipeDir: 'right',
columnIndex: 2 // Original column for right part of potential pair
}, {
time: 7400,
type: 'tap',
columnIndex: 0
}]
},
"test3": {
musicAsset: "test3",
config: {
// Dodajemy konfigurację HP
playerMaxHP: 10,
bossMaxHP: 30
}
}
// Placeholder for test1 and test2
// "test1_raw": { musicAsset: "test1", rawRhythmMap: [ /* ... map data ... */ ] },
// "test2_raw": { musicAsset: "test2", rawRhythmMap: [ /* ... map data ... */ ] }
};
var currentActiveRhythmMap = null;
var currentMusic = null;
var noteTravelTime = 2200;
var hitWindowPerfect = 190;
var hitWindowGood = 260;
var MIN_SWIPE_DISTANCE = 60;
var notes = [];
var nextNoteIdx = 0;
var gameStartTime = 0;
var score = 0;
var combo = 0;
var maxCombo = 0;
var swipeStart = null;
var inputLocked = false;
var hpBarsInitialized = false;
var scoreTxt = new Text2('Score: 0', {
size: 100,
// Możemy dostosować, jeśli za duże/małe
fill: 0xFFFFFF,
// Biały
alpha: 1
});
scoreTxt.anchor.set(0.5, 0); // Kotwiczenie na środku obiektu Tekstowego
scoreTxt.x = 0;
scoreTxt.y = 0; // Trochę niżej niż pierwotne 20, aby dać więcej miejsca od góry
LK.gui.top.addChild(scoreTxt);
console.log("Restored scoreTxt: X=" + scoreTxt.x + " Y=" + scoreTxt.y + " Visible:" + scoreTxt.visible);
var comboTxt = new Text2('Combo: 0', {
size: 80,
fill: 0xFFFF00,
// Żółty
alpha: 1
});
comboTxt.anchor.set(0.5, 0.5); // Kotwiczenie na środku obiektu Tekstowego
comboTxt.x = gameScreenWidth / 2;
comboTxt.y = scoreTxt.y + scoreTxt.height / 2 + 60; // Pozycjonowanie poniżej scoreTxt, uwzględniając jego wysokość
LK.gui.top.addChild(comboTxt);
console.log("Restored comboTxt: X=" + comboTxt.x + " Y=" + comboTxt.y + " Visible:" + comboTxt.visible);
var hitZoneY = 1800;
var hitZoneVisualWidth = playfieldWidth;
var hitZoneLine = LK.getAsset('lineAsset', {
anchorX: 0.5,
anchorY: 0.5,
x: gameScreenWidth / 2,
y: hitZoneY,
width: hitZoneVisualWidth,
height: 4,
alpha: 0.6
});
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');
// Reset HP System
gameOverFlag = false;
// playerMaxHP i bossMaxHP są teraz ustawiane w loadSong z konfiguracji piosenki
playerCurrentHP = playerMaxHP;
bossCurrentHP = bossMaxHP;
// Upewnij się, że paski HP są gotowe przed aktualizacją
if (playerHpBarFill && bossHpBarFill) {
updatePlayerHpDisplay();
updateBossHpDisplay();
}
}
function loadSong(songKey) {
var songData = allSongData[songKey];
if (!songData) {
console.log("Error: Song data not found for key: " + songKey);
if (allSongData["defaultTestTrack"]) {
songData = allSongData["defaultTestTrack"];
console.log("Fallback to defaultTestTrack");
} else {
currentActiveRhythmMap = [];
console.log("No fallback song data found.");
return;
}
}
if (songData.config) {
playerMaxHP = songData.config.playerMaxHP || 10;
bossMaxHP = songData.config.bossMaxHP || 50;
} else {
playerMaxHP = 10;
bossMaxHP = 50;
}
if (currentMusic && typeof currentMusic.stop === 'function') {
currentMusic.stop();
}
currentMusic = null;
hpBarsInitialized = false;
resetGameState();
nextNoteIdx = 0;
if (songData.rawRhythmMap) {
currentActiveRhythmMap = processRawRhythmMap(songData.rawRhythmMap, songKey);
} else {
currentActiveRhythmMap = [];
console.log("Warning: No rawRhythmMap for song: " + songKey + ". Game will have no notes.");
}
gameStartTime = Date.now();
if (songData.musicAsset) {
currentMusic = LK.getSound(songData.musicAsset);
if (currentMusic && typeof currentMusic.play === 'function') {
currentMusic.play();
console.log("Music asset for " + songKey + " PLAYING. gameStartTime: " + gameStartTime);
} else {
console.log("Warning: Music asset '" + songData.musicAsset + "' not found or not playable.");
}
} else {
console.log("No musicAsset defined for " + songKey + ". Map will run on system time only.");
}
console.log("Loaded and processed song: " + songKey + " PlayerHP: " + playerMaxHP + " BossHP: " + bossMaxHP);
}
function setupHpBars() {
// Boss HP Bar (Top Center)
if (bossHpBarContainer) {
bossHpBarContainer.destroy();
} // Usuń stary, jeśli istnieje
bossHpBarContainer = new Container();
bossHpBarContainer.x = gameScreenWidth / 2;
bossHpBarContainer.y = 50; // Pozycja Y na górze
LK.gui.top.addChild(bossHpBarContainer);
var bossBg = LK.getAsset('hpBarBackground', {
width: hpBarWidth,
height: hpBarHeight,
anchorX: 0.5,
anchorY: 0.5
});
bossHpBarContainer.addChild(bossBg);
bossHpBarFill = LK.getAsset('bossHpFill', {
width: hpBarWidth,
height: hpBarHeight,
anchorX: 0,
// Kotwica po lewej dla łatwej zmiany szerokości
anchorY: 0.5,
x: -hpBarWidth / 2 // Pozycja X, aby zaczynał się od lewej krawędzi tła
});
bossHpBarContainer.addChild(bossHpBarFill);
// Player HP Bar (Bottom Center)
if (playerHpBarContainer) {
playerHpBarContainer.destroy();
} // Usuń stary, jeśli istnieje
playerHpBarContainer = new Container();
playerHpBarContainer.x = gameScreenWidth / 2;
// Calculate a fallback height for the game area (iPad Pro 12" retina: 2048x2732)
var gameScreenHeight = Math.round(gameScreenWidth * (2732 / 2048));
playerHpBarContainer.y = gameScreenHeight - 80; // Pozycja Y na dole
LK.gui.top.addChild(playerHpBarContainer);
var playerBg = LK.getAsset('hpBarBackground', {
width: hpBarWidth,
height: hpBarHeight,
anchorX: 0.5,
anchorY: 0.5
});
playerHpBarContainer.addChild(playerBg);
playerHpBarFill = LK.getAsset('playerHpFill', {
width: hpBarWidth,
height: hpBarHeight,
anchorX: 0,
// Kotwica po lewej
anchorY: 0.5,
x: -hpBarWidth / 2
});
playerHpBarContainer.addChild(playerHpBarFill);
updateBossHpDisplay();
updatePlayerHpDisplay();
}
function updatePlayerHpDisplay() {
if (playerHpBarFill) {
var healthPercent = playerCurrentHP / playerMaxHP;
playerHpBarFill.width = hpBarWidth * Math.max(0, healthPercent);
}
}
function updateBossHpDisplay() {
if (bossHpBarFill) {
var healthPercent = bossCurrentHP / bossMaxHP;
bossHpBarFill.width = hpBarWidth * Math.max(0, healthPercent);
}
}
function spawnNotes() {
var now = Date.now();
if (!currentActiveRhythmMap) {
return;
}
while (nextNoteIdx < currentActiveRhythmMap.length) {
var noteData = currentActiveRhythmMap[nextNoteIdx]; // Use processed map
var noteTargetHitTime = gameStartTime + noteData.time;
if (noteTargetHitTime - noteTravelTime <= now) {
var targetCenterX;
if (noteData.partOfWiderSwipe && noteData.widerSwipePairCenterX !== undefined) {
if (noteData.partOfWiderSwipe === 'leftHalf') {
targetCenterX = noteData.widerSwipePairCenterX - SWIPE_NOTE_WIDTH / 2;
} else if (noteData.partOfWiderSwipe === 'rightHalf') {
targetCenterX = noteData.widerSwipePairCenterX + SWIPE_NOTE_WIDTH / 2;
} else {
// Fallback if partOfWiderSwipe has unexpected value, use original column if available
targetCenterX = columnCenterXs[noteData.originalColumnHint !== undefined ? noteData.originalColumnHint : Math.floor(NUM_COLUMNS / 2)];
}
} else if (noteData.columnIndex !== undefined && noteData.columnIndex >= 0 && noteData.columnIndex < NUM_COLUMNS) {
targetCenterX = columnCenterXs[noteData.columnIndex];
} else {
targetCenterX = playfieldStartX + playfieldWidth / 2;
}
// Pass original noteData to Note constructor for potential debugging/reference if needed
var n = new Note(noteData.type, noteData.swipeDir, noteTargetHitTime, targetCenterX);
n.mapData = noteData; // Attach original map data to the note instance
if (noteData.partOfWiderSwipe) {
n.isWiderSwipePart = true;
}
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];
var timeToRemoveAfterJudged = 700; // ms po targetHitTime dla ocenionych notatek
var timeToRemoveIfNotJudged = noteTravelTime / 2 + hitWindowGood + 500; // Dłuższy czas, jeśli nieoceniona, liczony od targetHitTime
if (n.judged && now > n.targetHitTime + timeToRemoveAfterJudged) {
if (n.parent) {
n.destroy();
}
notes.splice(i, 1);
} else if (!n.judged && now > n.targetHitTime + timeToRemoveIfNotJudged) {
if (n.noteType !== 'trap') {}
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 currentNoteAsset = n.noteAsset;
if (!currentNoteAsset) {
continue;
}
var currentNoteWidth = currentNoteAsset.width * n.scale.x;
var currentNoteHeight = currentNoteAsset.height * n.scale.y;
if (n.noteType === 'swipe') {
currentNoteWidth = SWIPE_NOTE_WIDTH * n.scale.x;
currentNoteHeight = SWIPE_NOTE_WIDTH * n.scale.y;
}
var dx = x - n.x;
var dy = y - n.y;
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);
// Animacja dla comboTxt
if (combo > 1) {
// Mała animacja tylko jeśli combo jest większe niż 1
var originalScale = comboTxt.scale.x; // Zakładamy, że skala X i Y jest taka sama
tween(comboTxt.scale, {
x: originalScale * 1.3,
y: originalScale * 1.3
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(comboTxt.scale, {
x: originalScale,
y: originalScale
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
}
function resetCombo() {
combo = 0;
comboTxt.setText('Combo: 0');
}
function checkGameEnd() {
if (gameOverFlag) {
// Jeśli gra już się zakończyła, nic nie rób
return;
}
// Warunek Przegranej Gracza
if (playerCurrentHP <= 0) {
gameOverFlag = true;
console.log("GAME OVER - Player HP depleted");
if (currentMusic && typeof currentMusic.stop === 'function') {
currentMusic.stop();
}
// Tutaj możesz dodać specjalny ekran przegranej, np. LK.showGameOver();
// Na razie użyjemy showYouWin jako placeholder dla dowolnego ekranu końca
LK.gui.top.add(new Text2('GAME OVER', {
x: gameScreenWidth / 2,
y: LK.data.height / 2,
anchorX: 0.5,
size: 100,
fill: 0xff0000
}));
// Można też zablokować dalsze spawnowanie notatek, np. ustawiając nextNoteIdx na dużą wartość
// lub dodając flagę do spawnNotes()
return; // Zakończ dalsze sprawdzanie
}
// Warunek Wygranej (Boss Pokonany)
if (bossCurrentHP <= 0) {
gameOverFlag = true;
console.log("YOU WIN - Boss HP depleted");
if (currentMusic && typeof currentMusic.stop === 'function') {
currentMusic.stop();
}
LK.showYouWin(); // Użyj standardowego ekranu wygranej
// Można też zablokować dalsze spawnowanie
return;
}
// Oryginalny warunek końca piosenki (jeśli nadal istotny, np. boss ucieka)
// Na razie, jeśli piosenka się skończy, a boss wciąż żyje (i gracz też), gra po prostu się kończy.
// Można by tu dodać np. "Boss Escaped!"
if (currentActiveRhythmMap && nextNoteIdx >= currentActiveRhythmMap.length && notes.length === 0) {
if (!gameOverFlag) {
// Jeśli gra się jeszcze nie zakończyła przez HP
gameOverFlag = true; // Oznaczamy koniec gry
console.log("SONG ENDED - Boss might still be alive");
if (currentMusic && typeof currentMusic.stop === 'function') {
currentMusic.stop();
}
// Możesz tu dodać inny ekran, np. "Song Cleared"
// Jeśli nie było wygranej/przegranej przez HP, a piosenka się skończyła.
// Calculate a fallback height for the game area (iPad Pro 12" retina: 2048x2732)
var gameScreenHeight = Math.round(gameScreenWidth * (2732 / 2048));
LK.gui.top.add(new Text2('SONG ENDED', {
x: gameScreenWidth / 2,
y: gameScreenHeight / 2,
anchorX: 0.5,
size: 100,
fill: 0xffff00
}));
}
}
}
game.onNoteMiss = function (note) {
if (!note || note.judged) {
return;
}
note.judged = true;
note.showHitFeedback('miss');
resetCombo();
// Player HP Loss
if (!gameOverFlag) {
// Tylko jeśli gra się jeszcze nie skończyła
playerCurrentHP = Math.max(0, playerCurrentHP - 1);
updatePlayerHpDisplay();
console.log("Player HP after miss: " + playerCurrentHP);
// Warunek przegranej zostanie sprawdzony w checkGameEnd()
}
if (note.parent) {
LK.effects.flashObject(note, 0xff0000, 300);
}
};
game.down = function (x, y, obj) {
if (inputLocked) {
return;
}
swipeStart = {
x: x,
y: y,
time: Date.now()
};
var noteUnderCursor = findNoteAt(x, y, 'trap');
if (noteUnderCursor && !noteUnderCursor.judged && noteUnderCursor.isInHitWindow()) {
noteUnderCursor.judged = true;
noteUnderCursor.showHitFeedback('miss'); // Traps show as 'miss' for feedback
resetCombo();
LK.effects.flashScreen(0xff0000, 400);
// Player HP Loss for Trap
if (!gameOverFlag) {
playerCurrentHP = Math.max(0, playerCurrentHP - 1);
updatePlayerHpDisplay();
console.log("Player HP after TRAP: " + playerCurrentHP);
}
inputLocked = true;
LK.setTimeout(function () {
inputLocked = false;
}, 200);
return;
}
noteUnderCursor = findNoteAt(x, y, 'tap');
if (noteUnderCursor && !noteUnderCursor.judged && noteUnderCursor.isInHitWindow()) {
var result = noteUnderCursor.getHitAccuracy();
noteUnderCursor.judged = true;
noteUnderCursor.showHitFeedback(result);
if (result !== 'miss') {
addScore(result);
addCombo();
// Boss HP Damage for Swipe
if (!gameOverFlag) {
if (result === 'perfect') {
bossCurrentHP = Math.max(0, bossCurrentHP - 2);
} else if (result === 'good') {
bossCurrentHP = Math.max(0, bossCurrentHP - 1);
}
updateBossHpDisplay();
console.log("Boss HP after SWIPE (" + result + "): " + bossCurrentHP);
}
} else {
resetCombo();
// Player HP Loss for missed swipe (if judged as miss here)
// Similar to tap, if a swipe is judged "miss" here instead of timing out,
// we might need an explicit penalty. For now, onNoteMiss handles timed-out misses.
}
swipedNoteSuccessfully = true; // Ten fragment już tam był
notesActuallyHitCount++; // Ten fragment już tam był
}
};
game.up = function (x, y, obj) {
if (inputLocked || !swipeStart) {
// console.log("Swipe Rejected: Input locked or no swipe start");
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);
console.log("--- Swipe Action END ---");
console.log("Swipe Start (X,Y,Time): " + swipeStart.x + "," + swipeStart.y + "," + swipeStart.time);
console.log("Swipe End (X,Y,Time): " + swipeEndX + "," + swipeEndY + "," + swipeEndTime);
console.log("Swipe Delta (dX,dY,Dist): " + dx + "," + dy + "," + dist);
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';
}
console.log("Potential Swipe. Detected Dir: " + detectedDir);
var swipeBoundingBox = {
x: Math.min(swipeStart.x, swipeEndX),
y: Math.min(swipeStart.y, swipeEndY),
width: Math.abs(dx),
height: Math.abs(dy)
};
console.log("Swipe BBox: x=" + swipeBoundingBox.x + ", y=" + swipeBoundingBox.y + ", w=" + swipeBoundingBox.width + ", h=" + swipeBoundingBox.height);
var notesHitThisSwipe = [];
for (var i = 0; i < notes.length; i++) {
var n = notes[i];
if (n.judged || n.noteType !== 'swipe') {
continue;
}
console.log("Checking Note Idx " + i + " (TargetTime: " + n.targetHitTime + ", Dir: " + n.swipeDir + ", X: " + n.x + ", Y: " + n.y + ", Scale: " + n.scale.x + ", Alpha: " + n.alpha + ")");
var overallSwipeTimeMatchesNote = false;
// Check if the note's target hit time falls within the swipe's duration, with tolerance
if (n.targetHitTime >= swipeStart.time - hitWindowGood && n.targetHitTime <= swipeEndTime + hitWindowGood) {
overallSwipeTimeMatchesNote = true;
}
// Also consider if the swipe itself (its start or end) happens within the note's broader hit window
// This helps for notes that should be hit AT the start or AT the end of a swipe.
if (!overallSwipeTimeMatchesNote) {
if (swipeStart.time >= n.targetHitTime - hitWindowGood && swipeStart.time <= n.targetHitTime + hitWindowGood || swipeEndTime >= n.targetHitTime - hitWindowGood && swipeEndTime <= n.targetHitTime + hitWindowGood) {
overallSwipeTimeMatchesNote = true;
}
}
console.log(" Note " + i + " - overallSwipeTimeMatchesNote: " + overallSwipeTimeMatchesNote);
if (!overallSwipeTimeMatchesNote) {
continue;
}
if (n.alpha === 0) {
console.log(" Note " + i + " - REJECTED (alpha is 0)");
continue;
}
var noteCurrentWidth = SWIPE_NOTE_WIDTH * n.scale.x;
var noteCurrentHeight = SWIPE_NOTE_WIDTH * n.scale.y;
var noteBoundingBox = {
x: n.x - noteCurrentWidth / 2,
y: n.y - noteCurrentHeight / 2,
width: noteCurrentWidth,
height: noteCurrentHeight
};
console.log(" Note " + i + " BBox: x=" + noteBoundingBox.x + ", y=" + noteBoundingBox.y + ", w=" + noteBoundingBox.width + ", h=" + noteBoundingBox.height);
var intersects = rectsIntersect(swipeBoundingBox, noteBoundingBox);
console.log(" Note " + i + " - rectsIntersect: " + intersects);
if (intersects) {
console.log(" Note " + i + " - Detected Dir: " + detectedDir + ", Note Dir: " + n.swipeDir);
if (detectedDir === n.swipeDir) {
var verticalProximity = Math.abs(n.y - n.centerY);
var verticalTolerance = noteCurrentHeight / 1.5; // How close to hit line note should be
console.log(" Note " + i + " - Vertical Proximity: " + verticalProximity + " (Tolerance: " + verticalTolerance + ")");
if (verticalProximity < verticalTolerance) {
console.log(" Note " + i + " - PASSED ALL CHECKS. Added to potential hits.");
notesHitThisSwipe.push(n);
} else {
console.log(" Note " + i + " - FAILED Vertical Proximity Check.");
}
} else {
console.log(" Note " + i + " - FAILED Direction Check.");
}
}
}
if (notesHitThisSwipe.length > 0) {
console.log("Processing " + notesHitThisSwipe.length + " notes initially hit by this swipe.");
notesHitThisSwipe.sort(function (a, b) {
var da = Math.abs(swipeEndTime - a.targetHitTime);
var db = Math.abs(swipeEndTime - b.targetHitTime);
return da - db;
});
var maxNotesToHitPerSwipe = 1;
if (notesHitThisSwipe.length > 0 && notesHitThisSwipe[0].isWiderSwipePart) {
// Check the best candidate
maxNotesToHitPerSwipe = 2;
}
var notesActuallyHitCount = 0;
for (var k = 0; k < notesHitThisSwipe.length && notesActuallyHitCount < maxNotesToHitPerSwipe; k++) {
var noteToJudge = notesHitThisSwipe[k];
if (noteToJudge.judged) {
continue;
}
if (notesActuallyHitCount === 1 && notesHitThisSwipe[0].isWiderSwipePart) {
if (!noteToJudge.isWiderSwipePart || noteToJudge.mapData.widerSwipePairCenterX !== notesHitThisSwipe[0].mapData.widerSwipePairCenterX || noteToJudge.mapData.partOfWiderSwipe === notesHitThisSwipe[0].mapData.partOfWiderSwipe) {
console.log(" Skipping note in wider swipe check - not matching pair for first hit note.");
continue;
}
}
var result = noteToJudge.getHitAccuracy();
console.log(" JUDGING Note Hit! Result: " + result + " for note at X=" + noteToJudge.x + ", Y=" + noteToJudge.y);
noteToJudge.judged = true;
noteToJudge.showHitFeedback(result);
if (result !== 'miss') {
addScore(result);
addCombo();
} else {
resetCombo();
}
swipedNoteSuccessfully = true;
notesActuallyHitCount++;
}
if (notesActuallyHitCount === 0 && notesHitThisSwipe.length > 0) {
console.log(" Notes were candidates, but none met final hit criteria (e.g. wider swipe pair logic).");
}
} else {
console.log("No notes candidates after iterating.");
}
} else {
console.log("Swipe too short (Dist: " + dist + ", Min: " + MIN_SWIPE_DISTANCE + ")");
}
inputLocked = true;
LK.setTimeout(function () {
inputLocked = false;
}, 80);
swipeStart = null;
};
game.move = function (x, y, obj) {};
game.update = function () {
if (!hpBarsInitialized) {
setupHpBars();
hpBarsInitialized = true;
}
spawnNotes();
for (var i = 0; i < notes.length; i++) {
if (notes[i] && notes[i].update) {
notes[i].update();
}
}
removeOldNotes();
checkGameEnd();
};
// Load the specific song for testing
loadSong("defaultTestTrack");
// loadSong("defaultTestTrack"); // Możesz to odkomentować, aby załadować starą mapę testową ===================================================================
--- original.js
+++ change.js
@@ -607,21 +607,19 @@
}
}
function loadSong(songKey) {
var songData = allSongData[songKey];
- if (!songData /* USUNIĘTO: || !songData.rawRhythmMap - bo test3 nie ma teraz mapy, a defaultTestTrack może mieć */) {
- // Sprawdzamy tylko czy songData istnieje
+ if (!songData) {
console.log("Error: Song data not found for key: " + songKey);
- if (allSongData["defaultTestTrack"] /* USUNIĘTO: && allSongData["defaultTestTrack"].rawRhythmMap */) {
+ if (allSongData["defaultTestTrack"]) {
songData = allSongData["defaultTestTrack"];
console.log("Fallback to defaultTestTrack");
} else {
currentActiveRhythmMap = [];
console.log("No fallback song data found.");
return;
}
}
- // Ustaw Max HP z konfiguracji piosenki lub wartości domyślne
if (songData.config) {
playerMaxHP = songData.config.playerMaxHP || 10;
bossMaxHP = songData.config.bossMaxHP || 50;
} else {
@@ -631,17 +629,15 @@
if (currentMusic && typeof currentMusic.stop === 'function') {
currentMusic.stop();
}
currentMusic = null;
- hpBarsInitialized = false; // Resetuj flagę!
+ hpBarsInitialized = false;
resetGameState();
- // USUNIĘTO STĄD: setupHpBars();
nextNoteIdx = 0;
- // Jeśli rawRhythmMap nie istnieje dla piosenki, ustaw pustą mapę
if (songData.rawRhythmMap) {
currentActiveRhythmMap = processRawRhythmMap(songData.rawRhythmMap, songKey);
} else {
- currentActiveRhythmMap = []; // Pusta mapa, jeśli nie zdefiniowano
+ currentActiveRhythmMap = [];
console.log("Warning: No rawRhythmMap for song: " + songKey + ". Game will have no notes.");
}
gameStartTime = Date.now();
if (songData.musicAsset) {
@@ -904,11 +900,13 @@
currentMusic.stop();
}
// Możesz tu dodać inny ekran, np. "Song Cleared"
// Jeśli nie było wygranej/przegranej przez HP, a piosenka się skończyła.
+ // Calculate a fallback height for the game area (iPad Pro 12" retina: 2048x2732)
+ var gameScreenHeight = Math.round(gameScreenWidth * (2732 / 2048));
LK.gui.top.add(new Text2('SONG ENDED', {
x: gameScreenWidth / 2,
- y: LK.data.height / 2,
+ y: gameScreenHeight / 2,
anchorX: 0.5,
size: 100,
fill: 0xffff00
}));
@@ -1130,8 +1128,12 @@
swipeStart = null;
};
game.move = function (x, y, obj) {};
game.update = function () {
+ if (!hpBarsInitialized) {
+ setupHpBars();
+ hpBarsInitialized = true;
+ }
spawnNotes();
for (var i = 0; i < notes.length; i++) {
if (notes[i] && notes[i].update) {
notes[i].update();