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