Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var frameSprite = LK.getAsset(frameAsset.assetId, {' Line Number: 1005
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'set')' in or related to this line: 'self.anchor.set(options.anchorX, options.anchorY);' Line Number: 997
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'set')' in or related to this line: 'self.anchor.set(options.anchorX, options.anchorY);' Line Number: 997
Code edit (7 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: self.startAttackPattern is not a function' in or related to this line: 'self.startAttackPattern();' Line Number: 588
Code edit (8 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'visual')' in or related to this line: 'if (attack.visual && typeof attack.visual.update === 'function') {' Line Number: 603
User prompt
Please fix the bug: 'ReferenceError: createAttack is not defined' in or related to this line: 'var attack = createAttack('circle');' Line Number: 358
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: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.playerGraphics);' Line Number: 262
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.playerGraphics);' Line Number: 262
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.playerGraphics);' Line Number: 262
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.playerGraphics);' Line Number: 262
User prompt
Please fix the bug: 'self.attachAsset is not a function' in or related to this line: 'self.playerGraphics = self.attachAsset('player', {' Line Number: 259
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
User prompt
Please fix the bug: 'this.addChildAt is not a function' in or related to this line: 'Container.prototype.addChild.call(self, self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.addChild(self.bossGraphics); // Add the bossGraphics as a child' Line Number: 657
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
var Boss = Container.expand(function () {
var self = Container.call(this);
// Definicje list klatek dla fireballi
var circleFrames = [LK.getAsset('fireball0', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball00', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball01', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball02', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball03', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball04', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball05', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball06', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball07', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball08', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball09', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball1', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball10', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball11', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball12', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball13', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball14', {
anchorX: 0.5,
anchorY: 0.5
})];
var lineFrames = [LK.getAsset('fireball2', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball3', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball4', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball5', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball6', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball7', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball8', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball9', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball15', {
anchorX: 0.5,
anchorY: 0.5
}), LK.getAsset('fireball16', {
anchorX: 0.5,
anchorY: 0.5
})];
// Używamy attachAsset, który już dodaje jako dziecko i ustawia pozycję relatywną na podstawie anchor.
self.bossGraphics = self.attachAsset('bossIdle', {
// Dziecko self
anchorX: 0.5,
anchorY: 0.5
// Pozycja relatywna na 0,0 jest domyślna i poprawna przy anchorX/Y 0.5
});
// Dodajemy funkcje animacji bossa (NIE ataków fireballi)
// Ta funkcja tworzy obiekt SpriteAnimation dla animacji ataku Bossa (nie fireballi)
self.createBossAttackAnim = function () {
var frames = [LK.getAsset('bossAttack0', {}), LK.getAsset('bossAttack1', {}), LK.getAsset('bossAttack2', {}), LK.getAsset('bossAttack3', {})];
var bossAttackAnim = new SpriteAnimation({
frames: frames,
frameDuration: 100,
// Czas trwania klatki animacji ataku Bossa (w ms)
loop: false,
anchorX: 0.5,
anchorY: 0.5,
// Pozycja animacji jako dziecka bossa - ustawiamy na 0,0 relatywnie do rodzica (self)
x: 0,
y: 0
});
return bossAttackAnim;
};
// Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle)
self.playBossAttackAnim = function (attackType) {
// Upewnij się, że poprzednia animacja ataku Bossa jest całkowicie usunięta, zanim zaczniemy nową
if (self.bossAttackAnim) {
self.bossAttackAnim.stop(); // Zatrzymujemy animację
// Usuń poprzednią animację z jej rodzica (Bossa) przed zniszczeniem
if (self.bossAttackAnim.parent === self) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
} else if (self.bossAttackAnim.parent) {
// Fallback na wypadek, gdyby rodzic był inny
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy(); // Usuwamy poprzednią animację
self.bossAttackAnim = null; // Ustawiamy na null po zniszczeniu
}
// *** Zmieniona logika: Zmiana grafiki bossa tylko jeśli to NIE jest atak szarży (attackType 2) ***
if (attackType !== 2) {
// Jeśli to atak koła (0) lub linii (1)
// Usuń obecną główną grafikę bossa (idle) jako dziecko bossa
if (self.bossGraphics && self.bossGraphics.parent === self) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
} else if (self.bossGraphics && self.bossGraphics.parent) {
// Fallback
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = null; // Wyczyść referencję
// Tworzymy nową animację bossa i dodajemy ją JAKO DZIECKO BOSSA
self.bossAttackAnim = self.addChild(self.createBossAttackAnim());
// Pozycja animacji jako dziecka bossa jest już ustawiona na 0,0 w createBossAttackAnim
// Metoda update dla TEJ NOWEJ animacji (definiowana tylko dla ataków 0 i 1)
self.bossAttackAnim.update = function () {
// Sprawdź, czy ten obiekt animacji jest nadal aktywny self.bossAttackAnim bossa
// Użyj 'this' dla obiektu animacji, 'self' dla obiektu Boss
if (self.bossAttackAnim !== this || !this.playing || this.frames.length === 0) {
// Jeśli już nie jesteśmy aktualną animacją lub nie gramy, zakończ
return;
}
this.frameTimer++;
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
// Przelicz ms na klatki gry (przy 60fps)
this.frameTimer = 0;
this.removeChildren(); // Usuń sprite klatki z kontenera animacji (z obiektu animacji)
this.currentFrame++;
if (this.currentFrame >= this.frames.length) {
// Animacja skończona, wracamy do idle
// *** Dodaj grafikę idle z powrotem JAKO DZIECKO BOSSA i ustaw self.bossGraphics ***
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// Pozycja relatywna
y: 0 // Pozycja relatywna
}));
// *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go ***
if (this.parent === self) {
this.parent.removeChild(this); // Użyj 'this' dla obiektu animacji
} else if (this.parent) {
// Fallback
this.parent.removeChild(this);
}
this.destroy(); // Zniszcz obiekt animacji
self.bossAttackAnim = null; // Wyczyść referencję w obiekcie bossa
} else {
this.addChild(this.frames[this.currentFrame]); // Dodaj sprite następnej klatki (do obiektu animacji)
}
}
};
}
// Else (attackType === 2, czyli chargeAttack): playBossAttackAnim nic nie robi z grafiką.
// chargeAttack sam zadba o ustawienie grafiki 'idle' JAKO DZIECKA BOSSA.
};
// *** NOWA funkcja createAttack (SpriteAnimation) - Używa SpriteAnimation ***
// Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
// Obiekt logiczny ataku będzie przechowywał activationFrame. Aktywność kolizji (isActive)
// będzie ustawiana w pętli update bossa na podstawie aktualnej klatki visual.
self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) {
// Użyj przekazanej framesList, domyślną oryginalną jeśli nie przekazana
var frames = framesList || [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})];
var spriteAnim = game.addChild(new SpriteAnimation({
// Wizualizacja fireballa jest dodawana do kontenera 'game'
frames: frames,
frameDuration: frameDurationMs,
// Użyj wartości przekazanej do konstruktora SpriteAnimation
loop: false,
// Animacja nie zapętla się
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
spriteAnim.scaleX = 1.6; // Ręczne skalowanie wizualizacji
spriteAnim.scaleY = 1.6;
var attack = {
// Obiekt logiczny ataku
x: x,
y: y,
radius: 60,
// Promień kolizji
visual: spriteAnim,
// Referencja do wizualizacji (SpriteAnimation)
// lifeTime: Math.floor(duration * (60 / 1000)), // Czas życia w klatkach gry - USUNIĘTO, bo SpriteAnimation zarządza czasem życia wizualizacji
isActive: false,
// Flaga aktywności kolizji, domyślnie false
activationFrame: activationFrame // Przechowuj activationFrame w obiekcie ataku
};
self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa
};
// Zmodyfikowana funkcja circleAttack - Teraz używa NOWEJ createAttack
self.circleAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku
var center = {
// Ustaw środek koła na pozycji bossa
x: self.x,
y: self.y
};
var count = isNewBossPlusMode ? 12 : 8; // Liczba pocisków w ataku (więcej w New Boss+)
var radius = 300; // Promień koła, na którym rozmieszczone są pociski
var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Czas życia pocisku w ms
// circleFrames array jest zdefiniowane raz na początku klasy Boss
// Pętla tworząca wiele pocisków rozmieszczonych w kole Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO
for (var i = 0; i < count; i++) {
// Iteruj tyle razy, ile ma być pocisków (count)
var angle = i / count * Math.PI * 2; // Oblicz kąt dla bieżącego pocisku (równo rozmieszczone na okręgu)
var posX = center.x + Math.cos(angle) * radius; // Oblicz współrzędną X na okręgu
var posY = center.y + Math.sin(angle) * radius; // Oblicz współrzędną Y na okręgu
// Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku
var delay = i * 20 + 20; // Zapewnij, że nawet pierwszy pocisk ma opóźnienie
// Użyj setTimeout do stworzenia każdego pocisku po małym opóźnieniu
LK.setTimeout(function (x, y) {
// Używamy closure do "zamknięcia" wartości x, y (frames jest globalne/dostępne)
return function () {
// Sprawdź czy boss nadal istnieje i gra jest w stanie 'game' zanim stworzysz atak po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
// Wywołaj funkcję createAttack dla tego pocisku z zapamiętaną pozycją i parametrami
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 8 (aktywacja od klatki fireball08)
// frameDurationMs = 250ms (przykład prędkości)
// framesList = circleFrames (używamy zmiennej zdefiniowanej poza funkcją)
self.createAttack(x, y, attackLifeTime, 8, 250, circleFrames); // Używamy 250ms dla frameDurationMs
}
};
}(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
// Zmodyfikowana funkcja lineAttack - Używa synchronicznej pętli i wywołuje NOWĄ createAttack
self.lineAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku
// Tworzy linię ataków
if (!player) {
return;
}
var targetX = player.x;
var targetY = player.y;
var count = isNewBossPlusMode ? 8 : 5; // Liczba pocisków w linii
var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Czas życia pocisku w ms
var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Opóźnienie między pociskami w ms
// lineFrames array jest zdefiniowane raz na początku klasy
// Pętla tworząca pociski w linii z opóźnieniem (jak w souls5.txt)
for (var i = 0; i < count; i++) {
var t = i / (count - 1); // Współczynnik interpolacji
var posX = self.x + (targetX - self.x) * t; // Interpolowana pozycja X
var posY = self.y + (targetY - self.y) * t; // Interpolowana pozycja Y
var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie przed stworzeniem pocisku, skalowane
LK.setTimeout(function (x, y) {
// Używamy closure do przekazania pozycji
return function () {
// Sprawdź warunki przed stworzeniem ataku po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
// Wywołaj funkcję createAttack
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 3 (indeks 'fireball5' na liście lineFrames)
// frameDurationMs = 100ms (przykład prędkości)
// framesList = lineFrames (używamy zmiennej zdefiniowanej poza funkcją)
self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // Przekaż parametry
}
};
}(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
// Zmodyfikowana funkcja chargeAttack - tworzy pociski podczas szarży i wywołuje NOWĄ createAttack
self.chargeAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk szarży
// Upewnij się, że gracz istnieje
if (!player) {
return;
}
// Obsługa grafiki bossa podczas szarży (pokazuje grafikę idle) - to pozostaje z nowszego kodu, jest ok.
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent === self) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
} else if (self.bossAttackAnim.parent) {
// Fallback na wypadek, gdyby rodzic był inny
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
}
self.bossAttackAnim.destroy();
self.bossAttackAnim = null;
}
if (self.bossGraphics && self.bossGraphics.parent === self) {
self.bossGraphics.parent.removeChild(self.bossGraphics);
} else if (self.bossGraphics && self.bossGraphics.parent) {
// Fallback
self.bossGraphics.parent.removeChild(self.bossGraphics);
}
self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
// Oblicz kierunek do gracza i parametry szarży
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
dx /= distance;
dy /= distance;
}
var startX = self.x; // Pozycja początkowa szarży
var startY = self.y;
var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży
var chargeDuration = isNewBossPlusMode ?
// Czas trwania szarży
600 : 800;
var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków wzdłuż ścieżki
var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku
// Tween (animacja ruchu) bossa podczas szarży
tween(self, {
x: self.x + dx * chargeDistance,
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
// Czas trwania, skalowany
easing: tween.easeIn,
// Krzywa animacji
onFinish: function onFinish() {
// Funkcja po zakończeniu szarży
if (self && !self.dead && gameState.currentState === "game") {
// Faza repozycji
self.repositioning = true;
LK.setTimeout(function () {
if (self && !self.dead) {
self.repositioning = false;
}
}, 500 * self.attackSpeedMultiplier);
// Tween powrotu do pozycji startowej
tween(self, {
x: startX,
y: startY
}, {
duration: 1000 * self.attackSpeedMultiplier,
easing: tween.easeOut
});
// Utworzenie ataków (fireballi) wzdłuż ścieżki szarży
// Fireballe ze szarży używają domyślnej listy klatek z createAttack (circleFrames)
for (var i = 0; i < attacksOnPathCount; i++) {
var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji
var currentChargeX = self.x; // Aktualna pozycja bossa (koniec szarży)
var currentChargeY = self.y;
var posX = startX + (currentChargeX - startX) * t; // Interpolowana pozycja X
var posY = startY + (currentChargeY - startY) * t; // Interpolowana pozycja Y
// Utwórz atak
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków)
// frameDurationMs = 140ms (domyślna prędkość)
// framesList = circleFrames (używamy listy klatek ataku koła)
self.createAttack(posX, posY, attackLifeTime, 4, 140, circleFrames); // Przekaż parametry
}
}
}
});
};
self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame)
self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame)
self.speed = 5; // Prędkość ruchu bossa
self.attackCooldown = 0; // Czas do następnego ataku (w klatkach gry)
self.attackPattern = 0;
self.attacks = []; // Aktywne ataki bossa (obiekty JS z pozycją, promieniem, itp.)
self.stunned = false;
self.stunDuration = 0;
self.dead = false;
self.phase = 1;
self.attackSpeedMultiplier = 1; // Mnożnik prędkości ataków (1 = normalna, < 1 = szybsza)
self.repositioning = false; // Flaga informująca czy boss się przemieszcza po szarży
// startAttackPattern - PRZENIESIONO JEGO DEFINICJĘ TUTAJ, ZGODNIE Z ORYGINALNYM KODEM
self.startAttackPattern = function () {
// !!! Ustaw tymczasowy cooldown na początku, ZANIM sprawdzisz warunki powrotu !!!
// To zapobiegnie natychmiastowemu ponownemu wywołaniu, jeśli metoda wróci wcześnie
self.attackCooldown = 1; // Ustaw minimalny cooldown (np. 1 klatka) od razu
// Sprawdź warunki wczesnego powrotu: boss jest martwy, gra nie jest w stanie "game" LUB boss jest w stanie "repositioning"
if (self.dead || gameState.currentState !== "game" || self.repositioning) {
// Dodano || self.repositioning
return; // Jeśli warunek spełniony, wróć
}
// Decydujemy O WZORCU ATAKU przed zmianą grafiki bossa
self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków (0, 1, 2)
// 🔥 Przekazujemy typ ataku do playBossAttackAnim - ta funkcja teraz decyduje CZY zmienić grafikę bossa
self.playBossAttackAnim(self.attackPattern); // <--- PRZEKAZUJEMY TYP ATAKU
// Następnie wywołujemy właściwą funkcję ataku (chargeAttack itp.)
// Logika graficzna dla szarży jest TERAZ W chargeAttack
switch (self.attackPattern) {
case 0:
self.circleAttack(); // Wywołaj atak koła
break;
case 1:
self.lineAttack(); // Wywołaj atak linii
break;
case 2:
self.chargeAttack(); // Wywołaj atak szarży
break;
}
// Ustaw właściwy czas odnowienia dla następnego ataku
self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds * multiplier
};
self.takeDamage = function (amount) {
// Funkcja obsługująca otrzymywanie obrażeń przez bossa
if (self.dead || gameState.currentState !== "game") {
// Boss otrzymuje obrażenia tylko w stanie gry
return;
}
self.health -= amount; // Zmniejsz zdrowie
self.health = Math.max(0, self.health); // Upewnij się, że zdrowie nie spadnie poniżej zera
LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku
if (!isNewBossPlusMode) {
// Przejście fazy bossa priorytetem przy 50% zdrowia tylko w standardowym trybie
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2; // Zmień fazę na 2
self.speed += 2; // Boss staje się szybszy (ruch)
self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej
tween(self, {
tint: 0xFF3300 // Pomarańczowy/czerwony odcień
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
if (self.health <= 0) {
self.die(); // Wywołaj funkcję śmierci
}
// ui.updateBossHealth jest w game.update
};
self.die = function () {
// Funkcja obsługująca śmierć bossa
if (self.dead || gameState.currentState !== "game") {
return;
}
self.dead = true; // Ustaw flagę śmierci
if (!isNewBossPlusMode) {
// Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu
LK.getSound('victory').play();
}
// Wyczyść pozostałe ataki przy śmierci bossa
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
self.attacks = []; // Wyczyść tablicę ataków logicznych
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Po zakończeniu animacji śmierci
if (self && self.destroy && !self.destroyed) {
self.destroy(); // Zniszcz obiekt bossa
}
storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów
// ZAWSZE przechodzimy do Grill Screena
gameState.showGrillScreen();
}
});
};
self.update = function () {
// Główna metoda aktualizacji bossa
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
if (!self.dead && self.attacks) {
// Jeśli zmieniono stan i boss nie umiera, wyczyść ataki
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
self.attacks = [];
}
return;
}
// Nie aktualizuj jeśli boss jest martwy (pozwól animacji śmierci działać)
if (self.dead) {
return;
}
// --- LOGIKA RUCHU BOSS ---
// Poruszaj się w kierunku gracza jeśli spełnione warunki
if (!self.repositioning && self.attackCooldown > 30 && player && !player.dead) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var moveSpeed = self.speed;
if (distance > 150) {
// Poruszaj się tylko jeśli gracz jest dalej niż 150 jednostek
var moveX = dx / distance * moveSpeed;
var moveY = dy / distance * moveSpeed;
var nextX = self.x + moveX;
var nextY = self.y + moveY;
// Ograniczenia areny
var halfWidth = self.width * self.scaleX / 2;
var halfHeight = self.height * self.scaleY / 2;
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 100 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
// Grafika jako dziecko podąża automatycznie.
}
}
// --- KONIEC LOGIKA RUCHU BOSS ---
// --- OBSŁUGA ATAKÓW BOSS (Aktualizacja Animacji, Kolizje i Usuwanie) ---
// Ataki wizualne to teraz obiekty SpriteAnimation, które same zarządzają klatkami.
// Obiekt logiczny w self.attacks zawiera referencję visual (SpriteAnimation) i activationFrame.
// Kolizja aktywuje się, gdy animacja osiągnie activationFrame.
// Usunięcie następuje po kolizji LUB gdy animacja się zakończy (visual.destroyed).
// Aktualizuj istniejące ataki bossa (pociski), sprawdzaj kolizje i usuwaj zakończone
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i];
// *** Krok 1: Aktualizuj wizualizację animacji ataku ***
// Obiekt SpriteAnimation ma swoją metodę update, którą musimy wywołać.
if (attack.visual && attack.visual.update) {
attack.visual.update();
}
// *** Krok 2: Zaktualizuj flagę aktywności kolizji (isActive) na podstawie klatki animacji ***
// Kolizja jest aktywna od momentu osiągnięcia activationFrame zdefiniowanego dla danego ataku.
if (attack.visual && typeof attack.visual.currentFrame !== 'undefined' && typeof attack.activationFrame !== 'undefined') {
if (attack.visual.currentFrame >= attack.activationFrame) {
attack.isActive = true; // Aktywuj kolizję
} else {
attack.isActive = false; // Kolizja nieaktywna przed activationFrame
}
} else {
// Jeśli visual nie ma wymaganych pól, kolizja jest nieaktywna.
attack.isActive = false;
}
// *** Krok 3: Sprawdź kolizję z graczem jeśli atak jest aktywny ***
// Kolizja wymaga, aby atak był isActive (czyli animacja osiągnęła klatkę aktywacji) I gracz spełnia warunki.
if (attack.isActive && player && !player.dead && !player.invulnerable && attack.visual && !attack.visual.destroyed) {
var dx_p = player.x - attack.x;
var dy_p = player.y - attack.y;
var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
var playerRadius_p = player.width / 2; // Zakładamy promień kolizji gracza
var attackRadius = attack.radius; // Promień kolizji ataku z attackData
if (distance_p < playerRadius_p + attackRadius) {
// Kolizja wykryta!
player.takeDamage(1); // Gracz otrzymuje obrażenia
// Usuń wizualizację i obiekt logiczny natychmiast po trafieniu
if (attack.visual && attack.visual.destroy) {
attack.visual.destroy(); // Zniszcz obiekt wizualny (SpriteAnimation)
}
self.attacks.splice(i, 1); // Usuń trafiony atak logiczny z listy
// break; // Opcjonalnie: zatrzymaj sprawdzanie kolizji dla tej klatki po trafieniu gracza (jeśli tylko jeden atak może trafić naraz)
}
}
// *** Krok 4: Usuń atak z listy, jeśli jego wizualizacja została zniszczona ***
// SpriteAnimation ustawia visual.destroyed na true po zakończeniu animacji (jeśli loop=false)
// LUB jeśli zostało wywołane visual.destroy() (np. po kolizji powyżej).
// USUNIĘTO: Sprawdzanie lifeTime <= 0, bo SpriteAnimation zarządza czasem życia wizualizacji wizualizacji.
if (attack.visual && attack.visual.destroyed) {
// console.log("Removing attack because visual is destroyed."); // Log do debugowania (można odkomentować dla testów)
self.attacks.splice(i, 1); // Usuń logiczny obiekt ataku z listy
}
}
// --- KONIEC OBSŁUGA ATAKÓW BOSS ---
if (self.attackCooldown > 0) {
// Zmniejszaj cooldown
self.attackCooldown--;
}
// Sprawdź, czy nadszedł czas na nowy atak
if (self.attackCooldown <= 0 && !self.repositioning) {
self.startAttackPattern();
}
};
return self; // Zwróć instancję obiektu Boss
});
// Zamykająca klamra dla Container.expand klasy Boss
// Zamykająca klamra dla Container.expand klasy Boss
var Player = Container.expand(function () {
var self = Container.call(this);
// Zamiast var playerGraphics = ...
self.playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Player properties
self.health = 5; // Default health (will be overridden in startGame)
self.speed = 8; // Movement speed (NIE UŻYWANE OBECNIE W UPDATE?)
self.rolling = false; // Flag for roll state
self.rollDirection = {
x: 0,
y: 0
}; // Direction of roll
self.rollSpeed = 20; // Speed during roll
self.rollDuration = 300; // Roll duration in ms
self.rollCooldown = 0; // Cooldown between rolls (frames)
self.invulnerable = false; // Invulnerability flag
self.invulnerabilityFrames = 0; // Frames of invulnerability left
self.dead = false; // Dead state
self.rollTimeoutId = null; // Store timeout ID for roll duration
self.invulnerabilityTimeoutId = null; // Store timeout ID for invulnerability (NIE UŻYWANE?)
self.rollAnimationInterval = null; // Store interval ID for roll animation
self.hasRolledThroughBossThisRoll = false; // Track if boss was hit during this roll
// Clear all roll-related timeouts and intervals
self.clearRollTimeouts = function () {
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
self.rollTimeoutId = null;
}
if (self.rollAnimationInterval) {
LK.clearInterval(self.rollAnimationInterval);
self.rollAnimationInterval = null;
}
};
// Roll mechanic
self.roll = function (direction) {
if (!self.rolling && self.rollCooldown <= 0 && !self.dead) {
self.rolling = true;
self.rollDirection = direction;
self.rollCooldown = 45;
self.invulnerable = true;
self.invulnerabilityFrames = 30;
self.hasRolledThroughBossThisRoll = false;
// Ukryj normalną grafikę gracza
if (self.playerGraphics && !self.playerGraphics.destroyed) {
self.playerGraphics.visible = false;
}
// --- Roll Animation (turlanie) ---
var rollFrames = ['roll', 'roll0', 'roll1', 'roll2'];
var currentFrame = 0;
var rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
var rollAnimationInterval = LK.setInterval(function () {
if (rollAnimationSprite && rollAnimationSprite.destroy) {
rollAnimationSprite.destroy();
}
currentFrame = (currentFrame + 1) % rollFrames.length;
rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
}, 70); // co 70ms zmienia klatkę podczas turlania
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
}
self.rollTimeoutId = LK.setTimeout(function () {
self.rolling = false;
// --- Po rollu zakończ turlanie ---
if (rollAnimationInterval) {
LK.clearInterval(rollAnimationInterval);
}
if (rollAnimationSprite && rollAnimationSprite.destroy) {
rollAnimationSprite.destroy();
}
// --- Stand Up Animation (wstawanie + machnięcie mieczem) ---
var standUpFrames = ['roll3', 'roll4'];
var standUpFrame = 0;
var standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
var standUpInterval = LK.setInterval(function () {
if (standUpSprite && standUpSprite.destroy) {
standUpSprite.destroy();
}
standUpFrame++;
if (standUpFrame < standUpFrames.length) {
standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
}));
} else {
// Koniec animacji stand-up
LK.clearInterval(standUpInterval);
if (standUpSprite && standUpSprite.destroy) {
standUpSprite.destroy();
}
// Przywróć normalną grafikę gracza
if (self.playerGraphics && !self.playerGraphics.destroyed) {
self.playerGraphics.visible = true;
}
}
}, 100); // 100ms między klatkami wstawania
self.rollTimeoutId = null;
}, self.rollDuration);
}
};
// Take damage method
self.takeDamage = function (amount) {
// Check if player can take damage (not invulnerable and not dead)
if (!self.invulnerable && !self.dead) {
self.health -= amount;
// Flash effect when hit
LK.effects.flashObject(self, 0xFF0000, 200);
// Check if player is dead
if (self.health <= 0) {
self.health = 0; // Ensure health doesn't go below 0 visually
self.die();
return; // Zakończ, jeśli gracz umarł
}
// Set brief invulnerability after hit (if not rolling)
// Nietykalność po trafieniu jest teraz KRÓTSZA niż turlanie
if (!self.rolling) {
self.invulnerable = true;
self.invulnerabilityFrames = 30; // Krótsza nietykalność po trafieniu (np. 0.5s)
}
}
};
// Die method
self.die = function () {
if (self.dead) {
// Zapobiegaj wielokrotnemu wywołaniu
return;
}
self.dead = true;
// Increment death counter
storage.totalDeaths = (storage.totalDeaths || 0) + 1;
// Increment max hearts after death (up to a maximum of 10)
if (!storage.maxHearts || storage.maxHearts < 5) {
// Upewnij się, że startujemy od min 5
storage.maxHearts = 5;
}
if (storage.maxHearts < 10) {
storage.maxHearts = storage.maxHearts + 1;
}
self.clearRollTimeouts(); // Anuluj ewentualne trwające turlanie
// Death animation
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Zniszcz obiekt gracza PO zakończeniu animacji
if (self && self.destroy && !self.destroyed) {
self.destroy();
}
// Set death reason flag to true (player died)
gameOverReasonIsDeath = true;
// Show game over screen - gameState zarządza przejściem
gameState.gameOver(true); // Przekaż true (śmierć gracza)
}
});
};
// Update method called every frame
self.update = function () {
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
// Wyczyść timery, jeśli stan gry się zmienił
if (self.rolling) {
self.rolling = false;
} // Przerwij turlanie
self.clearRollTimeouts();
return;
}
// Nie aktualizuj jeśli gracz jest martwy (ale pozwól animacji śmierci działać)
if (self.dead) {
return;
}
// Handle roll cooldown
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
// Handle invulnerability frames (zarówno z turlania, jak i z otrzymania obrażeń)
if (self.invulnerable && self.invulnerabilityFrames > 0) {
self.invulnerabilityFrames--;
// Blinking effect during invulnerability
// Miga szybciej
self.alpha = self.invulnerabilityFrames % 4 > 1 ? 0.3 : 1;
if (self.invulnerabilityFrames <= 0) {
self.invulnerable = false;
self.alpha = 1; // Restore full opacity when invulnerability ends
}
} else if (self.invulnerable && self.invulnerabilityFrames <= 0) {
// Upewnij się, że stan jest spójny jeśli klatki się skończyły
self.invulnerable = false;
self.alpha = 1;
}
// Handle movement during roll
if (self.rolling) {
var rollDx = self.rollDirection.x * self.rollSpeed;
var rollDy = self.rollDirection.y * self.rollSpeed;
var nextX = self.x + rollDx;
var nextY = self.y + rollDy;
// Ograniczenia areny (ściany na 100px marginesie, mapa 2048x2732)
// Uwzględnij rozmiar gracza (zakładając anchor 0.5, 0.5)
var halfWidth = self.width / 2;
var halfHeight = self.height / 2;
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 100 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
// Sprawdź kolizję turlania z bossem
// Sprawdź tylko jeśli boss istnieje, nie jest martwy i JESZCZE nie zadałeś obrażeń tym turlaniem
if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
// Uproszczone sprawdzenie kolizji na podstawie odległości między środkami
var dx_b = self.x - boss.x;
var dy_b = self.y - boss.y;
var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b);
// Uproszczony promień kolizji (połówka szerokości/wysokości)
var playerCollisionRadius = self.width / 2; // Zakładamy, że kolizja gracza jest okrągła
var bossCollisionRadius = boss.width / 2; // Zakładamy, że kolizja bossa jest okrągła
// Jeśli odległość między środkami jest mniejsza niż suma promieni kolizji
if (distance_b < playerCollisionRadius + bossCollisionRadius) {
// Kolizja wykryta podczas turlania
boss.takeDamage(10); // ZADAJ 10 OBRAŻEŃ BOSSOWI
self.hasRolledThroughBossThisRoll = true; // Oznacz, że zadałeś obrażenia w tym turlaniu
// Opcjonalny efekt wizualny/dźwiękowy trafienia bossa turlaniem
LK.effects.flashObject(boss, 0x00FF00, 200); // Mignij bossa na zielono
// LK.getSound('playerHitBossSound').play(); // Wymagałoby dodania nowego assetu dźwiękowego
}
}
} else {
// --- Podstawowy ruch gracza (jeśli nie turla się) ---
// Ta część była nieobecna, dodajmy prosty ruch oparty na wejściu (jeśli LK go dostarcza)
// Zakładając, że mamy dostęp do stanu klawiszy/joysticka np. przez LK.controls
// To jest PRZYKŁAD - musisz dostosować do API LK
/*
var moveX = 0;
var moveY = 0;
if (LK.controls.left) moveX -= 1;
if (LK.controls.right) moveX += 1;
if (LK.controls.up) moveY -= 1;
if (LK.controls.down) moveY += 1;
if (moveX !== 0 || moveY !== 0) {
// Normalizuj wektor ruchu, jeśli poruszasz się po przekątnej
var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY);
var normalizedX = moveX / moveMagnitude;
var normalizedY = moveY / moveMagnitude;
var nextX = self.x + normalizedX * self.speed; // Użyj self.speed
var nextY = self.y + normalizedY * self.speed;
// Ograniczenia areny (jak w turlaniu)
var halfWidth = self.width / 2;
var halfHeight = self.height / 2;
var minX = 100 + halfWidth;
var maxX = 2048 - 100 - halfWidth;
var minY = 100 + halfHeight;
var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
}
*/
// Jeśli nie masz łatwego dostępu do stanu klawiszy, gracz będzie się ruszał tylko podczas turlania.
}
};
return self;
});
// Shape class implementation for creating simple shapes
var Shape = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
var width = options.width || 100;
var height = options.height || 100;
var color = options.color || 0xFFFFFF;
var shape = options.shape || 'box';
// Create the shape as an asset
var asset = self.attachAsset(shape, {
anchorX: 0.5,
anchorY: 0.5,
width: width,
height: height,
tint: color
});
// Set width and height for easier access
self.width = width;
self.height = height;
// Add anchor property to Shape for positioning
self.anchor = {
set: function set(x, y) {
// This mimics the behavior of the anchor.set method
self.anchorX = x;
self.anchorY = y;
}
};
return self;
});
var SpriteAnimation = Container.expand(function (options) {
var self = Container.call(this);
// options = { frames: [], frameDuration: 100, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 };
// 🔥 ZMODYFIKOWANA LOGIKA: PRZYJMUJEMY TABLICĘ GOTOWYCH SPRITE'ÓW w options.frames!
self.frames = options.frames || []; // self.frames TO TERAZ obiekty sprite, a NIE definicje assetów!
self.frameDuration = options.frameDuration || 100; // in milliseconds
self.loop = options.loop !== undefined ? options.loop : false;
self.currentFrame = 0;
self.frameTimer = 0; // in game frames (assuming 60fps)
self.playing = true;
self.destroyed = false; // Manual destroyed flag
// Ustawiamy pozycję i anchor na kontenerze animacji (self)
// Pozycja kontenera jest ustawiana w createAttack za pomocą game.addChild(new SpriteAnimation({x: x, y: y, ...}))
self.x = options.x || 0;
self.y = options.y || 0;
// 🔥 Usunięto próbę ustawienia anchor na kontenerze SpriteAnimation, bo Container tego nie ma.
// Anchor JEST JUŻ ustawiony na poszczególnych sprite'ach klatek (przy tworzeniu circleFrames/lineFrames).
// *** ZMODYFIKOWANA LOGIKA: Dodajemy PRZYJĘTE SPRITE'Y klatek (z self.frames) jako dzieci i zarządzamy widocznością ***
var frameSprites = self.frames; // Używamy bezpośrednio tablicy przyjętych sprite'ów
if (frameSprites.length === 0) {
console.warn("SpriteAnimation created with no frames!");
self.destroyed = true;
self.playing = false;
} else {
frameSprites.forEach(function (frameSprite, index) {
// 🔥 frameSprite jest JUŻ gotowym obiektem sprite z poprawnym anchor ustawionym w klasie Boss.
// Nie wywołujemy już tutaj LK.getAsset.
if (frameSprite && frameSprite.visible !== undefined && frameSprite.addChild !== undefined) {
// Sprawdź czy obiekt wygląda jak sprite/display object
// Upewnij się, że jest niewidoczny na początku, chyba że to pierwsza klatka
frameSprite.visible = index === self.currentFrame;
// Dodaj sprite jako dziecko kontenera animacji (self)
self.addChild(frameSprite);
} else {
console.error("SpriteAnimation: Invalid sprite object provided in frames array at index:", index, frameSprite);
// Jeśli dane wejściowe są złe, oznacz animację jako zniszczoną
self.destroyed = true;
self.playing = false;
}
});
}
self.update = function () {
// Aktualizuj tylko jeśli gramy, mamy klatki i nie jesteśmy zniszczeni
if (!self.playing || self.frames.length === 0 || self.destroyed) {
return;
}
self.frameTimer++; // Przyrost timera w klatkach gry
// Sprawdź, czy minął czas na zmianę klatki (przelicz frameDuration z ms na klatki gry przy 60fps)
var frameThreshold = self.frameDuration / (1000 / 60);
if (frameThreshold <= 0 || !isFinite(frameThreshold)) {
frameThreshold = 1; // Minimalny próg to 1 klatka gry
}
if (self.frameTimer >= frameThreshold) {
self.frameTimer = 0; // Resetuj timer
var prevFrameIndex = self.currentFrame; // Zapisz indeks poprzedniej klatki
self.currentFrame++; // Przejdź do następnej klatki
// Sprawdź, czy animacja dobiegła końca
if (self.currentFrame >= self.frames.length) {
// Sprawdź względem długości tablicy klatek/sprite'ów
if (self.loop) {
self.currentFrame = 0; // Zapętl
} else {
// Animacja się zakończyła i nie zapętla
self.currentFrame = self.frames.length - 1; // Pozostań na ostatniej klatce
self.playing = false; // Zatrzymaj odtwarzanie
// *** Ustawiamy flagę destroyed tutaj, gdy animacja się zakończy ***
self.destroyed = true;
// Nie ukrywaj ostatniej klatki, pozostań na niej widoczny do momentu zniszczenia
return; // Zakończ update, bo animacja się skończyła i nie zapętla
}
}
// *** ZMODYFIKOWANA LOGIKA: Ukryj poprzednią klatkę i pokaż aktualną ***
// Upewnij się, że poprzedni sprite istnieje i jest widoczny (powinien istnieć i być w self.frames)
if (self.frames[prevFrameIndex] && self.frames[prevFrameIndex].visible !== undefined) {
self.frames[prevFrameIndex].visible = false; // Ukryj poprzednią klatkę
} else {
// Coś poszło nie tak z poprzednim sprite'em
// console.warn("SpriteAnimation: Attempted to hide invalid sprite at index:", prevFrameIndex, self.frames[prevFrameIndex]); // Log ostrzeżenia
}
// Upewnij się, że nowy sprite istnieje i jest widoczny (powinien istnieć i być w self.frames)
if (self.frames[self.currentFrame] && self.frames[self.currentFrame].visible !== undefined) {
self.frames[self.currentFrame].visible = true; // Pokaż aktualną klatkę
} else {
// Coś poszło nie tak, nowa klatka nie istnieje lub nie jest poprawnym obiektem sprite?
console.error("SpriteAnimation: Attempted to show invalid sprite at index:", self.currentFrame, self.frames[self.currentFrame]);
self.destroyed = true; // Oznacz jako zniszczony, aby zapobiec dalszym błędom
self.playing = false;
}
}
};
// Dodajemy metodę destroy, która czyści dzieci i oznacza obiekt jako zniszczony
// Ta metoda powinna być wywoływana przez Boss.update po kolizji LUB gdy animacja się zakończy (visual.destroyed = true)
self.destroy = function () {
if (self.destroyed) {
return;
} // Jeśli już zniszczony, nic nie rób
// console.log("Destroying SpriteAnimation:", self); // Log do debugowania
self.destroyed = true; // Ustaw flagę zniszczenia
self.playing = false; // Zatrzymaj odtwarzanie
// Usuń wszystkie dzieci (sprity klatek), które zostały dodane do kontenera animacji
// removeChildren() w LK Container powinna zniszczyć obiekty sprite
self.removeChildren();
// Usuń sam obiekt SpriteAnimation z jego rodzica (powinien być 'game', dodany w createAttack)
if (self.parent) {
self.parent.removeChild(self);
}
// Opcjonalnie: dodatkowe czyszczenie referencji
self.frames = null; // Wyczyść referencję do sprite'ów klatek
self.update = null; // Usuń referencję do metody update, jeśli nie jest już potrzebna po zniszczeniu
};
// Dodajemy metodę stop, która zatrzymuje animację (przydatne np. gdy boss umiera)
self.stop = function () {
self.playing = false;
// Opcjonalnie: ukryj wszystkie klatki po zatrzymaniu? Zależne od oczekiwanego zachowania.
// self.frames.forEach(s => { if(s && s.visible !== undefined) s.visible = false; });
// console.log("Stopped SpriteAnimation:", self); // Log do debugowania
};
return self; // Zwróć instancję obiektu SpriteAnimation
});
// Zamykająca klamra dla Container.expand klasy SpriteAnimation
// Zamykająca klamra dla Container.expand klasy SpriteAnimation
// Zamykająca klamra dla Container.expand klasy SpriteAnimation
var UI = Container.expand(function () {
var self = Container.call(this);
// Create heart containers (wizualizacja zdrowia)
self.hearts = [];
self.heartContainer = new Container(); // Kontener na ikony serc
self.addChild(self.heartContainer);
// Title and messages (teksty na ekranach)
self.titleText = new Text2("ROLL SOULS", {
size: 150,
fill: 0xFFFFFF
});
self.titleText.anchor.set(0.5, 0.5);
self.addChild(self.titleText);
self.messageText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
self.messageText.anchor.set(0.5, 0.5);
self.addChild(self.messageText);
// Deaths counter (licznik śmierci)
self.deathsText = new Text2("Deaths: 0", {
size: 40,
fill: 0xFFFFFF
});
self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu
self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi
self.deathsText.y = 50; // Pozycja Y od góry
self.addChild(self.deathsText);
// Tutorial text (teksty tutoriali)
self.tutorialText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze
self.tutorialText.x = 2048 / 2; // Pozycja X na środku
self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu)
self.addChild(self.tutorialText);
// --- Timer Text ---
self.timerText = new Text2("2:00", {
// Początkowe wyświetlanie czasu (2 minuty)
size: 60,
// Rozmiar czcionki
fill: 0xFFFFFF // Biały kolor
});
self.timerText.anchor.set(0, 0); // Punkt odniesienia w lewym górnym rogu
self.timerText.x = 50; // Pozycja X od lewej krawędzi
self.timerText.y = 50; // Pozycja Y od górnej krawędzi
self.timerText.alpha = 0; // Domyślnie ukryty
self.addChild(self.timerText);
// --- Boss Health Bar (wizualizacja zdrowia bossa) ---
self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa
self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo
self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera)
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
// Tło paska zdrowia bossa (szare) - nadal używamy Shape dla tła
self.bossHealthBarBg = new Shape({
width: barWidth,
height: barHeight,
color: 0x555555,
// Szary kolor tła
shape: 'box'
});
self.bossHealthBarBg.anchor.set(0.5, 0.5); // Ustaw punkt odniesienia na środek
self.bossHealthBarContainer.addChild(self.bossHealthBarBg);
// Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła, żeby lewa krawędź paska zdrowia była na środku kontenera (czyli na środku ekranu)
// Szerokość i wysokość są brane z assetu, nie ustawiamy ich tutaj jawnie (chyba że chcemy skalować)
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
// Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
// Sprawdź poprawność danych wejściowych
current = Math.max(0, current || 0);
max = Math.max(1, max || 1); // Max musi być co najmniej 1
// Usuń istniejące serca tylko jeśli liczba się zmieniła lub nie ma serc
if (self.hearts.length !== max) {
while (self.hearts.length > 0) {
var oldHeart = self.hearts.pop();
if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) {
oldHeart.destroy();
}
}
self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc
// Stwórz nowe ikony serc
for (var i = 0; i < max; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
x: i * 50,
// Pozycja w poziomie (rozstawione co 50 jednostek)
y: 0,
tint: i < current ? 0xFF0000 : 0x555555 // Czerwone jeśli zdrowie jest >=, szare jeśli <
});
self.hearts.push(heart);
self.heartContainer.addChild(heart);
}
// Wyśrodkuj kontener serc w poziomie i ustaw pozycję pionową
self.heartContainer.x = (2048 - max * 50) / 2 + 25; // Dodano +25 aby wycentrować lepiej (bo anchorX = 0.5)
self.heartContainer.y = 100; // Pozycja pionowa
} else {
// Zaktualizuj tylko kolory istniejących serc
for (var j = 0; j < self.hearts.length; j++) {
if (self.hearts[j]) {
self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
}
}
}
};
// Aktualizuje wizualizację paska zdrowia bossa
self.updateBossHealth = function (current, max) {
// Sprawdź poprawność danych
current = Math.max(0, current || 0);
max = Math.max(1, max || 1); // Max musi być co najmniej 1
// Oblicz szerokość paska zdrowia na podstawie aktualnego i maksymalnego zdrowia
var barWidth = 800; // Całkowita szerokość paska (musi być taka sama jak szerokość assetu bossHpbar)
var currentWidth = current / max * barWidth;
// Upewnij się, że pasek zdrowia (obiekt sprite) istnieje przed aktualizacją
if (self.bossHealthBar) {
// Zmień szerokość sprite'a, aby odzwierciedlić aktualne zdrowie
self.bossHealthBar.width = currentWidth;
// Ustaw widoczność kontenera paska zdrowia bossa
// Pokaż jeśli (stan gry lub game over) ORAZ (boss istnieje i żyje LUB jesteśmy w stanie game over z powodu czasu w Boss+)
var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
}
// Upewnij się, że pasek zdrowia tła też jest widoczny/ukryty tak samo jak kontener
if (self.bossHealthBarBg) {
self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
}
};
// Wyświetla komunikat na środku ekranu
self.showMessage = function (message, duration) {
self.messageText.setText(message);
self.messageText.alpha = 1; // Ustaw pełną przezroczystość
// Wyczyść istniejący timer zanikania, jeśli istnieje
if (self.messageTimeout) {
LK.clearTimeout(self.messageTimeout);
self.messageTimeout = null;
}
// Zaplanuj zanikanie po czasie, jeśli duration > 0
if (duration && duration > 0) {
self.messageTimeout = LK.setTimeout(function () {
tween(self.messageText, {
alpha: 0 // Zaniknij do przezroczystości 0
}, {
duration: 500,
// Czas trwania zanikania
onFinish: function onFinish() {
self.messageTimeout = null;
} // Reset ID
});
}, duration);
} else {
// If duration is 0 or not provided, message stays visible
self.messageText.alpha = 1;
}
};
// Wyświetla tekst tutorialu
self.showTutorial = function (text) {
self.tutorialText.setText(text);
self.tutorialText.alpha = 1; // Ustaw pełną przezroczystość
};
// Ukrywa tekst tutorialu (zanikając)
self.hideTutorial = function () {
tween(self.tutorialText, {
alpha: 0 // Zaniknij
}, {
duration: 500 // Czas trwania
});
};
// Aktualizuje licznik śmierci
self.updateDeathsCounter = function () {
// Upewnij się, że storage.totalDeaths jest liczbą
var deaths = storage.totalDeaths || 0;
self.deathsText.setText("Deaths: " + deaths);
};
// Aktualizuje wyświetlanie czasu timera
self.updateTimerDisplay = function (seconds) {
// Upewnij się, że sekundy są liczbą >= 0
seconds = Math.max(0, seconds || 0);
var minutes = Math.floor(seconds / 60);
var remainingSeconds = seconds % 60;
// Formatuj sekundy z wiodącym zerem jeśli < 10
var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
self.timerText.setText(minutes + ':' + formattedSeconds);
};
// Pozycjonuje elementy UI w zależności od stanu gry
// Ta funkcja jest wywoływana przez metody w gameState
self.positionElements = function (state) {
// Pozycje stałe (niezależne od stanu)
self.deathsText.x = 2048 - 50;
self.deathsText.y = 50;
self.timerText.x = 50;
self.timerText.y = 50;
self.heartContainer.y = 100; // Pozycja pionowa serc
self.bossHealthBarContainer.x = 2048 / 2;
self.bossHealthBarContainer.y = 150; // Pasek HP bossa poniżej timera/serc
// Resetuj widoczność przed ustawieniem dla danego stanu
self.titleText.alpha = 0;
self.messageText.alpha = 0;
self.tutorialText.alpha = 0;
self.timerText.alpha = 0;
self.heartContainer.alpha = 0;
self.bossHealthBarContainer.alpha = 0; // Zostanie włączony przez updateBossHealth jeśli trzeba
self.deathsText.alpha = 1; // Licznik śmierci zawsze widoczny
switch (state) {
case "title":
// Pozycje dla ekranu tytułowego
self.titleText.x = 2048 / 2;
self.titleText.y = 800;
self.titleText.alpha = 1; // Pokaż tytuł
self.messageText.x = 2048 / 2;
self.messageText.y = 1000; // "Tap to Start" (ustawiane przez showMessage)
// self.messageText.alpha = 1; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200; // "Swipe to Roll..." (ustawiane przez showTutorial)
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
break;
case "game":
// Pozycje dla stanu gry (walka z bossem)
// self.titleText.alpha = 0; // Ustawione domyślnie
self.messageText.x = 2048 / 2;
self.messageText.y = 1500; // Komunikaty w trakcie gry (np. przyspieszenie bossa)
// self.messageText.alpha = 0; // Ustawiane przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 200; // Tutorial w trakcie gry ("Swipe to roll away!")
// self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
// Pokaż serca i timer podczas gry
self.heartContainer.alpha = 1;
self.timerText.alpha = 1;
// Pasek zdrowia bossa jest zarządzany przez updateBossHealth
break;
case "grillMenu":
// Nowy stan dla Grill Screena
// self.titleText.alpha = 0; // Ustawione domyślnie
self.messageText.x = 2048 / 2;
self.messageText.y = 500; // Pozycja dla komunikatów "Rest in peace..."
// self.messageText.alpha = 0; // Ustawiane przez showMessage
// self.tutorialText.alpha = 0; // Ustawione domyślnie
break;
case "gameOver":
// Pozycje dla ekranu Game Over
self.titleText.x = 2048 / 2;
self.titleText.y = 800; // Tytuł "YOU DIED" lub komunikat zwycięstwa Boss+
// self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
self.messageText.x = 2048 / 2;
self.messageText.y = 1000; // Pusty lub inny komunikat
// self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
self.tutorialText.x = 2048 / 2;
self.tutorialText.y = 1200; // Pusty
// self.tutorialText.alpha = 0; // Ustawiane w gameState.gameOver przez showTutorial
// Widoczność paska HP bossa zarządzana przez updateBossHealth
break;
case "intro": // Pozycje dla ekranów intro/tutoriali
case "fakeTutorial":
case "realTutorial":
// Większość elementów ukryta (ustawione domyślnie wyżej)
break;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111 // Ciemne tło domyślne
});
/****
* Game Code
****/
// Komentarze o assetach pominięte dla zwięzłości
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var currentSceneElements = new Container();
// Zmienne gry
var player;
var boss;
var ui;
var walls = []; // Ściany areny bossa
// Zmienna do przechowywania aktywnego tła sceny
var currentBackground = null;
// Flaga do śledzenia, czy jesteśmy w trybie New Boss+
var isNewBossPlusMode = false;
// Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
var gameOverReasonIsDeath = false;
// Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła)
function clearScene() {
while (currentSceneElements.children.length > 0) {
var child = currentSceneElements.children[0];
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne
} else if (child && child.parent) {
child.parent.removeChild(child); // Fallback
} else {
// Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć)
currentSceneElements.children.shift();
}
}
// Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach,
// ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie.
// Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements.
}
// Obiekt zarządzający stanami gry
var gameState = {
currentState: "title",
// Początkowy stan gry
gameDuration: 120,
// Domyślny czas walki z bossem w sekundach (2 minuty)
remainingTime: 0,
// Pozostały czas
gameTimerInterval: null,
// ID interwału timera
fakeTutorialTimerId: null,
// ID timera przechodzącego do prawdziwego tutorialu
bossSpeedIncreased: false,
// Flaga przyspieszenia bossa
touchStart: {
x: 0,
y: 0
},
// Początek gestu
touchEnd: {
x: 0,
y: 0
},
// Koniec gestu
// Inicjalizacja gry (wywoływana raz na początku)
init: function init() {
// Resetuj stan przy każdym uruchomieniu
storage.totalDeaths = 0;
storage.maxHearts = 5; // Zaczynaj zawsze z 5 sercami
isNewBossPlusMode = false;
gameOverReasonIsDeath = false;
game.setBackgroundColor(0x111111); // Ustaw domyślny kolor tła
this.createWalls(); // Stwórz ściany areny
ui = game.addChild(new UI()); // Stwórz UI
// Zainicjalizuj UI dla ekranu tytułowego
ui.updateDeathsCounter();
ui.updateHearts(storage.maxHearts, storage.maxHearts); // Pokaż początkowe serca
ui.positionElements("title"); // Pozycjonuj elementy UI dla tytułu
game.addChild(currentSceneElements); // Dodaj kontener na elementy scen
// Rozpocznij muzykę w tle
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
this.showTitleScreen(); // Rozpocznij od ekranu tytułowego
},
// Tworzy/odświeża ściany areny bossa
createWalls: function createWalls() {
walls.forEach(function (wall) {
if (wall && wall.destroy) {
wall.destroy();
}
});
walls = [];
// Użyj getAsset zamiast new Shape dla ścian, jeśli 'wall' i 'floor' to assety
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
walls.push(leftWall);
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 1,
anchorY: 0,
x: 2048,
y: 0
})); // AnchorX=1 dla prawej ściany
rightWall.x = 2048; // Poprawka pozycji prawej ściany
walls.push(rightWall);
var topWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
walls.push(topWall);
var bottomWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
})); // AnchorY=1 dla dolnej ściany
bottomWall.y = 2732; // Poprawka pozycji dolnej ściany
walls.push(bottomWall);
// Ustaw ściany na spodzie (niski zIndex)
walls.forEach(function (wall) {
game.setChildIndex(wall, 0);
});
},
// ---------- Metody przejścia między stanami ----------
showTitleScreen: function showTitleScreen() {
// Zatrzymaj timery poprzedniego stanu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null;
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
clearScene(); // Wyczyść elementy poprzedniej sceny (Grill, GameOver, itp.)
// Usuń stare tło, jeśli istnieje
if (currentBackground) {
tween.stop(currentBackground); // Zatrzymaj animacje tła
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "title";
game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe dla tytułu
// Dodaj tło ekranu tytułowego
currentBackground = LK.getAsset('titleBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(currentBackground, 0); // Dodaj na spód
// Ukryj gracza/bossa jeśli istnieją
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
// Pokaż ściany (jako część tła menu)
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
// Ustaw UI dla tytułu
ui.positionElements("title");
ui.titleText.setText("Welcome Unchosen");
ui.showMessage("Tap to Start", 0);
ui.showTutorial("Swipe to Roll - Death is Progress");
// Resetuj stan gestu
this.touchStart = {
x: 0,
y: 0
};
this.touchEnd = {
x: 0,
y: 0
};
// Przycisk testowy do Grill Menu (jeśli nadal potrzebny)
var grillMenuTestButton = new Container();
grillMenuTestButton.interactive = true;
grillMenuTestButton.x = 2048 / 2;
grillMenuTestButton.y = 1600;
currentSceneElements.addChild(grillMenuTestButton);
var grillButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
grillMenuTestButton.addChild(grillButtonBg);
var grillButtonText = new Text2('Go to Grill Menu', {
size: 50,
fill: 0xFFFFFF
});
grillButtonText.anchor.set(0.5, 0.5);
grillMenuTestButton.addChild(grillButtonText);
grillMenuTestButton.down = function () {
gameState.showGrillScreen();
};
// --- Tap anywhere on title screen to start the intro ---
game.on('down', function () {
if (gameState.currentState === 'title') {
gameState.showIntro();
}
});
},
showIntro: function showIntro() {
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null; // Wyczyść timer
clearScene(); // Wyczyść elementy tytułu
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
} // Usuń stare tło
this.currentState = "intro";
game.setBackgroundColor(0x111111);
// Dodaj tło intro i animację zoom
currentBackground = LK.getAsset('introBg', {
anchorX: 0.5,
anchorY: 0.4,
x: 1000,
y: 1000,
scaleX: 1,
scaleY: 1
});
game.addChildAt(currentBackground, 0);
tween(currentBackground, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 22000,
easing: tween.linear,
repeat: Infinity,
yoyo: true
});
// Ukryj ściany na czas intro
if (player && player.destroy) {
player.destroy();
}
player = null; // Upewnij się, że nie ma gracza/bossa
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
}); // Ukryj ściany
ui.positionElements("intro"); // Ustaw UI dla intro (głównie ukrywa elementy)
// --- Teksty intro z timerami ---
// Funkcja do wyświetlania tekstu z fade-in, fade-out i kontynuacją
function showIntroText(text, delay, onComplete) {
var introText = new Text2(text, {
size: 90,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
introText.anchor.set(0.5, 0.5);
introText.x = 2048 / 2;
introText.y = 2232 / 2 - 400;
introText.alpha = 0;
currentSceneElements.addChild(introText);
LK.setTimeout(function () {
tween(introText, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
tween(introText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut
});
LK.setTimeout(function () {
try {
if (introText && introText.destroy) {
introText.destroy();
}
} catch (e) {}
if (onComplete) {
onComplete();
}
}, 1000); // czas na fade-out
}, 3000); // czas wyświetlania
}, delay);
}
// --- Startujemy sekwencję intro ---
showIntroText('From the creators of FromSoftware...', 2000, function () {
showIntroText('...I have no idea. I don’t know them.', 0, function () {
showIntroText('Silas GameStudio...', 0, function () {
showIntroText('Still looking for funding.', 0, function () {
showIntroText('Oh... and I don’t even exist.', 0, function () {
// <-- NOWY TEKST
// Po ostatnim tekście przechodzimy do howToScene
LK.setTimeout(function () {
if (typeof gameState.showFakeTutorial === 'function') {
gameState.showFakeTutorial();
} else {
console.error("Error: gameState.showFakeTutorial is not defined");
gameState.showTitleScreen(); // Fallback to title screen
}
}, 1000);
});
});
});
});
});
},
showFakeTutorial: function showFakeTutorial() {
clearScene(); // Wyczyść tekst intro
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null; // Wyczyść stary timer
this.currentState = "fakeTutorial";
// Zachowaj tło intro? Jeśli tak, usuń game.setBackgroundColor i nie niszcz currentBackground
// game.setBackgroundColor(0x000000); // Czarne tło - usunięte, aby zachować tło intro
ui.positionElements("fakeTutorial"); // Ustaw UI
// Tekst fałszywego tutorialu
var instructionText = new Text2('Press LPM to block.. or don’t press.', {
size: 100,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
// czarny cień
dropShadowDistance: 3,
// odległość cienia
dropShadowBlur: 4,
// lekkie rozmycie cienia
dropShadowAngle: Math.PI / 4 // kąt cienia
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2;
currentSceneElements.addChild(instructionText);
// Timer przechodzący do prawdziwego tutorialu
this.fakeTutorialTimerId = LK.setTimeout(function () {
try {
if (instructionText && instructionText.destroy) {
instructionText.destroy();
}
} catch (e) {}
gameState.fakeTutorialTimerId = null; // Zresetuj ID
// *** POPRAWKA: Sprawdzenie przed wywołaniem ***
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
} else {
console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!");
gameState.showTitleScreen(); // Awaryjny powrót do tytułu
}
} /*.bind(this)*/, 6000); // 6 sekund
},
// *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) ***
showRealTutorial: function showRealTutorial() {
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu
clearScene(); // Wyczyść elementy fake tutorialu/fake game over
// Zatrzymaj animację tła intro i usuń je
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "realTutorial";
game.setBackgroundColor(0x111111); // Ciemne tło
// Pokaż ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry)
// Tekst prawdziwego tutorialu
var tutorialTitle = new Text2('HOW TO PLAY', {
size: 100,
fill: 0xFFFFFF
});
tutorialTitle.anchor.set(0.5, 0.5);
tutorialTitle.x = 2048 / 2;
tutorialTitle.y = 600;
currentSceneElements.addChild(tutorialTitle);
var tutorialDesc = new Text2('Swipe the screen to ROLL.\nRoll THROUGH the boss attacks to deal damage.\nSurvive!', {
size: 60,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1600
});
tutorialDesc.anchor.set(0.5, 0.5);
tutorialDesc.x = 2048 / 2;
tutorialDesc.y = 1000;
currentSceneElements.addChild(tutorialDesc);
// Przycisk "Let's Roll!" do rozpoczęcia gry
var startButton = new Container();
startButton.interactive = true;
startButton.x = 2048 / 2;
startButton.y = 1500;
currentSceneElements.addChild(startButton);
var startButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
startButton.addChild(startButtonBg);
var startButtonText = new Text2("Let's Roll!", {
size: 50,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
startButton.addChild(startButtonText);
startButton.down = function () {
// clearScene(); // Niekonieczne, startGame to zrobi
gameState.startGame(); // Rozpocznij grę
};
},
// Obsługa inputu w stanie fakeTutorial (fałszywa śmierć)
handleFakeTutorialInput: function handleFakeTutorialInput() {
// Wyczyść timer, który miał prowadzić do prawdziwego tutorialu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null; // Zresetuj ID timera
}
clearScene(); // Wyczyść ekran fałszywego tutorialu
// Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver')
this.currentState = "fakeGameOver"; // Zmieniono z "gameOver"
// Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze
if (currentBackground) {
tween.stop(currentBackground);
}
ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED"
// Wyświetl "YOU DIED" na czerwono
var diedText = new Text2("YOU DIED", {
size: 150,
fill: 0xFF0000
}); // Czerwony kolor
diedText.anchor.set(0.5, 0.5);
diedText.x = 2048 / 2;
diedText.y = 600;
currentSceneElements.addChild(diedText);
// Wyświetl wyjaśnienie
var explanationText = new Text2("Did you check the title of the game?", {
size: 70,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800,
dropShadow: true,
dropShadowColor: 0x000000,
// cień czarny
dropShadowDistance: 3,
// odległość cienia
dropShadowBlur: 4,
// lekkie rozmycie cienia
dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni)
});
explanationText.anchor.set(0.5, 0.5);
explanationText.x = 2048 / 2;
explanationText.y = 800;
currentSceneElements.addChild(explanationText);
// Dodaj przycisk "How to play again"
var howToPlayButtonContainer = new Container();
howToPlayButtonContainer.interactive = true;
howToPlayButtonContainer.x = 2048 / 2;
howToPlayButtonContainer.y = 1300; // Pozycja przycisku
currentSceneElements.addChild(howToPlayButtonContainer);
var buttonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
howToPlayButtonContainer.addChild(buttonBg);
var buttonText = new Text2('How to play', {
size: 50,
fill: 0xFFFFFF
}); // Zmieniono tekst i rozmiar
buttonText.anchor.set(0.5, 0.5);
howToPlayButtonContainer.addChild(buttonText);
// Akcja przycisku: Przejdź do prawdziwego tutorialu
howToPlayButtonContainer.down = function () {
// *** POPRAWKA: Sprawdzenie przed wywołaniem ***
if (gameState && typeof gameState.showRealTutorial === 'function') {
gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
} else {
console.error("Error: gameState.showRealTutorial is not a function in fake input handler!");
gameState.showTitleScreen(); // Awaryjny powrót do tytułu
}
};
},
// Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu
startGame: function startGame() {
// Wyczyść timery z poprzednich stanów
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null;
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
clearScene(); // Wyczyść elementy tutorialu
// Usuń tło tutorialu/intro jeśli istnieje
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
this.currentState = "game"; // Ustaw stan na "game"
game.setBackgroundColor(0x111111);
// Stwórz gracza (jeśli nie istnieje)
if (player && player.destroy) {
player.destroy();
} // Zniszcz starego gracza
player = game.addChild(new Player()); // Stwórz nowego gracza
player.health = storage.maxHearts || 5; // Ustaw zdrowie gracza
// Resetuj stan gracza
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
if (player && typeof player.clearRollTimeouts === 'function') {
player.clearRollTimeouts();
}
player.hasRolledThroughBossThisRoll = false;
player.x = 2048 / 2;
player.y = 2732 / 2 + 400; // Pozycja startowa gracza
player.alpha = 1; // Upewnij się, że jest widoczny
// Stwórz bossa (jeśli nie istnieje)
if (boss && boss.destroy) {
boss.destroy();
} // Zniszcz starego bossa
boss = game.addChild(new Boss()); // Stwórz nowego bossa
boss.x = 2048 / 2;
boss.y = 2732 / 2 - 400; // Pozycja startowa bossa
boss.alpha = 1; // Upewnij się, że jest widoczny
// Resetuj stan bossa
boss.attackCooldown = 90; // Daj graczowi chwilę przed pierwszym atakiem (ok 1.5s)
boss.attacks = [];
boss.attackSpeedMultiplier = 1;
boss.repositioning = false;
boss.dead = false; // Upewnij się, że nie jest martwy
boss.phase = 1; // Zacznij od fazy 1
boss.tint = 0xFFFFFF; // Resetuj kolor (na wypadek przejścia fazy w poprzedniej grze)
boss.scale.set(1, 1); // Resetuj skalę (na wypadek animacji śmierci)
// Ustaw parametry bossa i gry zależnie od trybu
if (isNewBossPlusMode) {
boss.maxHealth = 2000;
boss.health = boss.maxHealth;
this.gameDuration = 600; // 10 minut
boss.attackSpeedMultiplier = 0.6; // Szybsze ataki od początku
boss.speed = 7; // Szybszy ruch
console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
} else {
boss.maxHealth = 200; // Standardowy boss HP
boss.health = boss.maxHealth;
this.gameDuration = 120; // 2 minuty
boss.speed = 5; // Standardowa prędkość
boss.attackSpeedMultiplier = 1; // Standardowa prędkość ataków
console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
}
// Pokaż ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
// Ustaw UI dla stanu gry
ui.positionElements("game");
ui.updateHearts(player.health, storage.maxHearts);
ui.showTutorial("Swipe to Roll!"); // Wyświetl krótki tutorial
ui.updateBossHealth(boss.health, boss.maxHealth); // Pokaż pasek HP bossa
// --- Timer walki z bossem ---
this.remainingTime = this.gameDuration; // Ustaw początkowy czas
ui.updateTimerDisplay(this.remainingTime); // Wyświetl czas
// Rozpocznij interwał timera
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
} // Wyczyść stary timer
this.bossSpeedIncreased = false; // Resetuj flagę przyspieszenia bossa
this.gameTimerInterval = LK.setInterval(function () {
// Aktualizuj tylko w stanie gry
if (gameState.currentState === "game") {
gameState.remainingTime--;
ui.updateTimerDisplay(gameState.remainingTime);
// --- Przyspieszenie bossa - TYLKO W STANDARDOWYM TRYBIE ---
if (!isNewBossPlusMode) {
var accelerationThreshold = gameState.gameDuration - 60; // Przyspieszenie na 60s przed końcem
if (gameState.remainingTime <= accelerationThreshold && !gameState.bossSpeedIncreased) {
gameState.bossSpeedIncreased = true;
if (boss && !boss.dead) {
// Sprawdź czy boss nadal żyje
boss.attackSpeedMultiplier *= 0.7; // Przyspiesz ataki
boss.speed += 1; // Lekko przyspiesz ruch
ui.showMessage("Boss attacks faster!", 2000); // Komunikat
}
}
}
// --- Koniec Przyspieszenia bossa ---
// Sprawdź koniec gry (czas minął)
if (gameState.remainingTime <= 0) {
LK.clearInterval(gameState.gameTimerInterval); // Zatrzymaj timer
gameState.gameTimerInterval = null;
if (isNewBossPlusMode) {
// Czas minął w Boss+ -> Zwycięstwo przez przetrwanie
gameOverReasonIsDeath = false; // Przyczyna: czas
gameState.gameOver(false); // Przejdź do ekranu końca gry (specjalny komunikat)
} else {
// Czas minął w standardowym trybie -> Przejdź do Grill Menu
// Zakładamy, że przetrwanie 2 minut w standardowym trybie to też "zwycięstwo"
gameState.showGrillScreen();
}
}
} else {
// Jeśli stan gry się zmienił, zatrzymaj timer
if (gameState.gameTimerInterval) {
LK.clearInterval(gameState.gameTimerInterval);
}
gameState.gameTimerInterval = null;
}
} /*.bind(this)*/, 1000); // Interwał co 1 sekundę
// Ukryj tutorial po chwili
LK.setTimeout(function () {
if (gameState.currentState === "game") {
// Sprawdź czy nadal jesteśmy w grze
ui.hideTutorial();
}
// Rozpoczęcie ataków bossa jest teraz obsługiwane przez Boss.update i jego cooldown
}, 3000); // 3 sekundy opóźnienia
},
// victory() - nieużywane, logika w timerze i boss.die()
showGrillScreen: function showGrillScreen() {
// Wyczyść timery
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
}
this.fakeTutorialTimerId = null;
this.bossSpeedIncreased = false; // Reset flagi
clearScene(); // Wyczyść elementy gry (ataki, itp.)
// Zniszcz gracza i bossa jeśli jeszcze istnieją
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null; // Boss powinien być już zniszczony przez die(), ale na wszelki wypadek
this.currentState = "grillMenu";
game.setBackgroundColor(0x333333); // Tło grilla
// Usuń tło gry
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
// Dodaj tło dla ekranu Grilla
currentBackground = LK.getAsset('grillMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(currentBackground, 0); // Na spód
// Ukryj ściany
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw UI dla Grill Menu
ui.positionElements("grillMenu");
ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza
// --- Przyciski na ekranie Grilla ---
var buttonYStart = 1000;
var buttonYOffset = 150;
// Przycisk "Rest"
var restButton = new Container();
restButton.interactive = true;
restButton.x = 2048 / 2;
restButton.y = buttonYStart;
currentSceneElements.addChild(restButton);
var restButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
restButton.addChild(restButtonBg);
var restButtonText = new Text2('Rest', {
size: 50,
fill: 0xFFFFFF
});
restButtonText.anchor.set(0.5, 0.5);
restButton.addChild(restButtonText);
restButton.down = function () {
ui.showMessage("Rest in peace...", 2000);
LK.setTimeout(function () {
ui.showMessage("Thank you for playing!", 3000);
}, 2500);
LK.setTimeout(function () {
// clearScene(); // Niekonieczne, showTitleScreen to zrobi
gameState.showTitleScreen(); // Wróć do tytułu
}, 6000); // Opóźnienie
};
// Przycisk "Upgrade Roll" (obecnie bez funkcji)
var upgradeButton = new Container();
upgradeButton.interactive = true;
upgradeButton.x = 2048 / 2;
upgradeButton.y = buttonYStart + buttonYOffset;
currentSceneElements.addChild(upgradeButton);
var upgradeButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeButton.addChild(upgradeButtonBg);
var upgradeButtonText = new Text2('Upgrade Roll', {
size: 50,
fill: 0xFFFFFF
});
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(upgradeButtonText);
upgradeButton.down = function () {
ui.showMessage("Not implemented yet!", 2000);
}; // Zmieniono komunikat
// Przycisk "New Boss+"
var newBossButton = new Container();
newBossButton.interactive = true;
newBossButton.x = 2048 / 2;
newBossButton.y = buttonYStart + buttonYOffset * 2;
currentSceneElements.addChild(newBossButton);
var newBossButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
newBossButton.addChild(newBossButtonBg);
var newBossButtonText = new Text2('New Boss+', {
size: 50,
fill: 0xFFFFFF
});
newBossButtonText.anchor.set(0.5, 0.5);
newBossButton.addChild(newBossButtonText);
newBossButton.down = function () {
// clearScene(); // Niekonieczne, startGame to zrobi
isNewBossPlusMode = true; // Ustaw flagę
gameState.startGame(); // Rozpocznij grę w trybie Boss+
};
},
// Przejście do stanu Game Over
// isDeath: true (gracz zginął), false (czas minął w Boss+)
gameOver: function gameOver(isDeath) {
this.currentState = "gameOver"; // Ustaw stan
gameOverReasonIsDeath = isDeath; // Zapisz przyczynę
// Zatrzymaj timer gry
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
this.gameTimerInterval = null;
this.bossSpeedIncreased = false; // Reset flagi przyspieszenia
// Zatrzymaj animację tła (jeśli istnieje, ale nie powinno w tym stanie)
if (currentBackground) {
tween.stop(currentBackground);
}
game.setBackgroundColor(0x000000); // Czarne tło
// Ukryj/zniszcz gracza i bossa (powinny być już zniszczone przez swoje metody die)
if (player && player.alpha !== 0) {
player.alpha = 0;
} // Ukryj, jeśli nie zniknął
if (boss && boss.alpha !== 0) {
boss.alpha = 0;
}
// Ukryj ściany
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Ustaw UI dla ekranu game over
ui.positionElements("gameOver");
// Logika komunikatu Game Over
var gameOverMessage = "YOU DIED"; // Domyślny komunikat
if (!isDeath) {
// Jeśli czas minął (tylko w Boss+)
gameOverMessage = "YOU SURVIVED... for now."; // Zmieniono komunikat zwycięstwa Boss+
}
// Wyświetl komunikat i tytuł
ui.titleText.setText(gameOverMessage);
ui.titleText.alpha = 1; // Pokaż tytuł
ui.showMessage("", 0); // Wyczyść ewentualny poprzedni komunikat
ui.showTutorial(""); // Ukryj tutorial
// Pasek zdrowia bossa jest zarządzany przez updateBossHealth w pętli gry
// W tym stanie updateBossHealth pokaże pasek tylko jeśli !isDeath && isNewBossPlusMode
// Zaplanuj restart gry po opóźnieniu
LK.setTimeout(function () {
// Wyczyść scenę przed restartem
clearScene();
// Zniszcz gracza/bossa jeśli jakimś cudem przetrwali
if (player && player.destroy) {
player.destroy();
}
player = null;
if (boss && boss.destroy) {
boss.destroy();
}
boss = null;
// Wyczyść ataki bossa (na wszelki wypadek) - teraz robione w Boss.die / Boss.update
// if (boss && boss.attacks) boss.attacks = [];
// Usuń tło game over (czarne)
if (currentBackground) {
currentBackground.destroy();
}
currentBackground = null;
// Resetuj UI do stanu "przed grą" (np. jak w tytule, ale bez tekstów)
// Pozycjonowanie jest ok, ale trzeba wyczyścić teksty
ui.titleText.setText("");
ui.titleText.alpha = 0;
ui.messageText.setText("");
ui.messageText.alpha = 0;
ui.tutorialText.setText("");
ui.tutorialText.alpha = 0;
ui.updateDeathsCounter(); // Zaktualizuj licznik śmierci
ui.updateBossHealth(0, 1); // Ukryj pasek HP
ui.updateHearts(0, storage.maxHearts); // Ukryj serca
// Flaga isNewBossPlusMode pozostaje niezmieniona (jeśli zginąłeś w Boss+, restartujesz w Boss+)
// gameOverReasonIsDeath zostanie zresetowana przy następnym wywołaniu gameOver
// Restartuj walkę (startGame użyje flagi isNewBossPlusMode)
gameState.startGame();
}, 4000); // Zwiększono opóźnienie do 4 sekund przed restartem
},
// Obsługa gestów dotykowych/myszy
processTouchGesture: function processTouchGesture() {
// --- Obsługa inputu w stanie fakeTutorial ---
if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) {
this.handleFakeTutorialInput(); // Wywołaj fałszywą śmierć
return; // Zakończ przetwarzanie gestu
}
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Minimalny dystans dla swipe (turlania)
var swipeThreshold = 50;
// --- Obsługa Tapnięcia ---
if (distance < swipeThreshold) {
// Sprawdź, czy tapnięcie było na interaktywnym obiekcie (przycisku)
// Zakładamy, że LK.Game obsługuje to przez przekazanie 'obj' do game.down/up
// Jeśli tapnięcie NIE było na przycisku:
if (this.currentState === "title") {
// Sprawdź czy tapnięcie nie było na przycisku Grill Menu
// Prosty sposób: załóżmy, że jeśli obj nie jest zdefiniowany w game.up, to kliknięto tło
// (Wymaga sprawdzenia, jak LK.Game przekazuje 'obj')
// Na razie zakładamy, że tapnięcie w tło przechodzi do intro
this.showIntro();
return;
}
// W innych stanach (gameOver, victory, grillMenu) tapnięcie w tło nic nie robi
// Obsługa przycisków dzieje się przez ich własne handlery .down/.up
return;
}
// --- Obsługa Swipe (Turlania) ---
// Działa tylko w stanie gry i gdy gracz żyje
if (this.currentState === "game" && player && !player.dead) {
// Normalizuj kierunek gestu
var direction = {
x: 0,
y: 0
};
if (distance > 0) {
// Unikaj dzielenia przez zero
direction.x = dx / distance;
direction.y = dy / distance;
}
// Wykonaj turlanie gracza
player.roll(direction);
}
// W innych stanach swipe jest ignorowany
}
};
// --- Obsługa inputu ---
game.down = function (x, y, obj) {
// Rejestruj początek dotyku/kliknięcia
// Stany, w których śledzimy gesty lub kliknięcia przycisków:
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchStart.x = x;
gameState.touchStart.y = y;
gameState.touchEnd.x = x; // Reset end point
gameState.touchEnd.y = y;
}
// Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down
};
game.up = function (x, y, obj) {
// Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
// Przetwórz gest (sprawdzi czy to tap czy swipe i podejmie akcję)
// Sprawdź, czy 'obj' istnieje - jeśli tak, to było kliknięcie na interaktywnym elemencie,
// którego logikę obsługuje jego własny handler .down/.up, więc nie rób nic więcej.
// Jeśli 'obj' nie istnieje, to było kliknięcie/swipe w tło.
game.up = function (x, y, obj) {
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x;
gameState.touchEnd.y = y;
// WAŻNE: Wywołuj zawsze processTouchGesture(), bez if (!obj)!
gameState.processTouchGesture();
}
};
}
// Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna
};
game.move = function (x, y, obj) {
// Śledź ruch palca/myszy dla swipe
var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver"];
if (trackStates.indexOf(gameState.currentState) !== -1) {
gameState.touchEnd.x = x; // Aktualizuj końcową pozycję na bieżąco
gameState.touchEnd.y = y;
}
};
// --- Główna pętla aktualizacji gry ---
game.update = function () {
// Aktualizuj UI niezależnie od stanu gry (ale tylko jeśli UI istnieje)
if (ui) {
ui.updateDeathsCounter();
// Aktualizuj pasek zdrowia bossa (jeśli boss istnieje i UI istnieje)
if (boss) {
// Upewnij się, że maxHealth jest sensowne
var maxHp = boss.maxHealth > 0 ? boss.maxHealth : 1;
ui.updateBossHealth(boss.health, maxHp);
} else {
// Jeśli boss nie istnieje, ukryj pasek (chyba że to wygrana Boss+ przez czas)
if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
// Domyślne maxHP do ukrycia paska to 1 (uniknięcie dzielenia przez 0)
ui.updateBossHealth(0, 1);
}
}
// Aktualizuj serca gracza (jeśli gracz istnieje i UI istnieje)
if (player) {
ui.updateHearts(player.health, storage.maxHearts);
} else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") {
// Ukryj serca poza grą/game over
ui.updateHearts(0, storage.maxHearts);
}
// Pozycjonowanie/widoczność elementów UI jest zarządzana przez gameState.positionElements() w metodach zmiany stanu.
}
// Logika specyficzna dla stanu gry
if (gameState.currentState === "game") {
if (player) {
player.update(); // Aktualizacja gracza (ruch turlania, nietykalność, kolizja turlania z bossem)
}
if (boss) {
boss.update(); // Aktualizacja bossa (ruch, cooldown, tworzenie/usuwanie ataków, kolizje ataków z graczem)
}
// Sprawdzanie kolizji i zmiany stanów (gameOver, grillScreen) są teraz wewnątrz metod update/die gracza i bossa.
}
// Logika dla innych stanów (np. animacje w intro, obsługa przycisków) jest zarządzana przez ich własne funkcje i handlery.
};
// --- Rozpoczęcie gry ---
// Inicjalizuj obiekt gameState, który zajmie się resztą.
gameState.init(); ===================================================================
--- original.js
+++ change.js
@@ -922,94 +922,91 @@
return self;
});
var SpriteAnimation = Container.expand(function (options) {
var self = Container.call(this);
- // options = { frames: [], frameDuration: 100, loop: false, anchorX: 0, anchorY: 0, x: 0, y: 0 };
+ // options = { frames: [], frameDuration: 100, loop: false, anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 };
+ // 🔥 ZMODYFIKOWANA LOGIKA: PRZYJMUJEMY TABLICĘ GOTOWYCH SPRITE'ÓW w options.frames!
+ self.frames = options.frames || []; // self.frames TO TERAZ obiekty sprite, a NIE definicje assetów!
self.frameDuration = options.frameDuration || 100; // in milliseconds
self.loop = options.loop !== undefined ? options.loop : false;
self.currentFrame = 0;
self.frameTimer = 0; // in game frames (assuming 60fps)
self.playing = true;
self.destroyed = false; // Manual destroyed flag
- // Ustawiamy pozycję na kontenerze animacji (self). Anchor NIE JEST ustawiany na kontenerze.
+ // Ustawiamy pozycję i anchor na kontenerze animacji (self)
+ // Pozycja kontenera jest ustawiana w createAttack za pomocą game.addChild(new SpriteAnimation({x: x, y: y, ...}))
self.x = options.x || 0;
self.y = options.y || 0;
- // 🔥 USUNIĘTO LINIĘ POWODUJĄCĄ BŁĄD: self.anchor.set(options.anchorX, options.anchorY);
- // *** ZMODYFIKOWANA LOGIKA: Dodajemy WSZYSTKIE klatki jako dzieci i zarządzamy widocznością ***
- self.sprites = []; // Tablica do przechowywania obiektów sprite klatek
- // Upewnij się, że options.frames zawiera listę assetów
- var frameAssets = options.frames || [];
- if (frameAssets.length === 0) {
+ // 🔥 Usunięto próbę ustawienia anchor na kontenerze SpriteAnimation, bo Container tego nie ma.
+ // Anchor JEST JUŻ ustawiony na poszczególnych sprite'ach klatek (przy tworzeniu circleFrames/lineFrames).
+ // *** ZMODYFIKOWANA LOGIKA: Dodajemy PRZYJĘTE SPRITE'Y klatek (z self.frames) jako dzieci i zarządzamy widocznością ***
+ var frameSprites = self.frames; // Używamy bezpośrednio tablicy przyjętych sprite'ów
+ if (frameSprites.length === 0) {
console.warn("SpriteAnimation created with no frames!");
- }
- frameAssets.forEach(function (frameAsset, index) {
- // Tworzymy nowy sprite z każdego assetu klatki
- // Używamy assetId do pobrania assetu i opcji anchor. Anchor jest ustawiany na INDYWIDUALNYM SPRICIE tworzonym przez LK.getAsset.
- // Check if frameAsset is an actual asset object with assetId or if it's directly the assetId string
- var assetId = _typeof(frameAsset) === 'object' && frameAsset.assetId ? frameAsset.assetId : frameAsset;
- var frameSprite = LK.getAsset(assetId, {
- // Użyj anchor z opcji SpriteAnimation, jeśli podany, w przeciwnym razie z assetu klatki, w przeciwnym razie domyślnie 0.5 (centrum)
- anchorX: options.anchorX !== undefined ? options.anchorX : frameAsset && frameAsset.anchorX !== undefined ? frameAsset.anchorX : 0.5,
- anchorY: options.anchorY !== undefined ? options.anchorY : frameAsset && frameAsset.anchorY !== undefined ? frameAsset.anchorY : 0.5
- // Pozycja relatywna do kontenera SpriteAnimation (self) - domyślnie 0,0 jest ok, bo anchor jest ustawiony na spricie
- });
- // Upewnij się, że sprite jest niewidoczny na początku, chyba że to pierwsza klatka (currentFrame = 0)
- frameSprite.visible = index === self.currentFrame;
- // Dodaj sprite jako dziecko kontenera animacji (self)
- self.addChild(frameSprite);
- self.sprites.push(frameSprite); // Zapisz referencję do sprite'a
- });
- // Jeśli nie ma klatek, ustaw na zniszczony i nie graj
- if (self.sprites.length === 0) {
self.destroyed = true;
self.playing = false;
+ } else {
+ frameSprites.forEach(function (frameSprite, index) {
+ // 🔥 frameSprite jest JUŻ gotowym obiektem sprite z poprawnym anchor ustawionym w klasie Boss.
+ // Nie wywołujemy już tutaj LK.getAsset.
+ if (frameSprite && frameSprite.visible !== undefined && frameSprite.addChild !== undefined) {
+ // Sprawdź czy obiekt wygląda jak sprite/display object
+ // Upewnij się, że jest niewidoczny na początku, chyba że to pierwsza klatka
+ frameSprite.visible = index === self.currentFrame;
+ // Dodaj sprite jako dziecko kontenera animacji (self)
+ self.addChild(frameSprite);
+ } else {
+ console.error("SpriteAnimation: Invalid sprite object provided in frames array at index:", index, frameSprite);
+ // Jeśli dane wejściowe są złe, oznacz animację jako zniszczoną
+ self.destroyed = true;
+ self.playing = false;
+ }
+ });
}
self.update = function () {
// Aktualizuj tylko jeśli gramy, mamy klatki i nie jesteśmy zniszczeni
- if (!self.playing || self.sprites.length === 0 || self.destroyed) {
+ if (!self.playing || self.frames.length === 0 || self.destroyed) {
return;
}
self.frameTimer++; // Przyrost timera w klatkach gry
// Sprawdź, czy minął czas na zmianę klatki (przelicz frameDuration z ms na klatki gry przy 60fps)
- // Upewnij się, że dzielenie jest poprawne i nie daje NaN lub Infinity jeśli frameDuration = 0
var frameThreshold = self.frameDuration / (1000 / 60);
if (frameThreshold <= 0 || !isFinite(frameThreshold)) {
- // Dodano sprawdzenie Infinity/NaN
frameThreshold = 1; // Minimalny próg to 1 klatka gry
}
if (self.frameTimer >= frameThreshold) {
self.frameTimer = 0; // Resetuj timer
var prevFrameIndex = self.currentFrame; // Zapisz indeks poprzedniej klatki
self.currentFrame++; // Przejdź do następnej klatki
// Sprawdź, czy animacja dobiegła końca
- if (self.currentFrame >= self.sprites.length) {
- // Sprawdź względem długości tablicy sprite'ów
+ if (self.currentFrame >= self.frames.length) {
+ // Sprawdź względem długości tablicy klatek/sprite'ów
if (self.loop) {
self.currentFrame = 0; // Zapętl
} else {
// Animacja się zakończyła i nie zapętla
- self.currentFrame = self.sprites.length - 1; // Pozostań na ostatniej klatce
+ self.currentFrame = self.frames.length - 1; // Pozostań na ostatniej klatce
self.playing = false; // Zatrzymaj odtwarzanie
- // *** Ustawiamy flagę destroyed tutaj, gdy animacja się zakończy (dla logiki usuwania w Boss.update) ***
+ // *** Ustawiamy flagę destroyed tutaj, gdy animacja się zakończy ***
self.destroyed = true;
// Nie ukrywaj ostatniej klatki, pozostań na niej widoczny do momentu zniszczenia
return; // Zakończ update, bo animacja się skończyła i nie zapętla
}
}
// *** ZMODYFIKOWANA LOGIKA: Ukryj poprzednią klatkę i pokaż aktualną ***
- // Upewnij się, że poprzedni sprite istnieje (powinien istnieć, jeśli sprites.length > 0 i prevFrameIndex jest poprawne)
- if (self.sprites[prevFrameIndex]) {
- self.sprites[prevFrameIndex].visible = false; // Ukryj poprzednią klatkę
+ // Upewnij się, że poprzedni sprite istnieje i jest widoczny (powinien istnieć i być w self.frames)
+ if (self.frames[prevFrameIndex] && self.frames[prevFrameIndex].visible !== undefined) {
+ self.frames[prevFrameIndex].visible = false; // Ukryj poprzednią klatkę
} else {
- // Coś poszło nie tak, poprzednia klatka nie istnieje? Może przy zapętlaniu z 0 na ostatnią?
- console.warn("SpriteAnimation: Attempted to hide undefined sprite at index:", prevFrameIndex, self.sprites);
+ // Coś poszło nie tak z poprzednim sprite'em
+ // console.warn("SpriteAnimation: Attempted to hide invalid sprite at index:", prevFrameIndex, self.frames[prevFrameIndex]); // Log ostrzeżenia
}
- // Upewnij się, że nowy sprite istnieje i jest widoczny (powinien istnieć, jeśli loop=true lub currentFrame < sprites.length)
- if (self.sprites[self.currentFrame]) {
- self.sprites[self.currentFrame].visible = true; // Pokaż aktualną klatkę
+ // Upewnij się, że nowy sprite istnieje i jest widoczny (powinien istnieć i być w self.frames)
+ if (self.frames[self.currentFrame] && self.frames[self.currentFrame].visible !== undefined) {
+ self.frames[self.currentFrame].visible = true; // Pokaż aktualną klatkę
} else {
- // Coś poszło nie tak, nowa klatka nie istnieje? Loguj błąd.
- console.error("SpriteAnimation: Attempted to show undefined sprite at index:", self.currentFrame, self.sprites);
+ // Coś poszło nie tak, nowa klatka nie istnieje lub nie jest poprawnym obiektem sprite?
+ console.error("SpriteAnimation: Attempted to show invalid sprite at index:", self.currentFrame, self.frames[self.currentFrame]);
self.destroyed = true; // Oznacz jako zniszczony, aby zapobiec dalszym błędom
self.playing = false;
}
}
@@ -1022,31 +1019,31 @@
} // Jeśli już zniszczony, nic nie rób
// console.log("Destroying SpriteAnimation:", self); // Log do debugowania
self.destroyed = true; // Ustaw flagę zniszczenia
self.playing = false; // Zatrzymaj odtwarzanie
- // Usuń wszystkie dzieci (sprity klatek)
- // removeChildren() w LK Container powinna zniszczyć obiekty sprite (pixi sprites)
- self.removeChildren(); // Usuwa wszystkie dzieci
- // Usuń sam obiekt SpriteAnimation z jego rodzica (powinien być 'game')
+ // Usuń wszystkie dzieci (sprity klatek), które zostały dodane do kontenera animacji
+ // removeChildren() w LK Container powinna zniszczyć obiekty sprite
+ self.removeChildren();
+ // Usuń sam obiekt SpriteAnimation z jego rodzica (powinien być 'game', dodany w createAttack)
if (self.parent) {
self.parent.removeChild(self);
}
// Opcjonalnie: dodatkowe czyszczenie referencji
- self.sprites = null;
- self.frames = null; // Wyczyść referencję do assetów klatek
+ self.frames = null; // Wyczyść referencję do sprite'ów klatek
self.update = null; // Usuń referencję do metody update, jeśli nie jest już potrzebna po zniszczeniu
};
// Dodajemy metodę stop, która zatrzymuje animację (przydatne np. gdy boss umiera)
self.stop = function () {
self.playing = false;
// Opcjonalnie: ukryj wszystkie klatki po zatrzymaniu? Zależne od oczekiwanego zachowania.
- // self.sprites.forEach(s => s.visible = false);
+ // self.frames.forEach(s => { if(s && s.visible !== undefined) s.visible = false; });
// console.log("Stopped SpriteAnimation:", self); // Log do debugowania
};
return self; // Zwróć instancję obiektu SpriteAnimation
});
// Zamykająca klamra dla Container.expand klasy SpriteAnimation
// Zamykająca klamra dla Container.expand klasy SpriteAnimation
+// Zamykająca klamra dla Container.expand klasy SpriteAnimation
var UI = Container.expand(function () {
var self = Container.call(this);
// Create heart containers (wizualizacja zdrowia)
self.hearts = [];