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
Code edit (18 edits merged)
Please save this source code
User prompt
Add 6 assets roll0, roll1, roll2, roll3, roll4, roll5.
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: howToScene is not defined' in or related to this line: 'howToScene();' Line Number: 1263
Code edit (3 edits merged)
Please save this source code
User prompt
fix problem with click "tap to start" it's not working
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1103
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'self.deathsText.alpha = 1;' Line Number: 366
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1229
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1228
/**** * 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) var Boss = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); 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.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 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óć } // 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 switch (self.attackPattern) { case 0: self.circleAttack(); break; case 1: self.lineAttack(); break; case 2: self.chargeAttack(); break; } // Ustaw WŁAŚCIWY czas odnowienia dla następnego ataku, PO zainicjowaniu obecnego // Mnożnik prędkości ataków bossa jest już ustawiony w gameState.startGame self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds (90-150 frames) * multiplier }; self.circleAttack = function () { LK.getSound('bossAttack').play(); var center = { 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+? 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) } }; 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 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 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 LK.setTimeout(function (x, y) { return function () { // Sprawdź, czy boss nadal żyje i gra jest w stanie gry zanim stworzysz atak if (self && !self.dead && gameState.currentState === "game") { self.createAttack(x, y, attackLifeTime); // Utwórz atak po opóźnieniu (czas życia skalowane w createAttack) } }; }(posX, posY), delay); } }; self.chargeAttack = function () { LK.getSound('bossAttack').play(); // Upewnij się, że gracz istnieje przed próbą odczytu jego pozycji if (!player) { return; } // Oblicz kierunek do gracza 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 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) tween(self, { x: self.x + dx * chargeDistance, // Przesunięcie y: self.y + dy * chargeDistance }, { duration: chargeDuration * self.attackSpeedMultiplier, // Czas trwania szarży, skalowane easing: tween.easeIn, onFinish: function onFinish() { // Sprawdź, czy boss nadal żyje i gra jest w stanie gry 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 LK.setTimeout(function () { if (self && !self.dead) { // Upewnij się, że boss nadal istnieje i żyje self.repositioning = false; // Zakończ stan repozycji po opóźnieniu // console.log("Boss repositioning finished"); // Debug log } }, 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 tween(self, { x: startX, y: startY }, { duration: 1000 * self.attackSpeedMultiplier, // Czas powrotu, skalowane easing: tween.easeOut // Nie potrzebujemy tutaj już onFinish do repozycji, bo obsłużono to powyżej }); // Utwórz ataki wzdłuż ścieżki szarży 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; // Interpolate between start and end position of the charge var posX = startX + (currentChargeX - startX) * t; var posY = startY + (currentChargeY - startY) * t; self.createAttack(posX, posY, attackLifeTime); // Czas życia skalowane w createAttack } } } }); }; self.createAttack = function (x, y, duration) { // Tworzy wizualne ostrzeżenie ataku (żółty okrąg) var warning = game.addChild(LK.getAsset('attack', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, alpha: 0.3, // Niska przezroczystość na początku tint: 0xFFFF00 // Żółty kolor ostrzeżenia })); // Animacja ostrzeżenia (miganie), skalowana przez mnożnik prędkości var warningDuration = isNewBossPlusMode ? 800 : 1000; // Krótsze ostrzeżenie w New Boss+? tween(warning, { alpha: 0.6 // Zwiększ przezroczystość }, { duration: warningDuration * self.attackSpeedMultiplier, // Skalowany czas trwania easing: tween.easeInOut, onFinish: function onFinish() { // Sprawdź, czy warning nadal istnieje przed zmianą koloru if (warning && !warning.destroyed) { // Zmień kolor na czerwony, sygnalizując faktyczny atak warning.tint = 0xFF0000; // Animacja sygnalizująca gotowość ataku, skalowana przez mnożnik prędkości tween(warning, { alpha: 0.8 // Zwiększ przezroczystość bardziej }, { duration: 200 * self.attackSpeedMultiplier, // Skalowany czas trwania onFinish: function onFinish() { // Sprawdź, czy warning nadal istnieje if (warning && !warning.destroyed) { // Dodaj atak do listy aktywnych ataków bossa (jako obiekt danych) var attack = { x: warning.x, // Pozycja ataku (ze środka) y: warning.y, radius: warning.width / 2, // Promień kolizji (połówka szerokości) visual: warning, // Referencja do obiektu wizualnego lifeTime: Math.floor(duration * self.attackSpeedMultiplier * (60 / 1000)) // Czas życia ataku w klatkach, skalowany. Przeliczamy ms na klatki (zakładając 60 FPS) i zaokrąglamy w dół. }; self.attacks.push(attack); // Usuwanie ataku przeniesione do Boss.update } // if warning exists } }); } // if warning exists } }); }; self.takeDamage = function (amount) { 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+?) if (!isNewBossPlusMode) { // Tylko w standardowym trybie if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; self.speed += 2; // Boss staje się szybszy (ruch) self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej (mnożnik < 1) // Wizualne przejście fazy (np. zmiana koloru) tween(self, { tint: 0xFF3300 // Pomarańczowy/czerwony odcień }, { duration: 1000, easing: tween.easeInOut }); } } // 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 () { if (self.dead || gameState.currentState !== "game") { // Boss umiera tylko w stanie gry return; } self.dead = true; // 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(); } }); self.attacks = []; // Animacja śmierci bossa (zanikanie i powiększanie) tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Sprawdź, czy boss nadal istnieje przed zniszczeniem 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 // !!! 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 () { // Aktualizuj tylko w stanie gry if (gameState.currentState !== "game") { // Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki if (!self.dead && self.attacks) { self.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); self.attacks = []; // Wyczyść tablicę ataków } return; } // Nie aktualizuj jeśli boss jest martwy i animacja śmierci działa (obiekt może być jeszcze widoczny) if (self.dead) { return; // Pozwól animacji śmierci działać } // --- 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). 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. if (distance > 150) { var moveX = dx / distance * moveSpeed; var moveY = dy / distance * moveSpeed; var nextX = self.x + moveX; var nextY = self.y + moveY; // 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; self.x = Math.max(minX, Math.min(nextX, maxX)); self.y = Math.max(minY, Math.min(nextY, maxY)); } } // --- 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 for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; // Zmniejsz czas życia ataku (w klatkach) if (attack.lifeTime > 0) { attack.lifeTime--; } // Sprawdź kolizję z graczem (jeśli gracz istnieje, żyje, nie jest nietykalny, a atak wizualnie istnieje) if (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' if (distance_p < playerRadius_p + attackRadius) { player.takeDamage(1); // Gracz otrzymuje obrażenia // Opcjonalnie: zniszcz wizualizację ataku natychmiast po trafieniu if (attack.visual && attack.visual.destroy) { attack.visual.destroy(); } 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ę // break; } } // Usuń atak, jeśli skończył mu się czas życia LUB jego wizualizacja została zniszczona if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroyed) { // Upewnij się, że wizualizacja jest zniszczona, jeśli jeszcze istnieje if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } self.attacks.splice(i, 1); // Usuń atak z tablicy } } // --- END BOSS ATTACK HANDLING --- // 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 if (self.attackCooldown <= 0 && !self.repositioning) { // Wywołanie startAttackPattern sprawdzi resztę warunków (dead, state) wewnątrz siebie self.startAttackPattern(); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var 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.hasRolledThroughBossThisRoll = false; // Track if boss was hit during this roll // Roll mechanic self.roll = function (direction) { // Check if player can roll (not already rolling and cooldown is 0) if (!self.rolling && self.rollCooldown <= 0 && !self.dead) { // Dodano sprawdzenie !self.dead self.rolling = true; self.rollDirection = direction; self.rollCooldown = 45; // Zwiększono cooldown turlania lekko (np. 45 klatek ~ 0.75s) self.invulnerable = true; // Player is invulnerable during roll self.invulnerabilityFrames = 30; // Set invulnerability frames (0.5s) self.hasRolledThroughBossThisRoll = false; // Reset boss hit tracking for this roll // Create roll effect var rollEffect = game.addChild(LK.getAsset('roll', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y })); // Animation for roll effect tween(rollEffect, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, // Czas trwania efektu easing: tween.easeOut, onFinish: function onFinish() { if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) { rollEffect.destroy(); } } }); // End roll state after duration // Wyczyść stary timeout jeśli istnieje (na wszelki wypadek) if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); } self.rollTimeoutId = LK.setTimeout(function () { self.rolling = false; self.rollTimeoutId = null; // Zresetuj ID po wykonaniu }, self.rollDuration); } }; // Clear all roll timeouts (useful when game state changes abruptly) self.clearRollTimeouts = function () { if (self.rollTimeoutId) { LK.clearTimeout(self.rollTimeoutId); self.rollTimeoutId = null; } // Invulnerability jest teraz zarządzana tylko przez self.invulnerabilityFrames w update }; // 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 UI = Container.expand(function () { var self = Container.call(this); // Create heart containers (wizualizacja zdrowia) self.hearts = []; self.heartContainer = new Container(); // Kontener na ikony serc self.addChild(self.heartContainer); // Title and messages (teksty na ekranach) self.titleText = new Text2("ROLL SOULS", { size: 150, fill: 0xFFFFFF }); self.titleText.anchor.set(0.5, 0.5); self.addChild(self.titleText); self.messageText = new Text2("", { size: 60, fill: 0xFFFFFF }); self.messageText.anchor.set(0.5, 0.5); self.addChild(self.messageText); // Deaths counter (licznik śmierci) self.deathsText = new Text2("Deaths: 0", { size: 40, fill: 0xFFFFFF }); self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi self.deathsText.y = 50; // Pozycja Y od góry self.addChild(self.deathsText); // Tutorial text (teksty tutoriali) self.tutorialText = new Text2("", { size: 50, fill: 0xFFFFFF }); self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze self.tutorialText.x = 2048 / 2; // Pozycja X na środku self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu) self.addChild(self.tutorialText); // --- Timer Text --- self.timerText = new Text2("2:00", { // Początkowe wyświetlanie czasu (2 minuty) size: 60, // Rozmiar czcionki fill: 0xFFFFFF // Biały kolor }); self.timerText.anchor.set(0, 0); // Punkt odniesienia w lewym górnym rogu self.timerText.x = 50; // Pozycja X od lewej krawędzi self.timerText.y = 50; // Pozycja Y od górnej krawędzi self.timerText.alpha = 0; // Domyślnie ukryty self.addChild(self.timerText); // --- Boss Health Bar (wizualizacja zdrowia bossa) --- self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera) self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty self.addChild(self.bossHealthBarContainer); var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar) var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar) // Tło paska zdrowia bossa (szare) - nadal używamy Shape dla tła self.bossHealthBarBg = new Shape({ width: barWidth, height: barHeight, color: 0x555555, // Szary kolor tła shape: 'box' }); self.bossHealthBarBg.anchor.set(0.5, 0.5); // Ustaw punkt odniesienia na środek self.bossHealthBarContainer.addChild(self.bossHealthBarBg); // Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar self.bossHealthBar = self.attachAsset('bossHpbar', { // Użyj assetu 'bossHpbar' anchorX: 0, // Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5) anchorY: 0.5, x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła, żeby lewa krawędź paska zdrowia była na środku kontenera (czyli na środku ekranu) // Szerokość i wysokość są brane z assetu, nie ustawiamy ich tutaj jawnie (chyba że chcemy skalować) }); self.bossHealthBarContainer.addChild(self.bossHealthBar); // Aktualizuje wizualizację serc na UI self.updateHearts = function (current, max) { // Sprawdź poprawność danych wejściowych current = Math.max(0, current || 0); max = Math.max(1, max || 1); // Max musi być co najmniej 1 // Usuń istniejące serca tylko jeśli liczba się zmieniła lub nie ma serc if (self.hearts.length !== max) { while (self.hearts.length > 0) { var oldHeart = self.hearts.pop(); if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) { oldHeart.destroy(); } } self.heartContainer.removeChildren(); // Usuń wizualne obiekty serc // Stwórz nowe ikony serc for (var i = 0; i < max; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: i * 50, // Pozycja w poziomie (rozstawione co 50 jednostek) y: 0, tint: i < current ? 0xFF0000 : 0x555555 // Czerwone jeśli zdrowie jest >=, szare jeśli < }); self.hearts.push(heart); self.heartContainer.addChild(heart); } // Wyśrodkuj kontener serc w poziomie i ustaw pozycję pionową self.heartContainer.x = (2048 - max * 50) / 2 + 25; // Dodano +25 aby wycentrować lepiej (bo anchorX = 0.5) self.heartContainer.y = 100; // Pozycja pionowa } else { // Zaktualizuj tylko kolory istniejących serc for (var j = 0; j < self.hearts.length; j++) { if (self.hearts[j]) { self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555; } } } }; // Aktualizuje wizualizację paska zdrowia bossa self.updateBossHealth = function (current, max) { // Sprawdź poprawność danych current = Math.max(0, current || 0); max = Math.max(1, max || 1); // Max musi być co najmniej 1 // Oblicz szerokość paska zdrowia na podstawie aktualnego i maksymalnego zdrowia var barWidth = 800; // Całkowita szerokość paska (musi być taka sama jak szerokość assetu bossHpbar) var currentWidth = current / max * barWidth; // Upewnij się, że pasek zdrowia (obiekt sprite) istnieje przed aktualizacją if (self.bossHealthBar) { // Zmień szerokość sprite'a, aby odzwierciedlić aktualne zdrowie self.bossHealthBar.width = currentWidth; // Ustaw widoczność kontenera paska zdrowia bossa // Pokaż jeśli (stan gry lub game over) ORAZ (boss istnieje i żyje LUB jesteśmy w stanie game over z powodu czasu w Boss+) var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode); self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0; } // Upewnij się, że pasek zdrowia tła też jest widoczny/ukryty tak samo jak kontener if (self.bossHealthBarBg) { self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha; } }; // Wyświetla komunikat na środku ekranu self.showMessage = function (message, duration) { self.messageText.setText(message); self.messageText.alpha = 1; // Ustaw pełną przezroczystość // Wyczyść istniejący timer zanikania, jeśli istnieje if (self.messageTimeout) { LK.clearTimeout(self.messageTimeout); self.messageTimeout = null; } // Zaplanuj zanikanie po czasie, jeśli duration > 0 if (duration && duration > 0) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 // Zaniknij do przezroczystości 0 }, { duration: 500, // Czas trwania zanikania onFinish: function onFinish() { self.messageTimeout = null; } // Reset ID }); }, duration); } else { // If duration is 0 or not provided, message stays visible self.messageText.alpha = 1; } }; // Wyświetla tekst tutorialu self.showTutorial = function (text) { self.tutorialText.setText(text); self.tutorialText.alpha = 1; // Ustaw pełną przezroczystość }; // Ukrywa tekst tutorialu (zanikając) self.hideTutorial = function () { tween(self.tutorialText, { alpha: 0 // Zaniknij }, { duration: 500 // Czas trwania }); }; // Aktualizuje licznik śmierci self.updateDeathsCounter = function () { // Upewnij się, że storage.totalDeaths jest liczbą var deaths = storage.totalDeaths || 0; self.deathsText.setText("Deaths: " + deaths); }; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) { // Upewnij się, że sekundy są liczbą >= 0 seconds = Math.max(0, seconds || 0); var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; // Formatuj sekundy z wiodącym zerem jeśli < 10 var formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds; self.timerText.setText(minutes + ':' + formattedSeconds); }; // Pozycjonuje elementy UI w zależności od stanu gry // Ta funkcja jest wywoływana przez metody w gameState self.positionElements = function (state) { // Pozycje stałe (niezależne od stanu) self.deathsText.x = 2048 - 50; self.deathsText.y = 50; self.timerText.x = 50; self.timerText.y = 50; self.heartContainer.y = 100; // Pozycja pionowa serc self.bossHealthBarContainer.x = 2048 / 2; self.bossHealthBarContainer.y = 150; // Pasek HP bossa poniżej timera/serc // Resetuj widoczność przed ustawieniem dla danego stanu self.titleText.alpha = 0; self.messageText.alpha = 0; self.tutorialText.alpha = 0; self.timerText.alpha = 0; self.heartContainer.alpha = 0; self.bossHealthBarContainer.alpha = 0; // Zostanie włączony przez updateBossHealth jeśli trzeba self.deathsText.alpha = 1; // Licznik śmierci zawsze widoczny switch (state) { case "title": // Pozycje dla ekranu tytułowego self.titleText.x = 2048 / 2; self.titleText.y = 800; self.titleText.alpha = 1; // Pokaż tytuł self.messageText.x = 2048 / 2; self.messageText.y = 1000; // "Tap to Start" (ustawiane przez showMessage) // self.messageText.alpha = 1; // Ustawiane przez showMessage self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // "Swipe to Roll..." (ustawiane przez showTutorial) // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial break; case "game": // Pozycje dla stanu gry (walka z bossem) // self.titleText.alpha = 0; // Ustawione domyślnie self.messageText.x = 2048 / 2; self.messageText.y = 1500; // Komunikaty w trakcie gry (np. przyspieszenie bossa) // self.messageText.alpha = 0; // Ustawiane przez showMessage self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; // Tutorial w trakcie gry ("Swipe to roll away!") // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial // Pokaż serca i timer podczas gry self.heartContainer.alpha = 1; self.timerText.alpha = 1; // Pasek zdrowia bossa jest zarządzany przez updateBossHealth break; case "grillMenu": // Nowy stan dla Grill Screena // self.titleText.alpha = 0; // Ustawione domyślnie self.messageText.x = 2048 / 2; self.messageText.y = 500; // Pozycja dla komunikatów "Rest in peace..." // self.messageText.alpha = 0; // Ustawiane przez showMessage // self.tutorialText.alpha = 0; // Ustawione domyślnie break; case "gameOver": // Pozycje dla ekranu Game Over self.titleText.x = 2048 / 2; self.titleText.y = 800; // Tytuł "YOU DIED" lub komunikat zwycięstwa Boss+ // self.titleText.alpha = 1; // Ustawiane w gameState.gameOver self.messageText.x = 2048 / 2; self.messageText.y = 1000; // Pusty lub inny komunikat // self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // Pusty // self.tutorialText.alpha = 0; // Ustawiane w gameState.gameOver przez showTutorial // Widoczność paska HP bossa zarządzana przez updateBossHealth break; case "intro": // Pozycje dla ekranów intro/tutoriali case "fakeTutorial": case "realTutorial": // Większość elementów ukryta (ustawione domyślnie wyżej) break; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło domyślne }); /**** * Game Code ****/ // Komentarze o assetach pominięte dla zwięzłości // Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia) 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("ROLL SOULS"); 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 () { // clearScene(); // Niekonieczne, showGrillScreen to zrobi gameState.showGrillScreen(); }; }, 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, anchorY: 0, x: 0, y: 0, scaleX: 1, scaleY: 1 }); game.addChildAt(currentBackground, 0); tween(currentBackground, { scaleX: 1.1, scaleY: 1.1 }, { duration: 30000, 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 var introText1 = new Text2('From the creators of FromSoftware...', { size: 90, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); introText1.anchor.set(0.5, 0.5); introText1.x = 2048 / 2; introText1.y = 2732 / 2; currentSceneElements.addChild(introText1); LK.setTimeout(function () { // Użyj try-catch dla destroy na wypadek, gdyby obiekt został już usunięty try { if (introText1 && introText1.destroy) { introText1.destroy(); } } catch (e) {} var introText2 = new Text2('...I have no idea. I don’t know them.', { size: 90, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); introText2.anchor.set(0.5, 0.5); introText2.x = 2048 / 2; introText2.y = 2732 / 2; currentSceneElements.addChild(introText2); // Timer do fake tutorialu // Użyj .bind(this) jeśli potrzebujesz 'this' z gameState wewnątrz callbacka this.fakeTutorialTimerId = LK.setTimeout(function () { try { if (introText2 && introText2.destroy) { introText2.destroy(); } } catch (e) {} gameState.fakeTutorialTimerId = null; // Zresetuj ID gameState.showFakeTutorial(); // Przejdź dalej } /*.bind(this)*/, 2000); // 2 sekundy }.bind(this), 5000); // 5 sekund, użyj bind(this) dla dostępu do this.fakeTutorialTimerId }, 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 }); 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 = 800; currentSceneElements.addChild(diedText); // Wyświetl wyjaśnienie var explanationText = new Text2("Did you check the title of the game?", { size: 60, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); explanationText.anchor.set(0.5, 0.5); explanationText.x = 2048 / 2; explanationText.y = 1000; 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; 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. 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
@@ -15,10 +15,10 @@
anchorY: 0.5
});
self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame)
self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame)
- self.speed = 5;
- self.attackCooldown = 0;
+ self.speed = 5; // Prędkość ruchu bossa
+ self.attackCooldown = 0; // Czas do następnego ataku (w klatkach)
self.attackPattern = 0;
self.attacks = []; // Aktywne ataki bossa (obiekty JS z pozycją, promieniem, itp.)
self.stunned = false;
self.stunDuration = 0;
@@ -70,8 +70,12 @@
};
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
+ 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?
@@ -92,8 +96,12 @@
}
};
self.chargeAttack = function () {
LK.getSound('bossAttack').play();
+ // Upewnij się, że gracz istnieje przed próbą odczytu jego pozycji
+ if (!player) {
+ return;
+ }
// Oblicz kierunek do gracza
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
@@ -126,8 +134,9 @@
LK.setTimeout(function () {
if (self && !self.dead) {
// Upewnij się, że boss nadal istnieje i żyje
self.repositioning = false; // Zakończ stan repozycji po opóźnieniu
+ // console.log("Boss repositioning finished"); // Debug log
}
}, 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
@@ -142,10 +151,13 @@
});
// Utwórz ataki wzdłuż ścieżki szarży
for (var i = 0; i < attacksOnPathCount; i++) {
var t = i / (attacksOnPathCount - 1);
- var posX = startX + (self.x - startX) * t;
- var posY = startY + (self.y - startY) * t;
+ var currentChargeX = self.x; // Get the end position of the charge
+ var currentChargeY = self.y;
+ // Interpolate between start and end position of the charge
+ var posX = startX + (currentChargeX - startX) * t;
+ var posY = startY + (currentChargeY - startY) * t;
self.createAttack(posX, posY, attackLifeTime); // Czas życia skalowane w createAttack
}
}
}
@@ -192,12 +204,12 @@
radius: warning.width / 2,
// Promień kolizji (połówka szerokości)
visual: warning,
// Referencja do obiektu wizualnego
- lifeTime: duration * self.attackSpeedMultiplier * (60 / 1000) // Czas życia ataku w klatkach, skalowany. Przeliczamy ms na klatki (zakładając 60 FPS)
+ lifeTime: Math.floor(duration * self.attackSpeedMultiplier * (60 / 1000)) // Czas życia ataku w klatkach, skalowany. Przeliczamy ms na klatki (zakładając 60 FPS) i zaokrąglamy w dół.
};
self.attacks.push(attack);
- // !!! USUNIĘTO LK.setTimeout, USUWANIE BĘDZIE TYLKO W BOSS.update() !!!
+ // Usuwanie ataku przeniesione do Boss.update
} // if warning exists
}
});
} // if warning exists
@@ -219,8 +231,9 @@
// Tylko w standardowym trybie
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2;
self.speed += 2; // Boss staje się szybszy (ruch)
+ self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej (mnożnik < 1)
// Wizualne przejście fazy (np. zmiana koloru)
tween(self, {
tint: 0xFF3300 // Pomarańczowy/czerwony odcień
}, {
@@ -232,10 +245,9 @@
// Sprawdzenie, czy boss został pokonany
if (self.health <= 0) {
self.die(); // Wywołaj funkcję śmierci
}
- // Opcjonalnie: Aktualizuj wizualne wskazanie zdrowia bossa (jeśli istnieje)
- // ui.updateBossHealth(self.health, self.maxHealth); // Wymagałoby to dodania do klasy UI
+ // Aktualizacja paska zdrowia jest w game.update -> ui.updateBossHealth
};
self.die = function () {
if (self.dead || gameState.currentState !== "game") {
// Boss umiera tylko w stanie gry
@@ -245,8 +257,15 @@
// 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();
+ }
+ });
+ self.attacks = [];
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
scaleX: 2,
@@ -262,16 +281,14 @@
// 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
// !!! Po pokonaniu bossa ZAWSZE przechodzimy do Grill Screena (niezależnie od trybu) !!!
- gameState.showGrillScreen();
+ gameState.showGrillScreen(); // <--- Zmieniono z gameOver na showGrillScreen
}
});
- // Zatrzymaj wszystkie timery związane z atakami bossa
- // Ataki zostaną wyczyszczone w gameState.showGrillScreen clean up
+ // Ataki zostaną wyczyszczone na początku die() lub w gameState.showGrillScreen clean up
};
self.update = function () {
- var playerRadius; // Declare playerRadius here
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
// Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki
if (!self.dead && self.attacks) {
@@ -283,61 +300,90 @@
self.attacks = []; // Wyczyść tablicę ataków
}
return;
}
- // Nie aktualizuj jeśli boss jest martwy i animacja śmierci dobiegła końca (obiekt niewidoczny)
- if (self.dead && self.alpha === 0) {
- // Gdy animacja śmierci dobiegła końca (obiekt niewidoczny)
- return;
- }
- // Jeśli gracz jest martwy, ale nadal widoczny, pozwól animacji zanikania działać (obsługiwane przez tween)
+ // Nie aktualizuj jeśli boss jest martwy i animacja śmierci działa (obiekt może być jeszcze widoczny)
if (self.dead) {
- return; // Nie wykonuj logiki ruchu i nietykalności jeśli gracz jest martwy
+ return; // Pozwól animacji śmierci działać
}
- // Obsługa cooldownu turlania
- if (self.rollCooldown > 0) {
- self.rollCooldown--;
- }
- // Obsługa klatek nietykalności
- if (self.invulnerable && self.invulnerabilityFrames > 0) {
- self.invulnerabilityFrames--;
- // Efekt migotania (zmiana przezroczystości)
- self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1; // Miga co kilka klatek
- if (self.invulnerabilityFrames <= 0) {
- self.invulnerable = false;
- self.alpha = 1; // Przywróć pełną przezroczystość gdy nietykalność minie
+ // --- 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).
+ 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.
+ if (distance > 150) {
+ var moveX = dx / distance * moveSpeed;
+ var moveY = dy / distance * moveSpeed;
+ var nextX = self.x + moveX;
+ var nextY = self.y + moveY;
+ // 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;
+ self.x = Math.max(minX, Math.min(nextX, maxX));
+ self.y = Math.max(minY, Math.min(nextY, maxY));
}
}
- // Obsługa ruchu podczas turlania - WYKONUJ TYLKO JEŚLI self.rolling JEST TRUE
- if (self.rolling) {
- // Jawne sprawdzenie flagi rolling
- self.x += self.rollDirection.x * self.rollSpeed;
- self.y += self.rollDirection.y * self.rollSpeed;
- // !!! NOWA LOGIKA: Sprawdzenie kolizji z bossem podczas turlania i zadawanie obrażeń !!!
- // 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 = self.x - boss.x;
- var dy = self.y - boss.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- // 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 < playerCollisionRadius + bossCollisionRadius) {
- // Kolizja wykryta podczas turlania
- boss.takeDamage(10); // ZADJ 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
+ // --- 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
+ for (var i = self.attacks.length - 1; i >= 0; i--) {
+ var attack = self.attacks[i];
+ // Zmniejsz czas życia ataku (w klatkach)
+ if (attack.lifeTime > 0) {
+ attack.lifeTime--;
+ }
+ // Sprawdź kolizję z graczem (jeśli gracz istnieje, żyje, nie jest nietykalny, a atak wizualnie istnieje)
+ if (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'
+ if (distance_p < playerRadius_p + attackRadius) {
+ player.takeDamage(1); // Gracz otrzymuje obrażenia
+ // Opcjonalnie: zniszcz wizualizację ataku natychmiast po trafieniu
+ if (attack.visual && attack.visual.destroy) {
+ attack.visual.destroy();
+ }
+ 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ę
+ // break;
}
}
+ // Usuń atak, jeśli skończył mu się czas życia LUB jego wizualizacja została zniszczona
+ if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroyed) {
+ // Upewnij się, że wizualizacja jest zniszczona, jeśli jeszcze istnieje
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy();
+ }
+ self.attacks.splice(i, 1); // Usuń atak z tablicy
+ }
}
- // Utrzymaj gracza w granicach ekranu (z uwzględnieniem ścian)
- // Ściany mają szerokość 100, więc gracz (środek) powinien być 100 jednostek od krawędzi mapy
- self.x = Math.max(100, Math.min(self.x, 2048 - 100));
- self.y = Math.max(100, Math.min(self.y, 2732 - 100));
+ // --- END BOSS ATTACK HANDLING ---
+ // 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
+ if (self.attackCooldown <= 0 && !self.repositioning) {
+ // Wywołanie startAttackPattern sprawdzi resztę warunków (dead, state) wewnątrz siebie
+ self.startAttackPattern();
+ }
};
return self;
});
var Player = Container.expand(function () {
@@ -347,32 +393,34 @@
anchorY: 0.5
});
// Player properties
self.health = 5; // Default health (will be overridden in startGame)
- self.speed = 8; // Movement speed
+ 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
+ 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
+ self.invulnerabilityTimeoutId = null; // Store timeout ID for invulnerability (NIE UŻYWANE?)
self.hasRolledThroughBossThisRoll = false; // Track if boss was hit during this roll
// Roll mechanic
self.roll = function (direction) {
// Check if player can roll (not already rolling and cooldown is 0)
- if (!self.rolling && self.rollCooldown <= 0) {
+ if (!self.rolling && self.rollCooldown <= 0 && !self.dead) {
+ // Dodano sprawdzenie !self.dead
self.rolling = true;
self.rollDirection = direction;
- self.rollCooldown = 30; // Set cooldown in frames
- self.invulnerable = true; // Player is invulnerable during roll
- self.invulnerabilityFrames = 30; // Set invulnerability frames
+ self.rollCooldown = 45; // Zwiększono cooldown turlania lekko (np. 45 klatek ~ 0.75s)
+ self.invulnerable = true;
+ // Player is invulnerable during roll
+ self.invulnerabilityFrames = 30; // Set invulnerability frames (0.5s)
self.hasRolledThroughBossThisRoll = false; // Reset boss hit tracking for this roll
// Create roll effect
var rollEffect = game.addChild(LK.getAsset('roll', {
anchorX: 0.5,
@@ -386,31 +434,34 @@
scaleX: 2,
scaleY: 2
}, {
duration: 300,
+ // Czas trwania efektu
easing: tween.easeOut,
onFinish: function onFinish() {
if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) {
rollEffect.destroy();
}
}
});
- // End roll after duration
+ // End roll state after duration
+ // Wyczyść stary timeout jeśli istnieje (na wszelki wypadek)
+ if (self.rollTimeoutId) {
+ LK.clearTimeout(self.rollTimeoutId);
+ }
self.rollTimeoutId = LK.setTimeout(function () {
self.rolling = false;
+ self.rollTimeoutId = null; // Zresetuj ID po wykonaniu
}, self.rollDuration);
}
};
- // Clear all roll and invulnerability timeouts
+ // Clear all roll timeouts (useful when game state changes abruptly)
self.clearRollTimeouts = function () {
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
self.rollTimeoutId = null;
}
- if (self.invulnerabilityTimeoutId) {
- LK.clearTimeout(self.invulnerabilityTimeoutId);
- self.invulnerabilityTimeoutId = null;
- }
+ // Invulnerability jest teraz zarządzana tylko przez self.invulnerabilityFrames w update
};
// Take damage method
self.takeDamage = function (amount) {
// Check if player can take damage (not invulnerable and not dead)
@@ -419,28 +470,38 @@
// 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;
+ return; // Zakończ, jeśli gracz umarł
}
- // Set invulnerability after hit
- self.invulnerable = true;
- self.invulnerabilityFrames = 60; // 1 second at 60fps
+ // 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 || 5) + 1;
+ storage.maxHearts = storage.maxHearts + 1;
}
+ self.clearRollTimeouts(); // Anuluj ewentualne trwające turlanie
// Death animation
tween(self, {
alpha: 0,
scaleX: 2,
@@ -448,78 +509,120 @@
}, {
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.gameOver(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 () {
- // Track last position for collision detection
- self.lastX = self.x;
- self.lastY = self.y;
- // Don't update if player is dead
+ // 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
+ // 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
- self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1;
+ // 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) {
- self.x += self.rollDirection.x * self.rollSpeed;
- self.y += self.rollDirection.y * self.rollSpeed;
- // Check for collision with boss during roll
+ 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) {
- var dx = self.x - boss.x;
- var dy = self.y - boss.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var playerCollisionRadius = self.width / 2;
- var bossCollisionRadius = boss.width / 2;
- if (distance < playerCollisionRadius + bossCollisionRadius) {
- // Collision with boss during roll - deal damage to boss
- boss.takeDamage(10);
- self.hasRolledThroughBossThisRoll = true;
- // Visual effect for hitting boss with roll
- LK.effects.flashObject(boss, 0x00FF00, 200);
+ // 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
}
}
- }
- // Keep player within screen bounds (accounting for walls)
- self.x = Math.max(100, Math.min(self.x, 2048 - 100));
- self.y = Math.max(100, Math.min(self.y, 2732 - 100));
- // Check for collision with boss attacks
- if (boss && !boss.dead && boss.attacks) {
- for (var i = 0; i < boss.attacks.length; i++) {
- var attack = boss.attacks[i];
- var dx = self.x - attack.x;
- var dy = self.y - attack.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var playerRadius = self.width / 2;
- // If player collides with attack and is not invulnerable
- if (distance < playerRadius + attack.radius && !self.invulnerable) {
- self.takeDamage(1);
- break; // Only take damage from one attack per frame
- }
+ } 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;
});
@@ -630,46 +733,63 @@
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
// Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
- // Usuń istniejące serca
- while (self.hearts.length > 0) {
- var heart = self.hearts.pop();
- if (heart && heart.destroy) {
- heart.destroy();
+ // 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;
+ }
+ }
}
- 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;
- self.heartContainer.y = 100; // Pozycja pionowa
};
// 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 (jeśli boss istnieje i żyje i jesteśmy w grze)
- self.bossHealthBarContainer.alpha = (gameState.currentState === "game" || gameState.currentState === "gameOver") && boss && !boss.dead && current > 0 ? 1 : 0; // Pokaż pasek w stanie gry ORAZ game over (przed zresetowaniem UI) tylko jeśli boss żyje i ma >0 HP.
+ // 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, gdy kontener jest widoczny
+ // Upewnij się, że pasek zdrowia tła też jest widoczny/ukryty tak samo jak kontener
if (self.bossHealthBarBg) {
self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
}
};
@@ -679,18 +799,26 @@
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) {
+ if (duration && duration > 0) {
self.messageTimeout = LK.setTimeout(function () {
tween(self.messageText, {
alpha: 0 // Zaniknij do przezroczystości 0
}, {
- duration: 500 // Czas trwania zanikania
+ 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) {
@@ -706,12 +834,16 @@
});
};
// Aktualizuje licznik śmierci
self.updateDeathsCounter = function () {
- self.deathsText.setText("Deaths: " + storage.totalDeaths);
+ // 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;
@@ -719,83 +851,78 @@
};
// 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"
+ 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 - Death is Progress"
- // Licznik śmierci i timer są pozycjonowane na stałe w UI constructor, zmieniamy tylko ich alpha/widoczność
- // Ukryj serca i timer na ekranie tytułowym
- self.heartContainer.alpha = 0;
- self.timerText.alpha = 0;
- // Ukryj pasek zdrowia bossa
- self.bossHealthBarContainer.alpha = 0;
+ 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; // Ukryj tytuł
+ // self.titleText.alpha = 0; // Ustawione domyślnie
self.messageText.x = 2048 / 2;
- self.messageText.y = 1500; // Komunikaty w trakcie gry (np. "YOU DIED" - choć to też w victory/gameOver state)
+ 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;
- // Pokaż pasek zdrowia bossa (jeśli boss żyje)
- // Pasek będzie widoczny tylko w stanie "game" dzięki updateBossHealth i update w game.update
- self.bossHealthBarContainer.alpha = 1;
+ // Pasek zdrowia bossa jest zarządzany przez updateBossHealth
break;
case "grillMenu":
// Nowy stan dla Grill Screena
- self.titleText.alpha = 0; // Ukryj tytuł na Grill Screenie (lub ustaw na "Grill Menu" jeśli chcesz)
- self.messageText.x = 2048 / 2; // Pozycjonowanie komunikatów/tekstów przycisków na środku
- self.messageText.y = 500; // Możesz dostosować pozycję
- self.messageText.setText(""); // Wyczyść domyślny messageText
- self.tutorialText.alpha = 0; // Ukryj tutorial
- // Ukryj serca i timer
- self.heartContainer.alpha = 0;
- self.timerText.alpha = 0;
- // Ukryj pasek zdrowia bossa
- self.bossHealthBarContainer.alpha = 0;
+ // 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 (można użyć tych samych co Victory lub zdefiniować osobno)
+ // Pozycje dla ekranu Game Over
self.titleText.x = 2048 / 2;
- self.titleText.y = 800; // Tytuł "YOU DIED" lub komunikat zwycięstwa
+ 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 lub "Tap to Restart" (choć restart jest timerem)
- // Ukryj serca i timer na ekranie Game Over
- self.heartContainer.alpha = 0;
- self.timerText.alpha = 0;
- // Pokaż pasek zdrowia bossa TYLKO JEŚLI TO ZWYCIĘSTWO PRZEZ CZAS W BOSS+ (gameover reason is not death)
- self.bossHealthBarContainer.alpha = gameState.gameOverReasonIsDeath === false && isNewBossPlusMode ? 1 : 0;
+ 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 (głównie ukryte elementy UI walki)
+ case "intro": // Pozycje dla ekranów intro/tutoriali
case "fakeTutorial":
case "realTutorial":
- self.titleText.alpha = 0; // Ukryj tytuł
- self.messageText.alpha = 0; // Ukryj komunikat
- self.tutorialText.alpha = 0; // Ukryj tutorial
- // Licznik śmierci jest zawsze widoczny
- self.deathsText.x = 2048 - 50; // Upewnij się, że jest w prawym górnym rogu
- self.deathsText.y = 50;
- // Ukryj serca i timer
- self.heartContainer.alpha = 0;
- self.timerText.alpha = 0;
- // Ukryj pasek zdrowia bossa
- self.bossHealthBarContainer.alpha = 0;
+ // Większość elementów ukryta (ustawione domyślnie wyżej)
break;
}
- // Upewnij się, że licznik śmierci jest zawsze widoczny
- self.deathsText.alpha = 1;
};
return self;
});
@@ -808,281 +935,259 @@
/****
* Game Code
****/
-// Zachowujemy na wypadek użycia w animacjach gracza/UI
-// Używamy dźwięku zwycięstwa z stworzylo zloto.txt
-// Przykład koloru, użyje grafiki z upit
-// Przykład koloru, użyje grafiki z upit
-// Asset dla tła Grill Screena (dostosuj wymiary jeśli Twoja grafika ma inne)
-// Zmieniono wymiary przycisku
-// Twój nowy asset dla paska zdrowia bossa (800x30)
-// Zachowujemy jako domyślne tło lub na wypadek potrzeby
+// Komentarze o assetach pominięte dla zwięzłości
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
-var currentSceneElements = new Container(); // Dodano z souls4.txt i rozbudowano dla Grill Screena
-// Zmienne gry (z stworzylo zloto.txt)
+var currentSceneElements = new Container();
+// Zmienne gry
var player;
var boss;
var ui;
var walls = []; // Ściany areny bossa
-// Dodano zmienną do przechowywania aktywnego tła sceny
+// Zmienna do przechowywania aktywnego tła sceny
var currentBackground = null;
-// Dodana flaga do śledzenia, czy jesteśmy w trybie New Boss+
+// Flaga do śledzenia, czy jesteśmy w trybie New Boss+
var isNewBossPlusMode = false;
-// Dodana flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
+// Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
var gameOverReasonIsDeath = false;
-// Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro/tutoriali i Grill Screena)
-// Nie usuwa tła sceny (currentBackground) ani elementów UI.
+// Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła)
function clearScene() {
- // Dodano z souls4.txt i rozbudowano dla Grill Screena
- // Usuń wszystkie dzieci z kontenera
while (currentSceneElements.children.length > 0) {
var child = currentSceneElements.children[0];
- // Upewnij się, że obiekt istnieje i ma metodę destroy przed wywołaniem
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne
+ } else if (child && child.parent) {
+ child.parent.removeChild(child); // Fallback
} else {
- currentSceneElements.removeChild(child); // Fallback
+ // Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć)
+ currentSceneElements.children.shift();
}
}
- // Nie resetujemy tutaj game.update ani input handlerów, bo są zarządzane przez gameState
+ // 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 (z stworzylo zloto.txt, rozbudowany o stany intro, timer i Grill Screen)
+// Obiekt zarządzający stanami gry
var gameState = {
currentState: "title",
// Początkowy stan gry
- // Zmienne dla timera walki z bossem
gameDuration: 120,
- // Czas trwania walki z bossem w sekundach (2 minuty) - Domyślne
+ // Domyślny czas walki z bossem w sekundach (2 minuty)
remainingTime: 0,
// Pozostały czas
gameTimerInterval: null,
// ID interwału timera
- // Zmienne dla fałszywego tutorialu
fakeTutorialTimerId: null,
// ID timera przechodzącego do prawdziwego tutorialu
- // Zmienne dla przyspieszenia bossa
bossSpeedIncreased: false,
- // Flaga informująca czy prędkość bossa została już zwiększona
- // Zmienne do śledzenia gestów dotykowych/myszy (dla turlania i tapowania)
+ // Flaga przyspieszenia bossa
touchStart: {
x: 0,
y: 0
},
+ // Początek gestu
touchEnd: {
x: 0,
y: 0
},
- // Inicjalizacja gry (wywoływana raz na początku aplikacji)
+ // Koniec gestu
+ // Inicjalizacja gry (wywoływana raz na początku)
init: function init() {
- // ZRESETUJ LICZNIK ŚMIERCI I USTAW DOMYŚLNĄ ILOŚĆ SERC NA START APLIKACJI
- // Resetuj licznik śmierci PRZY KAŻDYM URUCHOMIENIU GRY (załadowaniu strony)
+ // Resetuj stan przy każdym uruchomieniu
storage.totalDeaths = 0;
- // Ustaw początkową liczbę serc na 5 przy każdym uruchomieniu gry
- storage.maxHearts = 5;
- // Zresetuj flagę trybu New Boss+ na początku gry
+ storage.maxHearts = 5; // Zaczynaj zawsze z 5 sercami
isNewBossPlusMode = false;
- // Zresetuj flagę przyczyny Game Over
gameOverReasonIsDeath = false;
- // Nie dodajemy tutaj domyślnego tła 'bg'. Tła będą dodawane w metodach scen.
- game.setBackgroundColor(0x111111); // Ustaw domyślny kolor tła (widoczny np. podczas ładowania assetów)
- // Stwórz ściany areny bossa (potrzebne w stanie gry, ale stworzone wcześniej)
- this.createWalls();
- // Stwórz UI (ikony serc, teksty tytułów/komunikatów/tutoriali, licznik śmierci, timer, pasek zdrowia bossa)
- ui = game.addChild(new UI());
- // Pozycjonuj UI początkowo dla ekranu tytułowego
- ui.positionElements("title");
- ui.updateDeathsCounter(); // Zaktualizuj wyświetlanie licznika śmierci (powinien być 0)
- ui.updateHearts(storage.maxHearts, storage.maxHearts); // Zaktualizuj wyświetlanie serc (5 pełnych)
- // Dodaj kontener na elementy scen intro i Grill Screena do wyświetlania (nad UI)
- game.addChild(currentSceneElements); // currentSceneElements powinien być dodany po UI, żeby być nad nim
+ 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
}
});
- // Rozpocznij od ekranu tytułowego zarządzanego przez ten obiekt gameState
- this.showTitleScreen();
+ this.showTitleScreen(); // Rozpocznij od ekranu tytułowego
},
// Tworzy/odświeża ściany areny bossa
createWalls: function createWalls() {
- // Usuń istniejące ściany, jeśli istnieją
walls.forEach(function (wall) {
if (wall && wall.destroy) {
wall.destroy();
}
});
walls = [];
- // Lewa ściana
+ // 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);
- // Prawa ściana
var rightWall = game.addChild(LK.getAsset('wall', {
- anchorX: 0,
+ anchorX: 1,
anchorY: 0,
- x: 2048 - 100,
+ x: 2048,
y: 0
- }));
+ })); // AnchorX=1 dla prawej ściany
+ rightWall.x = 2048; // Poprawka pozycji prawej ściany
walls.push(rightWall);
- // Górna ściana
var topWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
walls.push(topWall);
- // Dolna ściana
var bottomWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
- anchorY: 0,
+ anchorY: 1,
x: 0,
- y: 2732 - 100
- }));
+ y: 2732
+ })); // AnchorY=1 dla dolnej ściany
+ bottomWall.y = 2732; // Poprawka pozycji dolnej ściany
walls.push(bottomWall);
- // Upewnij się, że ściany są na odpowiedniej warstwie (pod graczem i bosem)
- // Domyślnie obiekty są dodawane w kolejności, więc ściany dodane przed graczem i bosem będą pod nimi.
- // UI i currentSceneElements dodane na końcu w init są na górze.
+ // Ustaw ściany na spodzie (niski zIndex)
+ walls.forEach(function (wall) {
+ game.setChildIndex(wall, 0);
+ });
},
// ---------- Metody przejścia między stanami ----------
- // Przejście do ekranu tytułowego
showTitleScreen: function showTitleScreen() {
- clearScene(); // Wyczyść elementy poprzedniej sceny (np. z Grill Screena, Victory lub Fake Death)
- // Upewnij się, że timer fake tutorialu jest wyczyszczony
+ // Zatrzymaj timery poprzedniego stanu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
- this.fakeTutorialTimerId = null;
}
- // Zatrzymaj wszelkie trwające tweeny tła
+ 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);
- currentBackground.destroy(); // Usuń poprzednie tło
+ tween.stop(currentBackground); // Zatrzymaj animacje tła
+ currentBackground.destroy();
currentBackground = null;
}
- this.currentState = "title"; // Ustaw stan na tytuł
+ this.currentState = "title";
game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe dla tytułu
- // Dodaj tło ekranu tytułowego (titleBg)
- currentBackground = game.addChildAt(LK.getAsset('titleBg', {
- // Dodaj na spód (index 0)
+ // Dodaj tło ekranu tytułowego
+ currentBackground = LK.getAsset('titleBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
- }), 0); // Dodaj jako pierwsze dziecko gry (na samym spodzie)
- // Ukryj obiekty gry (gracz, boss) jeśli istnieją
- if (player) {
- player.alpha = 0;
+ });
+ game.addChildAt(currentBackground, 0); // Dodaj na spód
+ // Ukryj gracza/bossa jeśli istnieją
+ if (player && player.destroy) {
+ player.destroy();
}
- if (boss) {
- boss.alpha = 0;
+ player = null;
+ if (boss && boss.destroy) {
+ boss.destroy();
}
- // Pokaż ściany jako tło menu/intro (jeśli nie mają być widoczne, ustaw alpha na 0)
+ boss = null;
+ // Pokaż ściany (jako część tła menu)
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
- ui.positionElements("title"); // Pozycjonuj UI dla stanu tytułowego
- ui.titleText.setText("ROLL SOULS"); // Upewnij się, że tytuł jest poprawny
- ui.titleText.alpha = 1; // Pokaż tytuł
- ui.showMessage("Tap to Start", 0); // Wyświetl komunikat startowy
- ui.showTutorial("Swipe to Roll - Death is Progress"); // Wyświetl tekst tutorialu (można go ukryć i pokazać dopiero w prawdziwym tutorialu)
- // Resetuj stan touchGesture na wypadek kliknięcia w poprzednim stanie końcowym
+ // Ustaw UI dla tytułu
+ ui.positionElements("title");
+ ui.titleText.setText("ROLL SOULS");
+ 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
};
- // --- Dodaj testowy przycisk do Grill Menu ---
+ // Przycisk testowy do Grill Menu (jeśli nadal potrzebny)
var grillMenuTestButton = new Container();
- grillMenuTestButton.interactive = true; // Spraw, żeby przycisk był interaktywny
- grillMenuTestButton.x = 2048 / 2; // Wyśrodkuj poziomo
- grillMenuTestButton.y = 1600; // Ustaw pozycję pionową (np. poniżej innych tekstów)
- currentSceneElements.addChild(grillMenuTestButton); // Dodaj do kontenera sceny tytułowej
+ 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); // Dodaj tło przycisku
+ grillMenuTestButton.addChild(grillButtonBg);
var grillButtonText = new Text2('Go to Grill Menu', {
size: 50,
fill: 0xFFFFFF
- }); // Tekst przycisku
+ });
grillButtonText.anchor.set(0.5, 0.5);
- grillMenuTestButton.addChild(grillButtonText); // Dodaj tekst do przycisku
- // Logika przycisku: po kliknięciu przejdź do Grill Menu
+ grillMenuTestButton.addChild(grillButtonText);
grillMenuTestButton.down = function () {
- // Opcjonalnie: wyczyść elementy sceny tytułowej przed przejściem
- clearScene();
- // Przejdź do ekranu Grill Menu
+ // clearScene(); // Niekonieczne, showGrillScreen to zrobi
gameState.showGrillScreen();
};
- // --- Koniec dodawania przycisku testowego ---
},
- // Przejście do pierwszej sceny intro
showIntro: function showIntro() {
- clearScene(); // Wyczyść elementy poprzedniej sceny (Tytuł)
- // Upewnij się, że timer fake tutorialu jest wyczyszczony
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
- this.fakeTutorialTimerId = null;
}
- // Zatrzymaj wszelkie trwające tweeny tła
+ this.fakeTutorialTimerId = null; // Wyczyść timer
+ clearScene(); // Wyczyść elementy tytułu
if (currentBackground) {
tween.stop(currentBackground);
- currentBackground.destroy(); // Usuń poprzednie tło
+ currentBackground.destroy();
currentBackground = null;
- }
- this.currentState = "intro"; // Ustaw stan na intro
- game.setBackgroundColor(0x111111); // Ciemne tło bazowe dla intro
- // Dodaj tło scen intro (introBg)
- currentBackground = game.addChildAt(LK.getAsset('introBg', {
- // Dodaj na spód (index 0)
+ } // Usuń stare tło
+ this.currentState = "intro";
+ game.setBackgroundColor(0x111111);
+ // Dodaj tło intro i animację zoom
+ currentBackground = LK.getAsset('introBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
- scaleY: 1 // Upewnij się, że zaczyna w normalnym rozmiarze
- }), 0);
- // Zastosuj powolny zoom do tła intro
- // Zoom IN do 1.1x przez 30 sekund, potem zoom OUT, powtarzaj
+ scaleY: 1
+ });
+ game.addChildAt(currentBackground, 0);
tween(currentBackground, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 30000,
- // 30 sekund na jeden kierunek zoomu
easing: tween.linear,
repeat: Infinity,
- // Powtarzaj w nieskończoność
- yoyo: true // Zoomuj w tam i z powrotem
+ yoyo: true
});
- // Ukryj obiekty gry i ściany na czas intro/tutoriali
- if (player) {
- player.alpha = 0;
+ // Ukryj ściany na czas intro
+ if (player && player.destroy) {
+ player.destroy();
}
- if (boss) {
- boss.alpha = 0;
+ 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"); // Ukryj elementy UI walki (zostaw licznik śmierci)
- // Tekst pierwszej sceny intro (z souls4.txt)
+ ui.positionElements("intro"); // Ustaw UI dla intro (głównie ukrywa elementy)
+ // Teksty intro z timerami
var introText1 = new Text2('From the creators of FromSoftware...', {
size: 90,
fill: 0xFFFFFF,
align: 'center',
@@ -1091,16 +1196,16 @@
});
introText1.anchor.set(0.5, 0.5);
introText1.x = 2048 / 2;
introText1.y = 2732 / 2;
- currentSceneElements.addChild(introText1); // Dodaj do kontenera, który będzie czyszczony
- // Timer 1: Zmień tekst po 5 sekundach
+ currentSceneElements.addChild(introText1);
LK.setTimeout(function () {
- // Usuń pierwszy tekst
- if (introText1.parent) {
- introText1.destroy();
- }
- // Tekst drugiej sceny intro (z souls4.txt)
+ // Użyj try-catch dla destroy na wypadek, gdyby obiekt został już usunięty
+ try {
+ if (introText1 && introText1.destroy) {
+ introText1.destroy();
+ }
+ } catch (e) {}
var introText2 = new Text2('...I have no idea. I don’t know them.', {
size: 90,
fill: 0xFFFFFF,
align: 'center',
@@ -1109,44 +1214,33 @@
});
introText2.anchor.set(0.5, 0.5);
introText2.x = 2048 / 2;
introText2.y = 2732 / 2;
- currentSceneElements.addChild(introText2); // Dodaj do kontenera
- // Timer 2: Przejdź do fałszywego tutorialu po kolejnych 2 sekundach
+ currentSceneElements.addChild(introText2);
+ // Timer do fake tutorialu
+ // Użyj .bind(this) jeśli potrzebujesz 'this' z gameState wewnątrz callbacka
this.fakeTutorialTimerId = LK.setTimeout(function () {
- // ZAPISZ ID TIMERA
- // clearScene() zostanie wywołane na początku następnej metody
- // Zresetuj ID timera, bo się zakończył
- gameState.fakeTutorialTimerId = null;
- // Przejdź do fałszywego tutorialu
- gameState.showFakeTutorial();
- }.bind(this), 2000); // 2 sekundy opóźnienia po wyświetleniu drugiego tekstu. Użyj .bind(this) dla dostępu do this.fakeTutorialTimerId
- }.bind(this), 5000); // 5 sekund opóźnienia przed wyświetleniem drugiego tekstu. Użyj .bind(this) dla dostępu do this.fakeTutorialTimerId
+ try {
+ if (introText2 && introText2.destroy) {
+ introText2.destroy();
+ }
+ } catch (e) {}
+ gameState.fakeTutorialTimerId = null; // Zresetuj ID
+ gameState.showFakeTutorial(); // Przejdź dalej
+ } /*.bind(this)*/, 2000); // 2 sekundy
+ }.bind(this), 5000); // 5 sekund, użyj bind(this) dla dostępu do this.fakeTutorialTimerId
},
- // Przejście do sceny fałszywego tutorialu (Press LPM to block)
showFakeTutorial: function showFakeTutorial() {
- clearScene(); // Wyczyść poprzednie elementy (tekst intro)
- // Tło introBg i jego zoom powinny pozostać, bo nie są w currentSceneElements
- // Upewnij się, że timer fake tutorialu jest wyczyszczony (na wypadek gdybyśmy tu dotarli inaczej niż przez timer)
+ clearScene(); // Wyczyść tekst intro
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
- this.fakeTutorialTimerId = null;
}
- this.currentState = "fakeTutorial"; // Ustaw stan na fałszywy tutorial
- game.setBackgroundColor(0x000000); // Czarne tło - Może chcesz, żeby tło introBg było widoczne? Zmieniam na czarne zgodnie z oryginalnym souls4.txt, ale możesz to zmienić.
- // Ukryj obiekty gry i ściany
- if (player) {
- player.alpha = 0;
- }
- if (boss) {
- boss.alpha = 0;
- }
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 0;
- }
- });
- // Tekst fałszywego tutorialu - Zmieniony na "Press LPM to block"
+ 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',
@@ -1156,50 +1250,117 @@
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2;
currentSceneElements.addChild(instructionText);
- // Timer przechodzący do prawdziwego tutorialu - ZAPISZ JEGO ID
+ // Timer przechodzący do prawdziwego tutorialu
this.fakeTutorialTimerId = LK.setTimeout(function () {
- // clearScene() zostanie wywołane na początku następnej metody
- // Zresetuj ID timera, bo się zakończył
- gameState.fakeTutorialTimerId = null;
- // Przejdź do prawdziwego tutorialu
- gameState.showRealTutorial();
- }, 6000); // 6 sekund
+ 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
}
- // Wyczyść ekran fałszywego tutorialu
- clearScene();
- // Ustaw stan na game over (tymczasowo, żeby logika UI się zgadzała)
- this.currentState = "gameOver"; // Można użyć dedykowanego stanu 'fakeGameOver' jeśli chcemy inaczej obsłużyć UI
- // Zatrzymaj wszelkie trwające tweeny tła (np. zoom intro)
+ 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);
- // Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem "YOU DIED"
- // currentBackground.destroy();
- currentBackground = null;
}
- game.setBackgroundColor(0x000000); // Czarne tło bazowe za fake game over
+ 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
- });
+ fill: 0xFF0000
+ }); // Czerwony kolor
diedText.anchor.set(0.5, 0.5);
diedText.x = 2048 / 2;
diedText.y = 800;
currentSceneElements.addChild(diedText);
- // Wyświetl "Did you check the title of the game?"
+ // Wyświetl wyjaśnienie
var explanationText = new Text2("Did you check the title of the game?", {
size: 60,
fill: 0xFFFFFF,
- // Biały kolor
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
@@ -1217,209 +1378,220 @@
anchorX: 0.5,
anchorY: 0.5
});
howToPlayButtonContainer.addChild(buttonBg);
- var buttonText = new Text2('How to play again', {
- size: 60,
+ var buttonText = new Text2('How to play', {
+ size: 50,
fill: 0xFFFFFF
- }); // Zmniejszono rozmiar czcionki, żeby pasowała do przycisku
+ }); // Zmieniono tekst i rozmiar
buttonText.anchor.set(0.5, 0.5);
howToPlayButtonContainer.addChild(buttonText);
- // Akcja przycisku: Przejdź z powrotem do prawdziwego tutorialu
+ // Akcja przycisku: Przejdź do prawdziwego tutorialu
howToPlayButtonContainer.down = function () {
- gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
+ // *** 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)
+ // Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu
startGame: function startGame() {
- // Upewnij się, że timer fake tutorialu jest wyczyszczony
+ // Wyczyść timery z poprzednich stanów
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
- this.fakeTutorialTimerId = null;
}
- // Zatrzymaj wszelkie trwające tweeny tła
+ 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(); // Usuń tło intro/tytułu
+ currentBackground.destroy();
currentBackground = null;
}
this.currentState = "game"; // Ustaw stan na "game"
- game.setBackgroundColor(0x111111); // Ciemne tło bazowe dla gry
- // Pokaż obiekty gry (gracz, boss), ukryte w intro/tutorialu
- // Upewnij się, że istnieją przed ustawieniem alpha
- if (player) {
- player.alpha = 1;
- }
- if (boss) {
- boss.alpha = 1;
- }
- // Pokaż ściany areny
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 1;
- }
- });
- ui.positionElements("game"); // Pozycjonuj UI dla stanu gry (ikony serc, licznik śmierci, timer)
- // Stwórz gracza (jeśli nie istnieje lub zniszcz poprzedniego)
- if (player) {
+ game.setBackgroundColor(0x111111);
+ // Stwórz gracza (jeśli nie istnieje)
+ if (player && player.destroy) {
player.destroy();
- } // Zniszcz poprzedniego gracza jeśli był
+ } // Zniszcz starego gracza
player = game.addChild(new Player()); // Stwórz nowego gracza
- // Ustaw początkowe zdrowie gracza na podstawie storage.maxHearts (domyślnie 5 na start aplikacji, może wzrośnie po śmierciach)
- player.health = storage.maxHearts || 5;
- // Jawnie zresetuj stan turlania i nietykalności gracza na początku walki
+ player.health = storage.maxHearts || 5; // Ustaw zdrowie gracza
+ // Resetuj stan gracza
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
- player.clearRollTimeouts(); // Wyczyść timery turlania/nietykalności
- player.hasRolledThroughBossThisRoll = false; // Zresetuj flagę obrażeń od turlania dla nowego gracza
+ player.clearRollTimeouts();
+ player.hasRolledThroughBossThisRoll = false;
player.x = 2048 / 2;
player.y = 2732 / 2 + 400; // Pozycja startowa gracza
- // Stwórz bossa (jeśli nie istnieje lub zniszcz poprzedniego)
- if (boss) {
+ player.alpha = 1; // Upewnij się, że jest widoczny
+ // Stwórz bossa (jeśli nie istnieje)
+ if (boss && boss.destroy) {
boss.destroy();
- } // Zniszcz poprzedniego bossa jeśli był
+ } // Zniszcz starego bossa
boss = game.addChild(new Boss()); // Stwórz nowego bossa
boss.x = 2048 / 2;
boss.y = 2732 / 2 - 400; // Pozycja startowa bossa
- // Upewnij się, że boss zaczyna w stanie gotowości do ataku z wyczyszczonymi poprzednimi atakami
- boss.attackCooldown = 0; // Boss zaatakuje po pierwszym opóźnieniu
- boss.attacks = []; // Wyczyść wszelkie pozostałości po poprzednich atakach
- boss.attackSpeedMultiplier = 1; // Ustaw początkowy mnożnik prędkości ataków na 1 (normalny)
- boss.repositioning = false; // Zresetuj flagę repozycji bossa
- // !!! Ustaw zdrowie bossa i czas gry W ZALEŻNOŚCI OD TRYBU !!!
+ 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; // New Boss+ ma 2000 HP
+ boss.maxHealth = 2000;
boss.health = boss.maxHealth;
- this.gameDuration = 600; // New Boss+ ma 10 minut (600 sekund)
- // W trybie New Boss+ ataki są szybsze OD POCZĄTKU i NIE MA dodadkowego przyspieszenia po minucie
- boss.attackSpeedMultiplier = 0.6; // Przykładowy, niższy mnożnik (szybsze ataki) OD POCZĄTKU
- // Można tu dodać inne modyfikatory dla New Boss+, np. wyższą prędkość ruchu bossa
- boss.speed = 7; // Przykładowe szybsze poruszanie się bossa
+ 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 {
- // Standardowy boss
- boss.maxHealth = 200;
+ boss.maxHealth = 200; // Standardowy boss HP
boss.health = boss.maxHealth;
- this.gameDuration = 120; // Standardowy czas 2 minuty
- boss.speed = 5; // Standardowa prędkość poruszania się bossa
+ 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);
}
- // Aktualizuj UI (wyświetl serca i początkowy komunikat)
- ui.updateHearts(player.health, storage.maxHearts); // Wyświetl aktualne serca (powinno być 5 lub więcej)
- ui.showTutorial("Swipe to roll away from attacks!"); // Wyświetl tutorial dla stanu gry
- ui.updateBossHealth(boss.health, boss.maxHealth); // Zaktualizuj pasek zdrowia bossa z poprawnym HP
+ // 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 ZALEŻNIE OD TRYBU
- ui.updateTimerDisplay(this.remainingTime); // Wyświetl początkowy czas
+ this.remainingTime = this.gameDuration; // Ustaw początkowy czas
+ ui.updateTimerDisplay(this.remainingTime); // Wyświetl czas
// Rozpocznij interwał timera
- // Wyczyść poprzedni interwał timera, jeśli istniał (przy restartach gry)
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
- }
- // Zresetuj flagę przyspieszenia bossa po 1 minucie (dotyczy obu trybów)
- this.bossSpeedIncreased = false;
+ } // Wyczyść stary timer
+ this.bossSpeedIncreased = false; // Resetuj flagę przyspieszenia bossa
this.gameTimerInterval = LK.setInterval(function () {
- // Tylko aktualizuj timer i sprawdzaj warunek zwycięstwa/game over jeśli jesteśmy w stanie gry
+ // Aktualizuj tylko w stanie gry
if (gameState.currentState === "game") {
- gameState.remainingTime--; // Zmniejsz pozostały czas
- ui.updateTimerDisplay(gameState.remainingTime); // Zaktualizuj wyświetlanie timera na UI
+ gameState.remainingTime--;
+ ui.updateTimerDisplay(gameState.remainingTime);
// --- Przyspieszenie bossa - TYLKO W STANDARDOWYM TRYBIE ---
if (!isNewBossPlusMode) {
- // Przyspieszenie tylko w standardowym trybie
- var accelerationThreshold = gameState.gameDuration - 60; // Przyspieszenie na 60 sekund przed końcem
+ var accelerationThreshold = gameState.gameDuration - 60; // Przyspieszenie na 60s przed końcem
if (gameState.remainingTime <= accelerationThreshold && !gameState.bossSpeedIncreased) {
- gameState.bossSpeedIncreased = true; // Ustaw flagę
+ gameState.bossSpeedIncreased = true;
if (boss && !boss.dead) {
- // Upewnij się, że boss istnieje i żyje
- // Zmniejsz mnożnik prędkości ataków bossa (np. o kolejne 30% -> mnożnik 0.7 * 1 = 0.7)
- boss.attackSpeedMultiplier *= 0.7; // Przykładowe dalsze przyspieszenie ataków
- // Opcjonalnie: wyświetl komunikat o przyspieszeniu
+ // 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ź warunek końca gry (czas minął)
+ // Sprawdź koniec gry (czas minął)
if (gameState.remainingTime <= 0) {
- LK.clearInterval(gameState.gameTimerInterval); // Stop the timer
+ LK.clearInterval(gameState.gameTimerInterval); // Zatrzymaj timer
+ gameState.gameTimerInterval = null;
if (isNewBossPlusMode) {
- // Czas minął w trybie Boss+ (zwycięstwo przez przetrwanie)
- gameOverReasonIsDeath = false; // Czas minął
- gameState.gameOver(false); // Przejdź do stanu Game Over z komunikatem zwycięstwa Boss+
+ // 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 trybie standardowym (zwycięstwo przez przetrwanie)
- // Przejdź bezpośrednio do Grill Menu
+ // 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;
}
- }, 1000); // Interwał co 1 sekundę (1000 ms)
- // --- Koniec Timer walki z bossem ---
- // Zaplanuj rozpoczęcie ataków bossa po krótkim opóźnieniu
- // Nie potrzebujemy jawnie czyścić timeoutu, bo startAttackPattern ma sprawdzenie stanu gry
+ } /*.bind(this)*/, 1000); // Interwał co 1 sekundę
+ // Ukryj tutorial po chwili
LK.setTimeout(function () {
- // Sprawdź, czy nadal jesteśmy w stanie gry gdy timeout minie i czy boss istnieje i żyje
- if (gameState.currentState === "game" && boss && !boss.dead) {
- ui.hideTutorial(); // Ukryj tekst tutorialu
- boss.startAttackPattern(); // Rozpocznij ataki bossa
+ if (gameState.currentState === "game") {
+ // Sprawdź czy nadal jesteśmy w grze
+ ui.hideTutorial();
}
- }, 3000); // 3 sekundy opóźnienia przed pierwszym atakiem bossa
+ // Rozpoczęcie ataków bossa jest teraz obsługiwane przez Boss.update i jego cooldown
+ }, 3000); // 3 sekundy opóźnienia
},
- // Ta metoda zwycięstwa (przez czas w trybie standardowym) przechodzi teraz do Grill Screena
- victory: function victory() {
- // Ta metoda nie jest już używana do zarządzania stanami gry po zwycięstwie przez czas.
- // Logika przejścia do Grill Screena dla standardowego zwycięstwa przez czas jest teraz bezpośrednio w gameTimerInterval.
- console.log("Old victory state - transition is handled by gameTimerInterval now.");
- },
- // Przejście do ekranu Grilla po zwycięstwie (pokonaniu bossa) lub przetrwaniu czasu
+ // victory() - nieużywane, logika w timerze i boss.die()
showGrillScreen: function showGrillScreen() {
- clearScene(); // Wyczyść elementy poprzedniej sceny (gra, ataki, itp.)
- // Zatrzymaj timer gry, jeśli nadal działa (jeśli wygrana była przez zbicie HP bossa, a nie czasem)
- LK.clearInterval(this.gameTimerInterval);
- // Zresetuj flagę przyspieszenia bossa
- this.bossSpeedIncreased = false;
- this.currentState = "grillMenu"; // Ustaw nowy stan gry
- game.setBackgroundColor(0x333333); // Możesz ustawić kolor tła lub pozwolić assetowi to zrobić
- // Zatrzymaj wszelkie trwające tweeny tła gry i usuń tło gry, jeśli istnieje
+ // 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 = game.addChildAt(LK.getAsset('grillMenu', {
- // Użyj assetu 'grillMenu'
+ currentBackground = LK.getAsset('grillMenu', {
anchorX: 0.5,
anchorY: 0.5,
- // Ustaw środek jako punkt odniesienia
x: 2048 / 2,
- y: 2732 / 2 // Wyśrodkuj na ekranie
- }), 0); // Dodaj na spód
- // Ukryj obiekty gry (gracz, boss) jeśli istnieją (choć powinny być zniszczone)
- if (player) {
- player.alpha = 0;
- }
- if (boss) {
- boss.alpha = 0;
- }
- // Ukryj ściany areny
+ y: 2732 / 2
+ });
+ game.addChildAt(currentBackground, 0); // Na spód
+ // Ukryj ściany
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
- // Pozycjonuj elementy UI dla stanu Grill Screena
+ // Ustaw UI dla Grill Menu
ui.positionElements("grillMenu");
- // --- Dodaj przyciski na ekranie Grilla ---
- var buttonYStart = 1000; // Początkowa pozycja Y dla pierwszego przycisku
- var buttonYOffset = 150; // Odstęp pionowy między przyciskami
+ 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); // Dodaj do kontenera sceny
+ currentSceneElements.addChild(restButton);
var restButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -1429,28 +1601,24 @@
fill: 0xFFFFFF
});
restButtonText.anchor.set(0.5, 0.5);
restButton.addChild(restButtonText);
- // Logika przycisku "Rest"
restButton.down = function () {
- // Wyświetl komunikat "Rest in peace" i podziękowanie
- ui.showMessage("Rest in peace...", 2000); // Zniknie po 2 sekundach
+ ui.showMessage("Rest in peace...", 2000);
LK.setTimeout(function () {
- ui.showMessage("Thank you for playing!", 3000); // Zniknie po 3 sekundach
- }, 2500); // Pokaż podziękowanie po chwili
- // Zaplanuj powrót do ekranu tytułowego po wyświetleniu podziękowań
+ ui.showMessage("Thank you for playing!", 3000);
+ }, 2500);
LK.setTimeout(function () {
- // Upewnij się, że elementy Grill Screena zostały wyczyszczone przed powrotem do tytułu
- clearScene();
- gameState.showTitleScreen(); // Wróć do ekranu tytułowego
- }, 6000); // Opóźnienie przed powrotem do tytułu
+ // clearScene(); // Niekonieczne, showTitleScreen to zrobi
+ gameState.showTitleScreen(); // Wróć do tytułu
+ }, 6000); // Opóźnienie
};
- // Przycisk "Upgrade Roll"
+ // Przycisk "Upgrade Roll" (obecnie bez funkcji)
var upgradeButton = new Container();
upgradeButton.interactive = true;
upgradeButton.x = 2048 / 2;
upgradeButton.y = buttonYStart + buttonYOffset;
- currentSceneElements.addChild(upgradeButton); // Dodaj do kontenera sceny
+ currentSceneElements.addChild(upgradeButton);
var upgradeButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -1460,19 +1628,17 @@
fill: 0xFFFFFF
});
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(upgradeButtonText);
- // Logika przycisku "Upgrade Roll"
upgradeButton.down = function () {
- ui.showMessage("Rolling?", 2000); // Wyświetl śmieszny napis
- // W przyszłości można tu dodać rzeczywistą logikę ulepszenia turlania
- };
+ 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); // Dodaj do kontenera sceny
+ currentSceneElements.addChild(newBossButton);
var newBossButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -1482,240 +1648,217 @@
fill: 0xFFFFFF
});
newBossButtonText.anchor.set(0.5, 0.5);
newBossButton.addChild(newBossButtonText);
- // Logika przycisku "New Boss+"
newBossButton.down = function () {
- clearScene(); // Wyczyść przyciski i tło Grilla
- // Ustaw flagę trybu New Boss+
- isNewBossPlusMode = true;
- // Rozpocznij grę z nowymi parametrami (obsłużone w startGame)
- gameState.startGame();
+ // clearScene(); // Niekonieczne, startGame to zrobi
+ isNewBossPlusMode = true; // Ustaw flagę
+ gameState.startGame(); // Rozpocznij grę w trybie Boss+
};
- // --- Koniec dodawania przycisków ---
- // Ukryj pasek zdrowia bossa na ekranie Grilla
- ui.updateBossHealth(0, boss ? boss.maxHealth : 200); // Ustaw 0 zdrowia, ukryje pasek
},
- // Przejście do stanu Game Over (wywoływane przez player.die() lub przez timer)
- // Przyjmuje argument isDeath: true jeśli gracz zginął, false jeśli czas minął
+ // Przejście do stanu Game Over
+ // isDeath: true (gracz zginął), false (czas minął w Boss+)
gameOver: function gameOver(isDeath) {
- this.currentState = "gameOver"; // Ustaw stan na game over
- // Zapisz przyczynę Game Over, aby UI mogło jej użyć do pozycjonowania/widoczności elementów
- gameOverReasonIsDeath = isDeath;
- // Zatrzymaj timer gry, jeśli nadal działa
- LK.clearInterval(this.gameTimerInterval);
- // Upewnij się, że interwał timera jest wyczyszczony
- // Zresetuj flagę przyspieszenia bossa
- this.bossSpeedIncreased = false; // Zachowaj reset flagi przyspieszenia
- // Zatrzymaj wszelkie trwające tweeny tła
+ 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);
- // Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem game over
- // currentBackground.destroy();
- currentBackground = null;
}
- game.setBackgroundColor(0x000000); // Czarne tło bazowe za game over
- // Ukryj obiekty gry (są już zniszczone, ale to dodatkowe zabezpieczenie)
- // Upewnij się, że istnieją przed ustawieniem alpha
- if (player) {
+ 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;
- }
- if (boss) {
+ } // 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 pozycje UI dla ekranu game over
+ // Ustaw UI dla ekranu game over
ui.positionElements("gameOver");
- // Logika komunikatu Game Over w zależności od trybu i przyczyny
- var gameOverMessage = "YOU DIED"; // Domyślny komunikat dla śmierci gracza
+ // Logika komunikatu Game Over
+ var gameOverMessage = "YOU DIED"; // Domyślny komunikat
if (!isDeath) {
- // Jeśli Game Over nie nastąpiło przez śmierć gracza (czyli czas minął)
- if (isNewBossPlusMode) {
- // Czas minął w trybie Boss+ (zwycięstwo przez przetrwanie)
- gameOverMessage = "I haven't prepared a scene for this touch the grass"; // Komunikat zwycięstwa Boss+
- }
- // else: Czas minął w trybie standardowym - ten przypadek teraz przechodzi bezpośrednio do Grill Menu i nie wywołuje gameover()
+ // Jeśli czas minął (tylko w Boss+)
+ gameOverMessage = "YOU SURVIVED... for now."; // Zmieniono komunikat zwycięstwa Boss+
}
- // Jeśli isDeath jest true, gameOverMessage pozostaje "YOU DIED" (domyślne)
- // Wyświetl komunikat Game Over/Zwycięstwa
- ui.showMessage(gameOverMessage, 0); // Wyświetl na stałe do momentu restartu
- ui.titleText.setText(gameOverMessage); // Ustaw tekst tytułu
+ // 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 teraz zarządzany przez ui.positionElements("gameOver") i ui.updateBossHealth()
- // Zaplanuj restart gry po opóźnieniu (powrót do stanu startGame)
+ // 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 () {
- // Clean up player and boss (gdyby nie zostały zniszczone wcześniej - np. w player.die/boss.die)
- if (player) {
+ // Wyczyść scenę przed restartem
+ clearScene();
+ // Zniszcz gracza/bossa jeśli jakimś cudem przetrwali
+ if (player && player.destroy) {
player.destroy();
- player = null;
}
- if (boss) {
+ player = null;
+ if (boss && boss.destroy) {
boss.destroy();
- boss = null;
}
- // Wyczyść wszystkie aktywne ataki bossa i ich wizualizacje
- if (boss && boss.attacks) {
- // Sprawdź czy boss i jego ataki istniały przed zniszczeniem
- boss.attacks.forEach(function (attack) {
- if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy();
- }
- });
- boss.attacks = []; // Wyczyść tablicę ataków
- }
- // Usuń tło game over
+ 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 tytułowego przed potencjalnym restartem
- ui.positionElements("title"); // Pozycjonuj UI jak dla tytułu (ukryje tytuł gameover/zwycięstwa, komunikaty, paski itp.)
- ui.titleText.alpha = 0; // Ukryj tytuł ponownie
- ui.showMessage(""); // Wyczyść komunikat
- ui.showTutorial(""); // Wyczyść tutorial
- ui.updateDeathsCounter(); // Upewnij się, że licznik śmierci jest aktualny
- ui.updateBossHealth(0, 200); // Zresetuj i ukryj pasek zdrowia bossa
- // Flaga isNewBossPlusMode zachowa swoją wartość sprzed wywołania gameOver,
- // dzięki czemu restart w przypadku śmierci w Boss+ wróci do Boss+
- // Resetuj flagę przyczyny Game Over
- gameOverReasonIsDeath = false; // Reset po użyciu
- // Restartuj walkę z bossem (jeśli zginął w Boss+, startGame ustawi Boss+; jeśli zginął w standard, ustawi standard)
- gameState.startGame(); // Przejdź z powrotem do stanu gry
- }, 3000); // 3 sekundy opóźnienia przed restartem
+ 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 w zależności od stanu gry
+ // Obsługa gestów dotykowych/myszy
processTouchGesture: function processTouchGesture() {
- // --- Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) ---
- // Jeśli jesteśmy w stanie fakeTutorial i timer do prawdziwego tutorialu jeszcze działa,
- // każdy input (tapnięcie lub swipe) wywoła fałszywą śmierć.
+ // --- Obsługa inputu w stanie fakeTutorial ---
if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) {
- this.handleFakeTutorialInput(); // Wywołaj logikę fałszywej śmierci
+ this.handleFakeTutorialInput(); // Wywołaj fałszywą śmierć
return; // Zakończ przetwarzanie gestu
}
- // Oblicz kierunek i dystans gestu na podstawie zarejestrowanych pozycji startu i końca dotyku
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // --- Obsługa tapowania/gestów w stanach poza grą (z wyjątkiem Grill Menu) ---
- // Sprawdź, czy to było krótkie tapnięcie (dystans mniejszy niż próg turlania)
- if (distance < 50) {
- // Próg turlania to 50 jednostek
- // Jeśli jest to tapnięcie w stanie tytułowym, przejdź do intro
+ // 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 kliknięto w przycisk testowy - obsługa w game.down/up interaktywnych obiektów
- // Jeśli nie kliknięto w przycisk, tapnięcie przechodzi do intro
- if (this.touchStart.x === this.touchEnd.x && this.touchStart.y === this.touchEnd.y) {
- // Upewnij się, że to faktycznie tapnięcie (start i koniec w tym samym miejscu)
- // Dodatkowo można sprawdzić, czy pod klikniętym punktem NIE BYŁO interaktywnego obiektu (np. przycisku)
- // Ale prostsze jest poleganie na tym, że interaktywne obiekty mają własne handlery (game.down/up z obj)
- // Jeśli dotarliśmy tutaj, to prawdopodobnie tapnięto w tło
- this.showIntro(); // <--- Tap na ekranie tytułowym przechodzi do intro
- return; // Zakończ przetwarzanie gestu
- }
- }
- // Jeśli to tapnięcie w stanach game over lub victory (które teraz też prowadzą do Grill Screena lub restartu) - zignoruj tapnięcia
- // Restart/przejście do Grill Screena dzieje się automatycznie po Game Over/Victory timerem
- if (this.currentState === "gameOver") {
- // Zignoruj tapnięcie na ekranie Game Over, restart jest timerem
+ // 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;
}
- if (this.currentState === "victory") {
- // Ta metoda nie jest już wywoływana bezpośrednio, ale zabezpieczenie na wszelki wypadek
- 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;
}
- // Jeśli to tapnięcie w innych stanach (intro, real tutorial), zignoruj lub dodaj logikę jeśli potrzebna
- // W real tutorialu tapnięcie nie wywoła nic poza przyciskiem "Let's Roll"
- return; // Zakończ przetwarzanie gestu jeśli to tapnięcie i nie obsłużono stanu
+ // Wykonaj turlanie gracza
+ player.roll(direction);
}
- // --- Obsługa turlania w stanie gry ---
- // Tylko przetwarzaj gesty turlania (swipe) w stanie "game" i gdy gracz jest martwy
- if (this.currentState !== "game" || !player || player.dead) {
- return; // Zignoruj gesty swipe w innych stanach
- }
- // W tym miejscu wiemy, że currentState === "game", gracz żyje, i dystans gestu jest >= 50 (to swipe)
- // Minimalny dystans swipe dla turlania (już sprawdzony powyżej)
- // Normalizuj kierunek gestu
- var direction = {
- x: dx / distance,
- y: dy / distance
- };
- // Wykonaj turlanie gracza
- player.roll(direction);
+ // W innych stanach swipe jest ignorowany
}
};
-// --- Obsługa inputu (mapowanie zdarzeń LK na metody gameState) ---
+// --- Obsługa inputu ---
game.down = function (x, y, obj) {
- // Zarejestruj początek dotyku/kliknięcia tylko w stanach, które tego wymagają
- if (gameState.currentState === "title" || gameState.currentState === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu" || gameState.currentState === "gameOver") {
- // Dodano stan game over do śledzenia potencjalnych tapnięć na ekranie końcowym
+ // 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;
- // Zresetuj end point na początku gestu
- gameState.touchEnd.x = x;
+ 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) {
- // Zarejestruj koniec dotyku/kliknięcia tylko w stanach, które tego wymagają
- if (gameState.currentState === "title" || gameState.currentState === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu" || gameState.currentState === "gameOver") {
- // Dodano stan game over
+ // 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 zarejestrowany gest (tap lub swipe)
- gameState.processTouchGesture(); // Wywołaj metodę przetwarzającą gest
+ // 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.
+ 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ź aktualną pozycję dotyku/kursora, aby obliczyć dystans gestu na koniec
- if (gameState.currentState === "game" || gameState.currentState === "title" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu" || gameState.currentState === "gameOver") {
- // Śledź ruch w stanach, gdzie gesty są istotne. Dodano stan game over.
- gameState.touchEnd.x = x;
+ // Ś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 (np. licznik śmierci) niezależnie od stanu gry
+ // Aktualizuj UI niezależnie od stanu gry (ale tylko jeśli UI istnieje)
if (ui) {
ui.updateDeathsCounter();
- // Aktualizuj pasek zdrowia bossa w każdej klatce (jeśli boss istnieje)
+ // Aktualizuj pasek zdrowia bossa (jeśli boss istnieje i UI istnieje)
if (boss) {
- ui.updateBossHealth(boss.health, boss.maxHealth);
+ // 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 zdrowia bossa
- // Użyj poprawnego maxHP dla ukrycia/resetu w zależności od trybu
- // Sprawdź, czy nie jesteśmy w stanie game over po zwycięstwie Boss+, gdzie pasek ma pozostać widoczny
- if (!(gameState.currentState === "gameOver" && gameState.gameOverReasonIsDeath === false && isNewBossPlusMode)) {
- ui.updateBossHealth(0, isNewBossPlusMode ? 2000 : 200);
+ // 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);
}
}
- // Pozycjonowanie elementów UI jest obsługiwane przez positionElements wywoływane w metodach stanów
- // Komunikaty i tytuły są obsługiwane przez showMessage/showTutorial
- // Timer jest aktualizowany w interwale timera i wyświetlany przez updateTimerDisplay
+ // 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.
}
- // Wykonaj logikę gry (aktualizacja gracza, bossa, ataków) tylko w stanie "game"
+ // Logika specyficzna dla stanu gry
if (gameState.currentState === "game") {
if (player) {
- player.update(); // Aktualizacja gracza (ruch, turlanie, nietykalność, kolizja turlania z bossem)
+ player.update(); // Aktualizacja gracza (ruch turlania, nietykalność, kolizja turlania z bossem)
}
if (boss) {
- boss.update(); // Aktualizacja bossa (ruch, ataki, kolizje ataków z graczem)
+ boss.update(); // Aktualizacja bossa (ruch, cooldown, tworzenie/usuwanie ataków, kolizje ataków z graczem)
}
- // Aktualizuj wizualizację serc na UI (ma sens tylko w stanie gry)
- if (player && ui) {
- ui.updateHearts(player.health, storage.maxHearts);
- }
- // Kolizje ataków bossa z graczem i przejścia do stanów game over/victory
- // są obsługiwane wewnątrz metod update klas Boss i Player, wywołując odpowiednie metody gameState.
+ // Sprawdzanie kolizji i zmiany stanów (gameOver, grillScreen) są teraz wewnątrz metod update/die gracza i bossa.
}
- // Logika w innych stanach (title, intro, tutoriale, game over, grillMenu) jest obsługiwana przez ich własne metody/przyciski.
+ // 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 ---
-// Zamiast wywoływania konkretnej sceny, inicjalizujemy obiekt gameState
-// i wywołujemy jego metodę init(), która ustawia początkowy stan i rozpoczyna od ekranu tytułowego.
+// Inicjalizuj obiekt gameState, który zajmie się resztą.
gameState.init();
\ No newline at end of file