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
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: 563
User prompt
Please fix the bug: 'clearScene is not defined' in or related to this line: 'clearScene();' Line Number: 555
Code edit (1 edits merged)
Please save this source code
User prompt
Roll Souls
Initial prompt
reate a complete 2D meme parody game called Roll Souls using only placeholder graphics (squares, text, colors). The game should have a full flow from main menu to final boss and ending, all based around rolling to survive. Do not include custom art. Only use simple placeholder visuals like white square = player, red square = boss, gray = background, orange = grill, etc. The game flow should work like this: Main Menu Scene Name: mainMenuScene Show dark background Text: Welcome, Unchosen One. Button: Start Game → goes to introScene Intro Scene Name: introScene Show text: From the creators of FromSoftware... After 5 seconds, replace with: ...I have no idea. I don’t know them. After 2 more seconds → go to howToScene Fake Tutorial Scene Name: howToScene Show black screen Text: Press E to block. Or don’t press. If player presses E → Show YOU DIED Text: Did you check the title of the game? Button: How to Play AGAIN → goes to realTutorialScene If player does nothing for 6 seconds → auto go to realTutorialScene Real Tutorial Scene Name: realTutorialScene Show 3 tutorial messages: Click Left Mouse Button to ROLL toward the cursor. You can roll up to 3 times before stamina runs out. That’s it. That’s the whole game. Button: Let’s Roll → goes to boss1Scene Boss 1 Scene Name: boss1Scene Player: white square Player always moves slowly toward the mouse cursor Left-click = roll in direction of cursor Max 3 rolls, then cooldown Boss: red square, stands still, shoots blue bullets at player Player dies in 1 hit unless they have extra hearts Boss HP goes down slowly over 2 minutes If player survives 2 minutes → show GRILL LIT, go to grillScene HP/Heart System: Player starts with 1 heart = dies in 1 hit Every 5 total deaths, the player gains +1 heart (permanent) After 5 deaths = 2 hearts After 10 deaths = 3 hearts After 15 deaths = 4 hearts Taking damage removes 1 heart. If hearts = 0, show YOU DIED Grill Scene (Hub) Name: grillScene Background with orange square = grill Text: GRILL LIT Show 3 buttons: Upgrade Roll → shows random silly text like “You feel... no different.” Rest at Grill → shows “Resting...” Final Boss → goes to boss2Scene Final Boss Scene Name: boss2Scene Harder boss: faster bullets, less warning Same roll mechanics, same 2-minute survival Player movement = same: follows mouse If player survives → go to endingScene Ending Scene Name: endingScene Zoom in on player at grill Show text: TRUE ENDING UNLOCKED You have conquered all... but was it worth it? Thanks for playing Roll Souls. Try touching grass now. Optional fake buttons: New Game+, Summon Friend (does nothing) ✅ Use only placeholder graphics ✅ The game starts at mainMenuScene and must flow all the way to endingScene ✅ Player always moves toward cursor ✅ Full heart system included ✅ Every scene and transition should be implemented
/**** * 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.startAttackPattern = function () { if (self.dead || gameState.currentState !== "game") { // Ataki tylko w stanie gry 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ę 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") { // Powrót do oryginalnej pozycji po szarży tween(self, { x: startX, y: startY }, { duration: 1000 * self.attackSpeedMultiplier, // Czas powrotu, skalowane easing: tween.easeOut }); // 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 () { // Aktualizuj tylko w stanie gry lub gdy boss umiera (do zakończenia animacji) if (gameState.currentState !== "game" && !self.dead) { // Jeśli zmieniono stan gry i boss nie umiera, 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; } // 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 gry 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 } } // ... Movement logic ... } // 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); // Promień gracza (połówka szerokości) var playerRadius = player && player.width ? player.width / 2 : 0; if (distance < attack.radius + playerRadius) { // 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 // 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), tylko 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 }); /**** * Game Code ****/ // Globalny kontener na elementy scen intro/tutoriali (do łatwego czyszczenia) // Dodano z souls4.txt dla przycisków intro // Używamy dźwięku zwycięstwa z stworzylo zloto.txt // Zachowujemy na wypadek użycia w animacjach gracza/UI 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 // Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro/tutoriali) 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 przyspieszania 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; // Stwórz tło var bg = game.addChild(LK.getAsset('bg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 })); // Tło nie jest dodawane do currentSceneElements, bo ma być stałe // 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) }, // ---------- 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; } this.currentState = "title"; // Ustaw stan na tytuł // 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; } this.currentState = "intro"; // Ustaw stan na intro game.setBackgroundColor(0x111111); // Ciemne tło // 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 LK.setTimeout(function () { // clearScene() zostanie wywołane na początku następnej metody, aby usunąć ten tekst gameState.showFakeTutorial(); // Przejdź do fałszywego tutorialu }, 2000); // 2 sekundy opóźnienia po wyświetleniu drugiego tekstu }, 5000); // 5 sekund opóźnienia przed wyświetleniem drugiego tekstu }, // Przejście do sceny fałszywego tutorialu (Press LPM to block) showFakeTutorial: function showFakeTutorial() { clearScene(); // Wyczyść poprzednie elementy (tekst intro) this.currentState = "fakeTutorial"; // Ustaw stan na fałszywy tutorial game.setBackgroundColor(0x000000); // Czarne tło // 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 // 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) // 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 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: 80, fill: 0xFFFFFF }); 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(); gameState.startGame(); // Wywołaj metodę rozpoczynającą grę (walkę z bossem) }; }, // Przejście do stanu gry (walka z bossem) startGame: function startGame() { // Ta metoda rozpoczyna faktyczną walkę z bossem // Upewnij się, że timer fake tutorialu jest wyczyszczony if (this.fakeTutorialTimerId) { LK.clearTimeout(this.fakeTutorialTimerId); this.fakeTutorialTimerId = null; } this.currentState = "game"; // Ustaw stan na "game" // 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 wzrosnąć 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) // 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); // Zatrzymaj interwał timera // 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; // 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 = []; } // 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; // 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 = []; } // 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 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