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
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow' Line Number: 1199
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: 1196
Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow' Line Number: 1293
Code edit (1 edits merged)
Please save this source code
User prompt
add new asset grillMenu
Code edit (1 edits merged)
Please save this source code
User prompt
add asset bossHpbar
Code edit (5 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // Wywołaj logikę fałszywej śmierci' Line Number: 1228
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'set')' in or related to this line: 'self.bossHealthBarBg.anchor.set(0.5, 0.5); // Ustaw punkt odniesienia na środek' Line Number: 705
User prompt
Please fix the bug: 'Shape is not defined' in or related to this line: 'self.bossHealthBarBg = new Shape({' Line Number: 677
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: playerRadius is not defined' in or related to this line: 'if (distance < attack.radius + playerRadius) {' Line Number: 347
Code edit (9 edits merged)
Please save this source code
User prompt
add asset introBg
User prompt
add asset titleBg
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: 'Button2 is not defined' in or related to this line: 'var startButton = new Button2("Start Game", function () {' Line Number: 574
User prompt
Please fix the bug: 'clearScene is not defined' in or related to this line: 'clearScene();' Line Number: 555
/**** * 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; self.maxHealth = 100; self.speed = 5; self.attackCooldown = 0; 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 () { // Nie atakuj jeśli 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; } 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 następny atak z losowym opóźnieniem, skalowane przez mnożnik prędkości 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 = 8; // Liczba pocisków/ataków w kręgu var radius = 300; 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, 3000); // 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 var targetX = player.x; var targetY = player.y; var count = 5; 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 * 200 * 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, 2000); // Utwórz atak po opóźnieniu (czas życia skalowane w createAttack) } }; }(posX, posY), delay); } }; self.chargeAttack = function () { LK.getSound('bossAttack').play(); // 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; // Animacja szarży (boss przesuwa się szybko w kierunku gracza) tween(self, { x: self.x + dx * 500, // Przesunięcie o 500 jednostek w kierunku gracza y: self.y + dy * 500 }, { duration: 800 * 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 } }, 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 < 5; i++) { var t = i / 4; var posX = startX + (self.x - startX) * t; var posY = startY + (self.y - startY) * t; self.createAttack(posX, posY, 1500); // 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 tween(warning, { alpha: 0.6 // Zwiększ przezroczystość }, { duration: 1000 * 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: duration * self.attackSpeedMultiplier * (60 / 1000) // Czas życia ataku w klatkach, skalowany. Przeliczamy ms na klatki (zakładając 60 FPS) }; self.attacks.push(attack); // !!! USUNIĘTO LK.setTimeout, USUWANIE BĘDZIE TYLKO W 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; // Wizualne sygnalizowanie otrzymania obrażeń LK.effects.flashObject(self, 0xFFFFFF, 200); // Przejście fazy bossa przy 50% zdrowia if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; self.speed += 2; // Boss staje się szybszy (ruch) // 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(); } }; self.die = function () { if (self.dead || gameState.currentState !== "game") { // Boss umiera tylko w stanie gry return; } self.dead = true; LK.getSound('victory').play(); // Dźwięk zwycięstwa // 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 } storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów gameState.victory(); // Przejdź do stanu zwycięstwa (wywoła metodę w gameState) } }); // Zatrzymaj wszystkie timery związane z atakami bossa // Ataki zostaną wyczyszczone w gameState.victory/gameOver }; 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) { 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 dobiegła końca (obiekt niewidoczny) if (self.dead && self.alpha === 0) { // Jeśli boss jest całkowicie niewidoczny po śmierci, wyczyść pozostałe ataki if (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; } // Logika ruchu tylko gdy boss żyje i gra jest w stanie "game" if (!self.dead && gameState.currentState === "game") { // Aktualizacja cooldownu ataku if (self.attackCooldown > 0) { self.attackCooldown--; // !!! Zmieniono sprawdzanie cooldownu z === 0 na <= 0 !!! if (self.attackCooldown <= 0) { // Use <= 0 for robustness with floating point self.attackCooldown = 0; // Reset cooldown to exactly 0 after triggering attack self.startAttackPattern(); // This is where the next attack pattern starts } } // Poruszaj się w kierunku gracza jeśli boss nie jest oszołomiony, nie jest w stanie repozycji i gra jest w stanie "game" if (!self.stunned && !self.repositioning) { // Dodano && !self.repositioning var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Poprawiono literówkę: dy + dy -> dy * dy. Już była poprawiona w poprzedniej wersji. // Utrzymuj pewien dystans od gracza if (distance > 200) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } else { // Jeśli oszołomiony lub w repozycji, tylko aktualizuj czas oszołomienia (jeśli boss oszołomiony) // Aktualizacja czasu oszołomienia (stun) - tylko jeśli boss jest oszołomiony if (self.stunned) { self.stunDuration--; if (self.stunDuration <= 0) { self.stunned = false; } } // Stan repozycji nie ma oddzielnego timera w update, zależy od setTimeout w chargeAttack } } // Przetwarzaj aktywne ataki bossa (sprawdzaj kolizje z graczem i usuwaj po czasie życia) // Przetwarzaj ataki niezależnie od tego czy boss żyje, żeby pozostałe ataki po śmierci też znikały for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; // Upewnij się, że obiekt ataku i wizualizacja istnieją if (!attack || !attack.visual) { // Usuń z listy jeśli obiekt jest nieprawidłowy self.attacks.splice(i, 1); console.warn("Invalid attack object found in Boss.attacks:", attack); continue; } attack.lifeTime--; // Zmniejsz czas życia // Sprawdzenie kolizji z graczem i otrzymywanie obrażeń - tylko gdy gracz żyje, nie jest nietykalny i gra jest w stanie "game" if (player && !player.dead && !player.invulnerable && gameState.currentState === "game") { // Sprawdzenie kolizji z graczem (system kolizji z stworzylo zloto.txt - oparty na odległości między środkami) var dx = player.x - attack.x; var dy = player.y - attack.y; var distance = Math.sqrt(dx * dx + dy * dy); // Poprawiono literówkę: dy + dy -> dy * dy. Już była poprawiona w poprzedniej wersji. if (distance < attack.radius + playerRadius) { // Upewnij się, że playerRadius jest zdefiniowany // Kolizja wykryta // player.takeDamage(1) zwraca true jeśli gracz umrze po otrzymaniu obrażeń if (player.takeDamage(1)) { // Gracz umarł, obsługa game over nastąpi poprzez wywołanie gameState.gameOver() przez player.die() } // Wizualne sygnalizowanie trafienia atakiem (migotanie) // Sprawdź, czy wizualizacja nadal istnieje if (attack.visual && !attack.visual.destroyed) { LK.effects.flashObject(attack.visual, 0xFFFFFF, 200); } // Atak zazwyczaj trafia tylko raz. Możemy go usunąć po trafieniu. // Usuniemy go jednak po czasie życia, jak było w oryginalnym kodzie stworzylo_zloto. } } // Koniec sprawdzania kolizji tylko w stanie gry // Usuń ataki, których czas życia minął if (attack.lifeTime <= 0) { // Usuń z tablicy active attacks bossa self.attacks.splice(i, 1); // Zainicjuj animację zanikania i zniszczenie obiektu wizualnego // Check if the visual object exists and hasn't been destroyed if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { // Rozpocznij animację zanikania tween(attack.visual, { alpha: 0 }, { duration: 300 // Czas trwania zanikania w ms // No onFinish here, destruction is scheduled by timeout }); // Schedule destruction of the visual object after the fade out duration LK.setTimeout(function () { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }, 300); // Match the fade out duration } else { // If visual object is somehow missing or already destroyed, log a warning console.warn("Expired attack visual missing or already destroyed:", attack); } } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Pobieramy szerokość i wysokość z grafiki gracza self.width = playerGraphics.width; self.height = playerGraphics.height; self.speed = 12; self.rolling = false; // Czy gracz się turla self.rollCooldown = 0; // Czas do następnego turlania (w klatkach) self.rollDuration = 0; // Pozostały czas turlania (w klatkach) self.rollDirection = { x: 0, y: 0 }; // Kierunek turlania self.rollDistance = 300; // Dystans turlania self.rollSpeed = 20; // Prędkość turlania self.invulnerable = false; // Czy gracz jest nietykalny self.invulnerabilityFrames = 0; // Czas nietykalności po trafieniu lub turlaniu (w klatkach) // Zdrowie gracza jest ustawiane w gameState.startGame() self.health = storage.maxHearts || 5; // Domyślnie 5 serc jeśli storage.maxHearts nie jest ustawione self.dead = false; // Czy gracz jest martwy self.rollEffectTimeoutId = null; // ID timeoutu efektu turlania self.invulnerabilityTimeoutId = null; // ID timeoutu nietykalności // Funkcja turlania (wywoływana przez input) self.roll = function (direction) { // Nie można się turlać jeśli już się turlasz, jest cooldown, gracz jest martwy lub nie jesteśmy w stanie gry if (self.rolling || self.rollCooldown > 0 || self.dead || gameState.currentState !== "game") { return; } self.rolling = true; self.rollDuration = self.rollDistance / self.rollSpeed; // Czas trwania turlania obliczony z dystansu i prędkości self.rollDirection = direction; self.invulnerable = true; // Gracz jest nietykalny podczas turlania // Wizualny efekt turlania (niebieskie kółko) var rollEffect = game.addChild(LK.getAsset('roll', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.7 })); LK.getSound('roll').play(); // Dźwięk turlania // Zaplanuj koniec turlania i cooldown // Użyjemy self.rollEffectTimeoutId, żeby móc go wyczyścić przy restarcie lub śmierci self.rollEffectTimeoutId = LK.setTimeout(function () { // Sprawdź, czy gracz nadal istnieje i nie jest martwy zanim zakończysz turlanie if (self && !self.dead) { // Sprawdź, czy gracz nadal jest w stanie turlania ustawionym przez ten timeout if (self.rolling) { // Upewnij się, że flaga rolling nie została zmieniona przez nowe turlanie self.rolling = false; // Zakończ stan turlania self.rollCooldown = 30; // Ustaw cooldown // Mały bufor nietykalności po zakończeniu turlania // Użyjemy self.invulnerabilityTimeoutId self.invulnerabilityTimeoutId = LK.setTimeout(function () { // Sprawdź, czy gracz nadal żyje i nie zaczął nowego turlania if (!self.rolling && !self.dead) { self.invulnerable = false; // Zakończ nietykalność } }, 100); // 100ms dodatkowej nietykalności } else { // Jeśli flaga rolling została już zmieniona (np. rozpoczęto nowe turlanie), po prostu ustaw cooldown if (!self.dead) { self.rollCooldown = 30; } } } // Animacja zanikania efektu turlania i zniszczenie obiektu wizualnego // Sprawdź, czy efekt turlania nadal istnieje i nie został zniszczony if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) { tween(rollEffect, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) { rollEffect.destroy(); } } }); } self.rollEffectTimeoutId = null; // Wyczyść ID timeoutu po zakończeniu }, self.rollDuration * (1000 / 60)); // Czas trwania turlania w milisekundach }; // Metoda do czyszczenia timeoutów turlania/nietykalności (używane przy starcie gry/śmierci) self.clearRollTimeouts = function () { if (self.rollEffectTimeoutId) { LK.clearTimeout(self.rollEffectTimeoutId); self.rollEffectTimeoutId = null; } if (self.invulnerabilityTimeoutId) { LK.clearTimeout(self.invulnerabilityTimeoutId); self.invulnerabilityTimeoutId = null; } }; // Funkcja otrzymywania obrażeń self.takeDamage = function (amount) { // Nie można otrzymać obrażeń jeśli gracz jest nietykalny, martwy lub nie jesteśmy w stanie gry if (self.invulnerable || self.dead || gameState.currentState !== "game") { return false; // Nie otrzymano obrażeń } self.health -= amount; // Zmniejsz zdrowie LK.getSound('hit').play(); // Dźwięk trafienia // Sprawdzenie, czy gracz umarł if (self.health <= 0) { self.die(); // Wywołaj funkcję śmierci return true; // Gracz umarł } // Okres nietykalności po otrzymaniu obrażeń self.invulnerable = true; self.invulnerabilityFrames = 45; // 45 frames (ok. 0.75 sekundy) // Efekt migotania gracza LK.effects.flashObject(self, 0xFF0000, 500); return true; // Gracz otrzymał obrażenia (ale nie umarł) }; // Funkcja śmierci gracza self.die = function () { if (self.dead || gameState.currentState !== "game") { // Gracz umiera tylko w stanie gry return; } self.dead = true; LK.getSound('death').play(); // Dźwięk śmierci self.clearRollTimeouts(); // Wyczyść timery turlania/nietykalności przy śmierci // Wizualny efekt śmierci gracza (zanikanie i obrót) tween(self, { alpha: 0, rotation: Math.PI * 4 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { // Obiekt gracza zostanie zniszczony w gameState.gameOver clean up } }); // Aktualizacja licznika śmierci i sprawdzenie ulepszeń storage.totalDeaths = (storage.totalDeaths || 0) + 1; // Zwiększ licznik śmierci // Sprawdzenie, czy gracz powinien dostać dodatkowe serce (co 5 śmierci, max 10) storage.maxHearts = Math.min(10, 5 + Math.floor(storage.totalDeaths / 5)); // Ustaw max serc na podstawie śmierci, z limitem 10 // Zaktualizuj UI śmierci i serc if (ui) { ui.updateDeathsCounter(); // Zaktualizuj licznik śmierci ui.updateHearts(self.health, storage.maxHearts); // Zaktualizuj wyświetlanie serc (np. szarych przy śmierci) } // Przejdź do stanu game over po opóźnieniu (aby animacja śmierci mogła się zakończyć) LK.setTimeout(function () { gameState.gameOver(); // Wywołaj metodę game over w gameState }, 2000); // 2 sekundy opóźnienia }; self.update = function () { // Aktualizuj logikę gracza tylko w stanie gry if (gameState.currentState !== "game") { return; } // Nie aktualizuj jeśli gracz jest martwy (poza animacją śmierci) 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) if (self.dead) { return; // Nie wykonuj logiki ruchu i nietykalności jeśli gracz jest martwy } // 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 } } // 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; } // 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)); }; 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); // 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(); } } 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 }; // 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); } // Zaplanuj zanikanie po czasie, jeśli duration > 0 if (duration) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 // Zaniknij do przezroczystości 0 }, { duration: 500 // Czas trwania zanikania }); }, duration); } }; // 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 () { self.deathsText.setText("Deaths: " + storage.totalDeaths); }; // Aktualizuje wyświetlanie czasu timera self.updateTimerDisplay = function (seconds) { 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) { switch (state) { case "title": // Pozycje dla ekranu tytułowego self.titleText.x = 2048 / 2; self.titleText.y = 800; self.messageText.x = 2048 / 2; self.messageText.y = 1000; // "Tap to Start" 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; break; case "game": // Pozycje dla stanu gry (walka z bossem) self.titleText.alpha = 0; // Ukryj tytuł self.messageText.x = 2048 / 2; self.messageText.y = 1500; // Komunikaty w trakcie gry (np. "YOU DIED" - choć to też w victory/gameOver state) self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; // Tutorial w trakcie gry ("Swipe to roll away!") // Pokaż serca i timer podczas gry self.heartContainer.alpha = 1; self.timerText.alpha = 1; break; case "victory": // Pozycje dla ekranu zwycięstwa self.titleText.x = 2048 / 2; self.titleText.y = 800; // Tytuł "VICTORY ACHIEVED" self.messageText.x = 2048 / 2; self.messageText.y = 1000; // Komunikat o pokonanych bossach self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // "Tap to Continue" // Ukryj serca i timer na ekranie zwycięstwa self.heartContainer.alpha = 0; self.timerText.alpha = 0; break; case "gameOver": // Pozycje dla ekranu Game Over (można użyć tych samych co Victory lub zdefiniować osobno) self.titleText.x = 2048 / 2; self.titleText.y = 800; // Tytuł "YOU DIED" self.messageText.x = 2048 / 2; self.messageText.y = 1000; // Pusty lub inny komunikat self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; // Pusty lub "Tap to Restart" // Ukryj serca i timer na ekranie Game Over self.heartContainer.alpha = 0; self.timerText.alpha = 0; break; case "intro": // Pozycje dla ekranów intro/tutoriali (głównie ukryte elementy UI walki) 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; break; } // Upewnij się, że licznik śmierci jest zawsze widoczny self.deathsText.alpha = 1; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło domyślne }); /**** * 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 // Dodano nowe assety tła // Dodano z souls4.txt dla przycisków intro // Zachowujemy jako domyślne tło lub na wypadek potrzeby // Globalny kontener na elementy scen intro/tutoriali (do łatwego czyszczenia) var currentSceneElements = new Container(); // Dodano z souls4.txt // Zmienne gry (z stworzylo zloto.txt) var player; var boss; var ui; var walls = []; // Ściany areny bossa // Dodano zmienną do przechowywania aktywnego tła sceny var currentBackground = null; // Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro/tutoriali) // Nie usuwa tła sceny (currentBackground) ani elementów UI. function clearScene() { // Dodano z souls4.txt // 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 { currentSceneElements.removeChild(child); // Fallback } } // Nie resetujemy tutaj game.update ani input handlerów, bo są zarządzane przez gameState } // Obiekt zarządzający stanami gry (z stworzylo zloto.txt, rozbudowany o stany intro i timer) 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) 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) touchStart: { x: 0, y: 0 }, touchEnd: { x: 0, y: 0 }, // Inicjalizacja gry (wywoływana raz na początku aplikacji) 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) storage.totalDeaths = 0; // Ustaw początkową liczbę serc na 5 przy każdym uruchomieniu gry storage.maxHearts = 5; // 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) 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 do wyświetlania (nad UI) game.addChild(currentSceneElements); // currentSceneElements powinien być dodany po UI, żeby być nad nim // 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(); }, // 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 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, anchorY: 0, x: 2048 - 100, y: 0 })); 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, x: 0, y: 2732 - 100 })); 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. }, // ---------- Metody przejścia między stanami ---------- // Przejście do ekranu tytułowego showTitleScreen: function showTitleScreen() { clearScene(); // Wyczyść elementy poprzedniej sceny (np. z Victory lub Fake Death) // 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 if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); // Usuń poprzednie tło currentBackground = null; } this.currentState = "title"; // Ustaw stan na tytuł 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) 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; } if (boss) { boss.alpha = 0; } // Pokaż ściany jako tło menu/intro (jeśli nie mają być widoczne, ustaw alpha na 0) 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 this.touchStart = { x: 0, y: 0 }; this.touchEnd = { x: 0, y: 0 }; }, // 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 if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); // Usuń poprzednie tło 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) 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 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 }); // Ukryj obiekty gry i ściany na czas intro/tutoriali if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } 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) 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); // Dodaj do kontenera, który będzie czyszczony // Timer 1: Zmień tekst po 5 sekundach LK.setTimeout(function () { // Usuń pierwszy tekst if (introText1.parent) { introText1.destroy(); } // Tekst drugiej sceny intro (z souls4.txt) 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); // Dodaj do kontenera // Timer 2: Przejdź do fałszywego tutorialu po kolejnych 2 sekundach 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 }, // 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) 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" 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 - ZAPISZ JEGO ID 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 }, // Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) handleFakeTutorialInput: function handleFakeTutorialInput() { // Tylko obsłuż input, jeśli jesteśmy w stanie fakeTutorial i timer jeszcze działa if (this.currentState !== "fakeTutorial" || !this.fakeTutorialTimerId) { return; } // Wyczyść timer, który miał prowadzić do prawdziwego tutorialu 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) 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 // 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 "Did you check the title of the game?" 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 }); 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 again', { size: 60, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); howToPlayButtonContainer.addChild(buttonText); // Akcja przycisku: Przejdź z powrotem do prawdziwego tutorialu howToPlayButtonContainer.down = function () { gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu }; }, // Przejście do sceny prawdziwego tutorialu showRealTutorial: function showRealTutorial() { clearScene(); // Wyczyść poprzednie elementy (fałszywa śmierć lub fałszywy tutorial) // Tło introBg i jego zoom powinny pozostać // Upewnij się, że timer fake tutorialu jest wyczyszczony if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } this.currentState = "realTutorial"; // Ustaw stan na prawdziwy tutorial game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe - Może chcesz, żeby tło introBg było widoczne? // Ukryj obiekty gry i ściany if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); var yOffset = 2732 / 3; // Początkowa pozycja Y dla tekstu // Tekst tutorialu (zmodyfikowany do mechaniki stworzylo zloto.txt) var instructionText1 = new Text2('Swipe in a direction to ROLL.', { size: 80, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText1.anchor.set(0.5, 0.5); instructionText1.x = 2048 / 2; instructionText1.y = yOffset; currentSceneElements.addChild(instructionText1); var instructionText2 = new Text2('There is a short cooldown after each roll.', { size: 80, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText2.anchor.set(0.5, 0.5); instructionText2.x = 2048 / 2; instructionText2.y = yOffset + 150; currentSceneElements.addChild(instructionText2); var instructionText3 = new Text2('That\'s it. That\'s the whole game.', { size: 80, fill: 0xFFFFFF, align: 'center', wordWrap: true, wordWrapWidth: 1800 }); instructionText3.anchor.set(0.5, 0.5); instructionText3.x = 2048 / 2; instructionText3.y = yOffset + 300; currentSceneElements.addChild(instructionText3); // Przycisk "Let's Roll" (używamy button_bg) var rollButtonContainer = new Container(); rollButtonContainer.interactive = true; // Przycisk jest interaktywny rollButtonContainer.x = 2048 / 2; rollButtonContainer.y = yOffset + 600; currentSceneElements.addChild(rollButtonContainer); var buttonBg = LK.getAsset('button_bg', { anchorX: 0.5, anchorY: 0.5 }); rollButtonContainer.addChild(buttonBg); var rollButtonText = new Text2('Let\'s Roll', { size: 60, fill: 0xFFFFFF }); // Zmniejszono rozmiar czcionki, żeby pasowała do przycisku rollButtonText.anchor.set(0.5, 0.5); rollButtonContainer.addChild(rollButtonText); // Akcja przycisku: Rozpocznij walkę z bossem (przejdź do stanu gry) rollButtonContainer.down = function () { // Wyczyść elementy intro przed rozpoczęciem gry clearScene(); // Zatrzymaj tween zoomu tła intro i usuń tło if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); currentBackground = null; } gameState.startGame(); // Wywołaj metodę rozpoczynającą grę (walkę z bossem) }; }, // Przejście do stanu gry (walka z bossem) startGame: function startGame() { // 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 if (currentBackground) { tween.stop(currentBackground); currentBackground.destroy(); // Usuń tło intro/tytułu 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) { player.destroy(); } // Zniszcz poprzedniego gracza jeśli był 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.rolling = false; player.invulnerable = false; player.invulnerabilityFrames = 0; player.rollCooldown = 0; player.clearRollTimeouts(); // Wyczyść timery turlania/nietykalności player.x = 2048 / 2; player.y = 2732 / 2 + 400; // Pozycja startowa gracza // Stwórz bossa (jeśli nie istnieje lub zniszcz poprzedniego) if (boss) { boss.destroy(); } // Zniszcz poprzedniego bossa jeśli był 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 // Aktualizuj UI (wyświetl serca i początkowy komunikat) ui.updateHearts(player.health, storage.maxHearts); // Wyświetl aktualne serca (powinno być 5) ui.showTutorial("Swipe to roll away from attacks!"); // Wyświetl tutorial dla stanu gry // --- Timer walki z bossem --- this.remainingTime = this.gameDuration; // Ustaw początkowy czas (120 sekund) ui.updateTimerDisplay(this.remainingTime); // Wyświetl początkowy 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 na początku nowej gry this.bossSpeedIncreased = false; this.gameTimerInterval = LK.setInterval(function () { // Tylko aktualizuj timer i sprawdzaj warunek zwycięstwa jeśli jesteśmy w stanie gry if (gameState.currentState === "game") { gameState.remainingTime--; // Zmniejsz pozostały czas ui.updateTimerDisplay(gameState.remainingTime); // Zaktualizuj wyświetlanie timera na UI // --- Przyspieszenie bossa po 1 minucie --- // Jeśli pozostały czas <= 60 sekund i boss jeszcze nie został przyspieszony if (gameState.remainingTime <= 60 && !gameState.bossSpeedIncreased) { gameState.bossSpeedIncreased = true; // Ustaw flagę if (boss && !boss.dead) { // Upewnij się, że boss istnieje i żyje // Zmniejsz mnożnik prędkości ataków bossa (np. o 30% -> mnożnik 0.7) boss.attackSpeedMultiplier = 0.7; // Opcjonalnie: wyświetl komunikat o przyspieszeniu ui.showMessage("Boss attacks faster!", 2000); // Komunikat na 2 sekundy } } // --- Koniec Przyspieszenia bossa --- // Sprawdź warunek zwycięstwa (czas minął) if (gameState.remainingTime <= 0) { LK.clearInterval(gameState.gameTimerInterval); // Stop the timer // Obsłuż zwycięstwo przez przetrwanie // Boss cleanup jest obsługiwany w gameState.victory() gameState.victory(); // Przejdź do stanu zwycięstwa } } }, 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 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 } }, 3000); // 3 sekundy opóźnienia przed pierwszym atakiem bossa }, // Przejście do stanu Game Over (wywoływane przez player.die()) gameOver: function gameOver() { this.currentState = "gameOver"; // Ustaw stan na game over // 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; // Zatrzymaj wszelkie trwające tweeny tła 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ż niszczone, ale to dodatkowe zabezpieczenie) // Upewnij się, że istnieją przed ustawieniem alpha if (player) { player.alpha = 0; } if (boss) { boss.alpha = 0; } // Ukryj ściany walls.forEach(function (wall) { if (wall) { wall.alpha = 0; } }); // Ustaw pozycje UI dla ekranu game over ui.positionElements("gameOver"); // Wyświetl komunikat YOU DIED ui.showMessage("YOU DIED", 3000); // Komunikat zniknie po 3 sekundach ui.titleText.setText("YOU DIED"); // Ustaw tekst tytułu na "YOU DIED" ui.titleText.alpha = 1; // Pokaż tytuł "YOU DIED" ui.showTutorial(""); // Ukryj tutorial // Zaplanuj restart gry po opóźnieniu (powrót do stanu startGame) LK.setTimeout(function () { // Clean up player and boss (gdyby nie zostały zniszczone wcześniej - np. w player.die/boss.die) if (player) { player.destroy(); player = null; } if (boss) { 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 if (currentBackground) { currentBackground.destroy(); currentBackground = null; } // Resetuj UI do stanu tytułowego przed potencjalnym restartem ui.positionElements("title"); // Pozycjonuj UI jak dla tytułu ui.titleText.alpha = 0; // Ukryj tytuł ponownie ui.showMessage(""); // Wyczyść komunikat ui.showTutorial(""); // Wyczyść tutorial ui.updateDeathsCounter(); // Upewnij się, że licznik śmierci jest aktualny // Restartuj walkę z bossem bezpośrednio po Game Over gameState.startGame(); // Przejdź z powrotem do stanu gry (rozpocznij nową próbę) }, 3000); // 3 sekundy opóźnienia przed restartem }, // Przejście do stanu zwycięstwa (wywoływane przez boss.die() lub timer) victory: function victory() { this.currentState = "victory"; // Ustaw stan na zwycięstwo // Zatrzymaj timer gry, jeśli nadal działa (redundantne jeśli wygrana czasem, ale bezpieczniej) LK.clearInterval(this.gameTimerInterval); // Zresetuj flagę przyspieszenia bossa this.bossSpeedIncreased = false; // Zatrzymaj wszelkie trwające tweeny tła if (currentBackground) { tween.stop(currentBackground); // Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem zwycięstwa // currentBackground.destroy(); currentBackground = null; } game.setBackgroundColor(0x1a1a1a); // Kolor tła bazowego za ekranem zwycięstwa // Clean up player (już niszczony w player.die) if (player) { player.destroy(); player = null; } // --- Boss Cleanup (jeśli wygrana była przez timer, a nie przez boss.die) --- // Jeśli obiekt bossa nadal istnieje i nie jest oznaczony jako martwy (czyli nie umarł od obrażeń), zniszcz go i jego ataki if (boss && !boss.dead) { // Wyczyść aktywne ataki bossa i ich wizualizacje if (boss.attacks) { boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; // Wyczyść tablicę ataków bossa } // Zniszcz obiekt bossa if (boss.destroy && !boss.destroyed) { boss.destroy(); } boss = null; // Wyczyść globalną referencję } // --- Koniec Boss Cleanup --- ui.positionElements("victory"); // Pozycjonuj UI dla stanu zwycięstwa ui.titleText.setText("VICTORY ACHIEVED"); // Ustaw tekst tytułu ui.titleText.alpha = 1; // Pokaż tytuł ui.showMessage("Bosses Defeated: " + storage.bossesDefeated, 0); // Pokaż komunikat o pokonanych bossach (zostaje na ekranie) ui.showTutorial("Tap to Continue"); // Pokaż komunikat "Tap to Continue" // Zaplanuj przejście do ekranu tytułowego po opóźnieniu LK.setTimeout(function () { ui.showMessage(""); // Wyczyść komunikat o pokonanych bossach ui.showTutorial(""); // Wyczyść tutorial // Usuń tło zwycięstwa if (currentBackground) { currentBackground.destroy(); currentBackground = null; } gameState.showTitleScreen(); // Przejdź z powrotem do ekranu tytułowego }, 5000); // 5 sekund opóźnienia }, // Obsługa gestów dotykowych/myszy w zależności od stanu gry 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ć. if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) { this.handleFakeTutorialInput(); // Wywołaj logikę fałszywej śmierci 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ą --- // 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 if (this.currentState === "title") { this.showIntro(); // <--- Tap na ekranie tytułowym przechodzi do intro return; // Zakończ przetwarzanie gestu } // Jeśli jest to tapnięcie w stanie zwycięstwa, przejdź do ekranu tytułowego if (this.currentState === "victory") { this.showTitleScreen(); // <--- Tap na ekranie zwycięstwa wraca do tytułu return; // Zakończ przetwarzanie gestu } // Jeśli to tapnięcie w innych stanach (intro, real tutorial, game over), zignoruj lub dodaj logikę jeśli potrzebna // W real tutorialu tapnięcie nie wywoła nic poza przyciskiem "Let's Roll" // W game over restart jest timerem return; // Zakończ przetwarzanie gestu jeśli to tapnięcie i nie obsłużono stanu } // --- Obsługa turlania w stanie gry --- // Tylko przetwarzaj gesty turlania (swipe) w stanie "game" i gdy gracz nie 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); } }; // --- Obsługa inputu (mapowanie zdarzeń LK na metody gameState) --- 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 === "victory" || gameState.currentState === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial") { // Dodano stany tutoriali gameState.touchStart.x = x; gameState.touchStart.y = y; // Zresetuj end point na początku gestu gameState.touchEnd.x = x; gameState.touchEnd.y = y; } }; game.up = function (x, y, obj) { // Zarejestruj koniec dotyku/kliknięcia tylko w stanach, które tego wymagają if (gameState.currentState === "title" || gameState.currentState === "victory" || gameState.currentState === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial") { // Dodano stany tutoriali gameState.touchEnd.x = x; gameState.touchEnd.y = y; // Przetwórz zarejestrowany gest (tap lub swipe) gameState.processTouchGesture(); // Wywołaj metodę przetwarzającą gest } }; 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 === "victory" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial") { // Śledź ruch w stanach, gdzie gesty są istotne gameState.touchEnd.x = x; gameState.touchEnd.y = y; } }; // --- Główna pętla aktualizacji gry --- game.update = function () { // Aktualizuj UI (np. licznik śmierci) niezależnie od stanu gry if (ui) { ui.updateDeathsCounter(); // 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 } // Wykonaj logikę gry (aktualizacja gracza, bossa, ataków) tylko w stanie "game" if (gameState.currentState === "game") { if (player) { player.update(); // Aktualizacja gracza (ruch, turlanie, nietykalność) } if (boss) { boss.update(); // Aktualizacja bossa (ruch, ataki, 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. } // W innych stanach (title, intro, tutoriale, game over, victory) logika gry (ruch obiektów, kolizje) nie jest wykonywana. }; // --- 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. gameState.init();
===================================================================
--- original.js
+++ change.js
@@ -243,8 +243,9 @@
// Zatrzymaj wszystkie timery związane z atakami bossa
// Ataki zostaną wyczyszczone w gameState.victory/gameOver
};
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) {
@@ -322,10 +323,8 @@
// Sprawdzenie kolizji z graczem (system kolizji z stworzylo zloto.txt - oparty na odległości między środkami)
var dx = player.x - attack.x;
var dy = player.y - attack.y;
var distance = Math.sqrt(dx * dx + dy * dy); // Poprawiono literówkę: dy + dy -> dy * dy. Już była poprawiona w poprzedniej wersji.
- // Definiujemy playerRadius na podstawie rozmiaru gracza (połowa szerokości lub wysokości)
- var playerRadius = player.width / 2;
if (distance < attack.radius + playerRadius) {
// Upewnij się, że playerRadius jest zdefiniowany
// Kolizja wykryta
// player.takeDamage(1) zwraca true jeśli gracz umrze po otrzymaniu obrażeń
@@ -810,9 +809,9 @@
// ID interwału timera
// Zmienne dla fałszywego tutorialu
fakeTutorialTimerId: null,
// ID timera przechodzącego do prawdziwego tutorialu
- // Zmienne dla przyspieszania bossa
+ // 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)
touchStart: {
@@ -1221,11 +1220,11 @@
anchorY: 0.5
});
rollButtonContainer.addChild(buttonBg);
var rollButtonText = new Text2('Let\'s Roll', {
- size: 80,
+ size: 60,
fill: 0xFFFFFF
- });
+ }); // Zmniejszono rozmiar czcionki, żeby pasowała do przycisku
rollButtonText.anchor.set(0.5, 0.5);
rollButtonContainer.addChild(rollButtonText);
// Akcja przycisku: Rozpocznij walkę z bossem (przejdź do stanu gry)
rollButtonContainer.down = function () {
@@ -1274,9 +1273,9 @@
if (player) {
player.destroy();
} // Zniszcz poprzedniego gracza jeśli był
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 wzrosnąć po śmierciach)
+ // 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.rolling = false;
player.invulnerable = false;