User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {' Line Number: 650
User prompt
Please fix the bug: 'self.attachAsset is not a function' in or related to this line: 'self.bossGraphics = self.attachAsset('bossIdle', {' Line Number: 650
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.grillMenu = LK.getShape('grillMenu', {});' Line Number: 585
User prompt
Please fix the bug: 'LK.Text is not a constructor' in or related to this line: 'self.gameOverMessage = new LK.Text({' Line Number: 572
User prompt
Please fix the bug: 'LK.Text is not a constructor' in or related to this line: 'self.newBossPlusButtonText = new LK.Text({' Line Number: 557
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.newBossPlusButton = LK.getShape('button_bg', {}); // Example shape button' Line Number: 550
User prompt
Please fix the bug: 'LK.Text is not a constructor' in or related to this line: 'self.startButtonText = new LK.Text({' Line Number: 535
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.startButton = LK.getShape('button_bg', {}); // Example shape button' Line Number: 528
User prompt
Please fix the bug: 'DisplayObjectContainer is not defined' in or related to this line: 'self.playerHearts = new DisplayObjectContainer(); // Container for player hearts' Line Number: 522
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.bossHpBar = LK.getShape('bossHpbar', {});' Line Number: 519
Code edit (1 edits merged)
Please save this source code
Code edit (6 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: 470
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
add assets bossAttack0 to bossattack3
User prompt
add assets bossAttack0 to bossattack3
Code edit (5 edits merged)
Please save this source code
User prompt
add asset fireball13 fireball14 fireball15 fireball16
Code edit (2 edits merged)
Please save this source code
User prompt
add assets fireball00 to fireball12
User prompt
Please fix the bug: 'TypeError: self.clearRollTimeouts is not a function' in or related to this line: 'self.clearRollTimeouts();' Line Number: 589
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: SpriteAnimation is not defined' in or related to this line: 'var rollAnimation = game.addChild(new SpriteAnimation({' Line Number: 448
/****
* 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);
// *** MODYFIKACJA: Dodaj grafikę bossIdle jako DZIECKO obiektu bossa (self) ***
// 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
// 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,
// MODIFIED
y: 0 // MODIFIED
});
return bossAttackAnim;
};
// Zmieniamy sygnaturę funkcji, przyjmuje teraz typ ataku (do decydowania o grafice Bossa)
// Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle)
self.playBossAttackAnim = function (attackType) {
// <--- DODANO PARAMETR
// 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
// Sprawdź, czy rodzicem jest self przed próbą usunięcia
if (self.bossAttackAnim.parent === self) {
// MODIFIED check
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
// Sprawdź, czy rodzicem jest self przed próbą usunięcia
if (self.bossGraphics && self.bossGraphics.parent === self) {
// MODIFIED check
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()); // *** MODIFIED: DODANO DO self! ***
// 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', {
// *** MODIFIED: DODANO DO self! ***
anchorX: 0.5,
anchorY: 0.5,
x: 0,
// Pozycja relatywna
y: 0 // Pozycja relatywna
}));
// *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go ***
// Sprawdź, czy rodzicem jest self przed próbą usunięcia
if (this.parent === self) {
// MODIFIED check
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 DZIECKO BOSSA.
};
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 pozostaje jak w poprzedniej poprawionej wersji
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
};
// *** START Zmodyfikowana funkcja createAttack - przyjmuje frameDurationMs i activationFrame jako argumenty ***
// Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
// Ta funkcja tworzy obiekt ataku logicznego i jego wizualizację (SpriteAnimation)
// Zmodyfikowana funkcja createAttack - zawiera ZMIENIONĄ KOLEJNOŚĆ operacji add/remove w update klatek
// Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
// Ta funkcja tworzy obiekt ataku logicznego i jego wizualizację (SpriteAnimation)
self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) {
// Lista klatek animacji. Domyślna lista (np. dla szarży), jeśli framesList nie jest podane.
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({
frames: frames,
frameDuration: frameDurationMs,
loop: false,
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
// NATYCHMIAST ustaw pierwszą klatkę żeby nie było pustki:
spriteAnim.removeChildren();
spriteAnim.addChild(spriteAnim.frames[0]);
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 (przy 60fps)
isActive: false // Flaga aktywności kolizji, domyślnie false
};
// Metoda update dla tej konkretnej animacji spriteAnim (obiektu visual)
spriteAnim.update = function () {
// Sprawdź, czy animacja nadal gra i ma klatki
if (!this.playing || this.frames.length === 0) {
return;
}
this.frameTimer++;
// Sprawdź, czy minął czas na przejście do następnej klatki
if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
this.frameTimer = 0;
var previousFrameSprite = null; // Będziemy przechowywać poprzednią klatkę do usunięcia
// Jeśli obecna klatka istnieje, zapisz referencję i usuń ją PRZED zmianą currentFrame
// *** Zmieniona logika: Usuń obecną klatkę PO dodaniu nowej ***
// Zamiast usuwać tutaj, zrobimy to na końcu tej sekcji.
// this.removeChildren(); // Stara linia - USUNIĘTA/ZMIENIONA
this.currentFrame++; // Przejdź do następnej klatki
// Obsługa końca animacji (jeśli nie zapętlona)
if (this.currentFrame >= this.frames.length) {
// Zostań na ostatniej klatce jeśli nie zapętlona
this.currentFrame = this.frames.length - 1;
this.playing = false;
}
// *** MODYFIKACJA KOLEJNOŚCI: Najpierw dodaj nową klatkę, POTEM usuń poprzednią ***
// Dodaj nową obecną klatkę do wyświetlenia
var nextFrameSprite = this.frames[this.currentFrame];
// Sprawdź, czy nowa klatka istnieje i różni się od poprzedniej, zanim dodasz/usuniesz
// Dodajemy tylko jeśli nowa klatka istnieje
if (nextFrameSprite) {
this.addChild(nextFrameSprite); // Dodaj nową klatkę
}
// Usuń poprzednią klatkę (jeśli istniała, była dzieckiem, różni się od nowej i nadal jest dzieckiem)
// Sprawdzamy parent, bo w międzyczasie sprite mógł zostać usunięty inaczej
if (previousFrameSprite && previousFrameSprite.parent === this && previousFrameSprite !== nextFrameSprite) {
this.removeChild(previousFrameSprite); // Usuń starą klatkę
}
// *** Aktywuj kolizję OD PODANEJ activationFrame WZWYŻ ***
if (this.currentFrame >= activationFrame) {
attack.isActive = true;
}
}
};
self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa
};
// *** END Zmodyfikowana funkcja createAttack ***
// *** START Zmodyfikowana funkcja circleAttack ***
// Ta funkcja tworzy wiele pocisków w kole wokół bossa
// Ta funkcja tworzy wiele pocisków w kole wokół bossa Z MAŁYM OPÓŹNIENIEM między nimi
// Ta funkcja tworzy wiele pocisków w kole wokół bossa Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO
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 (krótszy w New Boss+?)
// Lista klatek animacji dla tego ataku (pociski koła)
var circleFrames = [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', {})];
// Pętla tworząca wiele pocisków rozmieszczonych w kole Z 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
// *** MODIFIED: Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku, w tym pierwszego ***
// Opóźnienie dla i-tego pocisku będzie wynosić 20ms + i * 20ms
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, frames) {
// Używamy closure do "zamknięcia" wartości x, y i frames dla każdego timeoutu
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 (prędkość animacji - można dostosować jeśli nadal miga, ale skupmy się na migraniu)
// framesList = circleFrames (lista klatek dla tego ataku)
self.createAttack(x, y, attackLifeTime, 8, 120, frames); // Używamy 250ms, które wcześniej wydawało się zwalniać migotanie
}
};
}(posX, posY, circleFrames), delay); // Przekaż aktualne posX, posY i circleFrames do closure
}
};
// *** END Zmodyfikowana funkcja circleAttack ***
// *** START Zmodyfikowana funkcja lineAttack ***
// Ta funkcja tworzy wiele pocisków w linii od bossa do gracza
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
// Lista klatek animacji dla tego ataku (pociski linii)
var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})];
// Pętla tworząca pociski w linii z opóźnieniem
for (var i = 0; i < count; i++) {
var t = i / (count - 1); // Współczynnik interpolacji (0 do 1)
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 "zamknięcia" wartości x i y dla każdego timeoutu
return function () {
// Sprawdź warunki przed stworzeniem ataku po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
// *** MODYFIKACJA: Wywołaj createAttack z activationFrame = 3, frameDurationMs = 100 i listą klatek dla linii ***
// Ustaw frameDuration na 100ms dla tego ataku (przykład innej prędkości)
// Indeks 3 odpowiada 'fireball5' na liście lineFrames [fireball2 (0), fireball3 (1), fireball4 (2), fireball5 (3), ...]
self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // *** MODIFIED: Added 3, 100, lineFrames ***
}
};
}(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
// *** END Zmodyfikowana funkcja lineAttack ***
// *** START Zmodyfikowana funkcja chargeAttack - tworzy pociski podczas szarży ***
// Ta funkcja zarządza ruchem bossa (szarżą) i tworzy pociski wzdłuż ścieżki szarży
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 (pozostaje bez zmian od poprzedniej poprawki - pokazuje grafikę idle)
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent === self) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
} else if (self.bossAttackAnim.parent) {
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) {
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 w jednostkach gry
var chargeDuration = isNewBossPlusMode ?
// Czas trwania szarży w ms
600 : 800;
var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków tworzonych wzdłuż ścieżki szarży
var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku w ms
// Tween (animacja ruchu) bossa podczas szarży
// Ten tween modyfikuje pozycję obiektu self (boss)
tween(self, {
x: self.x + dx * chargeDistance,
// Celowa pozycja X
y: self.y + dy * chargeDistance // Celowa pozycja Y
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
// Czas trwania szarży, skalowany
easing: tween.easeIn,
// Krzywa animacji (przyspieszenie)
onFinish: function onFinish() {
// Funkcja wywoływana po zakończeniu tweenu szarży
if (self && !self.dead && gameState.currentState === "game") {
// Sprawdź warunki
// Faza repozycji - boss zatrzymuje ruch i po chwili wraca
self.repositioning = true;
LK.setTimeout(function () {
if (self && !self.dead) {
self.repositioning = false; // Zakończ stan repozycji
}
}, 500 * self.attackSpeedMultiplier); // Czas repozycji, skalowany
// Tween powrotu do pozycji startowej
// Ten tween modyfikuje pozycję self (boss) - grafika jako dziecko podąży za nim.
tween(self, {
x: startX,
// Powrót do pozycji X startowej
y: startY // Powrót do pozycji Y startowej
}, {
duration: 1000 * self.attackSpeedMultiplier,
// Czas powrotu, skalowany
easing: tween.easeOut // Krzywa animacji (zwalnianie)
});
// Utworzenie ataków (fireballi) wzdłuż ścieżki szarży
// Fireballe ze szarży używają domyślnej listy klatek z createAttack (bo framesList nie jest podane)
for (var i = 0; i < attacksOnPathCount; i++) {
var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji (0 do 1)
var currentChargeX = self.x; // Aktualna pozycja X bossa (koniec szarży)
var currentChargeY = self.y; // Aktualna pozycja Y bossa (koniec szarży)
// Interpolujemy pozycję pocisku pomiędzy początkiem szarży (startX, startY) a końcem (currentChargeX, currentChargeY)
var posX = startX + (currentChargeX - startX) * t;
var posY = startY + (currentChargeY - startY) * t;
// Utwórz atak (fireball) w obliczonej pozycji
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList (opcjonalne)
// activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków)
// frameDurationMs = 140ms (domyślna prędkość animacji dla tych pocisków)
self.createAttack(posX, posY, attackLifeTime, 4, 140); // *** MODIFIED: Added 4, 140 ***
}
}
}
});
};
// *** END Zmodyfikowana funkcja chargeAttack ***
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
// Upewnij się, że zdrowie nie spadnie poniżej zera (chyba że do śmierci)
self.health = Math.max(0, self.health);
// Wizualne sygnalizowanie otrzymania obrażeń
LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku
// Przejście fazy bossa przy 50% zdrowia (może być wyłączone w New Boss+?)
if (!isNewBossPlusMode) {
// 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 (mnożnik < 1, np. 0.8 oznacza 80% oryginalnego cooldownu)
// Wizualne przejście fazy (np. zmiana koloru)
tween(self, {
tint: 0xFF3300 // Zmień odcień na pomarańczowy/czerwony
}, {
duration: 1000,
easing: tween.easeInOut // Krzywa animacji
});
}
}
// Sprawdzenie, czy boss został pokonany
if (self.health <= 0) {
self.die(); // Wywołaj funkcję śmierci
}
// Aktualizacja paska zdrowia jest w game.update -> ui.updateBossHealth
};
self.die = function () {
// Funkcja obsługująca śmierć bossa
if (self.dead || gameState.currentState !== "game") {
// Boss umiera tylko w stanie gry
return;
}
self.dead = true; // Ustaw flagę śmierci
// Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu (jeśli nie New Boss+)
if (!isNewBossPlusMode) {
LK.getSound('victory').play(); // Dźwięk zwycięstwa w trybie standardowym
}
// Wyczyść pozostałe ataki przy śmierci bossa
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy(); // Zniszcz wizualizację ataku
}
});
self.attacks = []; // Wyczyść tablicę ataków
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
// Zanik do przezroczystości
scaleX: 2,
// Podwój skalowanie w poziomie
scaleY: 2 // Podwój skalowanie w pionie
}, {
duration: 2000,
// Czas trwania animacji śmierci (w ms)
easing: tween.easeOut,
// Krzywa animacji (zwalnianie)
onFinish: function onFinish() {
// Funkcja wywoływana po zakończeniu animacji śmierci
// Sprawdź, czy boss nadal istnieje przed zniszczeniem (zabezpieczenie)
if (self && self.destroy && !self.destroyed) {
self.destroy(); // Zniszcz obiekt bossa
}
// Zwiększ licznik pokonanych bossów
storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów (pobierz z local storage lub zacznij od 0)
// !!! Po pokonaniu bossa ZAWSZE przechodzimy do Grill Screena (niezależnie od trybu) !!!
gameState.showGrillScreen(); // <--- Zmieniono z gameOver na showGrillScreen
}
});
// Ataki zostaną wyczyszczone na początku die() lub w gameState.showGrillScreen clean up
};
self.update = function () {
// Główna metoda aktualizacji bossa (wywoływana w game.update)
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
// Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki
// (Zabezpieczenie na wypadek zmiany stanu gry bez śmierci bossa)
if (!self.dead && self.attacks) {
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy(); // Zniszcz wizualizację ataku
}
});
self.attacks = []; // Wyczyść tablicę ataków logicznych
}
return; // Zakończ aktualizację, jeśli nie jesteśmy w stanie gry
}
// Nie aktualizuj jeśli boss jest martwy i animacja śmierci działa (obiekt może być jeszcze widoczny)
// Pozwalamy animacji śmierci działać niezależnie od reszty logiki update
if (self.dead) {
return;
}
// --- START NOWA LOGIKA RUCHU BOSS ---
// Boss porusza się w kierunku gracza tylko jeśli:
// - Nie jest martwy (już sprawdzone na początku update)
// - Gra jest w stanie 'game' (już sprawdzone na początku update)
// - Nie jest w trakcie "repozycji" po ataku szarży (flaga self.repositioning)
// - Cooldown ataku jest wystarczająco długi (np. > 30 klatek), co oznacza, że boss nie jest w trakcie przygotowania do nowego ataku
// - Gracz istnieje i nie jest martwy
// Zakładamy, że 'chargeAttack' sam zarządza ruchem bossa podczas samej szarży (przez tween).
if (!self.repositioning && self.attackCooldown > 30 && player && !player.dead) {
var dx = player.x - self.x; // Różnica pozycji X między graczem a bossem
var dy = player.y - self.y; // Różnica pozycji Y
var distance = Math.sqrt(dx * dx + dy * dy); // Odległość między graczem a bossem
var moveSpeed = self.speed; // Prędkość ruchu bossa
// Boss porusza się tylko jeśli gracz jest dalej niż pewna minimalna odległość (np. 150 jednostek)
// Zapobiega to "drganiom" bossa, gdy jest bardzo blisko gracza.
if (distance > 150) {
var moveX = dx / distance * moveSpeed; // Komponent ruchu X w kierunku gracza
var moveY = dy / distance * moveSpeed; // Komponent ruchu Y
var nextX = self.x + moveX; // Przewidywana następna pozycja X bossa
var nextY = self.y + moveY; // Przewidywana następna pozycja Y bossa
// Ograniczenia areny (ściany na 100px marginesie, mapa 2048x2732)
// Uwzględnij rozmiar bossa, aby jego środek nie wszedł w ścianę (zakładając anchor 0.5, 0.5)
var halfWidth = self.width * self.scaleX / 2; // Połowa szerokości bossa (uwzględniając skalowanie)
var halfHeight = self.height * self.scaleY / 2; // Połowa wysokości bossa
var minX = 100 + halfWidth; // Minimalna pozycja X, aby uniknąć ściany z lewej
var maxX = 2048 - 100 - halfWidth; // Maksymalna pozycja X, aby uniknąć ściany z prawej
var minY = 100 + halfHeight; // Minimalna pozycja Y, aby uniknąć ściany z góry
var maxY = 2732 - 100 - halfHeight; // Maksymalna pozycja Y, aby uniknąć ściany z dołu
// Ustaw nową pozycję bossa, ograniczając ją do granic areny
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
// --- DODANO: Zaktualizuj pozycję aktywnej grafiki, aby podążała za bossem ---
// Ten komentarz był wcześniej. Ponieważ grafika jest teraz dzieckiem Bossa i jej pozycja relatywna to 0,0,
// pozycja grafiki jest AUTOMATYCZNIE aktualizowana, gdy self.x i self.y się zmieniają.
// Nie potrzebujemy tutaj ręcznej aktualizacji pozycji grafiki.
// Kod poniżej jest usunięty, bo pozycja relatywna dziecka jest ustawiona na 0,0 przy dodawaniu.
// if (self.bossGraphics && self.bossGraphics.parent === self) { self.bossGraphics.x = 0; self.bossGraphics.y = 0; }
// if (self.bossAttackAnim && self.bossAttackAnim.parent === self) { self.bossAttackAnim.x = 0; self.bossAttackAnim.y = 0; }
}
}
// --- KONIEC NOWA LOGIKA RUCHU BOSS ---
// --- START OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) ---
// Aktualizuj istniejące ataki bossa (pociski) i sprawdzaj kolizje z graczem
// Iteruj od końca do początku tablicy self.attacks, aby bezpiecznie usuwać elementy (przy użyciu splice)
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i]; // Pobierz bieżący obiekt ataku logicznego
// Zmniejszaj czas życia ataku (w klatkach gry)
if (attack.lifeTime > 0) {
attack.lifeTime--;
}
// Sprawdź kolizję ataku z graczem tylko jeśli:
// - Flaga attack.isActive jest true (atak jest "groźny")
// - Gracz istnieje i nie jest martwy
// - Gracz nie jest nietykalny (np. po turlaniu)
// - Wizualizacja ataku istnieje i nie została jeszcze zniszczona
if (attack.isActive && player && !player.dead && !player.invulnerable && attack.visual && !attack.visual.destroyed) {
var dx_p = player.x - attack.x; // Różnica pozycji X między graczem a atakiem
var dy_p = player.y - attack.y; // Różnica pozycji Y
var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); // Odległość między graczem a atakiem
// Użyj promieni do sprawdzenia kolizji (kolizja kołowa)
var playerRadius_p = player.width / 2; // Promień gracza (zakładając, że gracz ma właściwość width)
var attackRadius = attack.radius; // Promień ataku (zdefiniowany w createAttack)
if (distance_p < playerRadius_p + attackRadius) {
// Wykryto kolizję! Gracz otrzymuje obrażenia.
player.takeDamage(1); // Zadaj 1 punkt obrażeń graczowi
// Opcjonalnie: zniszcz wizualizację ataku natychmiast po trafieniu gracza
if (attack.visual && attack.visual.destroy) {
attack.visual.destroy(); // Zniszcz sprite animacji
}
self.attacks.splice(i, 1); // Usuń obiekt ataku logicznego z tablicy
// Można dodać 'break;' tutaj, jeśli chcesz, aby gracz mógł otrzymać obrażenia tylko od jednego pocisku bossa na klatkę.
// W przeciwnym razie może otrzymać obrażenia od wielu pocisków w tej samej klatce, jeśli na siebie nałożą.
// break;
}
}
// Usuń atak, jeśli skończył mu się czas życia LUB jego wizualizacja została zniszczona (np. po kolizji z graczem)
if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroyed) {
// Upewnij się, że wizualizacja jest zniszczona, jeśli jeszcze istnieje (na wypadek, gdyby skończył się tylko lifeTime)
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy(); // Zniszcz sprite animacji
}
self.attacks.splice(i, 1); // Usuń obiekt ataku logicznego z tablicy self.attacks
}
}
// --- KONIEC OBSŁUGA ATAKÓW BOSS ---
// Zmniejszaj cooldown ataku w każdej klatce
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Sprawdź, czy nadszedł czas na rozpoczęcie nowego wzorca ataku
// Warunki: cooldown ataku <= 0, boss żyje, stan gry to "game", boss nie jest w trakcie repozycji po szarży
if (self.attackCooldown <= 0 && !self.repositioning) {
// Wywołanie startAttackPattern sprawdzi resztę warunków (dead, state) wewnątrz siebie
self.startAttackPattern(); // Rozpocznij nowy wzorzec ataku
}
};
return self; // Zwróć instancję obiektu 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);
// Initialize with default values
options = options || {};
self.frames = options.frames || [];
self.frameDuration = options.frameDuration || 100; // Default 100ms per frame
self.loop = options.loop !== undefined ? options.loop : true;
self.currentFrame = 0;
self.frameTimer = 0;
self.playing = true;
// Set initial position and anchor if provided
if (options.x !== undefined) {
self.x = options.x;
}
if (options.y !== undefined) {
self.y = options.y;
}
// Handle anchor options
if (options.anchorX !== undefined || options.anchorY !== undefined) {
var anchorX = options.anchorX !== undefined ? options.anchorX : 0;
var anchorY = options.anchorY !== undefined ? options.anchorY : 0;
// Apply anchor to all frames
self.frames.forEach(function (frame) {
frame.anchor.set(anchorX, anchorY);
});
}
// Add the first frame to display initially
if (self.frames.length > 0) {
self.removeChildren(); // zabezpieczenie
self.addChild(self.frames[self.currentFrame]);
}
// Animation update method
self.update = function () {
if (!self.playing || self.frames.length === 0) {
return;
}
self.frameTimer++;
if (self.frameTimer >= self.frameDuration / (1000 / 60)) {
self.frameTimer = 0;
if (self.children.length > 0) {
self.removeChild(self.children[0]); // Usuwamy tylko 1 aktualną klatkę
}
self.currentFrame++;
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
} else {
self.currentFrame = self.frames.length - 1;
self.playing = false;
}
}
self.addChild(self.frames[self.currentFrame]); // Dodajemy nową klatkę
}
};
// Method to stop animation
self.stop = function () {
self.playing = false;
};
// Method to start/resume animation
self.play = function () {
self.playing = true;
};
// Method to set a specific frame
self.gotoFrame = function (frameIndex) {
if (frameIndex >= 0 && frameIndex < self.frames.length) {
self.removeChildren();
self.currentFrame = frameIndex;
self.addChild(self.frames[self.currentFrame]);
}
};
return self;
});
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
****/
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
// Komentarze o assetach pominięte dla zwięzłości
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
@@ -7,314 +7,441 @@
/****
* 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);
+ // *** MODYFIKACJA: Dodaj grafikę bossIdle jako DZIECKO obiektu bossa (self) ***
+ // 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
+ // 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,
- x: self.x,
- y: self.y
+ // Pozycja animacji jako dziecka bossa - ustawiamy na 0,0 relatywnie do rodzica (self)
+ x: 0,
+ // MODIFIED
+ y: 0 // MODIFIED
});
return bossAttackAnim;
};
- self.playBossAttackAnim = function () {
- // Upewnij się, że poprzednia animacja jest całkowicie usunięta, zanim zaczniemy nową
+ // Zmieniamy sygnaturę funkcji, przyjmuje teraz typ ataku (do decydowania o grafice Bossa)
+ // Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle)
+ self.playBossAttackAnim = function (attackType) {
+ // <--- DODANO PARAMETR
+ // 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
+ // Sprawdź, czy rodzicem jest self przed próbą usunięcia
+ if (self.bossAttackAnim.parent === self) {
+ // MODIFIED check
+ 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
}
- // Tworzymy nową animację bossa
- self.bossAttackAnim = game.addChild(self.createBossAttackAnim());
- // Ustawiamy pozycję animacji, żeby podążała za bossem
- self.bossAttackAnim.x = self.x;
- self.bossAttackAnim.y = self.y;
- self.bossAttackAnim.update = function () {
- if (!self.bossAttackAnim.playing || self.bossAttackAnim.frames.length === 0) {
- return;
+ // *** 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
+ // Sprawdź, czy rodzicem jest self przed próbą usunięcia
+ if (self.bossGraphics && self.bossGraphics.parent === self) {
+ // MODIFIED check
+ self.bossGraphics.parent.removeChild(self.bossGraphics);
+ } else if (self.bossGraphics && self.bossGraphics.parent) {
+ // Fallback
+ self.bossGraphics.parent.removeChild(self.bossGraphics);
}
- self.bossAttackAnim.frameTimer++;
- if (self.bossAttackAnim.frameTimer >= self.bossAttackAnim.frameDuration / (1000 / 60)) {
- self.bossAttackAnim.frameTimer = 0;
- self.bossAttackAnim.removeChildren();
- self.bossAttackAnim.currentFrame++;
- if (self.bossAttackAnim.currentFrame >= self.bossAttackAnim.frames.length) {
- // Animacja skończona, wracamy do idle
- self.bossAttackAnim.destroy();
- self.bossGraphics = game.addChild(LK.getAsset('bossIdle', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: self.x,
- y: self.y
- }));
- } else {
- self.bossAttackAnim.addChild(self.bossAttackAnim.frames[self.bossAttackAnim.currentFrame]);
+ self.bossGraphics = null; // Wyczyść referencję
+ // Tworzymy nową animację bossa i dodajemy ją JAKO DZIECKO BOSSA
+ self.bossAttackAnim = self.addChild(self.createBossAttackAnim()); // *** MODIFIED: DODANO DO self! ***
+ // 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', {
+ // *** MODIFIED: DODANO DO self! ***
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 0,
+ // Pozycja relatywna
+ y: 0 // Pozycja relatywna
+ }));
+ // *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go ***
+ // Sprawdź, czy rodzicem jest self przed próbą usunięcia
+ if (this.parent === self) {
+ // MODIFIED check
+ 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 DZIECKO BOSSA.
};
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)
+ 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 pozostaje jak w poprzedniej poprawionej wersji
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
+ 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óć
}
- // 🔥 Odpalenie animacji ataku bossa
- self.playBossAttackAnim();
- // Jeśli dotarliśmy tutaj, oznacza to, że możemy rozpocząć nowy wzorzec ataku
- self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków
+ // 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();
+ self.circleAttack(); // Wywołaj atak koła
break;
case 1:
- self.lineAttack();
+ self.lineAttack(); // Wywołaj atak linii
break;
case 2:
- self.chargeAttack();
+ 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
};
+ // *** START Zmodyfikowana funkcja createAttack - przyjmuje frameDurationMs i activationFrame jako argumenty ***
+ // Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
+ // Ta funkcja tworzy obiekt ataku logicznego i jego wizualizację (SpriteAnimation)
+ // Zmodyfikowana funkcja createAttack - zawiera ZMIENIONĄ KOLEJNOŚĆ operacji add/remove w update klatek
+ // Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
+ // Ta funkcja tworzy obiekt ataku logicznego i jego wizualizację (SpriteAnimation)
+ self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) {
+ // Lista klatek animacji. Domyślna lista (np. dla szarży), jeśli framesList nie jest podane.
+ 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({
+ frames: frames,
+ frameDuration: frameDurationMs,
+ loop: false,
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: x,
+ y: y
+ }));
+ // NATYCHMIAST ustaw pierwszą klatkę żeby nie było pustki:
+ spriteAnim.removeChildren();
+ spriteAnim.addChild(spriteAnim.frames[0]);
+ 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 (przy 60fps)
+ isActive: false // Flaga aktywności kolizji, domyślnie false
+ };
+ // Metoda update dla tej konkretnej animacji spriteAnim (obiektu visual)
+ spriteAnim.update = function () {
+ // Sprawdź, czy animacja nadal gra i ma klatki
+ if (!this.playing || this.frames.length === 0) {
+ return;
+ }
+ this.frameTimer++;
+ // Sprawdź, czy minął czas na przejście do następnej klatki
+ if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
+ this.frameTimer = 0;
+ var previousFrameSprite = null; // Będziemy przechowywać poprzednią klatkę do usunięcia
+ // Jeśli obecna klatka istnieje, zapisz referencję i usuń ją PRZED zmianą currentFrame
+ // *** Zmieniona logika: Usuń obecną klatkę PO dodaniu nowej ***
+ // Zamiast usuwać tutaj, zrobimy to na końcu tej sekcji.
+ // this.removeChildren(); // Stara linia - USUNIĘTA/ZMIENIONA
+ this.currentFrame++; // Przejdź do następnej klatki
+ // Obsługa końca animacji (jeśli nie zapętlona)
+ if (this.currentFrame >= this.frames.length) {
+ // Zostań na ostatniej klatce jeśli nie zapętlona
+ this.currentFrame = this.frames.length - 1;
+ this.playing = false;
+ }
+ // *** MODYFIKACJA KOLEJNOŚCI: Najpierw dodaj nową klatkę, POTEM usuń poprzednią ***
+ // Dodaj nową obecną klatkę do wyświetlenia
+ var nextFrameSprite = this.frames[this.currentFrame];
+ // Sprawdź, czy nowa klatka istnieje i różni się od poprzedniej, zanim dodasz/usuniesz
+ // Dodajemy tylko jeśli nowa klatka istnieje
+ if (nextFrameSprite) {
+ this.addChild(nextFrameSprite); // Dodaj nową klatkę
+ }
+ // Usuń poprzednią klatkę (jeśli istniała, była dzieckiem, różni się od nowej i nadal jest dzieckiem)
+ // Sprawdzamy parent, bo w międzyczasie sprite mógł zostać usunięty inaczej
+ if (previousFrameSprite && previousFrameSprite.parent === this && previousFrameSprite !== nextFrameSprite) {
+ this.removeChild(previousFrameSprite); // Usuń starą klatkę
+ }
+ // *** Aktywuj kolizję OD PODANEJ activationFrame WZWYŻ ***
+ if (this.currentFrame >= activationFrame) {
+ attack.isActive = true;
+ }
+ }
+ };
+ self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa
+ };
+ // *** END Zmodyfikowana funkcja createAttack ***
+ // *** START Zmodyfikowana funkcja circleAttack ***
+ // Ta funkcja tworzy wiele pocisków w kole wokół bossa
+ // Ta funkcja tworzy wiele pocisków w kole wokół bossa Z MAŁYM OPÓŹNIENIEM między nimi
+ // Ta funkcja tworzy wiele pocisków w kole wokół bossa Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO
self.circleAttack = function () {
- LK.getSound('bossAttack').play();
+ 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; // W trybie New Boss+ więcej pocisków
- var radius = 300;
- var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Krótszy czas życia w New Boss+?
+ 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 (krótszy w New Boss+?)
+ // Lista klatek animacji dla tego ataku (pociski koła)
+ var circleFrames = [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', {})];
+ // Pętla tworząca wiele pocisków rozmieszczonych w kole Z OPÓŹNIENIEM DLA KAŻDEGO
for (var i = 0; i < count; i++) {
- var angle = i / count * Math.PI * 2;
- var posX = center.x + Math.cos(angle) * radius;
- var posY = center.y + Math.sin(angle) * radius;
- self.createAttack(posX, posY, attackLifeTime); // Utwórz atak w danej pozycji z czasem życia (skalowane w createAttack)
+ // 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
+ // *** MODIFIED: Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku, w tym pierwszego ***
+ // Opóźnienie dla i-tego pocisku będzie wynosić 20ms + i * 20ms
+ 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, frames) {
+ // Używamy closure do "zamknięcia" wartości x, y i frames dla każdego timeoutu
+ 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 (prędkość animacji - można dostosować jeśli nadal miga, ale skupmy się na migraniu)
+ // framesList = circleFrames (lista klatek dla tego ataku)
+ self.createAttack(x, y, attackLifeTime, 8, 120, frames); // Używamy 250ms, które wcześniej wydawało się zwalniać migotanie
+ }
+ };
+ }(posX, posY, circleFrames), delay); // Przekaż aktualne posX, posY i circleFrames do closure
}
};
+ // *** END Zmodyfikowana funkcja circleAttack ***
+ // *** START Zmodyfikowana funkcja lineAttack ***
+ // Ta funkcja tworzy wiele pocisków w linii od bossa do gracza
self.lineAttack = function () {
- LK.getSound('bossAttack').play();
- // Tworzy linię ataków od bossa do gracza
- // Upewnij się, że gracz istnieje przed próbą odczytu jego pozycji
+ 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; // W trybie New Boss+ więcej pocisków w linii
- var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Krótszy czas życia?
- var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Mniejsze opóźnienie między atakami w linii
+ 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
+ // Lista klatek animacji dla tego ataku (pociski linii)
+ var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})];
+ // Pętla tworząca pociski w linii z opóźnieniem
for (var i = 0; i < count; i++) {
- var t = i / (count - 1); // Współczynnik interpolacji
- var posX = self.x + (targetX - self.x) * t;
- var posY = self.y + (targetY - self.y) * t;
- var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie dla kolejnych ataków w linii, skalowane
+ var t = i / (count - 1); // Współczynnik interpolacji (0 do 1)
+ 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 "zamknięcia" wartości x i y dla każdego timeoutu
return function () {
- // Sprawdź, czy boss nadal żyje i gra jest w stanie gry zanim stworzysz atak
+ // Sprawdź warunki przed stworzeniem ataku po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
- self.createAttack(x, y, attackLifeTime); // Utwórz atak po opóźnieniu (czas życia skalowane w createAttack)
+ // *** MODYFIKACJA: Wywołaj createAttack z activationFrame = 3, frameDurationMs = 100 i listą klatek dla linii ***
+ // Ustaw frameDuration na 100ms dla tego ataku (przykład innej prędkości)
+ // Indeks 3 odpowiada 'fireball5' na liście lineFrames [fireball2 (0), fireball3 (1), fireball4 (2), fireball5 (3), ...]
+ self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // *** MODIFIED: Added 3, 100, lineFrames ***
}
};
- }(posX, posY), delay);
+ }(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
+ // *** END Zmodyfikowana funkcja lineAttack ***
+ // *** START Zmodyfikowana funkcja chargeAttack - tworzy pociski podczas szarży ***
+ // Ta funkcja zarządza ruchem bossa (szarżą) i tworzy pociski wzdłuż ścieżki szarży
self.chargeAttack = function () {
- LK.getSound('bossAttack').play();
- // Upewnij się, że gracz istnieje przed próbą odczytu jego pozycji
+ LK.getSound('bossAttack').play(); // Odtwórz dźwięk szarży
+ // Upewnij się, że gracz istnieje
if (!player) {
return;
}
- // Ustawienie statycznego wyglądu bossa podczas ataku
- if (self.bossGraphics) {
- self.bossGraphics.destroy(); // Usuwamy poprzednią animację, jeśli była
+ // Obsługa grafiki bossa podczas szarży (pozostaje bez zmian od poprzedniej poprawki - pokazuje grafikę idle)
+ if (self.bossAttackAnim) {
+ self.bossAttackAnim.stop();
+ if (self.bossAttackAnim.parent === self) {
+ self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
+ } else if (self.bossAttackAnim.parent) {
+ self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
+ }
+ self.bossAttackAnim.destroy();
+ self.bossAttackAnim = null;
}
- self.bossGraphics = game.addChild(LK.getAsset('bossIdle', {
+ if (self.bossGraphics && self.bossGraphics.parent === self) {
+ self.bossGraphics.parent.removeChild(self.bossGraphics);
+ } else if (self.bossGraphics && self.bossGraphics.parent) {
+ self.bossGraphics.parent.removeChild(self.bossGraphics);
+ }
+ self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {
anchorX: 0.5,
anchorY: 0.5,
- x: self.x,
- y: self.y
+ x: 0,
+ y: 0
}));
- // Oblicz kierunek do gracza
+ // 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;
}
- // Zapisz oryginalną pozycję (z której zaczyna się szarża)
- var startX = self.x;
+ var startX = self.x; // Pozycja początkowa szarży
var startY = self.y;
- var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dłuższa szarża w New Boss+?
- var chargeDuration = isNewBossPlusMode ? 600 : 800; // Szybsza szarża w New Boss+?
- var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Więcej ataków na ścieżce szarży?
- var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Krótszy czas życia?
- // Animacja szarży (boss przesuwa się szybko w kierunku gracza)
+ var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży w jednostkach gry
+ var chargeDuration = isNewBossPlusMode ?
+ // Czas trwania szarży w ms
+ 600 : 800;
+ var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków tworzonych wzdłuż ścieżki szarży
+ var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku w ms
+ // Tween (animacja ruchu) bossa podczas szarży
+ // Ten tween modyfikuje pozycję obiektu self (boss)
tween(self, {
x: self.x + dx * chargeDistance,
- // Przesunięcie
- y: self.y + dy * chargeDistance
+ // Celowa pozycja X
+ y: self.y + dy * chargeDistance // Celowa pozycja Y
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
- // Czas trwania szarży, skalowane
+ // Czas trwania szarży, skalowany
easing: tween.easeIn,
+ // Krzywa animacji (przyspieszenie)
onFinish: function onFinish() {
- // Sprawdź, czy boss nadal żyje i gra jest w stanie gry
+ // Funkcja wywoływana po zakończeniu tweenu szarży
if (self && !self.dead && gameState.currentState === "game") {
- // --- Start Repositioning Phase ---
- self.repositioning = true; // Ustaw flagę repozycji po zakończeniu szarży
- // Czas na repozycję (np. 0.5 sekundy), skalowane
+ // Sprawdź warunki
+ // Faza repozycji - boss zatrzymuje ruch i po chwili wraca
+ self.repositioning = true;
LK.setTimeout(function () {
if (self && !self.dead) {
- self.repositioning = false; // Zakończ stan repozycji po opóźnieniu
+ self.repositioning = false; // Zakończ stan repozycji
}
- }, 500 * self.attackSpeedMultiplier); // Opóźnienie skalowane przez mnożnik prędkości ataków
- // --- End Repositioning Phase ---
- // Powrót do oryginalnej pozycji (startu szarży) po szarży
+ }, 500 * self.attackSpeedMultiplier); // Czas repozycji, skalowany
+ // Tween powrotu do pozycji startowej
+ // Ten tween modyfikuje pozycję self (boss) - grafika jako dziecko podąży za nim.
tween(self, {
x: startX,
- y: startY
+ // Powrót do pozycji X startowej
+ y: startY // Powrót do pozycji Y startowej
}, {
duration: 1000 * self.attackSpeedMultiplier,
- // Czas powrotu, skalowane
- easing: tween.easeOut
+ // Czas powrotu, skalowany
+ easing: tween.easeOut // Krzywa animacji (zwalnianie)
});
- // Utwórz ataki wzdłuż ścieżki szarży
+ // Utworzenie ataków (fireballi) wzdłuż ścieżki szarży
+ // Fireballe ze szarży używają domyślnej listy klatek z createAttack (bo framesList nie jest podane)
for (var i = 0; i < attacksOnPathCount; i++) {
- var t = i / (attacksOnPathCount - 1);
- var currentChargeX = self.x; // Get the end position of the charge
- var currentChargeY = self.y;
- // Interpolujemy pomiędzy początkiem a końcem szarży
+ var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji (0 do 1)
+ var currentChargeX = self.x; // Aktualna pozycja X bossa (koniec szarży)
+ var currentChargeY = self.y; // Aktualna pozycja Y bossa (koniec szarży)
+ // Interpolujemy pozycję pocisku pomiędzy początkiem szarży (startX, startY) a końcem (currentChargeX, currentChargeY)
var posX = startX + (currentChargeX - startX) * t;
var posY = startY + (currentChargeY - startY) * t;
- // Utwórz atak w danej pozycji
- self.createAttack(posX, posY, attackLifeTime); // Czas życia skalowane w createAttack
+ // Utwórz atak (fireball) w obliczonej pozycji
+ // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList (opcjonalne)
+ // activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków)
+ // frameDurationMs = 140ms (domyślna prędkość animacji dla tych pocisków)
+ self.createAttack(posX, posY, attackLifeTime, 4, 140); // *** MODIFIED: Added 4, 140 ***
}
}
}
});
};
- self.createAttack = function (x, y, duration, framesList) {
- 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', {}),
- // 🔥 brakowało
- LK.getAsset('fireball14', {}) // 🔥 brakowało
- ];
- var spriteAnim = game.addChild(new SpriteAnimation({
- frames: frames,
- frameDuration: 140,
- // 🕒 tutaj też spowolniłem jak chciałeś
- loop: false,
- anchorX: 0.5,
- anchorY: 0.5,
- x: x,
- y: y
- }));
- spriteAnim.scaleX = 1.6; // 🔥 ręcznie ustawiamy
- spriteAnim.scaleY = 1.6; // 🔥 ręcznie ustawiamy
- var attack = {
- x: x,
- y: y,
- radius: 60,
- visual: spriteAnim,
- lifeTime: Math.floor(duration * (60 / 1000)),
- isActive: false
- };
- spriteAnim.update = function () {
- if (!spriteAnim.playing || spriteAnim.frames.length === 0) {
- return;
- }
- spriteAnim.frameTimer++;
- if (spriteAnim.frameTimer >= spriteAnim.frameDuration / (1000 / 60)) {
- spriteAnim.frameTimer = 0;
- spriteAnim.removeChildren();
- spriteAnim.currentFrame++;
- if (spriteAnim.currentFrame >= spriteAnim.frames.length) {
- spriteAnim.currentFrame = spriteAnim.frames.length - 1;
- spriteAnim.playing = false;
- }
- spriteAnim.addChild(spriteAnim.frames[spriteAnim.currentFrame]);
- if (spriteAnim.currentFrame === 7 || spriteAnim.currentFrame === 4) {
- attack.isActive = true;
- }
- }
- };
- self.attacks.push(attack);
- };
- self.circleAttack = function () {
- var numAttacks = isNewBossPlusMode ? 12 : 8;
- var radius = 300;
- for (var i = 0; i < numAttacks; i++) {
- var angle = i / numAttacks * Math.PI * 2;
- var x = self.x + Math.cos(angle) * radius;
- var y = self.y + Math.sin(angle) * radius;
- self.createAttack(x, y, 3000 * self.attackSpeedMultiplier, [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', {})]);
- }
- };
- self.lineAttack = function () {
- var numAttacks = isNewBossPlusMode ? 8 : 5;
- var startX = self.x;
- var startY = self.y;
- var endX = player.x;
- var endY = player.y;
- var dx = (endX - startX) / numAttacks;
- var dy = (endY - startY) / numAttacks;
- for (var i = 0; i < numAttacks; i++) {
- var x = startX + dx * (i + 1);
- var y = startY + dy * (i + 1);
- self.createAttack(x, y, 2000 * self.attackSpeedMultiplier, [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})]);
- }
- };
+ // *** END Zmodyfikowana funkcja chargeAttack ***
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
// Upewnij się, że zdrowie nie spadnie poniżej zera (chyba że do śmierci)
self.health = Math.max(0, self.health);
// Wizualne sygnalizowanie otrzymania obrażeń
- LK.effects.flashObject(self, 0xFFFFFF, 200);
- // Przejście fazy bossa pri 50% zdrowia (może być wyłączone w New Boss+?)
+ LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku
+ // Przejście fazy bossa przy 50% zdrowia (może być wyłączone w New Boss+?)
if (!isNewBossPlusMode) {
// Tylko w standardowym trybie
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
- self.phase = 2;
+ self.phase = 2; // Zmień fazę na 2
self.speed += 2; // Boss staje się szybszy (ruch)
- self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej (mnożnik < 1)
+ self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej (mnożnik < 1, np. 0.8 oznacza 80% oryginalnego cooldownu)
// Wizualne przejście fazy (np. zmiana koloru)
tween(self, {
- tint: 0xFF3300 // Pomarańczowy/czerwony odcień
+ tint: 0xFF3300 // Zmień odcień na pomarańczowy/czerwony
}, {
duration: 1000,
- easing: tween.easeInOut
+ easing: tween.easeInOut // Krzywa animacji
});
}
}
// Sprawdzenie, czy boss został pokonany
@@ -323,144 +450,169 @@
}
// Aktualizacja paska zdrowia jest w game.update -> ui.updateBossHealth
};
self.die = function () {
+ // Funkcja obsługująca śmierć bossa
if (self.dead || gameState.currentState !== "game") {
// Boss umiera tylko w stanie gry
return;
}
- self.dead = true;
+ self.dead = true; // Ustaw flagę śmierci
// Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu (jeśli nie New Boss+)
if (!isNewBossPlusMode) {
LK.getSound('victory').play(); // Dźwięk zwycięstwa w trybie standardowym
}
// Wyczyść pozostałe ataki przy śmierci bossa
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy();
+ attack.visual.destroy(); // Zniszcz wizualizację ataku
}
});
- self.attacks = [];
+ self.attacks = []; // Wyczyść tablicę ataków
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
+ // Zanik do przezroczystości
scaleX: 2,
- scaleY: 2
+ // Podwój skalowanie w poziomie
+ scaleY: 2 // Podwój skalowanie w pionie
}, {
duration: 2000,
+ // Czas trwania animacji śmierci (w ms)
easing: tween.easeOut,
+ // Krzywa animacji (zwalnianie)
onFinish: function onFinish() {
- // Sprawdź, czy boss nadal istnieje przed zniszczeniem
+ // Funkcja wywoływana po zakończeniu animacji śmierci
+ // Sprawdź, czy boss nadal istnieje przed zniszczeniem (zabezpieczenie)
if (self && self.destroy && !self.destroyed) {
self.destroy(); // Zniszcz obiekt bossa
}
- // Zwiększ licznik pokonanych bossów TYLKO W STANDARDOWYM TRYBIE LUB MOŻE OSOBNO DLA NEW BOSS+?
- // Na razie zwiększamy zawsze, ale można to zmienić
- storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów
+ // Zwiększ licznik pokonanych bossów
+ storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów (pobierz z local storage lub zacznij od 0)
// !!! Po pokonaniu bossa ZAWSZE przechodzimy do Grill Screena (niezależnie od trybu) !!!
gameState.showGrillScreen(); // <--- Zmieniono z gameOver na showGrillScreen
}
});
// Ataki zostaną wyczyszczone na początku die() lub w gameState.showGrillScreen clean up
};
self.update = function () {
+ // Główna metoda aktualizacji bossa (wywoływana w game.update)
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
// Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki
+ // (Zabezpieczenie na wypadek zmiany stanu gry bez śmierci bossa)
if (!self.dead && self.attacks) {
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy();
+ attack.visual.destroy(); // Zniszcz wizualizację ataku
}
});
- self.attacks = []; // Wyczyść tablicę ataków
+ self.attacks = []; // Wyczyść tablicę ataków logicznych
}
- return;
+ return; // Zakończ aktualizację, jeśli nie jesteśmy w stanie gry
}
// Nie aktualizuj jeśli boss jest martwy i animacja śmierci działa (obiekt może być jeszcze widoczny)
+ // Pozwalamy animacji śmierci działać niezależnie od reszty logiki update
if (self.dead) {
- return; // Pozwól animacji śmierci działać
+ return;
}
- // --- START NEW MOVEMENT LOGIC ---
- // Poruszaj się w kierunku gracza tylko jeśli nie jesteś martwy, gra jest w stanie 'game',
- // nie jesteś w trakcie repozycji po szarży, cooldown ataku jest wystarczająco długi (nie przygotowujesz się do ataku),
- // i gracz istnieje i nie jest martwy.
- // Zakładamy, że 'chargeAttack' sam zarządza ruchem podczas szarży (przez tween).
+ // --- START NOWA LOGIKA RUCHU BOSS ---
+ // Boss porusza się w kierunku gracza tylko jeśli:
+ // - Nie jest martwy (już sprawdzone na początku update)
+ // - Gra jest w stanie 'game' (już sprawdzone na początku update)
+ // - Nie jest w trakcie "repozycji" po ataku szarży (flaga self.repositioning)
+ // - Cooldown ataku jest wystarczająco długi (np. > 30 klatek), co oznacza, że boss nie jest w trakcie przygotowania do nowego ataku
+ // - Gracz istnieje i nie jest martwy
+ // Zakładamy, że 'chargeAttack' sam zarządza ruchem bossa podczas samej szarży (przez tween).
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; // Użyj prędkości bossa
- // Poruszaj się tylko jeśli gracz jest dalej niż pewna odległość (np. 150 jednostek)
- // Zapobiega to "trzęsieniu się" bossa, gdy jest blisko gracza.
+ var dx = player.x - self.x; // Różnica pozycji X między graczem a bossem
+ var dy = player.y - self.y; // Różnica pozycji Y
+ var distance = Math.sqrt(dx * dx + dy * dy); // Odległość między graczem a bossem
+ var moveSpeed = self.speed; // Prędkość ruchu bossa
+ // Boss porusza się tylko jeśli gracz jest dalej niż pewna minimalna odległość (np. 150 jednostek)
+ // Zapobiega to "drganiom" bossa, gdy jest bardzo blisko gracza.
if (distance > 150) {
- var moveX = dx / distance * moveSpeed;
- var moveY = dy / distance * moveSpeed;
- var nextX = self.x + moveX;
- var nextY = self.y + moveY;
+ var moveX = dx / distance * moveSpeed; // Komponent ruchu X w kierunku gracza
+ var moveY = dy / distance * moveSpeed; // Komponent ruchu Y
+ var nextX = self.x + moveX; // Przewidywana następna pozycja X bossa
+ var nextY = self.y + moveY; // Przewidywana następna pozycja Y bossa
// Ograniczenia areny (ściany na 100px marginesie, mapa 2048x2732)
- // Uwzględnij rozmiar bossa (zakładając anchor 0.5, 0.5)
- var halfWidth = self.width * self.scaleX / 2; // Uwzględnij skalowanie
- 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;
+ // Uwzględnij rozmiar bossa, aby jego środek nie wszedł w ścianę (zakładając anchor 0.5, 0.5)
+ var halfWidth = self.width * self.scaleX / 2; // Połowa szerokości bossa (uwzględniając skalowanie)
+ var halfHeight = self.height * self.scaleY / 2; // Połowa wysokości bossa
+ var minX = 100 + halfWidth; // Minimalna pozycja X, aby uniknąć ściany z lewej
+ var maxX = 2048 - 100 - halfWidth; // Maksymalna pozycja X, aby uniknąć ściany z prawej
+ var minY = 100 + halfHeight; // Minimalna pozycja Y, aby uniknąć ściany z góry
+ var maxY = 2732 - 100 - halfHeight; // Maksymalna pozycja Y, aby uniknąć ściany z dołu
+ // Ustaw nową pozycję bossa, ograniczając ją do granic areny
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
+ // --- DODANO: Zaktualizuj pozycję aktywnej grafiki, aby podążała za bossem ---
+ // Ten komentarz był wcześniej. Ponieważ grafika jest teraz dzieckiem Bossa i jej pozycja relatywna to 0,0,
+ // pozycja grafiki jest AUTOMATYCZNIE aktualizowana, gdy self.x i self.y się zmieniają.
+ // Nie potrzebujemy tutaj ręcznej aktualizacji pozycji grafiki.
+ // Kod poniżej jest usunięty, bo pozycja relatywna dziecka jest ustawiona na 0,0 przy dodawaniu.
+ // if (self.bossGraphics && self.bossGraphics.parent === self) { self.bossGraphics.x = 0; self.bossGraphics.y = 0; }
+ // if (self.bossAttackAnim && self.bossAttackAnim.parent === self) { self.bossAttackAnim.x = 0; self.bossAttackAnim.y = 0; }
}
}
- // --- END NEW MOVEMENT LOGIC ---
- // --- START BOSS ATTACK HANDLING (Kolizje i Czas Życia) ---
- // Aktualizuj istniejące ataki bossa i sprawdzaj kolizje z graczem
- // Iteruj wstecz, aby bezpiecznie usuwać elementy z tablicy
+ // --- KONIEC NOWA LOGIKA RUCHU BOSS ---
+ // --- START OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) ---
+ // Aktualizuj istniejące ataki bossa (pociski) i sprawdzaj kolizje z graczem
+ // Iteruj od końca do początku tablicy self.attacks, aby bezpiecznie usuwać elementy (przy użyciu splice)
for (var i = self.attacks.length - 1; i >= 0; i--) {
- var attack = self.attacks[i];
- // Zmniejsz czas życia ataku (w klatkach)
+ var attack = self.attacks[i]; // Pobierz bieżący obiekt ataku logicznego
+ // Zmniejszaj czas życia ataku (w klatkach gry)
if (attack.lifeTime > 0) {
attack.lifeTime--;
}
- // Sprawdź kolizję z graczem (jeśli gracz istnieje, żyje, nie jest nietykalny, a atak wizualnie istnieje)
+ // Sprawdź kolizję ataku z graczem tylko jeśli:
+ // - Flaga attack.isActive jest true (atak jest "groźny")
+ // - Gracz istnieje i nie jest martwy
+ // - Gracz nie jest nietykalny (np. po turlaniu)
+ // - Wizualizacja ataku istnieje i nie została jeszcze zniszczona
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);
- // Użyj promieni do sprawdzenia kolizji
- var playerRadius_p = player.width / 2; // Załóżmy, że gracz ma właściwość width
- var attackRadius = attack.radius; // Promień ataku z obiektu 'attack'
+ var dx_p = player.x - attack.x; // Różnica pozycji X między graczem a atakiem
+ var dy_p = player.y - attack.y; // Różnica pozycji Y
+ var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); // Odległość między graczem a atakiem
+ // Użyj promieni do sprawdzenia kolizji (kolizja kołowa)
+ var playerRadius_p = player.width / 2; // Promień gracza (zakładając, że gracz ma właściwość width)
+ var attackRadius = attack.radius; // Promień ataku (zdefiniowany w createAttack)
if (distance_p < playerRadius_p + attackRadius) {
- player.takeDamage(1); // Gracz otrzymuje obrażenia
- // Opcjonalnie: zniszcz wizualizację ataku natychmiast po trafieniu
+ // Wykryto kolizję! Gracz otrzymuje obrażenia.
+ player.takeDamage(1); // Zadaj 1 punkt obrażeń graczowi
+ // Opcjonalnie: zniszcz wizualizację ataku natychmiast po trafieniu gracza
if (attack.visual && attack.visual.destroy) {
- attack.visual.destroy();
+ attack.visual.destroy(); // Zniszcz sprite animacji
}
- self.attacks.splice(i, 1); // Usuń trafiony atak z listy
- // Można dodać 'break;', jeśli gracz ma otrzymać obrażenia tylko od jednego ataku na klatkę
+ self.attacks.splice(i, 1); // Usuń obiekt ataku logicznego z tablicy
+ // Można dodać 'break;' tutaj, jeśli chcesz, aby gracz mógł otrzymać obrażenia tylko od jednego pocisku bossa na klatkę.
+ // W przeciwnym razie może otrzymać obrażenia od wielu pocisków w tej samej klatce, jeśli na siebie nałożą.
// break;
}
}
- // Usuń atak, jeśli skończył mu się czas życia LUB jego wizualizacja została zniszczona
+ // Usuń atak, jeśli skończył mu się czas życia LUB jego wizualizacja została zniszczona (np. po kolizji z graczem)
if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroyed) {
- // Upewnij się, że wizualizacja jest zniszczona, jeśli jeszcze istnieje
+ // Upewnij się, że wizualizacja jest zniszczona, jeśli jeszcze istnieje (na wypadek, gdyby skończył się tylko lifeTime)
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy();
+ attack.visual.destroy(); // Zniszcz sprite animacji
}
- self.attacks.splice(i, 1); // Usuń atak z tablicy
+ self.attacks.splice(i, 1); // Usuń obiekt ataku logicznego z tablicy self.attacks
}
}
- // --- END BOSS ATTACK HANDLING ---
+ // --- KONIEC OBSŁUGA ATAKÓW BOSS ---
// Zmniejszaj cooldown ataku w każdej klatce
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Sprawdź, czy nadszedł czas na rozpoczęcie nowego wzorca ataku
- // Warunki: cooldown <= 0, boss żyje, stan gry to "game", boss nie jest w trakcie repozycji
+ // Warunki: cooldown ataku <= 0, boss żyje, stan gry to "game", boss nie jest w trakcie repozycji po szarży
if (self.attackCooldown <= 0 && !self.repositioning) {
// Wywołanie startAttackPattern sprawdzi resztę warunków (dead, state) wewnątrz siebie
- self.startAttackPattern();
+ self.startAttackPattern(); // Rozpocznij nowy wzorzec ataku
}
};
- return self;
+ return self; // Zwróć instancję obiektu Boss
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Zamiast var playerGraphics = ...
@@ -800,35 +952,32 @@
});
}
// Add the first frame to display initially
if (self.frames.length > 0) {
- self.addChild(self.frames[0]);
+ self.removeChildren(); // zabezpieczenie
+ self.addChild(self.frames[self.currentFrame]);
}
// Animation update method
self.update = function () {
if (!self.playing || self.frames.length === 0) {
return;
}
self.frameTimer++;
- // Check if it's time to advance to the next frame
if (self.frameTimer >= self.frameDuration / (1000 / 60)) {
- // Convert ms to frames at 60fps
self.frameTimer = 0;
- // Remove current frame from display
- self.removeChildren();
- // Advance to next frame
+ if (self.children.length > 0) {
+ self.removeChild(self.children[0]); // Usuwamy tylko 1 aktualną klatkę
+ }
self.currentFrame++;
- // Loop if needed
if (self.currentFrame >= self.frames.length) {
if (self.loop) {
self.currentFrame = 0;
} else {
self.currentFrame = self.frames.length - 1;
self.playing = false;
}
}
- // Add the new current frame
- self.addChild(self.frames[self.currentFrame]);
+ self.addChild(self.frames[self.currentFrame]); // Dodajemy nową klatkę
}
};
// Method to stop animation
self.stop = function () {