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.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 self.attackCooldown = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds (90-150 frames przy 60 FPS) }; 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 } }; 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; // Opóźnienie dla kolejnych ataków w linii 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 } }; }(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, // Czas trwania szarży 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, 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); } } } }); }; 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) tween(warning, { alpha: 0.6 // Zwiększ przezroczystość }, { duration: 1000, 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 tween(warning, { alpha: 0.8 // Zwiększ przezroczystość bardziej }, { duration: 200, 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 // Czas życia ataku w klatkach }; self.attacks.push(attack); // Zaplanuj usunięcie ataku po jego czasie życia LK.setTimeout(function () { // Sprawdź, czy atak nadal istnieje w tablicy przed usunięciem var index = self.attacks.indexOf(attack); if (index !== -1) { self.attacks.splice(index, 1); // Usuń z listy ataków } // Sprawdź, czy wizualizacja ataku nadal istnieje przed zniszczeniem if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); // Zniszcz obiekt wizualny } }, duration); } // 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 // 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 // Nie ma bezpośredniej listy timerów, ale zatrzymanie głównej pętli gry i usunięcie ataków poniżej też pomaga }; self.update = function () { // Aktualizuj tylko w stanie gry if (gameState.currentState !== "game") { return; } // Nie aktualizuj jeśli boss jest martwy (poza animacją śmierci) if (self.dead && self.alpha === 0) { // Gdy animacja śmierci dobiegła końca (obiekt niewidoczny) return; } // Jeśli boss jest martwy, ale nadal widoczny, pozwól animacji zanikania działać (obsługiwane przez tween) if (self.dead) { // Można by tu dodać inną logikę dla stanu śmierci jeśli potrzeba // Nadal przetwarzaj ataki, żeby zanikły po śmierci bossa } // Aktualizacja cooldownu ataku if (self.attackCooldown > 0) { self.attackCooldown--; if (self.attackCooldown === 0) { self.startAttackPattern(); // Rozpocznij nowy wzorzec ataku gdy cooldown minie } } // Poruszaj się w kierunku gracza jeśli boss nie jest oszołomiony i żyje if (!self.stunned && !self.dead) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 200) { // Utrzymuj pewien dystans od gracza self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } else { // Aktualizacja czasu oszołomienia (stun) self.stunDuration--; if (self.stunDuration <= 0) { self.stunned = false; } } // Przetwarzaj aktywne ataki bossa (sprawdzaj kolizje z graczem) for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; // Upewnij się, że obiekt ataku, wizualizacja i gracz istnieją, gracz żyje i nie jest nietykalny if (!attack || !attack.visual || !player || player.dead || player.invulnerable) { continue; // Pomiń przetwarzanie } attack.lifeTime--; // 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, zakładamy gracza jako okrąg do kolizji z atakami bossa) var playerRadius = player.width / 2; // Używamy szerokości z obiektu gracza 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); } // Nie usuwamy ataku od razu, usuwany jest po czasie życia (lifeTime) } // Usuń ataki, których czas życia minął if (attack.lifeTime <= 0) { // Sprawdź, czy atak nadal istnieje w tablicy przed usunięciem var index = self.attacks.indexOf(attack); if (index !== -1) { self.attacks.splice(index, 1); } // Sprawdź, czy wizualizacja ataku nadal istnieje przed zniszczeniem if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); // Zniszcz obiekt wizualny } } } }; 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, tak jak w souls4.txt 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 LK.setTimeout(function () { // Sprawdź, czy gracz nadal istnieje i nie jest martwy zanim zakończysz turlanie if (self && !self.dead && !self.rolling) { // Sprawdź czy rolling nie zostało ponownie ustawione self.rolling = false; self.rollCooldown = 30; // 30 frames cooldown (ok. 0.5 sekundy przy 60 FPS) // Mały bufor nietykalności po zakończeniu turlania LK.setTimeout(function () { // Sprawdź, czy gracz nadal żyje i nie zaczął nowego turlania if (!self.rolling && !self.dead) { self.invulnerable = false; } }, 100); // 100ms dodatkowej nietykalności po rollCooldown } else { // Jeśli gracz już się turlął ponownie lub umarł, po prostu ustaw cooldown if (!self.dead) { self.rollCooldown = 30; } } // Animacja zanikania efektu turlania i zniszczenie obiektu wizualnego // Sprawdź, czy efekt turlania nadal istnieje if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) { tween(rollEffect, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) { rollEffect.destroy(); } } }); } }, self.rollDuration * (1000 / 60)); // Czas trwania turlania w milisekundach }; // 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 // 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 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) { // Można by tu dodać inną logikę dla stanu śmierci jeśli potrzeba 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 if (self.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); // Dodaj do kontenera UI // 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 // 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; // Można ukryć w jakimś specyficznym stanie jeśli potrzeba }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 // Ciemne tło }); /**** * Game Code ****/ // Zachowujemy na wypadek użycia w animacjach gracza/UI // Używamy dźwięku zwycięstwa z stworzylo zloto.txt // Dodano z souls4.txt dla przycisków intro // 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 // Dodatkowe zmienne dla timera gry (z souls4.txt, ale przeniesione do gameState) // var survivalTime = 120; // Przeniesione do gameState // var gameTimerInterval; // Przeniesione do gameState // var timerText; // Przeniesione do UI // Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro) 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 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 storage.totalDeaths = 0; // Resetuj licznik śmierci przy każdym uruchomieniu gry (załadowaniu strony) storage.maxHearts = 5; // Ustaw początkową liczbę serc na 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) // Można ręcznie ustawić indeksy Z lub polegać na kolejności dodawania (UI i currentSceneElements dodane na końcu będą 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) 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ł) 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 showFakeTutorial: function showFakeTutorial() { clearScene(); // Wyczyść poprzednie elementy (tekst intro) this.currentState = "fakeTutorial"; game.setBackgroundColor(0x000000); // Czarne tło // Tekst fałszywego tutorialu (z souls4.txt) var instructionText = new Text2('Press E 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 do przejścia do prawdziwego tutorialu LK.setTimeout(function () { // clearScene() zostanie wywołane na początku następnej metody gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu }, 6000); // 6 sekund }, // Przejście do sceny prawdziwego tutorialu showRealTutorial: function showRealTutorial() { clearScene(); // Wyczyść poprzednie elementy (tekst fałszywego tutorialu) this.currentState = "realTutorial"; 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 this.currentState = "game"; // Ustaw stan na "game" // Pokaż obiekty gry (gracz, boss), ukryte w intro/tutorialu 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) // 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; player.x = 2048 / 2; player.y = 2732 / 2 + 400; // 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; // 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 this.gameTimerInterval = LK.setInterval(function () { gameState.remainingTime--; // Zmniejsz pozostały czas ui.updateTimerDisplay(gameState.remainingTime); // Zaktualizuj wyświetlanie timera na UI // Sprawdź warunek zwycięstwa (czas minął) if (gameState.remainingTime <= 0) { LK.clearInterval(gameState.gameTimerInterval); // Zatrzymaj interwał timera // Obsłuż zwycięstwo przez przetrwanie // Upewnij się, że boss i jego ataki są zniszczone if (boss && !boss.dead) { // Jeśli boss nie umarł od obrażeń (czyli wygrana czasem) // 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 && boss.destroy && !boss.destroyed) { boss.destroy(); } boss = null; // Wyczyść globalną referencję } 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 LK.setTimeout(function () { ui.hideTutorial(); // Ukryj tekst tutorialu if (boss && !boss.dead && gameState.currentState === "game") { boss.startAttackPattern(); } // Rozpocznij ataki bossa jeśli boss żyje i gra jest w stanie gry }, 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 // Ukryj obiekty gry (są już niszczone, ale to dodatkowe zabezpieczenie) 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); // 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() { // 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 w stworzylo zloto.txt // 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, tutoriale, game over), zignoruj lub dodaj logikę jeśli potrzebna // Obecnie, tapnięcia w intro/tutorialach nic nie robią (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.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.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") { // Ś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
@@ -1,18 +1,14 @@
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
-var storage = LK.import("@upit/storage.v1", {
- totalDeaths: 0,
- maxHearts: 3,
- currentLevel: 0,
- bossesDefeated: 0
-});
+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,
@@ -22,18 +18,19 @@
self.maxHealth = 100;
self.speed = 5;
self.attackCooldown = 0;
self.attackPattern = 0;
- self.attacks = [];
+ self.attacks = []; // Aktywne ataki bossa (obiekty JS z pozycją, promieniem, itp.)
self.stunned = false;
self.stunDuration = 0;
self.dead = false;
self.phase = 1;
self.startAttackPattern = function () {
- if (self.dead) {
+ if (self.dead || gameState.currentState !== "game") {
+ // Ataki tylko w stanie gry
return;
}
- self.attackPattern = (self.attackPattern + 1) % 3;
+ self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków
switch (self.attackPattern) {
case 0:
self.circleAttack();
break;
@@ -43,377 +40,466 @@
case 2:
self.chargeAttack();
break;
}
- // Schedule next attack
- self.attackCooldown = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds
+ // Ustaw następny atak z losowym opóźnieniem
+ self.attackCooldown = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds (90-150 frames przy 60 FPS)
};
self.circleAttack = function () {
LK.getSound('bossAttack').play();
var center = {
x: self.x,
y: self.y
};
- var count = 8;
+ 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);
+ self.createAttack(posX, posY, 3000); // Utwórz atak w danej pozycji z czasem życia
}
};
self.lineAttack = function () {
LK.getSound('bossAttack').play();
- // Create a line of attacks from boss to player
+ // 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);
+ 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;
+ var delay = i * 200; // Opóźnienie dla kolejnych ataków w linii
LK.setTimeout(function (x, y) {
return function () {
- self.createAttack(x, y, 2000);
+ // 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
+ }
};
}(posX, posY), delay);
}
};
self.chargeAttack = function () {
LK.getSound('bossAttack').play();
- // Calculate direction to player
+ // 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;
}
- // Save original position
+ // Zapisz oryginalną pozycję
var startX = self.x;
var startY = self.y;
- // Charge animation
+ // 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,
+ // Czas trwania szarży
easing: tween.easeIn,
onFinish: function onFinish() {
- // Return to original position
- tween(self, {
- x: startX,
- y: startY
- }, {
- duration: 1000,
- easing: tween.easeOut
- });
- // Create attacks along the charge path
- 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);
+ // 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,
+ 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);
+ }
}
}
});
};
self.createAttack = function (x, y, duration) {
- // Create attack warning
+ // 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,
- tint: 0xFFFF00
+ // Niska przezroczystość na początku
+ tint: 0xFFFF00 // Żółty kolor ostrzeżenia
}));
- // Warning animation
+ // Animacja ostrzeżenia (miganie)
tween(warning, {
- alpha: 0.6
+ alpha: 0.6 // Zwiększ przezroczystość
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
- // Change to actual attack
- warning.tint = 0xFF0000;
- tween(warning, {
- alpha: 0.8
- }, {
- duration: 200,
- onFinish: function onFinish() {
- // Add to active attacks
- var attack = {
- x: warning.x,
- y: warning.y,
- radius: warning.width / 2,
- visual: warning,
- lifeTime: duration
- };
- self.attacks.push(attack);
- // Remove after duration
- LK.setTimeout(function () {
- var index = self.attacks.indexOf(attack);
- if (index !== -1) {
- self.attacks.splice(index, 1);
- }
- // Fade out and destroy
- tween(warning, {
- alpha: 0
- }, {
- duration: 300,
- onFinish: function onFinish() {
- warning.destroy();
- }
- });
- }, duration);
- }
- });
+ // 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
+ tween(warning, {
+ alpha: 0.8 // Zwiększ przezroczystość bardziej
+ }, {
+ duration: 200,
+ 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 // Czas życia ataku w klatkach
+ };
+ self.attacks.push(attack);
+ // Zaplanuj usunięcie ataku po jego czasie życia
+ LK.setTimeout(function () {
+ // Sprawdź, czy atak nadal istnieje w tablicy przed usunięciem
+ var index = self.attacks.indexOf(attack);
+ if (index !== -1) {
+ self.attacks.splice(index, 1); // Usuń z listy ataków
+ }
+ // Sprawdź, czy wizualizacja ataku nadal istnieje przed zniszczeniem
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy(); // Zniszcz obiekt wizualny
+ }
+ }, duration);
+ } // if warning exists
+ }
+ });
+ } // if warning exists
}
});
};
self.takeDamage = function (amount) {
- if (self.dead) {
+ if (self.dead || gameState.currentState !== "game") {
+ // Boss otrzymuje obrażenia tylko w stanie gry
return;
}
self.health -= amount;
- // Visual feedback
+ // Wizualne sygnalizowanie otrzymania obrażeń
LK.effects.flashObject(self, 0xFFFFFF, 200);
- // Phase transition at 50% health
+ // Przejście fazy bossa przy 50% zdrowia
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2;
- self.speed += 2;
- // Visual phase transition
+ self.speed += 2; // Boss staje się szybszy
+ // Wizualne przejście fazy (np. zmiana koloru)
tween(self, {
- tint: 0xFF3300
+ tint: 0xFF3300 // Pomarańczowy/czerwony odcień
}, {
duration: 1000,
easing: tween.easeInOut
});
}
- // Check if boss is defeated
+ // Sprawdzenie, czy boss został pokonany
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
- if (self.dead) {
+ if (self.dead || gameState.currentState !== "game") {
+ // Boss umiera tylko w stanie gry
return;
}
self.dead = true;
- LK.getSound('victory').play();
- // Death animation
+ 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() {
- self.destroy();
- storage.bossesDefeated = (storage.bossesDefeated || 0) + 1;
- gameState.victory();
+ // 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
+ // Nie ma bezpośredniej listy timerów, ale zatrzymanie głównej pętli gry i usunięcie ataków poniżej też pomaga
};
self.update = function () {
- if (self.dead) {
+ // Aktualizuj tylko w stanie gry
+ if (gameState.currentState !== "game") {
return;
}
- // Update attack cooldown
+ // Nie aktualizuj jeśli boss jest martwy (poza animacją śmierci)
+ if (self.dead && self.alpha === 0) {
+ // Gdy animacja śmierci dobiegła końca (obiekt niewidoczny)
+ return;
+ }
+ // Jeśli boss jest martwy, ale nadal widoczny, pozwól animacji zanikania działać (obsługiwane przez tween)
+ if (self.dead) {
+ // Można by tu dodać inną logikę dla stanu śmierci jeśli potrzeba
+ // Nadal przetwarzaj ataki, żeby zanikły po śmierci bossa
+ }
+ // Aktualizacja cooldownu ataku
if (self.attackCooldown > 0) {
self.attackCooldown--;
if (self.attackCooldown === 0) {
- self.startAttackPattern();
+ self.startAttackPattern(); // Rozpocznij nowy wzorzec ataku gdy cooldown minie
}
}
- // Move toward player if not stunned
- if (!self.stunned) {
+ // Poruszaj się w kierunku gracza jeśli boss nie jest oszołomiony i żyje
+ if (!self.stunned && !self.dead) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 200) {
- // Keep some distance
+ // Utrzymuj pewien dystans od gracza
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
} else {
- // Update stun duration
+ // Aktualizacja czasu oszołomienia (stun)
self.stunDuration--;
if (self.stunDuration <= 0) {
self.stunned = false;
}
}
- // Process active attacks
+ // Przetwarzaj aktywne ataki bossa (sprawdzaj kolizje z graczem)
for (var i = self.attacks.length - 1; i >= 0; i--) {
var attack = self.attacks[i];
+ // Upewnij się, że obiekt ataku, wizualizacja i gracz istnieją, gracz żyje i nie jest nietykalny
+ if (!attack || !attack.visual || !player || player.dead || player.invulnerable) {
+ continue; // Pomiń przetwarzanie
+ }
attack.lifeTime--;
- // Check for collision with player
+ // 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);
- if (distance < attack.radius + player.width / 2 && player.takeDamage(1)) {
- // Visual feedback
- LK.effects.flashObject(attack.visual, 0xFFFFFF, 200);
+ // Promień gracza (połówka szerokości, zakładamy gracza jako okrąg do kolizji z atakami bossa)
+ var playerRadius = player.width / 2; // Używamy szerokości z obiektu gracza
+ 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);
+ }
+ // Nie usuwamy ataku od razu, usuwany jest po czasie życia (lifeTime)
}
- // Remove expired attacks
+ // Usuń ataki, których czas życia minął
if (attack.lifeTime <= 0) {
- self.attacks.splice(i, 1);
- attack.visual.destroy();
+ // Sprawdź, czy atak nadal istnieje w tablicy przed usunięciem
+ var index = self.attacks.indexOf(attack);
+ if (index !== -1) {
+ self.attacks.splice(index, 1);
+ }
+ // Sprawdź, czy wizualizacja ataku nadal istnieje przed zniszczeniem
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy(); // Zniszcz obiekt wizualny
+ }
}
}
};
return self;
});
-var Button2 = Container.expand(function (text, onClick) {
- var self = Container.call(this);
- var bg = self.attachAsset('floor', {
- width: 400,
- height: 100
- });
- var label = new Text2(text, {
- size: 50,
- fill: 0xffffff
- });
- label.y = -10;
- self.addChild(label);
- self.interactive = true;
- self.on('click', onClick);
- 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, tak jak w souls4.txt
+ self.width = playerGraphics.width;
+ self.height = playerGraphics.height;
self.speed = 12;
- self.rolling = false;
- self.rollCooldown = 0;
- self.rollDuration = 0;
+ 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
- };
- self.rollDistance = 300;
- self.rollSpeed = 20;
- self.invulnerable = false;
- self.invulnerabilityFrames = 0;
- self.health = storage.maxHearts || 3;
- self.dead = false;
+ }; // 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) {
- if (self.rolling || self.rollCooldown > 0 || self.dead) {
+ // 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;
+ self.rollDuration = self.rollDistance / self.rollSpeed; // Czas trwania turlania obliczony z dystansu i prędkości
self.rollDirection = direction;
- self.invulnerable = true;
- // Visual effect for rolling
+ 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
}));
- // Start the roll tween
- LK.getSound('roll').play();
- // Cleanup after roll
+ LK.getSound('roll').play(); // Dźwięk turlania
+ // Zaplanuj koniec turlania i cooldown
LK.setTimeout(function () {
- self.rolling = false;
- self.rollCooldown = 30; // 30 frames cooldown (0.5 seconds)
- LK.setTimeout(function () {
- self.invulnerable = false;
- }, 100); // Small buffer of invulnerability after roll
- tween(rollEffect, {
- alpha: 0
- }, {
- duration: 200,
- onFinish: function onFinish() {
- rollEffect.destroy();
+ // Sprawdź, czy gracz nadal istnieje i nie jest martwy zanim zakończysz turlanie
+ if (self && !self.dead && !self.rolling) {
+ // Sprawdź czy rolling nie zostało ponownie ustawione
+ self.rolling = false;
+ self.rollCooldown = 30; // 30 frames cooldown (ok. 0.5 sekundy przy 60 FPS)
+ // Mały bufor nietykalności po zakończeniu turlania
+ LK.setTimeout(function () {
+ // Sprawdź, czy gracz nadal żyje i nie zaczął nowego turlania
+ if (!self.rolling && !self.dead) {
+ self.invulnerable = false;
+ }
+ }, 100); // 100ms dodatkowej nietykalności po rollCooldown
+ } else {
+ // Jeśli gracz już się turlął ponownie lub umarł, po prostu ustaw cooldown
+ if (!self.dead) {
+ self.rollCooldown = 30;
}
- });
- }, self.rollDuration * (1000 / 60)); // Convert frames to ms
+ }
+ // Animacja zanikania efektu turlania i zniszczenie obiektu wizualnego
+ // Sprawdź, czy efekt turlania nadal istnieje
+ if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) {
+ tween(rollEffect, {
+ alpha: 0
+ }, {
+ duration: 200,
+ onFinish: function onFinish() {
+ if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) {
+ rollEffect.destroy();
+ }
+ }
+ });
+ }
+ }, self.rollDuration * (1000 / 60)); // Czas trwania turlania w milisekundach
};
+ // Funkcja otrzymywania obrażeń
self.takeDamage = function (amount) {
- if (self.invulnerable || self.dead) {
- return false;
+ // 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;
- LK.getSound('hit').play();
+ self.health -= amount; // Zmniejsz zdrowie
+ LK.getSound('hit').play(); // Dźwięk trafienia
+ // Sprawdzenie, czy gracz umarł
if (self.health <= 0) {
- self.die();
- return true;
+ self.die(); // Wywołaj funkcję śmierci
+ return true; // Gracz umarł
}
- // Invulnerability period
+ // Okres nietykalności po otrzymaniu obrażeń
self.invulnerable = true;
- self.invulnerabilityFrames = 45; // 45 frames (0.75 seconds)
- // Flash effect
+ self.invulnerabilityFrames = 45; // 45 frames (ok. 0.75 sekundy)
+ // Efekt migotania gracza
LK.effects.flashObject(self, 0xFF0000, 500);
- return true;
+ return true; // Gracz otrzymał obrażenia (ale nie umarł)
};
+ // Funkcja śmierci gracza
self.die = function () {
- if (self.dead) {
+ if (self.dead || gameState.currentState !== "game") {
+ // Gracz umiera tylko w stanie gry
return;
}
self.dead = true;
- LK.getSound('death').play();
- // Death visual effect
+ LK.getSound('death').play(); // Dźwięk śmierci
+ // Wizualny efekt śmierci gracza (zanikanie i obrót)
tween(self, {
alpha: 0,
rotation: Math.PI * 4
}, {
duration: 1500,
- easing: tween.easeInOut
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ // Obiekt gracza zostanie zniszczony w gameState.gameOver clean up
+ }
});
- // Update total deaths
- storage.totalDeaths = (storage.totalDeaths || 0) + 1;
- // Check if player should get an upgrade
- if (storage.totalDeaths % 3 === 0) {
- storage.maxHearts = (storage.maxHearts || 3) + 1;
+ // 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)
}
- // Restart the level after delay
+ // Przejdź do stanu game over po opóźnieniu (aby animacja śmierci mogła się zakończyć)
LK.setTimeout(function () {
- gameState.gameOver();
- }, 2000);
+ gameState.gameOver(); // Wywołaj metodę game over w gameState
+ }, 2000); // 2 sekundy opóźnienia
};
self.update = function () {
- // Handle roll cooldown
+ // Aktualizuj 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) {
+ // Można by tu dodać inną logikę dla stanu śmierci jeśli potrzeba
+ return; // Nie wykonuj logiki ruchu i nietykalności jeśli gracz jest martwy
+ }
+ // Obsługa cooldownu turlania
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
- // Handle invulnerability frames
+ // Obsługa klatek nietykalności
if (self.invulnerable && self.invulnerabilityFrames > 0) {
self.invulnerabilityFrames--;
- // Blinking effect
- self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1;
+ // 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;
+ self.alpha = 1; // Przywróć pełną przezroczystość gdy nietykalność minie
}
}
- // Handle rolling movement
+ // Obsługa ruchu podczas turlania
if (self.rolling) {
self.x += self.rollDirection.x * self.rollSpeed;
self.y += self.rollDirection.y * self.rollSpeed;
}
- // Keep player within bounds
+ // 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
+ // Create heart containers (wizualizacja zdrowia)
self.hearts = [];
- self.heartContainer = new Container();
+ self.heartContainer = new Container(); // Kontener na ikony serc
self.addChild(self.heartContainer);
- // Title and messages
+ // Title and messages (teksty na ekranach)
self.titleText = new Text2("ROLL SOULS", {
size: 150,
fill: 0xFFFFFF
});
@@ -424,456 +510,760 @@
fill: 0xFFFFFF
});
self.messageText.anchor.set(0.5, 0.5);
self.addChild(self.messageText);
- // Deaths counter
+ // Deaths counter (licznik śmierci)
self.deathsText = new Text2("Deaths: 0", {
size: 40,
fill: 0xFFFFFF
});
- self.deathsText.anchor.set(1, 0);
+ 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
+ // Tutorial text (teksty tutoriali)
self.tutorialText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
- self.tutorialText.anchor.set(0.5, 0);
+ 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); // Dodaj do kontenera UI
+ // Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
- // Clear existing hearts
+ // Usuń istniejące serca
while (self.hearts.length > 0) {
var heart = self.hearts.pop();
- heart.destroy();
+ if (heart && heart.destroy) {
+ heart.destroy();
+ }
}
- self.heartContainer.removeChildren();
- // Create new hearts
+ 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
+ tint: i < current ? 0xFF0000 : 0x555555 // Czerwone jeśli zdrowie jest >=, szare jeśli <
});
self.hearts.push(heart);
self.heartContainer.addChild(heart);
}
- // Center heart container
+ // Wyśrodkuj kontener serc w poziomie i ustaw pozycję pionową
self.heartContainer.x = (2048 - max * 50) / 2;
- self.heartContainer.y = 100;
+ 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;
- // Clear any existing timeout
+ self.messageText.alpha = 1; // Ustaw pełną przezroczystość
+ // Wyczyść istniejący timer zanikania, jeśli istnieje
if (self.messageTimeout) {
LK.clearTimeout(self.messageTimeout);
}
- // Fade out after duration
+ // Zaplanuj zanikanie po czasie, jeśli duration > 0
if (duration) {
self.messageTimeout = LK.setTimeout(function () {
tween(self.messageText, {
- alpha: 0
+ alpha: 0 // Zaniknij do przezroczystości 0
}, {
- duration: 500
+ duration: 500 // Czas trwania zanikania
});
}, duration);
}
};
+ // Wyświetla tekst tutorialu
self.showTutorial = function (text) {
self.tutorialText.setText(text);
- self.tutorialText.alpha = 1;
+ self.tutorialText.alpha = 1; // Ustaw pełną przezroczystość
};
+ // Ukrywa tekst tutorialu (zanikając)
self.hideTutorial = function () {
tween(self.tutorialText, {
- alpha: 0
+ alpha: 0 // Zaniknij
}, {
- duration: 500
+ 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) {
- // Position based on game 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;
+ self.messageText.y = 1000; // "Tap to Start"
self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 1200;
- self.deathsText.x = 2048 - 50;
- self.deathsText.y = 50;
+ 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":
- self.titleText.alpha = 0;
+ // Pozycje dla stanu gry (walka z bossem)
+ self.titleText.alpha = 0; // Ukryj tytuł
self.messageText.x = 2048 / 2;
- self.messageText.y = 1500;
+ 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;
- self.deathsText.x = 2048 - 50;
- self.deathsText.y = 50;
+ 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;
+ self.titleText.y = 800; // Tytuł "VICTORY ACHIEVED"
self.messageText.x = 2048 / 2;
- self.messageText.y = 1000;
+ self.messageText.y = 1000; // Komunikat o pokonanych bossach
self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 1200;
- self.deathsText.x = 2048 - 50;
- self.deathsText.y = 50;
+ 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
+ // 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; // Można ukryć w jakimś specyficznym stanie jeśli potrzeba
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
- backgroundColor: 0x111111
+ backgroundColor: 0x111111 // Ciemne tło
});
/****
* Game Code
****/
-// === Dodane funkcje ===
-// Game variables
-function clearScene() {
- while (game.children.length > 0) {
- game.removeChild(game.children[0]);
- }
-}
-var gameState = "menu";
-var deathCount = 0;
-var maxHearts = 5;
-var currentHearts = 5;
-var gameTimer = 120;
-var timerText;
-var boss, player;
-function showMainMenu() {
- clearScene();
- var title = new Text2("ROLL SOULS", {
- size: 120,
- fill: 0xffffff
- });
- title.x = 1024;
- title.y = 400;
- game.addChild(title);
- var startButton = new Button2("Start Game", function () {
- showIntro();
- });
- startButton.x = 1024;
- startButton.y = 800;
- game.addChild(startButton);
-}
-function showIntro() {
- clearScene();
- var introText = new Text2("Only those who roll shall survive...", {
- size: 60,
- fill: 0xffffff
- });
- introText.x = 1024;
- introText.y = 600;
- game.addChild(introText);
- var continueButton = new Button2("Continue", function () {
- showFakeTutorial();
- });
- continueButton.x = 1024;
- continueButton.y = 1000;
- game.addChild(continueButton);
-}
-function showFakeTutorial() {
- clearScene();
- var fakeText = new Text2("Press the button to roll!", {
- size: 60,
- fill: 0xff0000
- });
- fakeText.x = 1024;
- fakeText.y = 600;
- game.addChild(fakeText);
- var rollButton = new Button2("Roll", function () {
- showRealTutorial();
- });
- rollButton.x = 1024;
- rollButton.y = 1000;
- game.addChild(rollButton);
-}
-function showRealTutorial() {
- clearScene();
- var realText = new Text2("Swipe to roll and survive!", {
- size: 60,
- fill: 0xffffff
- });
- realText.x = 1024;
- realText.y = 600;
- game.addChild(realText);
- var startButton = new Button2("Start Boss Fight", function () {
- startGame();
- });
- startButton.x = 1024;
- startButton.y = 1000;
- game.addChild(startButton);
-}
-function startGame() {
- clearScene();
- initGame();
- timerText = new Text2("Time Left: 120", {
- size: 50,
- fill: 0xffffff
- });
- timerText.x = 50;
- timerText.y = 50;
- game.addChild(timerText);
- LK.setInterval(function () {
- if (gameState === "bossFight") {
- gameTimer--;
- timerText.setText("Time Left: " + gameTimer);
- if (gameTimer <= 0) {
- victory();
- }
- }
- }, 1000);
-}
-function initGame() {
- gameState = "bossFight";
- player = new Player();
- player.x = 1024;
- player.y = 2048;
- game.addChild(player);
- boss = new Boss();
- boss.x = 1024;
- boss.y = 400;
- game.addChild(boss);
- currentHearts = 5 + Math.min(Math.floor(deathCount / 5), 5);
- updateHeartUI();
-}
-function gameOver() {
- deathCount++;
- showMainMenu();
-}
-function victory() {
- clearScene();
- var victoryText = new Text2("Victory!", {
- size: 100,
- fill: 0x00ff00
- });
- victoryText.x = 1024;
- victoryText.y = 700;
- game.addChild(victoryText);
- var restartButton = new Button2("Restart", function () {
- showMainMenu();
- });
- restartButton.x = 1024;
- restartButton.y = 1000;
- game.addChild(restartButton);
-}
-function updateHeartUI() {
- // Update heart display if needed
-}
-showMainMenu();
+// Zachowujemy na wypadek użycia w animacjach gracza/UI
+// Używamy dźwięku zwycięstwa z stworzylo zloto.txt
+// Dodano z souls4.txt dla przycisków intro
+// 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 = [];
+var walls = []; // Ściany areny bossa
+// Dodatkowe zmienne dla timera gry (z souls4.txt, ale przeniesione do gameState)
+// var survivalTime = 120; // Przeniesione do gameState
+// var gameTimerInterval; // Przeniesione do gameState
+// var timerText; // Przeniesione do UI
+// Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro)
+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 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() {
- // Create background
+ // ZRESETUJ LICZNIK ŚMIERCI I USTAW DOMYŚLNĄ ILOŚĆ SERC NA START APLIKACJI
+ storage.totalDeaths = 0; // Resetuj licznik śmierci przy każdym uruchomieniu gry (załadowaniu strony)
+ storage.maxHearts = 5; // Ustaw początkową liczbę serc na 5
+ // Stwórz tło
var bg = game.addChild(LK.getAsset('bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
}));
- // Create walls
+ // 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();
- // Create UI
+ // 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();
- // Show title screen
- this.showTitleScreen();
- // Start background music
+ 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() {
- // Left wall
+ // 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);
- // Right wall
+ // Prawa ściana
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0,
anchorY: 0,
x: 2048 - 100,
y: 0
}));
walls.push(rightWall);
- // Top wall
+ // Górna ściana
var topWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
walls.push(topWall);
- // Bottom wall
+ // 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)
+ // Można ręcznie ustawić indeksy Z lub polegać na kolejności dodawania (UI i currentSceneElements dodane na końcu będą na górze)
},
+ // ---------- Metody przejścia między stanami ----------
+ // Przejście do ekranu tytułowego
showTitleScreen: function showTitleScreen() {
- this.currentState = "title";
- ui.positionElements("title");
- ui.titleText.alpha = 1;
- ui.showMessage("Tap to Start", 0);
- ui.showTutorial("Swipe to Roll - Death is Progress");
+ clearScene(); // Wyczyść elementy poprzedniej sceny (np. z Victory)
+ 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ł)
+ 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
+ showFakeTutorial: function showFakeTutorial() {
+ clearScene(); // Wyczyść poprzednie elementy (tekst intro)
+ this.currentState = "fakeTutorial";
+ game.setBackgroundColor(0x000000); // Czarne tło
+ // Tekst fałszywego tutorialu (z souls4.txt)
+ var instructionText = new Text2('Press E 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 do przejścia do prawdziwego tutorialu
+ LK.setTimeout(function () {
+ // clearScene() zostanie wywołane na początku następnej metody
+ gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
+ }, 6000); // 6 sekund
+ },
+ // Przejście do sceny prawdziwego tutorialu
+ showRealTutorial: function showRealTutorial() {
+ clearScene(); // Wyczyść poprzednie elementy (tekst fałszywego tutorialu)
+ this.currentState = "realTutorial";
+ 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() {
- this.currentState = "game";
- ui.positionElements("game");
- // Create player
- player = game.addChild(new Player());
+ // Ta metoda rozpoczyna faktyczną walkę z bossem
+ this.currentState = "game"; // Ustaw stan na "game"
+ // Pokaż obiekty gry (gracz, boss), ukryte w intro/tutorialu
+ 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)
+ // 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;
player.x = 2048 / 2;
player.y = 2732 / 2 + 400;
- player.health = storage.maxHearts || 3;
- // Create boss
- boss = game.addChild(new Boss());
+ // 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;
- // Update UI
- ui.updateHearts(player.health, storage.maxHearts);
- ui.showTutorial("Swipe to roll away from attacks!");
+ // 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
+ this.gameTimerInterval = LK.setInterval(function () {
+ gameState.remainingTime--; // Zmniejsz pozostały czas
+ ui.updateTimerDisplay(gameState.remainingTime); // Zaktualizuj wyświetlanie timera na UI
+ // Sprawdź warunek zwycięstwa (czas minął)
+ if (gameState.remainingTime <= 0) {
+ LK.clearInterval(gameState.gameTimerInterval); // Zatrzymaj interwał timera
+ // Obsłuż zwycięstwo przez przetrwanie
+ // Upewnij się, że boss i jego ataki są zniszczone
+ if (boss && !boss.dead) {
+ // Jeśli boss nie umarł od obrażeń (czyli wygrana czasem)
+ // 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 && boss.destroy && !boss.destroyed) {
+ boss.destroy();
+ }
+ boss = null; // Wyczyść globalną referencję
+ }
+ 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
LK.setTimeout(function () {
- ui.hideTutorial();
- boss.startAttackPattern();
- }, 3000);
+ ui.hideTutorial(); // Ukryj tekst tutorialu
+ if (boss && !boss.dead && gameState.currentState === "game") {
+ boss.startAttackPattern();
+ } // Rozpocznij ataki bossa jeśli boss żyje i gra jest w stanie gry
+ }, 3000); // 3 sekundy opóźnienia przed pierwszym atakiem bossa
},
+ // Przejście do stanu Game Over (wywoływane przez player.die())
gameOver: function gameOver() {
- // Show game over message
- ui.showMessage("YOU DIED", 3000);
- // Restart after delay
+ 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
+ // Ukryj obiekty gry (są już niszczone, ale to dodatkowe zabezpieczenie)
+ 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
+ // 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;
}
- // Restart
- gameState.startGame();
- }, 3000);
+ // 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";
- ui.positionElements("victory");
- ui.titleText.setText("VICTORY ACHIEVED");
- ui.titleText.alpha = 1;
- ui.showMessage("Bosses Defeated: " + storage.bossesDefeated, 0);
- ui.showTutorial("Tap to Continue");
- // Clean up player
+ 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);
+ // 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 () {
- // Return to title after delay
- LK.setTimeout(function () {
- gameState.showTitleScreen();
- }, 5000);
- }, 3000);
+ 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() {
- if (this.currentState === "title") {
- this.startGame();
- return;
- }
- if (this.currentState === "victory") {
- this.showTitleScreen();
- return;
- }
- // Only process roll gestures during gameplay
- if (this.currentState !== "game" || !player || player.dead) {
- return;
- }
- // Calculate swipe direction and distance
+ // 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);
- // Minimum swipe distance
+ // --- 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) {
- return;
+ // Próg turlania to 50 jednostek w stworzylo zloto.txt
+ // 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, tutoriale, game over), zignoruj lub dodaj logikę jeśli potrzebna
+ // Obecnie, tapnięcia w intro/tutorialach nic nie robią (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
}
- // Normalize direction
+ // --- 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
};
- // Execute roll
+ // Wykonaj turlanie gracza
player.roll(direction);
}
};
-// Event handlers
+// --- Obsługa inputu (mapowanie zdarzeń LK na metody gameState) ---
game.down = function (x, y, obj) {
- gameState.touchStart.x = x;
- gameState.touchStart.y = y;
+ // Zarejestruj początek dotyku/kliknięcia tylko w stanach, które tego wymagają
+ if (gameState.currentState === "title" || gameState.currentState === "victory" || gameState.currentState === "game") {
+ 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) {
- gameState.touchEnd.x = x;
- gameState.touchEnd.y = y;
- gameState.processTouchGesture();
+ // Zarejestruj koniec dotyku/kliknięcia tylko w stanach, które tego wymagają
+ if (gameState.currentState === "title" || gameState.currentState === "victory" || gameState.currentState === "game") {
+ 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) {
- // Only used for tracking the current touch position
- gameState.touchEnd.x = x;
- gameState.touchEnd.y = y;
+ // Śledź aktualną pozycję dotyku/kursora, aby obliczyć dystans gestu na koniec
+ if (gameState.currentState === "game" || gameState.currentState === "title" || gameState.currentState === "victory") {
+ // Śledź ruch w stanach, gdzie gesty są istotne
+ gameState.touchEnd.x = x;
+ gameState.touchEnd.y = y;
+ }
};
-// Main update loop
+// --- Główna pętla aktualizacji gry ---
game.update = function () {
- // Only update game objects during gameplay
+ // 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();
+ player.update(); // Aktualizacja gracza (ruch, turlanie, nietykalność)
}
if (boss) {
- boss.update();
+ boss.update(); // Aktualizacja bossa (ruch, ataki, kolizje ataków z graczem)
}
- // Update hearts UI
+ // 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.
}
- // Always update deaths counter
- if (ui) {
- ui.updateDeathsCounter();
- }
+ // W innych stanach (title, intro, tutoriale, game over, victory) logika gry (ruch obiektów, kolizje) nie jest wykonywana.
};
-// Initialize the game
+// --- 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();
\ No newline at end of file