Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: SpriteAnimation is not defined' in or related to this line: 'var rollAnimation = game.addChild(new SpriteAnimation({' Line Number: 448
Code edit (18 edits merged)
Please save this source code
User prompt
Add 6 assets roll0, roll1, roll2, roll3, roll4, roll5.
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: howToScene is not defined' in or related to this line: 'howToScene();' Line Number: 1263
Code edit (3 edits merged)
Please save this source code
User prompt
fix problem with click "tap to start" it's not working
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1103
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'alpha')' in or related to this line: 'self.deathsText.alpha = 1;' Line Number: 366
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1229
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1228
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow' Line Number: 1199
User prompt
Please fix the bug: 'Uncaught ReferenceError: Player is not defined' in or related to this line: 'player = game.addChild(new Player()); // Stwórz nowego gracza' Line Number: 1196
Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught RangeError: Maximum call stack size exceeded' in or related to this line: 'this.handleFakeTutorialInput(); // REMOVED self-recursive call that caused stack overflow' Line Number: 1293
Code edit (1 edits merged)
Please save this source code
User prompt
add new asset grillMenu
Code edit (1 edits merged)
Please save this source code
User prompt
add asset bossHpbar
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame)
self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame)
self.speed = 5;
self.attackCooldown = 0;
self.attackPattern = 0;
self.attacks = []; // Aktywne ataki bossa (obiekty JS z pozycją, promieniem, itp.)
self.stunned = false;
self.stunDuration = 0;
self.dead = false;
self.phase = 1;
self.attackSpeedMultiplier = 1; // Mnożnik prędkości ataków (1 = normalna, < 1 = szybsza)
self.repositioning = false; // Flaga informująca czy boss się przemieszcza po szarży
self.startAttackPattern = function () {
// !!! Ustaw tymczasowy cooldown na początku, ZANIM sprawdzisz warunki powrotu !!!
// To zapobiegnie natychmiastowemu ponownemu wywołaniu, jeśli metoda wróci wcześnie
self.attackCooldown = 1; // Ustaw minimalny cooldown (np. 1 klatka) od razu
// Sprawdź warunki wczesnego powrotu: boss jest martwy, gra nie jest w stanie "game" LUB boss jest w stanie "repositioning"
if (self.dead || gameState.currentState !== "game" || self.repositioning) {
// Dodano || self.repositioning
return; // Jeśli warunek spełniony, wróć
}
// Jeśli dotarliśmy tutaj, oznacza to, że możemy rozpocząć nowy wzorzec ataku
self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków
switch (self.attackPattern) {
case 0:
self.circleAttack();
break;
case 1:
self.lineAttack();
break;
case 2:
self.chargeAttack();
break;
}
// Ustaw WŁAŚCIWY czas odnowienia dla następnego ataku, PO zainicjowaniu obecnego
// Mnożnik prędkości ataków bossa jest już ustawiony w gameState.startGame
self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds (90-150 frames) * multiplier
};
self.circleAttack = function () {
LK.getSound('bossAttack').play();
var center = {
x: self.x,
y: self.y
};
var count = isNewBossPlusMode ? 12 : 8; // W trybie New Boss+ więcej pocisków
var radius = 300;
var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Krótszy czas życia w New Boss+?
for (var i = 0; i < count; i++) {
var angle = i / count * Math.PI * 2;
var posX = center.x + Math.cos(angle) * radius;
var posY = center.y + Math.sin(angle) * radius;
self.createAttack(posX, posY, attackLifeTime); // Utwórz atak w danej pozycji z czasem życia (skalowane w createAttack)
}
};
self.lineAttack = function () {
LK.getSound('bossAttack').play();
// Tworzy linię ataków od bossa do gracza
var targetX = player.x;
var targetY = player.y;
var count = isNewBossPlusMode ? 8 : 5; // W trybie New Boss+ więcej pocisków w linii
var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Krótszy czas życia?
var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Mniejsze opóźnienie między atakami w linii
for (var i = 0; i < count; i++) {
var t = i / (count - 1); // Współczynnik interpolacji
var posX = self.x + (targetX - self.x) * t;
var posY = self.y + (targetY - self.y) * t;
var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie dla kolejnych ataków w linii, skalowane
LK.setTimeout(function (x, y) {
return function () {
// Sprawdź, czy boss nadal żyje i gra jest w stanie gry zanim stworzysz atak
if (self && !self.dead && gameState.currentState === "game") {
self.createAttack(x, y, attackLifeTime); // Utwórz atak po opóźnieniu (czas życia skalowane w createAttack)
}
};
}(posX, posY), delay);
}
};
self.chargeAttack = function () {
LK.getSound('bossAttack').play();
// Oblicz kierunek do gracza
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
dx /= distance;
dy /= distance;
}
// Zapisz oryginalną pozycję (z której zaczyna się szarża)
var startX = self.x;
var startY = self.y;
var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dłuższa szarża w New Boss+?
var chargeDuration = isNewBossPlusMode ? 600 : 800; // Szybsza szarża w New Boss+?
var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Więcej ataków na ścieżce szarży?
var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Krótszy czas życia?
// Animacja szarży (boss przesuwa się szybko w kierunku gracza)
tween(self, {
x: self.x + dx * chargeDistance,
// Przesunięcie
y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
// Czas trwania szarży, skalowane
easing: tween.easeIn,
onFinish: function onFinish() {
// Sprawdź, czy boss nadal żyje i gra jest w stanie gry
if (self && !self.dead && gameState.currentState === "game") {
// --- Start Repositioning Phase ---
self.repositioning = true; // Ustaw flagę repozycji po zakończeniu szarży
// Czas na repozycję (np. 0.5 sekundy), skalowane
LK.setTimeout(function () {
if (self && !self.dead) {
// Upewnij się, że boss nadal istnieje i żyje
self.repositioning = false; // Zakończ stan repozycji po opóźnieniu
}
}, 500 * self.attackSpeedMultiplier); // Opóźnienie skalowane przez mnożnik prędkości ataków
// --- End Repositioning Phase ---
// Powrót do oryginalnej pozycji (startu szarży) po szarży
tween(self, {
x: startX,
y: startY
}, {
duration: 1000 * self.attackSpeedMultiplier,
// Czas powrotu, skalowane
easing: tween.easeOut
// Nie potrzebujemy tutaj już onFinish do repozycji, bo obsłużono to powyżej
});
// Utwórz ataki wzdłuż ścieżki szarży
for (var i = 0; i < attacksOnPathCount; i++) {
var t = i / (attacksOnPathCount - 1);
var posX = startX + (self.x - startX) * t;
var posY = startY + (self.y - startY) * t;
self.createAttack(posX, posY, attackLifeTime); // Czas życia skalowane w createAttack
}
}
}
});
};
self.createAttack = function (x, y, duration) {
// Tworzy wizualne ostrzeżenie ataku (żółty okrąg)
var warning = game.addChild(LK.getAsset('attack', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.3,
// Niska przezroczystość na początku
tint: 0xFFFF00 // Żółty kolor ostrzeżenia
}));
// Animacja ostrzeżenia (miganie), skalowana przez mnożnik prędkości
var warningDuration = isNewBossPlusMode ? 800 : 1000; // Krótsze ostrzeżenie w New Boss+?
tween(warning, {
alpha: 0.6 // Zwiększ przezroczystość
}, {
duration: warningDuration * self.attackSpeedMultiplier,
// Skalowany czas trwania
easing: tween.easeInOut,
onFinish: function onFinish() {
// Sprawdź, czy warning nadal istnieje przed zmianą koloru
if (warning && !warning.destroyed) {
// Zmień kolor na czerwony, sygnalizując faktyczny atak
warning.tint = 0xFF0000;
// Animacja sygnalizująca gotowość ataku, skalowana przez mnożnik prędkości
tween(warning, {
alpha: 0.8 // Zwiększ przezroczystość bardziej
}, {
duration: 200 * self.attackSpeedMultiplier,
// Skalowany czas trwania
onFinish: function onFinish() {
// Sprawdź, czy warning nadal istnieje
if (warning && !warning.destroyed) {
// Dodaj atak do listy aktywnych ataków bossa (jako obiekt danych)
var attack = {
x: warning.x,
// Pozycja ataku (ze środka)
y: warning.y,
radius: warning.width / 2,
// Promień kolizji (połówka szerokości)
visual: warning,
// Referencja do obiektu wizualnego
lifeTime: duration * self.attackSpeedMultiplier * (60 / 1000) // Czas życia ataku w klatkach, skalowany. Przeliczamy ms na klatki (zakładając 60 FPS)
};
self.attacks.push(attack);
// !!! USUNIĘTO LK.setTimeout, USUWANIE BĘDZIE TYLKO W BOSS.update() !!!
} // if warning exists
}
});
} // if warning exists
}
});
};
self.takeDamage = function (amount) {
if (self.dead || gameState.currentState !== "game") {
// Boss otrzymuje obrażenia tylko w stanie gry
return;
}
self.health -= amount; // Zmniejsz zdrowie
// Upewnij się, że zdrowie nie spadnie poniżej zera (chyba że do śmierci)
self.health = Math.max(0, self.health);
// Wizualne sygnalizowanie otrzymania obrażeń
LK.effects.flashObject(self, 0xFFFFFF, 200);
// Przejście fazy bossa pri 50% zdrowia (może być wyłączone w New Boss+?)
if (!isNewBossPlusMode) {
// Tylko w standardowym trybie
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2;
self.speed += 2; // Boss staje się szybszy (ruch)
// Wizualne przejście fazy (np. zmiana koloru)
tween(self, {
tint: 0xFF3300 // Pomarańczowy/czerwony odcień
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
// Sprawdzenie, czy boss został pokonany
if (self.health <= 0) {
self.die(); // Wywołaj funkcję śmierci
}
// Opcjonalnie: Aktualizuj wizualne wskazanie zdrowia bossa (jeśli istnieje)
// ui.updateBossHealth(self.health, self.maxHealth); // Wymagałoby to dodania do klasy UI
};
self.die = function () {
if (self.dead || gameState.currentState !== "game") {
// Boss umiera tylko w stanie gry
return;
}
self.dead = true;
// Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu (jeśli nie New Boss+)
if (!isNewBossPlusMode) {
LK.getSound('victory').play(); // Dźwięk zwycięstwa w trybie standardowym
}
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Sprawdź, czy boss nadal istnieje przed zniszczeniem
if (self && self.destroy && !self.destroyed) {
self.destroy(); // Zniszcz obiekt bossa
}
// Zwiększ licznik pokonanych bossów TYLKO W STANDARDOWYM TRYBIE LUB MOŻE OSOBNO DLA NEW BOSS+?
// Na razie zwiększamy zawsze, ale można to zmienić
storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów
// !!! Po pokonaniu bossa ZAWSZE przechodzimy do Grill Screena (niezależnie od trybu) !!!
gameState.showGrillScreen();
}
});
// Zatrzymaj wszystkie timery związane z atakami bossa
// Ataki zostaną wyczyszczone w gameState.showGrillScreen clean up
};
self.update = function () {
var playerRadius; // Declare playerRadius here
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
// Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki
if (!self.dead && self.attacks) {
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
self.attacks = []; // Wyczyść tablicę ataków
}
return;
}
// Nie aktualizuj jeśli boss jest martwy i animacja śmierci dobiegła końca (obiekt niewidoczny)
if (self.dead && self.alpha === 0) {
// Gdy animacja śmierci dobiegła końca (obiekt niewidoczny)
return;
}
// Jeśli gracz jest martwy, ale nadal widoczny, pozwól animacji zanikania działać (obsługiwane przez tween)
if (self.dead) {
return; // Nie wykonuj logiki ruchu i nietykalności jeśli gracz jest martwy
}
// Obsługa cooldownu turlania
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
// Obsługa klatek nietykalności
if (self.invulnerable && self.invulnerabilityFrames > 0) {
self.invulnerabilityFrames--;
// Efekt migotania (zmiana przezroczystości)
self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1; // Miga co kilka klatek
if (self.invulnerabilityFrames <= 0) {
self.invulnerable = false;
self.alpha = 1; // Przywróć pełną przezroczystość gdy nietykalność minie
}
}
// Obsługa ruchu podczas turlania - WYKONUJ TYLKO JEŚLI self.rolling JEST TRUE
if (self.rolling) {
// Jawne sprawdzenie flagi rolling
self.x += self.rollDirection.x * self.rollSpeed;
self.y += self.rollDirection.y * self.rollSpeed;
// !!! NOWA LOGIKA: Sprawdzenie kolizji z bossem podczas turlania i zadawanie obrażeń !!!
// Sprawdź tylko jeśli boss istnieje, nie jest martwy i JESZCZE nie zadałeś obrażeń tym turlaniem
if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
// Uproszczone sprawdzenie kolizji na podstawie odległości między środkami
var dx = self.x - boss.x;
var dy = self.y - boss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Uproszczony promień kolizji (połówka szerokości/wysokości)
var playerCollisionRadius = self.width / 2; // Zakładamy, że kolizja gracza jest okrągła
var bossCollisionRadius = boss.width / 2; // Zakładamy, że kolizja bossa jest okrągła
// Jeśli odległość między środkami jest mniejsza niż suma promieni kolizji
if (distance < playerCollisionRadius + bossCollisionRadius) {
// Kolizja wykryta podczas turlania
boss.takeDamage(10); // ZADJ 10 OBRAŻEŃ BOSSOWI
self.hasRolledThroughBossThisRoll = true; // Oznacz, że zadałeś obrażenia w tym turlaniu
// Opcjonalny efekt wizualny/dźwiękowy trafienia bossa turlaniem
LK.effects.flashObject(boss, 0x00FF00, 200); // Mignij bossa na zielono
// LK.getSound('playerHitBossSound').play(); // Wymagałoby dodania nowego assetu dźwiękowego
}
}
}
// 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 Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Player properties
self.health = 5; // Default health (will be overridden in startGame)
self.speed = 8; // Movement speed
self.rolling = false; // Flag for roll state
self.rollDirection = {
x: 0,
y: 0
}; // Direction of roll
self.rollSpeed = 20; // Speed during roll
self.rollDuration = 300; // Roll duration in ms
self.rollCooldown = 0; // Cooldown between rolls
self.invulnerable = false; // Invulnerability flag
self.invulnerabilityFrames = 0; // Frames of invulnerability left
self.dead = false; // Dead state
self.rollTimeoutId = null; // Store timeout ID for roll duration
self.invulnerabilityTimeoutId = null; // Store timeout ID for invulnerability
self.hasRolledThroughBossThisRoll = false; // Track if boss was hit during this roll
// Roll mechanic
self.roll = function (direction) {
// Check if player can roll (not already rolling and cooldown is 0)
if (!self.rolling && self.rollCooldown <= 0) {
self.rolling = true;
self.rollDirection = direction;
self.rollCooldown = 30; // Set cooldown in frames
self.invulnerable = true; // Player is invulnerable during roll
self.invulnerabilityFrames = 30; // Set invulnerability frames
self.hasRolledThroughBossThisRoll = false; // Reset boss hit tracking for this roll
// Create roll effect
var rollEffect = game.addChild(LK.getAsset('roll', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
}));
// Animation for roll effect
tween(rollEffect, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) {
rollEffect.destroy();
}
}
});
// End roll after duration
self.rollTimeoutId = LK.setTimeout(function () {
self.rolling = false;
}, self.rollDuration);
}
};
// Clear all roll and invulnerability timeouts
self.clearRollTimeouts = function () {
if (self.rollTimeoutId) {
LK.clearTimeout(self.rollTimeoutId);
self.rollTimeoutId = null;
}
if (self.invulnerabilityTimeoutId) {
LK.clearTimeout(self.invulnerabilityTimeoutId);
self.invulnerabilityTimeoutId = null;
}
};
// Take damage method
self.takeDamage = function (amount) {
// Check if player can take damage (not invulnerable and not dead)
if (!self.invulnerable && !self.dead) {
self.health -= amount;
// Flash effect when hit
LK.effects.flashObject(self, 0xFF0000, 200);
// Check if player is dead
if (self.health <= 0) {
self.die();
return;
}
// Set invulnerability after hit
self.invulnerable = true;
self.invulnerabilityFrames = 60; // 1 second at 60fps
}
};
// Die method
self.die = function () {
if (self.dead) {
return;
}
self.dead = true;
// Increment death counter
storage.totalDeaths = (storage.totalDeaths || 0) + 1;
// Increment max hearts after death (up to a maximum of 10)
if (storage.maxHearts < 10) {
storage.maxHearts = (storage.maxHearts || 5) + 1;
}
// Death animation
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self && self.destroy && !self.destroyed) {
self.destroy();
}
// Set death reason flag to true (player died)
gameOverReasonIsDeath = true;
// Show game over screen
gameState.gameOver(true);
}
});
};
// Update method called every frame
self.update = function () {
// Track last position for collision detection
self.lastX = self.x;
self.lastY = self.y;
// Don't update if player is dead
if (self.dead) {
return;
}
// Handle roll cooldown
if (self.rollCooldown > 0) {
self.rollCooldown--;
}
// Handle invulnerability frames
if (self.invulnerable && self.invulnerabilityFrames > 0) {
self.invulnerabilityFrames--;
// Blinking effect during invulnerability
self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1;
if (self.invulnerabilityFrames <= 0) {
self.invulnerable = false;
self.alpha = 1; // Restore full opacity when invulnerability ends
}
}
// Handle movement during roll
if (self.rolling) {
self.x += self.rollDirection.x * self.rollSpeed;
self.y += self.rollDirection.y * self.rollSpeed;
// Check for collision with boss during roll
if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
var dx = self.x - boss.x;
var dy = self.y - boss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var playerCollisionRadius = self.width / 2;
var bossCollisionRadius = boss.width / 2;
if (distance < playerCollisionRadius + bossCollisionRadius) {
// Collision with boss during roll - deal damage to boss
boss.takeDamage(10);
self.hasRolledThroughBossThisRoll = true;
// Visual effect for hitting boss with roll
LK.effects.flashObject(boss, 0x00FF00, 200);
}
}
}
// Keep player within screen bounds (accounting for walls)
self.x = Math.max(100, Math.min(self.x, 2048 - 100));
self.y = Math.max(100, Math.min(self.y, 2732 - 100));
// Check for collision with boss attacks
if (boss && !boss.dead && boss.attacks) {
for (var i = 0; i < boss.attacks.length; i++) {
var attack = boss.attacks[i];
var dx = self.x - attack.x;
var dy = self.y - attack.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var playerRadius = self.width / 2;
// If player collides with attack and is not invulnerable
if (distance < playerRadius + attack.radius && !self.invulnerable) {
self.takeDamage(1);
break; // Only take damage from one attack per frame
}
}
}
};
return self;
});
// Shape class implementation for creating simple shapes
var Shape = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};
var width = options.width || 100;
var height = options.height || 100;
var color = options.color || 0xFFFFFF;
var shape = options.shape || 'box';
// Create the shape as an asset
var asset = self.attachAsset(shape, {
anchorX: 0.5,
anchorY: 0.5,
width: width,
height: height,
tint: color
});
// Set width and height for easier access
self.width = width;
self.height = height;
// Add anchor property to Shape for positioning
self.anchor = {
set: function set(x, y) {
// This mimics the behavior of the anchor.set method
self.anchorX = x;
self.anchorY = y;
}
};
return self;
});
var UI = Container.expand(function () {
var self = Container.call(this);
// Create heart containers (wizualizacja zdrowia)
self.hearts = [];
self.heartContainer = new Container(); // Kontener na ikony serc
self.addChild(self.heartContainer);
// Title and messages (teksty na ekranach)
self.titleText = new Text2("ROLL SOULS", {
size: 150,
fill: 0xFFFFFF
});
self.titleText.anchor.set(0.5, 0.5);
self.addChild(self.titleText);
self.messageText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
self.messageText.anchor.set(0.5, 0.5);
self.addChild(self.messageText);
// Deaths counter (licznik śmierci)
self.deathsText = new Text2("Deaths: 0", {
size: 40,
fill: 0xFFFFFF
});
self.deathsText.anchor.set(1, 0); // Wyrównanie do prawego górnego rogu
self.deathsText.x = 2048 - 50; // Pozycja X od prawej krawędzi
self.deathsText.y = 50; // Pozycja Y od góry
self.addChild(self.deathsText);
// Tutorial text (teksty tutoriali)
self.tutorialText = new Text2("", {
size: 50,
fill: 0xFFFFFF
});
self.tutorialText.anchor.set(0.5, 0); // Wyrównanie do środka na górze
self.tutorialText.x = 2048 / 2; // Pozycja X na środku
self.tutorialText.y = 200; // Pozycja Y (może być różna w zależności od stanu)
self.addChild(self.tutorialText);
// --- Timer Text ---
self.timerText = new Text2("2:00", {
// Początkowe wyświetlanie czasu (2 minuty)
size: 60,
// Rozmiar czcionki
fill: 0xFFFFFF // Biały kolor
});
self.timerText.anchor.set(0, 0); // Punkt odniesienia w lewym górnym rogu
self.timerText.x = 50; // Pozycja X od lewej krawędzi
self.timerText.y = 50; // Pozycja Y od górnej krawędzi
self.timerText.alpha = 0; // Domyślnie ukryty
self.addChild(self.timerText);
// --- Boss Health Bar (wizualizacja zdrowia bossa) ---
self.bossHealthBarContainer = new Container(); // Kontener na pasek zdrowia bossa
self.bossHealthBarContainer.x = 2048 / 2; // Wyśrodkuj poziomo
self.bossHealthBarContainer.y = 150; // Pozycja Y (poniżej timera)
self.bossHealthBarContainer.alpha = 0; // Domyślnie ukryty
self.addChild(self.bossHealthBarContainer);
var barWidth = 800; // Szerokość paska zdrowia (musi być taka sama jak szerokość assetu bossHpbar)
var barHeight = 30; // Wysokość paska zdrowia (musi być taka sama jak wysokość assetu bossHpbar)
// Tło paska zdrowia bossa (szare) - nadal używamy Shape dla tła
self.bossHealthBarBg = new Shape({
width: barWidth,
height: barHeight,
color: 0x555555,
// Szary kolor tła
shape: 'box'
});
self.bossHealthBarBg.anchor.set(0.5, 0.5); // Ustaw punkt odniesienia na środek
self.bossHealthBarContainer.addChild(self.bossHealthBarBg);
// Właściwy pasek zdrowia bossa (czerwony) - UŻYWAMY TERAZ NOWEGO ASSETU bossHpbar
self.bossHealthBar = self.attachAsset('bossHpbar', {
// Użyj assetu 'bossHpbar'
anchorX: 0,
// Ustaw punkt odniesienia na lewą krawędź (0), środek pionowo (0.5)
anchorY: 0.5,
x: -barWidth / 2 // Przesuń w lewo o połowę szerokości tła, żeby lewa krawędź paska zdrowia była na środku kontenera (czyli na środku ekranu)
// Szerokość i wysokość są brane z assetu, nie ustawiamy ich tutaj jawnie (chyba że chcemy skalować)
});
self.bossHealthBarContainer.addChild(self.bossHealthBar);
// Aktualizuje wizualizację serc na UI
self.updateHearts = function (current, max) {
// 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
};
// Aktualizuje wizualizację paska zdrowia bossa
self.updateBossHealth = function (current, max) {
// Oblicz szerokość paska zdrowia na podstawie aktualnego i maksymalnego zdrowia
var barWidth = 800; // Całkowita szerokość paska (musi być taka sama jak szerokość assetu bossHpbar)
var currentWidth = current / max * barWidth;
// Upewnij się, że pasek zdrowia (obiekt sprite) istnieje przed aktualizacją
if (self.bossHealthBar) {
// Zmień szerokość sprite'a, aby odzwierciedlić aktualne zdrowie
self.bossHealthBar.width = currentWidth;
// Ustaw widoczność kontenera paska zdrowia bossa (jeśli boss istnieje i żyje i jesteśmy w grze)
self.bossHealthBarContainer.alpha = (gameState.currentState === "game" || gameState.currentState === "gameOver") && boss && !boss.dead && current > 0 ? 1 : 0; // Pokaż pasek w stanie gry ORAZ game over (przed zresetowaniem UI) tylko jeśli boss żyje i ma >0 HP.
}
// Upewnij się, że pasek zdrowia tła też jest widoczny, gdy kontener jest widoczny
if (self.bossHealthBarBg) {
self.bossHealthBarBg.alpha = self.bossHealthBarContainer.alpha;
}
};
// Wyświetla komunikat na środku ekranu
self.showMessage = function (message, duration) {
self.messageText.setText(message);
self.messageText.alpha = 1; // Ustaw pełną przezroczystość
// Wyczyść istniejący timer zanikania, jeśli istnieje
if (self.messageTimeout) {
LK.clearTimeout(self.messageTimeout);
}
// 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;
// Ukryj pasek zdrowia bossa
self.bossHealthBarContainer.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;
// Pokaż pasek zdrowia bossa (jeśli boss żyje)
// Pasek będzie widoczny tylko w stanie "game" dzięki updateBossHealth i update w game.update
self.bossHealthBarContainer.alpha = 1;
break;
case "grillMenu":
// Nowy stan dla Grill Screena
self.titleText.alpha = 0; // Ukryj tytuł na Grill Screenie (lub ustaw na "Grill Menu" jeśli chcesz)
self.messageText.x = 2048 / 2; // Pozycjonowanie komunikatów/tekstów przycisków na środku
self.messageText.y = 500; // Możesz dostosować pozycję
self.messageText.setText(""); // Wyczyść domyślny messageText
self.tutorialText.alpha = 0; // Ukryj tutorial
// Ukryj serca i timer
self.heartContainer.alpha = 0;
self.timerText.alpha = 0;
// Ukryj pasek zdrowia bossa
self.bossHealthBarContainer.alpha = 0;
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" lub komunikat zwycięstwa
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" (choć restart jest timerem)
// Ukryj serca i timer na ekranie Game Over
self.heartContainer.alpha = 0;
self.timerText.alpha = 0;
// Pokaż pasek zdrowia bossa TYLKO JEŚLI TO ZWYCIĘSTWO PRZEZ CZAS W BOSS+ (gameover reason is not death)
self.bossHealthBarContainer.alpha = gameState.gameOverReasonIsDeath === false && isNewBossPlusMode ? 1 : 0;
break;
case "intro": // Pozycje dla ekranów intro/tutoriali (głównie ukryte elementy UI walki)
case "fakeTutorial":
case "realTutorial":
self.titleText.alpha = 0; // Ukryj tytuł
self.messageText.alpha = 0; // Ukryj komunikat
self.tutorialText.alpha = 0; // Ukryj tutorial
// Licznik śmierci jest zawsze widoczny
self.deathsText.x = 2048 - 50; // Upewnij się, że jest w prawym górnym rogu
self.deathsText.y = 50;
// Ukryj serca i timer
self.heartContainer.alpha = 0;
self.timerText.alpha = 0;
// Ukryj pasek zdrowia bossa
self.bossHealthBarContainer.alpha = 0;
break;
}
// Upewnij się, że licznik śmierci jest zawsze widoczny
self.deathsText.alpha = 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111 // Ciemne tło domyślne
});
/****
* Game Code
****/
// Zachowujemy na wypadek użycia w animacjach gracza/UI
// Używamy dźwięku zwycięstwa z stworzylo zloto.txt
// Przykład koloru, użyje grafiki z upit
// Przykład koloru, użyje grafiki z upit
// Asset dla tła Grill Screena (dostosuj wymiary jeśli Twoja grafika ma inne)
// Zmieniono wymiary przycisku
// Twój nowy asset dla paska zdrowia bossa (800x30)
// Zachowujemy jako domyślne tło lub na wypadek potrzeby
// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
var currentSceneElements = new Container(); // Dodano z souls4.txt i rozbudowano dla Grill Screena
// Zmienne gry (z stworzylo zloto.txt)
var player;
var boss;
var ui;
var walls = []; // Ściany areny bossa
// Dodano zmienną do przechowywania aktywnego tła sceny
var currentBackground = null;
// Dodana flaga do śledzenia, czy jesteśmy w trybie New Boss+
var isNewBossPlusMode = false;
// Dodana flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
var gameOverReasonIsDeath = false;
// Funkcja do czyszczenia elementów z kontenera currentSceneElements (dla scen intro/tutoriali i Grill Screena)
// Nie usuwa tła sceny (currentBackground) ani elementów UI.
function clearScene() {
// Dodano z souls4.txt i rozbudowano dla Grill Screena
// Usuń wszystkie dzieci z kontenera
while (currentSceneElements.children.length > 0) {
var child = currentSceneElements.children[0];
// Upewnij się, że obiekt istnieje i ma metodę destroy przed wywołaniem
if (child && child.destroy) {
child.destroy(); // Użyj destroy jeśli dostępne
} else {
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, timer i Grill Screen)
var gameState = {
currentState: "title",
// Początkowy stan gry
// Zmienne dla timera walki z bossem
gameDuration: 120,
// Czas trwania walki z bossem w sekundach (2 minuty) - Domyślne
remainingTime: 0,
// Pozostały czas
gameTimerInterval: null,
// ID interwału timera
// Zmienne dla fałszywego tutorialu
fakeTutorialTimerId: null,
// ID timera przechodzącego do prawdziwego tutorialu
// Zmienne dla przyspieszenia bossa
bossSpeedIncreased: false,
// Flaga informująca czy prędkość bossa została już zwiększona
// Zmienne do śledzenia gestów dotykowych/myszy (dla turlania i tapowania)
touchStart: {
x: 0,
y: 0
},
touchEnd: {
x: 0,
y: 0
},
// Inicjalizacja gry (wywoływana raz na początku aplikacji)
init: function init() {
// ZRESETUJ LICZNIK ŚMIERCI I USTAW DOMYŚLNĄ ILOŚĆ SERC NA START APLIKACJI
// Resetuj licznik śmierci PRZY KAŻDYM URUCHOMIENIU GRY (załadowaniu strony)
storage.totalDeaths = 0;
// Ustaw początkową liczbę serc na 5 przy każdym uruchomieniu gry
storage.maxHearts = 5;
// Zresetuj flagę trybu New Boss+ na początku gry
isNewBossPlusMode = false;
// Zresetuj flagę przyczyny Game Over
gameOverReasonIsDeath = false;
// Nie dodajemy tutaj domyślnego tła 'bg'. Tła będą dodawane w metodach scen.
game.setBackgroundColor(0x111111); // Ustaw domyślny kolor tła (widoczny np. podczas ładowania assetów)
// Stwórz ściany areny bossa (potrzebne w stanie gry, ale stworzone wcześniej)
this.createWalls();
// Stwórz UI (ikony serc, teksty tytułów/komunikatów/tutoriali, licznik śmierci, timer, pasek zdrowia bossa)
ui = game.addChild(new UI());
// Pozycjonuj UI początkowo dla ekranu tytułowego
ui.positionElements("title");
ui.updateDeathsCounter(); // Zaktualizuj wyświetlanie licznika śmierci (powinien być 0)
ui.updateHearts(storage.maxHearts, storage.maxHearts); // Zaktualizuj wyświetlanie serc (5 pełnych)
// Dodaj kontener na elementy scen intro i Grill Screena do wyświetlania (nad UI)
game.addChild(currentSceneElements); // currentSceneElements powinien być dodany po UI, żeby być nad nim
// Rozpocznij muzykę w tle
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Rozpocznij od ekranu tytułowego zarządzanego przez ten obiekt gameState
this.showTitleScreen();
},
// Tworzy/odświeża ściany areny bossa
createWalls: function createWalls() {
// Usuń istniejące ściany, jeśli istnieją
walls.forEach(function (wall) {
if (wall && wall.destroy) {
wall.destroy();
}
});
walls = [];
// Lewa ściana
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
walls.push(leftWall);
// Prawa ściana
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0,
anchorY: 0,
x: 2048 - 100,
y: 0
}));
walls.push(rightWall);
// Górna ściana
var topWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
walls.push(topWall);
// Dolna ściana
var bottomWall = game.addChild(LK.getAsset('floor', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - 100
}));
walls.push(bottomWall);
// Upewnij się, że ściany są na odpowiedniej warstwie (pod graczem i bosem)
// Domyślnie obiekty są dodawane w kolejności, więc ściany dodane przed graczem i bosem będą pod nimi.
// UI i currentSceneElements dodane na końcu w init są na górze.
},
// ---------- Metody przejścia między stanami ----------
// Przejście do ekranu tytułowego
showTitleScreen: function showTitleScreen() {
clearScene(); // Wyczyść elementy poprzedniej sceny (np. z Grill Screena, Victory lub Fake Death)
// Upewnij się, że timer fake tutorialu jest wyczyszczony
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
// Zatrzymaj wszelkie trwające tweeny tła
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy(); // Usuń poprzednie tło
currentBackground = null;
}
this.currentState = "title"; // Ustaw stan na tytuł
game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe dla tytułu
// Dodaj tło ekranu tytułowego (titleBg)
currentBackground = game.addChildAt(LK.getAsset('titleBg', {
// Dodaj na spód (index 0)
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}), 0); // Dodaj jako pierwsze dziecko gry (na samym spodzie)
// Ukryj obiekty gry (gracz, boss) jeśli istnieją
if (player) {
player.alpha = 0;
}
if (boss) {
boss.alpha = 0;
}
// Pokaż ściany jako tło menu/intro (jeśli nie mają być widoczne, ustaw alpha na 0)
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("title"); // Pozycjonuj UI dla stanu tytułowego
ui.titleText.setText("ROLL SOULS"); // Upewnij się, że tytuł jest poprawny
ui.titleText.alpha = 1; // Pokaż tytuł
ui.showMessage("Tap to Start", 0); // Wyświetl komunikat startowy
ui.showTutorial("Swipe to Roll - Death is Progress"); // Wyświetl tekst tutorialu (można go ukryć i pokazać dopiero w prawdziwym tutorialu)
// Resetuj stan touchGesture na wypadek kliknięcia w poprzednim stanie końcowym
this.touchStart = {
x: 0,
y: 0
};
this.touchEnd = {
x: 0,
y: 0
};
// --- Dodaj testowy przycisk do Grill Menu ---
var grillMenuTestButton = new Container();
grillMenuTestButton.interactive = true; // Spraw, żeby przycisk był interaktywny
grillMenuTestButton.x = 2048 / 2; // Wyśrodkuj poziomo
grillMenuTestButton.y = 1600; // Ustaw pozycję pionową (np. poniżej innych tekstów)
currentSceneElements.addChild(grillMenuTestButton); // Dodaj do kontenera sceny tytułowej
var grillButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
grillMenuTestButton.addChild(grillButtonBg); // Dodaj tło przycisku
var grillButtonText = new Text2('Go to Grill Menu', {
size: 50,
fill: 0xFFFFFF
}); // Tekst przycisku
grillButtonText.anchor.set(0.5, 0.5);
grillMenuTestButton.addChild(grillButtonText); // Dodaj tekst do przycisku
// Logika przycisku: po kliknięciu przejdź do Grill Menu
grillMenuTestButton.down = function () {
// Opcjonalnie: wyczyść elementy sceny tytułowej przed przejściem
clearScene();
// Przejdź do ekranu Grill Menu
gameState.showGrillScreen();
};
// --- Koniec dodawania przycisku testowego ---
},
// Przejście do pierwszej sceny intro
showIntro: function showIntro() {
clearScene(); // Wyczyść elementy poprzedniej sceny (Tytuł)
// Upewnij się, że timer fake tutorialu jest wyczyszczony
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
// Zatrzymaj wszelkie trwające tweeny tła
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy(); // Usuń poprzednie tło
currentBackground = null;
}
this.currentState = "intro"; // Ustaw stan na intro
game.setBackgroundColor(0x111111); // Ciemne tło bazowe dla intro
// Dodaj tło scen intro (introBg)
currentBackground = game.addChildAt(LK.getAsset('introBg', {
// Dodaj na spód (index 0)
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1 // Upewnij się, że zaczyna w normalnym rozmiarze
}), 0);
// Zastosuj powolny zoom do tła intro
// Zoom IN do 1.1x przez 30 sekund, potem zoom OUT, powtarzaj
tween(currentBackground, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 30000,
// 30 sekund na jeden kierunek zoomu
easing: tween.linear,
repeat: Infinity,
// Powtarzaj w nieskończoność
yoyo: true // Zoomuj w tam i z powrotem
});
// Ukryj obiekty gry i ściany na czas intro/tutoriali
if (player) {
player.alpha = 0;
}
if (boss) {
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
}); // Ukryj ściany
ui.positionElements("intro"); // Ukryj elementy UI walki (zostaw licznik śmierci)
// Tekst pierwszej sceny intro (z souls4.txt)
var introText1 = new Text2('From the creators of FromSoftware...', {
size: 90,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
introText1.anchor.set(0.5, 0.5);
introText1.x = 2048 / 2;
introText1.y = 2732 / 2;
currentSceneElements.addChild(introText1); // Dodaj do kontenera, który będzie czyszczony
// Timer 1: Zmień tekst po 5 sekundach
LK.setTimeout(function () {
// Usuń pierwszy tekst
if (introText1.parent) {
introText1.destroy();
}
// Tekst drugiej sceny intro (z souls4.txt)
var introText2 = new Text2('...I have no idea. I don’t know them.', {
size: 90,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
introText2.anchor.set(0.5, 0.5);
introText2.x = 2048 / 2;
introText2.y = 2732 / 2;
currentSceneElements.addChild(introText2); // Dodaj do kontenera
// Timer 2: Przejdź do fałszywego tutorialu po kolejnych 2 sekundach
this.fakeTutorialTimerId = LK.setTimeout(function () {
// ZAPISZ ID TIMERA
// clearScene() zostanie wywołane na początku następnej metody
// Zresetuj ID timera, bo się zakończył
gameState.fakeTutorialTimerId = null;
// Przejdź do fałszywego tutorialu
gameState.showFakeTutorial();
}.bind(this), 2000); // 2 sekundy opóźnienia po wyświetleniu drugiego tekstu. Użyj .bind(this) dla dostępu do this.fakeTutorialTimerId
}.bind(this), 5000); // 5 sekund opóźnienia przed wyświetleniem drugiego tekstu. Użyj .bind(this) dla dostępu do this.fakeTutorialTimerId
},
// Przejście do sceny fałszywego tutorialu (Press LPM to block)
showFakeTutorial: function showFakeTutorial() {
clearScene(); // Wyczyść poprzednie elementy (tekst intro)
// Tło introBg i jego zoom powinny pozostać, bo nie są w currentSceneElements
// Upewnij się, że timer fake tutorialu jest wyczyszczony (na wypadek gdybyśmy tu dotarli inaczej niż przez timer)
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
this.currentState = "fakeTutorial"; // Ustaw stan na fałszywy tutorial
game.setBackgroundColor(0x000000); // Czarne tło - Może chcesz, żeby tło introBg było widoczne? Zmieniam na czarne zgodnie z oryginalnym souls4.txt, ale możesz to zmienić.
// Ukryj obiekty gry i ściany
if (player) {
player.alpha = 0;
}
if (boss) {
boss.alpha = 0;
}
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Tekst fałszywego tutorialu - Zmieniony na "Press LPM to block"
var instructionText = new Text2('Press LPM to block.. or don’t press.', {
size: 100,
fill: 0xFFFFFF,
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2;
currentSceneElements.addChild(instructionText);
// Timer przechodzący do prawdziwego tutorialu - ZAPISZ JEGO ID
this.fakeTutorialTimerId = LK.setTimeout(function () {
// clearScene() zostanie wywołane na początku następnej metody
// Zresetuj ID timera, bo się zakończył
gameState.fakeTutorialTimerId = null;
// Przejdź do prawdziwego tutorialu
gameState.showRealTutorial();
}, 6000); // 6 sekund
},
// Obsługa inputu w stanie fakeTutorial (fałszywa śmierć)
handleFakeTutorialInput: function handleFakeTutorialInput() {
// Wyczyść timer, który miał prowadzić do prawdziwego tutorialu
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null; // Zresetuj ID timera
}
// Wyczyść ekran fałszywego tutorialu
clearScene();
// Ustaw stan na game over (tymczasowo, żeby logika UI się zgadzała)
this.currentState = "gameOver"; // Można użyć dedykowanego stanu 'fakeGameOver' jeśli chcemy inaczej obsłużyć UI
// Zatrzymaj wszelkie trwające tweeny tła (np. zoom intro)
if (currentBackground) {
tween.stop(currentBackground);
// Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem "YOU DIED"
// currentBackground.destroy();
currentBackground = null;
}
game.setBackgroundColor(0x000000); // Czarne tło bazowe za fake game over
// Wyświetl "YOU DIED" na czerwono
var diedText = new Text2("YOU DIED", {
size: 150,
fill: 0xFF0000 // Czerwony kolor
});
diedText.anchor.set(0.5, 0.5);
diedText.x = 2048 / 2;
diedText.y = 800;
currentSceneElements.addChild(diedText);
// Wyświetl "Did you check the title of the game?"
var explanationText = new Text2("Did you check the title of the game?", {
size: 60,
fill: 0xFFFFFF,
// Biały kolor
align: 'center',
wordWrap: true,
wordWrapWidth: 1800
});
explanationText.anchor.set(0.5, 0.5);
explanationText.x = 2048 / 2;
explanationText.y = 1000;
currentSceneElements.addChild(explanationText);
// Dodaj przycisk "How to play again"
var howToPlayButtonContainer = new Container();
howToPlayButtonContainer.interactive = true;
howToPlayButtonContainer.x = 2048 / 2;
howToPlayButtonContainer.y = 1300; // Pozycja przycisku
currentSceneElements.addChild(howToPlayButtonContainer);
var buttonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
howToPlayButtonContainer.addChild(buttonBg);
var buttonText = new Text2('How to play again', {
size: 60,
fill: 0xFFFFFF
}); // Zmniejszono rozmiar czcionki, żeby pasowała do przycisku
buttonText.anchor.set(0.5, 0.5);
howToPlayButtonContainer.addChild(buttonText);
// Akcja przycisku: Przejdź z powrotem do prawdziwego tutorialu
howToPlayButtonContainer.down = function () {
gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
};
},
// Przejście do stanu gry (walka z bossem)
startGame: function startGame() {
// Upewnij się, że timer fake tutorialu jest wyczyszczony
if (this.fakeTutorialTimerId) {
LK.clearTimeout(this.fakeTutorialTimerId);
this.fakeTutorialTimerId = null;
}
// Zatrzymaj wszelkie trwające tweeny tła
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy(); // Usuń tło intro/tytułu
currentBackground = null;
}
this.currentState = "game"; // Ustaw stan na "game"
game.setBackgroundColor(0x111111); // Ciemne tło bazowe dla gry
// Pokaż obiekty gry (gracz, boss), ukryte w intro/tutorialu
// Upewnij się, że istnieją przed ustawieniem alpha
if (player) {
player.alpha = 1;
}
if (boss) {
boss.alpha = 1;
}
// Pokaż ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 1;
}
});
ui.positionElements("game"); // Pozycjonuj UI dla stanu gry (ikony serc, licznik śmierci, timer)
// Stwórz gracza (jeśli nie istnieje lub zniszcz poprzedniego)
if (player) {
player.destroy();
} // Zniszcz poprzedniego gracza jeśli był
player = game.addChild(new Player()); // Stwórz nowego gracza
// Ustaw początkowe zdrowie gracza na podstawie storage.maxHearts (domyślnie 5 na start aplikacji, może wzrośnie po śmierciach)
player.health = storage.maxHearts || 5;
// Jawnie zresetuj stan turlania i nietykalności gracza na początku walki
player.rolling = false;
player.invulnerable = false;
player.invulnerabilityFrames = 0;
player.rollCooldown = 0;
player.clearRollTimeouts(); // Wyczyść timery turlania/nietykalności
player.hasRolledThroughBossThisRoll = false; // Zresetuj flagę obrażeń od turlania dla nowego gracza
player.x = 2048 / 2;
player.y = 2732 / 2 + 400; // Pozycja startowa gracza
// Stwórz bossa (jeśli nie istnieje lub zniszcz poprzedniego)
if (boss) {
boss.destroy();
} // Zniszcz poprzedniego bossa jeśli był
boss = game.addChild(new Boss()); // Stwórz nowego bossa
boss.x = 2048 / 2;
boss.y = 2732 / 2 - 400; // Pozycja startowa bossa
// Upewnij się, że boss zaczyna w stanie gotowości do ataku z wyczyszczonymi poprzednimi atakami
boss.attackCooldown = 0; // Boss zaatakuje po pierwszym opóźnieniu
boss.attacks = []; // Wyczyść wszelkie pozostałości po poprzednich atakach
boss.attackSpeedMultiplier = 1; // Ustaw początkowy mnożnik prędkości ataków na 1 (normalny)
boss.repositioning = false; // Zresetuj flagę repozycji bossa
// !!! Ustaw zdrowie bossa i czas gry W ZALEŻNOŚCI OD TRYBU !!!
if (isNewBossPlusMode) {
boss.maxHealth = 2000; // New Boss+ ma 2000 HP
boss.health = boss.maxHealth;
this.gameDuration = 600; // New Boss+ ma 10 minut (600 sekund)
// W trybie New Boss+ ataki są szybsze OD POCZĄTKU i NIE MA dodadkowego przyspieszenia po minucie
boss.attackSpeedMultiplier = 0.6; // Przykładowy, niższy mnożnik (szybsze ataki) OD POCZĄTKU
// Można tu dodać inne modyfikatory dla New Boss+, np. wyższą prędkość ruchu bossa
boss.speed = 7; // Przykładowe szybsze poruszanie się bossa
} else {
// Standardowy boss
boss.maxHealth = 200;
boss.health = boss.maxHealth;
this.gameDuration = 120; // Standardowy czas 2 minuty
boss.speed = 5; // Standardowa prędkość poruszania się bossa
boss.attackSpeedMultiplier = 1; // Standardowa prędkość ataków
}
// Aktualizuj UI (wyświetl serca i początkowy komunikat)
ui.updateHearts(player.health, storage.maxHearts); // Wyświetl aktualne serca (powinno być 5 lub więcej)
ui.showTutorial("Swipe to roll away from attacks!"); // Wyświetl tutorial dla stanu gry
ui.updateBossHealth(boss.health, boss.maxHealth); // Zaktualizuj pasek zdrowia bossa z poprawnym HP
// --- Timer walki z bossem ---
this.remainingTime = this.gameDuration; // Ustaw początkowy czas ZALEŻNIE OD TRYBU
ui.updateTimerDisplay(this.remainingTime); // Wyświetl początkowy czas
// Rozpocznij interwał timera
// Wyczyść poprzedni interwał timera, jeśli istniał (przy restartach gry)
if (this.gameTimerInterval) {
LK.clearInterval(this.gameTimerInterval);
}
// Zresetuj flagę przyspieszenia bossa po 1 minucie (dotyczy obu trybów)
this.bossSpeedIncreased = false;
this.gameTimerInterval = LK.setInterval(function () {
// Tylko aktualizuj timer i sprawdzaj warunek zwycięstwa/game over jeśli jesteśmy w stanie gry
if (gameState.currentState === "game") {
gameState.remainingTime--; // Zmniejsz pozostały czas
ui.updateTimerDisplay(gameState.remainingTime); // Zaktualizuj wyświetlanie timera na UI
// --- Przyspieszenie bossa - TYLKO W STANDARDOWYM TRYBIE ---
if (!isNewBossPlusMode) {
// Przyspieszenie tylko w standardowym trybie
var accelerationThreshold = gameState.gameDuration - 60; // Przyspieszenie na 60 sekund przed końcem
if (gameState.remainingTime <= accelerationThreshold && !gameState.bossSpeedIncreased) {
gameState.bossSpeedIncreased = true; // Ustaw flagę
if (boss && !boss.dead) {
// Upewnij się, że boss istnieje i żyje
// Zmniejsz mnożnik prędkości ataków bossa (np. o kolejne 30% -> mnożnik 0.7 * 1 = 0.7)
boss.attackSpeedMultiplier *= 0.7; // Przykładowe dalsze przyspieszenie ataków
// Opcjonalnie: wyświetl komunikat o przyspieszeniu
ui.showMessage("Boss attacks faster!", 2000); // Komunikat
}
}
}
// --- Koniec Przyspieszenia bossa ---
// Sprawdź warunek końca gry (czas minął)
if (gameState.remainingTime <= 0) {
LK.clearInterval(gameState.gameTimerInterval); // Stop the timer
if (isNewBossPlusMode) {
// Czas minął w trybie Boss+ (zwycięstwo przez przetrwanie)
gameOverReasonIsDeath = false; // Czas minął
gameState.gameOver(false); // Przejdź do stanu Game Over z komunikatem zwycięstwa Boss+
} else {
// Czas minął w trybie standardowym (zwycięstwo przez przetrwanie)
// Przejdź bezpośrednio do Grill Menu
gameState.showGrillScreen();
}
}
}
}, 1000); // Interwał co 1 sekundę (1000 ms)
// --- Koniec Timer walki z bossem ---
// Zaplanuj rozpoczęcie ataków bossa po krótkim opóźnieniu
// Nie potrzebujemy jawnie czyścić timeoutu, bo startAttackPattern ma sprawdzenie stanu gry
LK.setTimeout(function () {
// Sprawdź, czy nadal jesteśmy w stanie gry gdy timeout minie i czy boss istnieje i żyje
if (gameState.currentState === "game" && boss && !boss.dead) {
ui.hideTutorial(); // Ukryj tekst tutorialu
boss.startAttackPattern(); // Rozpocznij ataki bossa
}
}, 3000); // 3 sekundy opóźnienia przed pierwszym atakiem bossa
},
// Ta metoda zwycięstwa (przez czas w trybie standardowym) przechodzi teraz do Grill Screena
victory: function victory() {
// Ta metoda nie jest już używana do zarządzania stanami gry po zwycięstwie przez czas.
// Logika przejścia do Grill Screena dla standardowego zwycięstwa przez czas jest teraz bezpośrednio w gameTimerInterval.
console.log("Old victory state - transition is handled by gameTimerInterval now.");
},
// Przejście do ekranu Grilla po zwycięstwie (pokonaniu bossa) lub przetrwaniu czasu
showGrillScreen: function showGrillScreen() {
clearScene(); // Wyczyść elementy poprzedniej sceny (gra, ataki, itp.)
// Zatrzymaj timer gry, jeśli nadal działa (jeśli wygrana była przez zbicie HP bossa, a nie czasem)
LK.clearInterval(this.gameTimerInterval);
// Zresetuj flagę przyspieszenia bossa
this.bossSpeedIncreased = false;
this.currentState = "grillMenu"; // Ustaw nowy stan gry
game.setBackgroundColor(0x333333); // Możesz ustawić kolor tła lub pozwolić assetowi to zrobić
// Zatrzymaj wszelkie trwające tweeny tła gry i usuń tło gry, jeśli istnieje
if (currentBackground) {
tween.stop(currentBackground);
currentBackground.destroy();
currentBackground = null;
}
// Dodaj tło dla ekranu Grilla
currentBackground = game.addChildAt(LK.getAsset('grillMenu', {
// Użyj assetu 'grillMenu'
anchorX: 0.5,
anchorY: 0.5,
// Ustaw środek jako punkt odniesienia
x: 2048 / 2,
y: 2732 / 2 // Wyśrodkuj na ekranie
}), 0); // Dodaj na spód
// Ukryj obiekty gry (gracz, boss) jeśli istnieją (choć powinny być zniszczone)
if (player) {
player.alpha = 0;
}
if (boss) {
boss.alpha = 0;
}
// Ukryj ściany areny
walls.forEach(function (wall) {
if (wall) {
wall.alpha = 0;
}
});
// Pozycjonuj elementy UI dla stanu Grill Screena
ui.positionElements("grillMenu");
// --- Dodaj przyciski na ekranie Grilla ---
var buttonYStart = 1000; // Początkowa pozycja Y dla pierwszego przycisku
var buttonYOffset = 150; // Odstęp pionowy między przyciskami
// Przycisk "Rest"
var restButton = new Container();
restButton.interactive = true;
restButton.x = 2048 / 2;
restButton.y = buttonYStart;
currentSceneElements.addChild(restButton); // Dodaj do kontenera sceny
var restButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
restButton.addChild(restButtonBg);
var restButtonText = new Text2('Rest', {
size: 50,
fill: 0xFFFFFF
});
restButtonText.anchor.set(0.5, 0.5);
restButton.addChild(restButtonText);
// Logika przycisku "Rest"
restButton.down = function () {
// Wyświetl komunikat "Rest in peace" i podziękowanie
ui.showMessage("Rest in peace...", 2000); // Zniknie po 2 sekundach
LK.setTimeout(function () {
ui.showMessage("Thank you for playing!", 3000); // Zniknie po 3 sekundach
}, 2500); // Pokaż podziękowanie po chwili
// Zaplanuj powrót do ekranu tytułowego po wyświetleniu podziękowań
LK.setTimeout(function () {
// Upewnij się, że elementy Grill Screena zostały wyczyszczone przed powrotem do tytułu
clearScene();
gameState.showTitleScreen(); // Wróć do ekranu tytułowego
}, 6000); // Opóźnienie przed powrotem do tytułu
};
// Przycisk "Upgrade Roll"
var upgradeButton = new Container();
upgradeButton.interactive = true;
upgradeButton.x = 2048 / 2;
upgradeButton.y = buttonYStart + buttonYOffset;
currentSceneElements.addChild(upgradeButton); // Dodaj do kontenera sceny
var upgradeButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
upgradeButton.addChild(upgradeButtonBg);
var upgradeButtonText = new Text2('Upgrade Roll', {
size: 50,
fill: 0xFFFFFF
});
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(upgradeButtonText);
// Logika przycisku "Upgrade Roll"
upgradeButton.down = function () {
ui.showMessage("Rolling?", 2000); // Wyświetl śmieszny napis
// W przyszłości można tu dodać rzeczywistą logikę ulepszenia turlania
};
// Przycisk "New Boss+"
var newBossButton = new Container();
newBossButton.interactive = true;
newBossButton.x = 2048 / 2;
newBossButton.y = buttonYStart + buttonYOffset * 2;
currentSceneElements.addChild(newBossButton); // Dodaj do kontenera sceny
var newBossButtonBg = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
newBossButton.addChild(newBossButtonBg);
var newBossButtonText = new Text2('New Boss+', {
size: 50,
fill: 0xFFFFFF
});
newBossButtonText.anchor.set(0.5, 0.5);
newBossButton.addChild(newBossButtonText);
// Logika przycisku "New Boss+"
newBossButton.down = function () {
clearScene(); // Wyczyść przyciski i tło Grilla
// Ustaw flagę trybu New Boss+
isNewBossPlusMode = true;
// Rozpocznij grę z nowymi parametrami (obsłużone w startGame)
gameState.startGame();
};
// --- Koniec dodawania przycisków ---
// Ukryj pasek zdrowia bossa na ekranie Grilla
ui.updateBossHealth(0, boss ? boss.maxHealth : 200); // Ustaw 0 zdrowia, ukryje pasek
},
// Przejście do stanu Game Over (wywoływane przez player.die() lub przez timer)
// Przyjmuje argument isDeath: true jeśli gracz zginął, false jeśli czas minął
gameOver: function gameOver(isDeath) {
this.currentState = "gameOver"; // Ustaw stan na game over
// Zapisz przyczynę Game Over, aby UI mogło jej użyć do pozycjonowania/widoczności elementów
gameOverReasonIsDeath = isDeath;
// Zatrzymaj timer gry, jeśli nadal działa
LK.clearInterval(this.gameTimerInterval);
// Upewnij się, że interwał timera jest wyczyszczony
// Zresetuj flagę przyspieszenia bossa
this.bossSpeedIncreased = false; // Zachowaj reset flagi przyspieszenia
// Zatrzymaj wszelkie trwające tweeny tła
if (currentBackground) {
tween.stop(currentBackground);
// Nie usuwamy tła od razu, może chcemy, żeby było widać je za ekranem game over
// currentBackground.destroy();
currentBackground = null;
}
game.setBackgroundColor(0x000000); // Czarne tło bazowe za game over
// Ukryj obiekty gry (są już zniszczone, ale to dodatkowe zabezpieczenie)
// Upewnij się, że istnieją przed ustawieniem alpha
if (player) {
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");
// Logika komunikatu Game Over w zależności od trybu i przyczyny
var gameOverMessage = "YOU DIED"; // Domyślny komunikat dla śmierci gracza
if (!isDeath) {
// Jeśli Game Over nie nastąpiło przez śmierć gracza (czyli czas minął)
if (isNewBossPlusMode) {
// Czas minął w trybie Boss+ (zwycięstwo przez przetrwanie)
gameOverMessage = "I haven't prepared a scene for this touch the grass"; // Komunikat zwycięstwa Boss+
}
// else: Czas minął w trybie standardowym - ten przypadek teraz przechodzi bezpośrednio do Grill Menu i nie wywołuje gameover()
}
// Jeśli isDeath jest true, gameOverMessage pozostaje "YOU DIED" (domyślne)
// Wyświetl komunikat Game Over/Zwycięstwa
ui.showMessage(gameOverMessage, 0); // Wyświetl na stałe do momentu restartu
ui.titleText.setText(gameOverMessage); // Ustaw tekst tytułu
ui.titleText.alpha = 1; // Pokaż tytuł
ui.showTutorial(""); // Ukryj tutorial
// Pasek zdrowia bossa jest teraz zarządzany przez ui.positionElements("gameOver") i ui.updateBossHealth()
// Zaplanuj restart gry po opóźnieniu (powrót do stanu startGame)
LK.setTimeout(function () {
// Clean up player and boss (gdyby nie zostały zniszczone wcześniej - np. w player.die/boss.die)
if (player) {
player.destroy();
player = null;
}
if (boss) {
boss.destroy();
boss = null;
}
// Wyczyść wszystkie aktywne ataki bossa i ich wizualizacje
if (boss && boss.attacks) {
// Sprawdź czy boss i jego ataki istniały przed zniszczeniem
boss.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
attack.visual.destroy();
}
});
boss.attacks = []; // Wyczyść tablicę ataków
}
// Usuń tło game over
if (currentBackground) {
currentBackground.destroy();
currentBackground = null;
}
// Resetuj UI do stanu tytułowego przed potencjalnym restartem
ui.positionElements("title"); // Pozycjonuj UI jak dla tytułu (ukryje tytuł gameover/zwycięstwa, komunikaty, paski itp.)
ui.titleText.alpha = 0; // Ukryj tytuł ponownie
ui.showMessage(""); // Wyczyść komunikat
ui.showTutorial(""); // Wyczyść tutorial
ui.updateDeathsCounter(); // Upewnij się, że licznik śmierci jest aktualny
ui.updateBossHealth(0, 200); // Zresetuj i ukryj pasek zdrowia bossa
// Flaga isNewBossPlusMode zachowa swoją wartość sprzed wywołania gameOver,
// dzięki czemu restart w przypadku śmierci w Boss+ wróci do Boss+
// Resetuj flagę przyczyny Game Over
gameOverReasonIsDeath = false; // Reset po użyciu
// Restartuj walkę z bossem (jeśli zginął w Boss+, startGame ustawi Boss+; jeśli zginął w standard, ustawi standard)
gameState.startGame(); // Przejdź z powrotem do stanu gry
}, 3000); // 3 sekundy opóźnienia przed restartem
},
// Obsługa gestów dotykowych/myszy w zależności od stanu gry
processTouchGesture: function processTouchGesture() {
// --- Obsługa inputu w stanie fakeTutorial (fałszywa śmierć) ---
// Jeśli jesteśmy w stanie fakeTutorial i timer do prawdziwego tutorialu jeszcze działa,
// każdy input (tapnięcie lub swipe) wywoła fałszywą śmierć.
if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) {
this.handleFakeTutorialInput(); // Wywołaj logikę fałszywej śmierci
return; // Zakończ przetwarzanie gestu
}
// Oblicz kierunek i dystans gestu na podstawie zarejestrowanych pozycji startu i końca dotyku
var dx = this.touchEnd.x - this.touchStart.x;
var dy = this.touchEnd.y - this.touchStart.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// --- Obsługa tapowania/gestów w stanach poza grą (z wyjątkiem Grill Menu) ---
// Sprawdź, czy to było krótkie tapnięcie (dystans mniejszy niż próg turlania)
if (distance < 50) {
// Próg turlania to 50 jednostek
// Jeśli jest to tapnięcie w stanie tytułowym, przejdź do intro
if (this.currentState === "title") {
// Sprawdź, czy kliknięto w przycisk testowy - obsługa w game.down/up interaktywnych obiektów
// Jeśli nie kliknięto w przycisk, tapnięcie przechodzi do intro
if (this.touchStart.x === this.touchEnd.x && this.touchStart.y === this.touchEnd.y) {
// Upewnij się, że to faktycznie tapnięcie (start i koniec w tym samym miejscu)
// Dodatkowo można sprawdzić, czy pod klikniętym punktem NIE BYŁO interaktywnego obiektu (np. przycisku)
// Ale prostsze jest poleganie na tym, że interaktywne obiekty mają własne handlery (game.down/up z obj)
// Jeśli dotarliśmy tutaj, to prawdopodobnie tapnięto w tło
this.showIntro(); // <--- Tap na ekranie tytułowym przechodzi do intro
return; // Zakończ przetwarzanie gestu
}
}
// Jeśli to tapnięcie w stanach game over lub victory (które teraz też prowadzą do Grill Screena lub restartu) - zignoruj tapnięcia
// Restart/przejście do Grill Screena dzieje się automatycznie po Game Over/Victory timerem
if (this.currentState === "gameOver") {
// Zignoruj tapnięcie na ekranie Game Over, restart jest timerem
return;
}
if (this.currentState === "victory") {
// Ta metoda nie jest już wywoływana bezpośrednio, ale zabezpieczenie na wszelki wypadek
return;
}
// Jeśli to tapnięcie w innych stanach (intro, real tutorial), zignoruj lub dodaj logikę jeśli potrzebna
// W real tutorialu tapnięcie nie wywoła nic poza przyciskiem "Let's Roll"
return; // Zakończ przetwarzanie gestu jeśli to tapnięcie i nie obsłużono stanu
}
// --- Obsługa turlania w stanie gry ---
// Tylko przetwarzaj gesty turlania (swipe) w stanie "game" i gdy gracz jest martwy
if (this.currentState !== "game" || !player || player.dead) {
return; // Zignoruj gesty swipe w innych stanach
}
// W tym miejscu wiemy, że currentState === "game", gracz żyje, i dystans gestu jest >= 50 (to swipe)
// Minimalny dystans swipe dla turlania (już sprawdzony powyżej)
// Normalizuj kierunek gestu
var direction = {
x: dx / distance,
y: dy / distance
};
// Wykonaj turlanie gracza
player.roll(direction);
}
};
// --- 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 === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu" || gameState.currentState === "gameOver") {
// Dodano stan game over do śledzenia potencjalnych tapnięć na ekranie końcowym
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 === "game" || gameState.currentState === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu" || gameState.currentState === "gameOver") {
// Dodano stan game over
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 === "fakeTutorial" || gameState.currentState === "realTutorial" || gameState.currentState === "grillMenu" || gameState.currentState === "gameOver") {
// Śledź ruch w stanach, gdzie gesty są istotne. Dodano stan game over.
gameState.touchEnd.x = x;
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();
// Aktualizuj pasek zdrowia bossa w każdej klatce (jeśli boss istnieje)
if (boss) {
ui.updateBossHealth(boss.health, boss.maxHealth);
} else {
// Jeśli boss nie istnieje, ukryj pasek zdrowia bossa
// Użyj poprawnego maxHP dla ukrycia/resetu w zależności od trybu
// Sprawdź, czy nie jesteśmy w stanie game over po zwycięstwie Boss+, gdzie pasek ma pozostać widoczny
if (!(gameState.currentState === "gameOver" && gameState.gameOverReasonIsDeath === false && isNewBossPlusMode)) {
ui.updateBossHealth(0, isNewBossPlusMode ? 2000 : 200);
}
}
// 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ść, kolizja turlania z bossem)
}
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.
}
// Logika w innych stanach (title, intro, tutoriale, game over, grillMenu) jest obsługiwana przez ich własne metody/przyciski.
};
// --- 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
@@ -339,8 +339,191 @@
self.y = Math.max(100, Math.min(self.y, 2732 - 100));
};
return self;
});
+var Player = Container.expand(function () {
+ var self = Container.call(this);
+ var playerGraphics = self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Player properties
+ self.health = 5; // Default health (will be overridden in startGame)
+ self.speed = 8; // Movement speed
+ self.rolling = false; // Flag for roll state
+ self.rollDirection = {
+ x: 0,
+ y: 0
+ }; // Direction of roll
+ self.rollSpeed = 20; // Speed during roll
+ self.rollDuration = 300; // Roll duration in ms
+ self.rollCooldown = 0; // Cooldown between rolls
+ self.invulnerable = false; // Invulnerability flag
+ self.invulnerabilityFrames = 0; // Frames of invulnerability left
+ self.dead = false; // Dead state
+ self.rollTimeoutId = null; // Store timeout ID for roll duration
+ self.invulnerabilityTimeoutId = null; // Store timeout ID for invulnerability
+ self.hasRolledThroughBossThisRoll = false; // Track if boss was hit during this roll
+ // Roll mechanic
+ self.roll = function (direction) {
+ // Check if player can roll (not already rolling and cooldown is 0)
+ if (!self.rolling && self.rollCooldown <= 0) {
+ self.rolling = true;
+ self.rollDirection = direction;
+ self.rollCooldown = 30; // Set cooldown in frames
+ self.invulnerable = true; // Player is invulnerable during roll
+ self.invulnerabilityFrames = 30; // Set invulnerability frames
+ self.hasRolledThroughBossThisRoll = false; // Reset boss hit tracking for this roll
+ // Create roll effect
+ var rollEffect = game.addChild(LK.getAsset('roll', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: self.x,
+ y: self.y
+ }));
+ // Animation for roll effect
+ tween(rollEffect, {
+ alpha: 0,
+ scaleX: 2,
+ scaleY: 2
+ }, {
+ duration: 300,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (rollEffect && rollEffect.destroy && !rollEffect.destroyed) {
+ rollEffect.destroy();
+ }
+ }
+ });
+ // End roll after duration
+ self.rollTimeoutId = LK.setTimeout(function () {
+ self.rolling = false;
+ }, self.rollDuration);
+ }
+ };
+ // Clear all roll and invulnerability timeouts
+ self.clearRollTimeouts = function () {
+ if (self.rollTimeoutId) {
+ LK.clearTimeout(self.rollTimeoutId);
+ self.rollTimeoutId = null;
+ }
+ if (self.invulnerabilityTimeoutId) {
+ LK.clearTimeout(self.invulnerabilityTimeoutId);
+ self.invulnerabilityTimeoutId = null;
+ }
+ };
+ // Take damage method
+ self.takeDamage = function (amount) {
+ // Check if player can take damage (not invulnerable and not dead)
+ if (!self.invulnerable && !self.dead) {
+ self.health -= amount;
+ // Flash effect when hit
+ LK.effects.flashObject(self, 0xFF0000, 200);
+ // Check if player is dead
+ if (self.health <= 0) {
+ self.die();
+ return;
+ }
+ // Set invulnerability after hit
+ self.invulnerable = true;
+ self.invulnerabilityFrames = 60; // 1 second at 60fps
+ }
+ };
+ // Die method
+ self.die = function () {
+ if (self.dead) {
+ return;
+ }
+ self.dead = true;
+ // Increment death counter
+ storage.totalDeaths = (storage.totalDeaths || 0) + 1;
+ // Increment max hearts after death (up to a maximum of 10)
+ if (storage.maxHearts < 10) {
+ storage.maxHearts = (storage.maxHearts || 5) + 1;
+ }
+ // Death animation
+ tween(self, {
+ alpha: 0,
+ scaleX: 2,
+ scaleY: 2
+ }, {
+ duration: 1000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (self && self.destroy && !self.destroyed) {
+ self.destroy();
+ }
+ // Set death reason flag to true (player died)
+ gameOverReasonIsDeath = true;
+ // Show game over screen
+ gameState.gameOver(true);
+ }
+ });
+ };
+ // Update method called every frame
+ self.update = function () {
+ // Track last position for collision detection
+ self.lastX = self.x;
+ self.lastY = self.y;
+ // Don't update if player is dead
+ if (self.dead) {
+ return;
+ }
+ // Handle roll cooldown
+ if (self.rollCooldown > 0) {
+ self.rollCooldown--;
+ }
+ // Handle invulnerability frames
+ if (self.invulnerable && self.invulnerabilityFrames > 0) {
+ self.invulnerabilityFrames--;
+ // Blinking effect during invulnerability
+ self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1;
+ if (self.invulnerabilityFrames <= 0) {
+ self.invulnerable = false;
+ self.alpha = 1; // Restore full opacity when invulnerability ends
+ }
+ }
+ // Handle movement during roll
+ if (self.rolling) {
+ self.x += self.rollDirection.x * self.rollSpeed;
+ self.y += self.rollDirection.y * self.rollSpeed;
+ // Check for collision with boss during roll
+ if (boss && !boss.dead && !self.hasRolledThroughBossThisRoll) {
+ var dx = self.x - boss.x;
+ var dy = self.y - boss.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var playerCollisionRadius = self.width / 2;
+ var bossCollisionRadius = boss.width / 2;
+ if (distance < playerCollisionRadius + bossCollisionRadius) {
+ // Collision with boss during roll - deal damage to boss
+ boss.takeDamage(10);
+ self.hasRolledThroughBossThisRoll = true;
+ // Visual effect for hitting boss with roll
+ LK.effects.flashObject(boss, 0x00FF00, 200);
+ }
+ }
+ }
+ // Keep player within screen bounds (accounting for walls)
+ self.x = Math.max(100, Math.min(self.x, 2048 - 100));
+ self.y = Math.max(100, Math.min(self.y, 2732 - 100));
+ // Check for collision with boss attacks
+ if (boss && !boss.dead && boss.attacks) {
+ for (var i = 0; i < boss.attacks.length; i++) {
+ var attack = boss.attacks[i];
+ var dx = self.x - attack.x;
+ var dy = self.y - attack.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var playerRadius = self.width / 2;
+ // If player collides with attack and is not invulnerable
+ if (distance < playerRadius + attack.radius && !self.invulnerable) {
+ self.takeDamage(1);
+ break; // Only take damage from one attack per frame
+ }
+ }
+ }
+ };
+ return self;
+});
// Shape class implementation for creating simple shapes
var Shape = Container.expand(function (options) {
var self = Container.call(this);
options = options || {};