User prompt
Please fix the bug: 'self.addChild is not a function' in or related to this line: 'self.bossGraphics = self.addChild(LK.getAsset('bossIdle', {' Line Number: 650
User prompt
Please fix the bug: 'self.attachAsset is not a function' in or related to this line: 'self.bossGraphics = self.attachAsset('bossIdle', {' Line Number: 650
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.grillMenu = LK.getShape('grillMenu', {});' Line Number: 585
User prompt
Please fix the bug: 'LK.Text is not a constructor' in or related to this line: 'self.gameOverMessage = new LK.Text({' Line Number: 572
User prompt
Please fix the bug: 'LK.Text is not a constructor' in or related to this line: 'self.newBossPlusButtonText = new LK.Text({' Line Number: 557
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.newBossPlusButton = LK.getShape('button_bg', {}); // Example shape button' Line Number: 550
User prompt
Please fix the bug: 'LK.Text is not a constructor' in or related to this line: 'self.startButtonText = new LK.Text({' Line Number: 535
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.startButton = LK.getShape('button_bg', {}); // Example shape button' Line Number: 528
User prompt
Please fix the bug: 'DisplayObjectContainer is not defined' in or related to this line: 'self.playerHearts = new DisplayObjectContainer(); // Container for player hearts' Line Number: 522
User prompt
Please fix the bug: 'LK.getShape is not a function' in or related to this line: 'self.bossHpBar = LK.getShape('bossHpbar', {});' Line Number: 519
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: self.startAttackPattern is not a function' in or related to this line: 'self.startAttackPattern();' Line Number: 470
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
add assets bossAttack0 to bossattack3
User prompt
add assets bossAttack0 to bossattack3
Code edit (5 edits merged)
Please save this source code
User prompt
add asset fireball13 fireball14 fireball15 fireball16
Code edit (2 edits merged)
Please save this source code
User prompt
add assets fireball00 to fireball12
User prompt
Please fix the bug: 'TypeError: self.clearRollTimeouts is not a function' in or related to this line: 'self.clearRollTimeouts();' Line Number: 589
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: 'Uncaught ReferenceError: SpriteAnimation is not defined' in or related to this line: 'var rollAnimation = game.addChild(new SpriteAnimation({' Line Number: 448
/**** * Classes ****/ // End gameState /**** * Player class ****/ var Player = Container.expand(function () { var self = Container.call(this); // Attach player graphic (depends on asset) self.playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 5; // Default player health (will be set by storage.maxHearts in gameState.startGame) self.speed = 8; // Player movement speed self.rollSpeed = 12; // Player roll speed self.rolling = false; self.rollDuration = 30; // Roll duration in frames (0.5 seconds at 60fps) self.rollTimer = 0; self.rollDirectionX = 0; self.rollDirectionY = 0; self.invulnerable = false; // Player invulnerability flag self.invulnerabilityDuration = 60; // Invulnerability duration after rolling/getting hit self.invulnerabilityTimer = 0; self.dead = false; // Create player roll animation (depends on assets) self.createRollAnim = function () { var frames = [LK.getAsset('roll0', {}), LK.getAsset('roll1', {}), LK.getAsset('roll2', {}), LK.getAsset('roll3', {}), LK.getAsset('roll4', {})]; var rollAnim = new SpriteAnimation({ frames: frames, frameDuration: 60, // Roll animation speed loop: true, // Roll animation loops anchorX: 0.5, anchorY: 0.5, x: 0, // Relative position y: 0 // Relative position }); return rollAnim; }; self.rollAnim = null; // Will hold the current roll animation object self.startRoll = function () { if (self.rolling || self.dead || gameState.currentState !== "game") { return; // Cannot roll if already rolling, dead, or not in game state } self.rolling = true; self.rollTimer = self.rollDuration; self.invulnerable = true; // Invulnerable during roll self.invulnerabilityTimer = self.invulnerabilityDuration; // Capture current movement direction for roll self.rollDirectionX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0); self.rollDirectionY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0); // Ensure there's a direction to roll, default if no input if (self.rollDirectionX === 0 && self.rollDirectionY === 0) { // Default roll direction (e.g., last movement direction, or down) // For simplicity, let's just prevent roll if no direction pressed. // Or set a default if needed: self.rollDirectionY = 1; // Example: roll down self.rolling = false; // Cancel roll if no direction input self.invulnerable = false; self.invulnerabilityTimer = 0; return; // Exit if no roll direction } // Normalize roll direction vector (optional, but good for consistent speed) var rollMagnitude = Math.sqrt(self.rollDirectionX * self.rollDirectionX + self.rollDirectionY * self.rollDirectionY); if (rollMagnitude > 0) { self.rollDirectionX /= rollMagnitude; self.rollDirectionY /= rollMagnitude; } // Remove idle graphic, add roll animation if (self.playerGraphics && self.playerGraphics.parent === self) { self.playerGraphics.parent.removeChild(self.playerGraphics); } else if (self.playerGraphics && self.playerGraphics.parent) { // Fallback self.playerGraphics.parent.removeChild(self.playerGraphics); } self.playerGraphics = null; self.rollAnim = self.addChild(self.createRollAnim()); // Add roll animation as child self.rollAnim.play(); // Start roll animation }; self.takeDamage = function (amount) { if (self.dead || self.invulnerable || gameState.currentState !== "game") { return; // Cannot take damage if dead, invulnerable, or not in game state } self.health -= amount; self.health = Math.max(0, self.health); // Health cannot go below zero LK.getSound('playerHit').play(); // Play player hit sound LK.effects.flashObject(self, 0xFF0000, 300); // Flash effect on hit self.invulnerable = true; // Become invulnerable after taking damage // Use a separate timer for hit invulnerability if needed, or just reuse roll timer logic // For simplicity, reuse invulnerabilityTimer: self.invulnerabilityTimer = self.invulnerabilityDuration; // Invulnerable for a short period // Update UI hearts immediately // ui.updateHearts(self.health, storage.maxHearts); // This is called in game.update -> ui.updateHearts if (self.health <= 0) { self.die(); // Player dies } }; self.die = function () { if (self.dead) { return; // Already dead } self.dead = true; // Stop all movement/actions self.rolling = false; self.invulnerable = false; // No longer invulnerable once dead // Stop/remove roll animation if active if (self.rollAnim) { self.rollAnim.stop(); if (self.rollAnim.parent === self) { self.rollAnim.parent.removeChild(self.rollAnim); } else if (self.rollAnim.parent) { // Fallback self.rollAnim.parent.removeChild(self.rollAnim); } self.rollAnim.destroy(); self.rollAnim = null; } // Add player death animation or graphic (if any) // For simplicity, just hide or remove the player graphic if (self.playerGraphics && self.playerGraphics.parent === self) { self.playerGraphics.parent.removeChild(self.playerGraphics); } else if (self.playerGraphics && self.playerGraphics.parent) { // Fallback self.playerGraphics.parent.removeChild(self.playerGraphics); } self.playerGraphics = null; // Clear reference // Trigger game over state after a short delay or death animation LK.setTimeout(function () { if (gameState.currentState === "game") { // Only transition if still in game state gameState.gameOver(true); // Call game over function with death reason } }, 1000); // Delay game over screen by 1 second }; self.update = function () { // Update only if in game state if (gameState.currentState !== "game") { // Stop rolling and remove animation if state changes if (self.rolling) { self.rolling = false; if (self.rollAnim) { self.rollAnim.stop(); if (self.rollAnim.parent === self) { self.rollAnim.parent.removeChild(self.rollAnim); } else if (self.rollAnim.parent) { // Fallback self.rollAnim.parent.removeChild(self.rollAnim); } self.rollAnim.destroy(); self.rollAnim = null; } // Add back idle graphic if it was removed if (!self.playerGraphics) { self.playerGraphics = self.addChild(self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 // Relative position })); } } return; // Exit update if not in game state } // Do not update if player is dead if (self.dead) { return; } // --- Rolling logic --- if (self.rolling) { self.x += self.rollDirectionX * self.rollSpeed; self.y += self.rollDirectionY * self.rollSpeed; self.rollTimer--; if (self.rollTimer <= 0) { self.rolling = false; // Stop and remove roll animation if (self.rollAnim) { self.rollAnim.stop(); if (self.rollAnim.parent === self) { self.rollAnim.parent.removeChild(self.rollAnim); } else if (self.rollAnim.parent) { // Fallback self.rollAnim.parent.removeChild(self.rollAnim); } self.rollAnim.destroy(); self.rollAnim = null; } // Add back idle graphic if (!self.playerGraphics) { self.playerGraphics = self.addChild(self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 // Relative position })); } } } else { // --- Movement logic (only when not rolling) --- var moveX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0); var moveY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0); var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY); if (moveMagnitude > 0) { moveX /= moveMagnitude; moveY /= moveMagnitude; self.x += moveX * self.speed; self.y += moveY * self.speed; } // No specific idle animation in this example, uses static player graphic } // --- Invulnerability timer --- if (self.invulnerable) { self.invulnerabilityTimer--; if (self.invulnerabilityTimer <= 0) { self.invulnerable = false; } // Optional: add visual effect for invulnerability (e.g., alpha blinking) self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1; // Simple blinking effect } else { self.alpha = 1; // Fully visible when not invulnerable } // --- Boundary checks (Arena borders) --- // Assuming arena borders are at 100px from edge, map 2048x2732 var halfWidth = self.width * self.scaleX / 2; var halfHeight = self.height * self.scaleY / 2; var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 100 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(self.x, maxX)); self.y = Math.max(minY, Math.min(self.y, maxY)); // --- Collision with Boss (only when not invulnerable) --- // Boss collision check is handled in player.update(), NOT boss.update() // It should only happen if player is NOT invulnerable // This collision check is handled in game.update -> player.update() // Let's add the check here if player.update is responsible for it. // Check collision with Boss if boss exists, is not dead, and player is not invulnerable if (boss && !boss.dead && !self.invulnerable) { var dx_b = boss.x - self.x; var dy_b = boss.y - self.y; var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b); var playerRadius_b = self.width / 2; var bossRadius_b = boss.width * boss.scaleX / 2; // Use scaled boss width if (distance_b < playerRadius_b + bossRadius_b) { // Collision detected with Boss body self.takeDamage(1); // Player takes 1 damage } } // Note: Collision with Boss *attacks* is handled in boss.update() }; return self; }); /**** * Initialize Game ****/ // End Player class /**** * UI object ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ /**** * Global variables ****/ var player = null; var boss = null; var ui = null; var storage = null; var isNewBossPlusMode = false; var game = null; var gameContainer = "gameContainer"; // ID elementu HTML, w którym będzie wyświetlana gra var gameOverReasonIsDeath = true; // true jeśli game over z powodu śmierci, false jeśli z powodu czasu w Boss+ /**** * Game state manager ****/ var gameState = function () { var self = {}; self.currentState = "loading"; // Initial state self.changeState = function (newState) { console.log("Changing state from", self.currentState, "to", newState); // Log state changes self.currentState = newState; // Manage visibility of game elements based on state // These position/visibility methods should be in gameState or UI, not individual element updates self.positionElements(); // Call positionElements on state change // Handle specific state transitions if (self.currentState === "title") { // Clear any game elements from previous attempts if necessary if (game) { game.removeChildren(); // Remove all children from the game container // Do not destroy game here, it's handled in createGame } // Ensure player and boss are null when returning to title player = null; boss = null; ui = null; // UI will be recreated in createGame if returning from game over // Ensure attacks are cleared if (boss && boss.attacks) { boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; } // If boss object is destroyed, self.attacks check might fail, add a check for that // Display title screen elements // In this simple example, we just show the start button via positionElements } else if (self.currentState === "game") { // Start the game loop when entering game state // The gameLoop is already running based on LK.init settings (60fps) // Player and Boss are created just before changing to 'game' state // Reset player/boss health or properties if necessary (or do it in their constructors) if (player) { player.health = storage.maxHearts; // Reset player health // ui.updateHearts(player.health, storage.maxHearts); // Update UI immediately } if (boss) { boss.health = boss.maxHealth; // Reset boss health // ui.updateBossHealth(boss.health, boss.maxHealth); // Update UI immediately boss.attackCooldown = 60; // Initial delay before first boss attack (1 second at 60fps) boss.stunned = false; // Ensure boss is not stunned boss.repositioning = false; // Ensure boss is not repositioning boss.dead = false; // Ensure boss is not dead boss.phase = 1; // Reset boss phase boss.attackSpeedMultiplier = 1; // Reset attack speed multiplier boss.attacks = []; // Clear any lingering attacks // Reset boss graphic state if needed (e.g., tint) boss.tint = 0xFFFFFF; // Reset tint } } else if (self.currentState === "gameOver") { // Stop game loop? LK manages this, or we can stop updates manually in gameLoop // In this example, gameLoop checks gameState.currentState // Display game over screen elements (handled by positionElements) // Stop boss attacks explicitly if boss exists and has a method for it if (boss) { boss.attackCooldown = 9999; // Prevent new attacks // Ensure existing attacks are cleaned up in boss.update when state changes } // Stop player movement, etc. (handled by player.update checking gameState.currentState) // Clean up game elements from screen? gameState.positionElements might hide them. } else if (self.currentState === "grillScreen") { // Similar cleanup/display logic for grill screen if (boss) { boss.attackCooldown = 9999; // Prevent new attacks } // Ensure existing attacks are cleaned up if (boss && boss.attacks) { boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; } // Hide player and boss (handled by positionElements) } }; self.positionElements = function () { // This function manages the visibility and potentially position of game elements based on the current state // It should be called whenever the state changes. var startButton = LK.getObjectById("startButton"); // Przykład pobierania przycisku var bossHpBar = LK.getObjectById("bossHpBar"); var playerHearts = LK.getObjectById("playerHearts"); // Zakładając, że UI ma kontener na serca var gameOverMessage = LK.getObjectById("gameOverMessage"); // Przykład wiadomości game over var grillMenu = LK.getObjectById("grillMenu"); // Przykład menu grill screen // Visibility logic if (startButton) { startButton.visible = self.currentState === "title"; } // Pasek HP bossa widoczny tylko w stanie "game" (lub game over po czasie w Boss+) if (bossHpBar) { bossHpBar.visible = self.currentState === "game" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode; } // Serca gracza widoczne tylko w stanie "game" if (playerHearts) { playerHearts.visible = self.currentState === "game"; } if (gameOverMessage) { gameOverMessage.visible = self.currentState === "gameOver" && gameOverReasonIsDeath; } // Game over tylko ze śmierci if (grillMenu) { grillMenu.visible = self.currentState === "grillScreen" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode; } // Menu grill screen lub game over po czasie // Show player and boss only in game state if (player) { player.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen"; } // Pokaż gracza w game, game over, grill screen if (boss) { boss.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen"; } // Pokaż bossa w game, game over, grill screen }; self.gameOver = function () { var reasonIsDeath = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; gameOverReasonIsDeath = reasonIsDeath; // Zapisz przyczynę game over self.changeState("gameOver"); // Wyświetl wiadomość Game Over lub Grill Screen w zależności od przyczyny i trybu (zajmie się tym positionElements) // Tutaj można dodać logikę czyszczenia obiektów, zatrzymania muzyki itp. LK.getSound('playerHit').stop(); // Stop player hit sound if it's playing }; self.showGrillScreen = function () { self.changeState("grillScreen"); // Logic for showing the grill screen }; // Metody zmiany stanu dla przycisków self.startGame = function () { var newBossPlus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; isNewBossPlusMode = newBossPlus; // Set the mode storage.newBossPlusMode = isNewBossPlusMode; // Save mode to storage // Wyczyść poprzednią grę i stwórz nową instancję createGame(); // This function now contains a setTimeout delay // Change state to game is now inside the setTimeout in createGame() // self.changeState("game"); // MOVED INSIDE TIMEOUT IN createGame }; self.returnToTitle = function () { // Clean up game elements before returning to title if (game) { game.removeChildren(); // Remove all display objects from the game container // Destroy player, boss, ui objects explicitly to clear references and updates if (player && player.destroy && !player.destroyed) { player.destroy(); } if (boss && boss.destroy && !boss.destroyed) { boss.destroy(); } if (ui && ui.destroy && !ui.destroyed) { ui.destroy(); } player = null; boss = null; ui = null; // game.destroy(); // Do not destroy game container itself, it's reused } // Clean up any lingering attacks if Boss object was null if (boss && boss.attacks) { // This check is redundant if boss is null, but safe boss.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); boss.attacks = []; } self.changeState("title"); }; return self; }(); // End Player class /**** * UI object ****/ var UI = function () { var self = {}; // Create UI elements (depend on assets/shapes) self.bossHpBar = LK.getAsset('bossHpbar', {}); self.bossHpBar.x = 1024 - self.bossHpBar.width / 2; // Center the bar self.bossHpBar.y = 50; // Position near top self.bossHpBar.visible = false; // Hide initially self.playerHearts = new DisplayObjectContainer(); // Container for player hearts // Hearts will be added as children to this container in updateHearts self.playerHearts.x = 100; // Position the hearts container self.playerHearts.y = 50; self.playerHearts.visible = false; // Hide initially // Create start button (depends on asset/shape) self.startButton = LK.getShape('button_bg', {}); // Example shape button self.startButton.x = 1024; // Center the button self.startButton.y = 1366; // Position it self.startButton.anchorX = 0.5; self.startButton.anchorY = 0.5; self.startButton.visible = false; // Hide initially // Add text to start button (depends on font/text rendering) self.startButtonText = new LK.Text({ text: "Start Game", color: 0x000000, size: 40, font: "Arial", anchorX: 0.5, anchorY: 0.5 }); self.startButton.addChild(self.startButtonText); // Add text as child of button // Add event listener to start button self.startButton.on("click", function () { if (gameState.currentState === "title") { gameState.startGame(); // Start game in standard mode } }); // Create Boss+ button (depends on asset/shape) self.newBossPlusButton = LK.getShape('button_bg', {}); // Example shape button self.newBossPlusButton.x = 1024; // Center self.newBossPlusButton.y = 1500; // Position below start self.newBossPlusButton.anchorX = 0.5; self.newBossPlusButton.anchorY = 0.5; self.newBossPlusButton.visible = false; // Add text to Boss+ button self.newBossPlusButtonText = new LK.Text({ text: "NEW BOSS+", color: 0x000000, size: 40, font: "Arial", anchorX: 0.5, anchorY: 0.5 }); self.newBossPlusButton.addChild(self.newBossPlusButtonText); // Add event listener to Boss+ button self.newBossPlusButton.on("click", function () { if (gameState.currentState === "title") { gameState.startGame(true); // Start game in Boss+ mode } }); // Create Game Over message (depends on text rendering) self.gameOverMessage = new LK.Text({ text: "Game Over!", color: 0xFF0000, size: 80, font: "Arial", anchorX: 0.5, anchorY: 0.5, x: 1024, // Center X y: 1366, // Center Y visible: false // Hide initially }); // Create Grill Screen menu (depends on shape) self.grillMenu = LK.getShape('grillMenu', {}); self.grillMenu.x = 1024; self.grillMenu.y = 1366; self.grillMenu.anchorX = 0.5; self.grillMenu.anchorY = 0.5; self.grillMenu.visible = false; // Hide initially // Add buttons/text to Grill Screen if needed // Add all UI elements to the game container (depth/layering handled by addChild order or setChildIndex) // Order matters for layering: background first, then game objects, then UI on top // Assuming UI should be on top of everything else in the 'game' container // Add UI elements AFTER adding player, boss, etc. in createGame // Or add them here and manage z-index // Let's add them here and assume later added elements are on top by default or managed by setChildIndex self.updateBossHealth = function (currentHp, maxHp) { // Ensure maxHp is at least 1 to prevent division by zero var safeMaxHp = Math.max(1, maxHp); // Calculate width based on health percentage var hpPercentage = currentHp / safeMaxHp; // Assuming original bossHpBar width was 800 self.bossHpBar.scaleX = hpPercentage; // Adjust position if anchor is not 0,0 to keep the bar filling from left // If anchorX is 0.5, the bar scales from the center. Need to adjust x. self.bossHpBar.x = 1024 - self.bossHpBar.width * self.bossHpBar.scaleX / 2; }; self.updateHearts = function (currentHearts, maxHearts) { // Clear existing heart graphics self.playerHearts.removeChildren(); // Add heart graphics based on currentHearts var heartSize = 40; // Match heart asset size var padding = 10; // Spacing between hearts for (var i = 0; i < maxHearts; i++) { var heartGraphic = LK.getShape('heart', {}); // Use heart shape asset // Position hearts horizontally within the container heartGraphic.x = i * (heartSize + padding); heartGraphic.y = 0; // All on the same vertical level // Tint heart based on current health if (i < currentHearts) { heartGraphic.tint = 0xFF0000; // Full heart color (Red) } else { heartGraphic.tint = 0x888888; // Empty heart color (Grayish) } self.playerHearts.addChild(heartGraphic); // Add heart to the container } // Position the container is done in gameState.positionElements or createGame }; // Add UI elements to game container in createGame after player/boss // This is handled in the modified createGame function self.update = function () { // UI elements are mostly passive and updated by boss/player health changes // No continuous update needed for UI elements themselves in this example, // but UI object could have update logic for animations, etc. }; return self; }(); // End UI object /**** * Boss class ****/ var Boss = Container.expand(function () { var self = Container.call(this); // Definicje list klatek dla fireballi - Zdefiniowane raz na początku klasy Boss var circleFrames = [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})]; var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})]; // *** MODYFIKACJA: Dodaj grafikę bossIdle jako DZIECKO obiektu bossa (self) *** // Używamy attachAsset, który już dodaje jako dziecko i ustawia pozycję relatywną na podstawie anchor. self.bossGraphics = self.attachAsset('bossIdle', { // Dziecko self anchorX: 0.5, anchorY: 0.5 // Pozycja relatywna na 0,0 jest domyślna i poprawna przy anchorX/Y 0.5 }); // Dodajemy funkcje animacji // Ta funkcja tworzy obiekt SpriteAnimation dla animacji ataku Bossa (nie fireballi) self.createBossAttackAnim = function () { var frames = [LK.getAsset('bossAttack0', {}), LK.getAsset('bossAttack1', {}), LK.getAsset('bossAttack2', {}), LK.getAsset('bossAttack3', {})]; var bossAttackAnim = new SpriteAnimation({ frames: frames, frameDuration: 100, // Czas trwania klatki animacji ataku Bossa (w ms) loop: false, anchorX: 0.5, anchorY: 0.5, // Pozycja animacji jako dziecka bossa - ustawiamy na 0,0 relatywnie do rodzica (self) x: 0, // MODIFIED y: 0 // MODIFIED }); return bossAttackAnim; }; // Zmieniamy sygnaturę funkcji, przyjmuje teraz typ ataku (do decydowania o grafice Bossa) // Ta funkcja zarządza grafiką Bossa (animacja ataku Bossa lub grafika idle) self.playBossAttackAnim = function (attackType) { // <--- DODANO PARAMETR // Upewnij się, że poprzednia animacja ataku Bossa jest całkowicie usunięta, zanim zaczniemy nową if (self.bossAttackAnim) { self.bossAttackAnim.stop(); // Zatrzymujemy animację // Usuń poprzednią animację z jej rodzica (Bossa) przed zniszczeniem // Sprawdź, czy rodzicem jest self przed próbą usunięcia if (self.bossAttackAnim.parent === self) { // MODIFIED check self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } else if (self.bossAttackAnim.parent) { // Fallback na wypadek, gdyby rodzic był inny self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); // Usuwamy poprzednią animację self.bossAttackAnim = null; // Ustawiamy na null po zniszczeniu } // *** Zmieniona logika: Zmiana grafiki bossa tylko jeśli to NIE jest atak szarży (attackType 2) *** if (attackType !== 2) { // Jeśli to atak koła (0) lub linii (1) // Usuń obecną główną grafikę bossa (idle) jako dziecko bossa // Sprawdź, czy rodzicem jest self przed próbą usunięcia if (self.bossGraphics && self.bossGraphics.parent === self) { // MODIFIED check self.bossGraphics.parent.removeChild(self.bossGraphics); } else if (self.bossGraphics && self.bossGraphics.parent) { // Fallback self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = null; // Wyczyść referencję // Tworzymy nową animację bossa i dodajemy ją JAKO DZIECKO BOSSA self.bossAttackAnim = self.addChild(self.createBossAttackAnim()); // *** MODIFIED: DODANO DO self! *** // Pozycja animacji jako dziecka bossa jest już ustawiona na 0,0 w createBossAttackAnim // Metoda update dla TEJ NOWEJ animacji (definiowana tylko dla ataków 0 i 1) self.bossAttackAnim.update = function () { // Sprawdź, czy ten obiekt animacji jest nadal aktywny self.bossAttackAnim bossa // Użyj 'this' dla obiektu animacji, 'self' dla obiektu Boss if (self.bossAttackAnim !== this || !this.playing || this.frames.length === 0) { // Jeśli już nie jesteśmy aktualną animacją lub nie gramy, zakończ return; } this.frameTimer++; if (this.frameTimer >= this.frameDuration / (1000 / 60)) { // Przelicz ms na klatki gry (przy 60fps) this.frameTimer = 0; this.removeChildren(); // Usuń sprite klatki z kontenera animacji (z obiektu animacji) this.currentFrame++; if (this.currentFrame >= this.frames.length) { // Animacja skończona, wracamy do idle // *** Dodaj grafikę idle z powrotem JAKO DZIECKO BOSSA i ustaw self.bossGraphics *** self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { // *** MODIFIED: DODANO DO self! *** anchorX: 0.5, anchorY: 0.5, x: 0, // Pozycja relatywna y: 0 // Pozycja relatywna })); // *** Usuń obiekt animacji z jego rodzica (Bossa) i zniszcz go *** // Sprawdź, czy rodzicem jest self przed próbą usunięcia if (this.parent === self) { // MODIFIED check this.parent.removeChild(this); // Użyj 'this' dla obiektu animacji } else if (this.parent) { // Fallback this.parent.removeChild(this); } this.destroy(); // Zniszcz obiekt animacji self.bossAttackAnim = null; // Wyczyść referencję w obiekcie bossa } else { this.addChild(this.frames[this.currentFrame]); // Dodaj sprite następnej klatki (do obiektu animacji) } } }; } // Else (attackType === 2, czyli chargeAttack): playBossAttackAnim nic nie robi z grafiką. // chargeAttack sam zadba o ustawienie grafiki 'idle' JAKO DZIECKO BOSSA. }; self.health = 100; // Domyślne zdrowie bossa (nadpisane w startGame) self.maxHealth = 100; // Domyślne max zdrowia bossa (nadpisane w startGame) self.speed = 5; // Prędkość ruchu bossa self.attackCooldown = 0; // Czas do następnego ataku (w klatkach gry) 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 // startAttackPattern pozostaje jak w poprzedniej poprawionej wersji 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óć } // Decydujemy O WZORCU ATAKU przed zmianą grafiki bossa self.attackPattern = (self.attackPattern + 1) % 3; // Cykl wzorców ataków (0, 1, 2) // 🔥 Przekazujemy typ ataku do playBossAttackAnim - ta funkcja teraz decyduje CZY zmienić grafikę bossa self.playBossAttackAnim(self.attackPattern); // <--- PRZEKAZUJEMY TYP ATAKU // Następnie wywołujemy właściwą funkcję ataku (chargeAttack itp.) // Logika graficzna dla szarży jest TERAZ W chargeAttack switch (self.attackPattern) { case 0: self.circleAttack(); // Wywołaj atak koła break; case 1: self.lineAttack(); // Wywołaj atak linii break; case 2: self.chargeAttack(); // Wywołaj atak szarży break; } // Ustaw właściwy czas odnowienia dla następnego ataku self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds * multiplier }; // Zmodyfikowana funkcja createAttack - NIE nadpisuje lokalnie spriteAnim.update (jak w poprzedniej próbie) // Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne) // Logika aktywacji kolizji (isActive) jest w Boss.update self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) { // Użyj przekazanej framesList, domyślną oryginalną jeśli nie przekazana var frames = framesList || [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})]; // console.log("CreateAttack called at:", x, y, " Frames array:", frames); // Debug log - można usunąć po naprawie // if (!frames || frames.length === 0 || frames.some(f => f === null || typeof f === 'undefined' || !f.texture)) { // Debug check - można usunąć po naprawie // console.error("Frames array is empty or contains invalid assets! Attack creation might fail."); // Debug log - można usunąć po naprawie // } var spriteAnim = game.addChild(new SpriteAnimation({ // Wizualizacja fireballa jest dodawana do kontenera 'game' frames: frames, frameDuration: frameDurationMs, // Użyj wartości przekazanej do konstruktora SpriteAnimation loop: false, // Animacja nie zapętla się anchorX: 0.5, anchorY: 0.5, x: x, y: y })); spriteAnim.scaleX = 1.6; // Ręczne skalowanie wizualizacji spriteAnim.scaleY = 1.6; var attack = { // Obiekt logiczny ataku x: x, y: y, radius: 60, // Promień kolizji visual: spriteAnim, // Referencja do wizualizacji (SpriteAnimation) lifeTime: Math.floor(duration * (60 / 1000)), // Czas życia w klatkach gry (przy 60fps) isActive: false, // Flaga aktywności kolizji, domyślnie false activationFrame: activationFrame // Przechowuj activationFrame w obiekcie ataku }; // USUNIĘTO: Lokalna definicja funkcji spriteAnim.update self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa }; // Zmodyfikowana funkcja circleAttack - Z pętlą setTimeout delay (jak w poprzedniej próbie, która dała wiele pocisków) self.circleAttack = function () { LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku var center = { // Ustaw środek koła na pozycji bossa x: self.x, y: self.y }; var count = isNewBossPlusMode ? 12 : 8; // Liczba pocisków w ataku (więcej w New Boss+) var radius = 300; // Promień koła, na którym rozmieszczone są pociski var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Czas życia pocisku w ms (krótszy w New Boss+?) // circleFrames array jest zdefiniowane raz na początku klasy Boss // Pętla tworząca wiele pocisków rozmieszczonych w kole Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO for (var i = 0; i < count; i++) { // Iteruj tyle razy, ile ma być pocisków (count) var angle = i / count * Math.PI * 2; // Oblicz kąt dla bieżącego pocisku (równo rozmieszczone na okręgu) var posX = center.x + Math.cos(angle) * radius; // Oblicz współrzędną X na okręgu var posY = center.y + Math.sin(angle) * radius; // Oblicz współrzędną Y na okręgu // Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku var delay = i * 20 + 20; // Zapewnij, że nawet pierwszy pocisk ma opóźnienie // Użyj setTimeout do stworzenia każdego pocisku po małym opóźnieniu LK.setTimeout(function (x, y) { // Używamy closure do "zamknięcia" wartości x, y (frames jest globalne/dostępne) return function () { // Sprawdź czy boss nadal istnieje i gra jest w stanie 'game' zanim stworzysz atak po opóźnieniu if (self && !self.dead && gameState.currentState === "game") { // Wywołaj funkcję createAttack dla tego pocisku z zapamiętaną pozycją i parametrami // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList // activationFrame = 8 (aktywacja od klatki fireball08) // frameDurationMs = 250ms (przykład prędkości) // framesList = circleFrames (używamy zmiennej zdefiniowanej poza funkcją) self.createAttack(x, y, attackLifeTime, 8, 250, circleFrames); // Używamy 250ms dla frameDurationMs } }; }(posX, posY), delay); // Przekaż aktualne posX, posY do closure } }; // *** START Zmodyfikowana funkcja lineAttack - Używa synchronicznej pętli i wywołuje nową createAttack *** self.lineAttack = function () { LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku // Tworzy linię ataków if (!player) { return; } var targetX = player.x; var targetY = player.y; var count = isNewBossPlusMode ? 8 : 5; // Liczba pocisków w linii var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Czas życia pocisku w ms var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Opóźnienie między pociskami w ms // lineFrames array jest zdefiniowane raz na początku klasy // Pętla tworząca pociski w linii z opóźnieniem (jak w souls5.txt) for (var i = 0; i < count; i++) { var t = i / (count - 1); // Współczynnik interpolacji var posX = self.x + (targetX - self.x) * t; // Interpolowana pozycja X var posY = self.y + (targetY - self.y) * t; // Interpolowana pozycja Y var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie przed stworzeniem pocisku, skalowane LK.setTimeout(function (x, y) { // Używamy closure do przekazania pozycji return function () { // Sprawdź warunki przed stworzeniem ataku po opóźnieniu if (self && !self.dead && gameState.currentState === "game") { // Wywołaj funkcję createAttack // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList // activationFrame = 3 (indeks 'fireball5' na liście lineFrames) // frameDurationMs = 100ms (przykład prędkości) // framesList = lineFrames (używamy zmiennej zdefiniowanej poza funkcją) self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // Przekaż parametry } }; }(posX, posY), delay); // Przekaż aktualne posX, posY do closure } }; // *** END Zmodyfikowana funkcja lineAttack *** // *** START Zmodyfikowana funkcja chargeAttack - tworzy pociski podczas szarży *** // Ta funkcja zarządza ruchem bossa (szarżą) i tworzy pociski wzdłuż ścieżki szarży self.chargeAttack = function () { LK.getSound('bossAttack').play(); // Odtwórz dźwięk szarży // Upewnij się, że gracz istnieje if (!player) { return; } // Obsługa grafiki bossa podczas szarży (pokazuje grafikę idle) if (self.bossAttackAnim) { self.bossAttackAnim.stop(); if (self.bossAttackAnim.parent === self) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } else if (self.bossAttackAnim.parent) { self.bossAttackAnim.parent.removeChild(self.bossAttackAnim); } self.bossAttackAnim.destroy(); self.bossAttackAnim = null; } if (self.bossGraphics && self.bossGraphics.parent === self) { self.bossGraphics.parent.removeChild(self.bossGraphics); } else if (self.bossGraphics && self.bossGraphics.parent) { self.bossGraphics.parent.removeChild(self.bossGraphics); } self.bossGraphics = self.addChild(LK.getAsset('bossIdle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 })); // Oblicz kierunek do gracza i parametry szarży 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; } var startX = self.x; // Pozycja początkowa szarży var startY = self.y; var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży var chargeDuration = isNewBossPlusMode ? // Czas trwania szarży 600 : 800; var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków wzdłuż ścieżki var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku // Tween (animacja ruchu) bossa podczas szarży tween(self, { x: self.x + dx * chargeDistance, y: self.y + dy * chargeDistance }, { duration: chargeDuration * self.attackSpeedMultiplier, // Czas trwania, skalowany easing: tween.easeIn, // Krzywa animacji onFinish: function onFinish() { // Po zakończeniu szarży if (self && !self.dead && gameState.currentState === "game") { // Faza repozycji self.repositioning = true; LK.setTimeout(function () { if (self && !self.dead) { self.repositioning = false; } }, 500 * self.attackSpeedMultiplier); // Tween powrotu do pozycji startowej tween(self, { x: startX, y: startY }, { duration: 1000 * self.attackSpeedMultiplier, easing: tween.easeOut }); // Utworzenie ataków (fireballi) wzdłuż ścieżki szarży // Fireballe ze szarży używają domyślnej listy klatek z createAttack (circleFrames) for (var i = 0; i < attacksOnPathCount; i++) { var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji var currentChargeX = self.x; // Aktualna pozycja bossa (koniec szarży) var currentChargeY = self.y; var posX = startX + (currentChargeX - startX) * t; // Interpolowana pozycja X var posY = startY + (currentChargeY - startY) * t; // Interpolowana pozycja Y // Utwórz atak // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList // activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków) // frameDurationMs = 140ms (domyślna prędkość) // framesList = circleFrames (używamy listy klatek ataku koła) self.createAttack(posX, posY, attackLifeTime, 4, 140, circleFrames); // Przekaż parametry } } } }); }; self.takeDamage = function (amount) { // Funkcja obsługująca otrzymywanie obrażeń przez bossa if (self.dead || gameState.currentState !== "game") { // Boss otrzymuje obrażenia tylko w stanie gry return; } self.health -= amount; // Zmniejsz zdrowie self.health = Math.max(0, self.health); // Upewnij się, że zdrowie nie spadnie poniżej zera LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku if (!isNewBossPlusMode) { // Przejście fazy bossa przy 50% zdrowia tylko w standardowym trybie if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; // Zmień fazę na 2 self.speed += 2; // Boss staje się szybszy (ruch) self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej tween(self, { tint: 0xFF3300 // Pomarańczowy/czerwony odcień }, { duration: 1000, easing: tween.easeInOut }); } } if (self.health <= 0) { self.die(); // Wywołaj funkcję śmierci } // ui.updateBossHealth jest w game.update }; self.die = function () { // Funkcja obsługująca śmierć bossa if (self.dead || gameState.currentState !== "game") { return; } self.dead = true; // Ustaw flagę śmierci if (!isNewBossPlusMode) { // Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu LK.getSound('victory').play(); } // Wyczyść pozostałe ataki przy śmierci bossa self.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); self.attacks = []; // Wyczyść tablicę ataków logicznych // Animacja śmierci bossa (zanikanie i powiększanie) tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { // Po zakończeniu animacji śmierci if (self && self.destroy && !self.destroyed) { self.destroy(); // Zniszcz obiekt bossa } storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów // ZAWSZE przechodzimy do Grill Screena gameState.showGrillScreen(); } }); }; self.update = function () { // Główna metoda aktualizacji bossa // Aktualizuj tylko w stanie gry if (gameState.currentState !== "game") { if (!self.dead && self.attacks) { // Jeśli zmieniono stan i boss nie umiera, wyczyść ataki self.attacks.forEach(function (attack) { if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } }); self.attacks = []; } return; } // Nie aktualizuj jeśli boss jest martwy (pozwól animacji śmierci działać) if (self.dead) { return; } // --- LOGIKA RUCHU BOSS --- // Poruszaj się w kierunku gracza jeśli spełnione warunki if (!self.repositioning && self.attackCooldown > 30 && player && !player.dead) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var moveSpeed = self.speed; if (distance > 150) { // Poruszaj się tylko jeśli gracz jest dalej niż 150 jednostek var moveX = dx / distance * moveSpeed; var moveY = dy / distance * moveSpeed; var nextX = self.x + moveX; var nextY = self.y + moveY; // Ograniczenia areny var halfWidth = self.width * self.scaleX / 2; var halfHeight = self.height * self.scaleY / 2; var minX = 100 + halfWidth; var maxX = 2048 - 100 - halfWidth; var minY = 100 + halfHeight; var maxY = 2732 - 100 - halfHeight; self.x = Math.max(minX, Math.min(nextX, maxX)); self.y = Math.max(minY, Math.min(nextY, maxY)); // Grafika jako dziecko podąża automatycznie. } } // --- KONIEC LOGIKA RUCHU BOSS --- // --- OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) --- // Aktualizuj istniejące ataki bossa (pociski) i sprawdzaj kolizje z graczem for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; // Aktualizuj flagę isActive na podstawie aktualnej klatki visual i przechowywanego activationFrame // Sprawdź czy visual istnieje i ma currentFrame if (attack.visual && typeof attack.visual.currentFrame !== 'undefined') { if (attack.visual.currentFrame >= attack.activationFrame) { attack.isActive = true; // Aktywuj kolizję } else { attack.isActive = false; // Kolizja nieaktywna } } else { attack.isActive = false; } if (attack.lifeTime > 0) { // Zmniejszaj czas życia attack.lifeTime--; } // Sprawdź kolizję z graczem jeśli spełnione warunki if (attack.isActive && player && !player.dead && !player.invulnerable && attack.visual && !attack.visual.destroyed) { var dx_p = player.x - attack.x; var dy_p = player.y - attack.y; var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); var playerRadius_p = player.width / 2; var attackRadius = attack.radius; if (distance_p < playerRadius_p + attackRadius) { player.takeDamage(1); // Gracz otrzymuje obrażenia if (attack.visual && attack.visual.destroy) { attack.visual.destroy(); } self.attacks.splice(i, 1); // Usuń trafiony atak // break; } } // Usuń atak, jeśli skończył mu się czas życia LUB wizualizacja zniszczona if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroy || attack.visual && attack.visual.destroyed) { // Dodano trzeci warunek dla pewności if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) { attack.visual.destroy(); } self.attacks.splice(i, 1); } } // --- KONIEC OBSŁUGA ATAKÓW BOSS --- if (self.attackCooldown > 0) { // Zmniejszaj cooldown self.attackCooldown--; } // Sprawdź, czy nadszedł czas na nowy atak if (self.attackCooldown <= 0 && !self.repositioning) { self.startAttackPattern(); } }; return self; // Zwróć instancję obiektu Boss })(); // End Boss class /**** * Game creation and loop ****/ // Global variables for game objects // Already defined at the beginning: var player, boss, ui, game, gameContainer, etc. var createGame = function createGame() { // Upewnij się, że poprzednia instancja gry jest posprzątana if (game) { game.destroy(); game = null; } game = new DisplayObjectContainer(); // Stwórz główny kontener gry LK.add(game); // Dodaj kontener gry do frameworka LK // Stwórz i dodaj podstawowe elementy tła od razu (nie zależą od assetów fireballi) gameState.createWalls(); // Tworzy i dodaje ściany gameState.createFloor(); // Tworzy i dodaje podłogę // *** MODYFIKACJA: Dodaj opóźnienie setTimeout przed tworzeniem elementów zależnych od assetów *** // Daje to assetom czas na załadowanie po odpaleniu onInit przez LK.init LK.setTimeout(function () { // *** Kod z oryginalnego createGame, który tworzył elementy zależne od assetów, PRZENIESIONY tutaj *** // Stwórz i dodaj gracza (zależy od assetów) player = new Player(); // Klasa Player prawdopodobnie używa assetów game.addChild(player); // Stwórz i dodaj bossa (mocno zależy od assetów dla grafiki i ataków) boss = new Boss(); // Klasa Boss używa wielu assetów game.addChild(boss); // Stwórz i dodaj elementy UI (niektóre elementy UI mogą używać assetów) ui = new UI(); // Klasa UI prawdopodobnie używa assetów (serca, przyciski, itp.) game.addChild(ui); // Add UI elements to the game container (order matters for layering) // Assuming UI should be on top of game elements game.addChild(ui.bossHpBar); game.addChild(ui.playerHearts); game.addChild(ui.startButton); // Start button is visible in title state game.addChild(ui.newBossPlusButton); // Boss+ button is visible in title state game.addChild(ui.gameOverMessage); // Game over message is hidden initially game.addChild(ui.grillMenu); // Grill menu is hidden initially // Start the game state (enables updates, attacks, etc.) // This was the last line in the original createGame gameState.changeState("title"); // Rozpocznij grę w stanie title console.log("Opóźnione kroki tworzenia gry wykonane."); // Opcjonalny log do potwierdzenia odpalenia timeoutu }, 500); // *** CZAS OPÓŹNIENIA (np. 500ms) - Dostosuj w razie potrzeby *** // To jest opóźnienie heurystyczne. Lepszym rozwiązaniem byłby właściwy mechanizm preładowania, jeśli dostępny. // Oryginalne linie kodu po ustawieniach createGame i przed gameState.changeState są teraz wewnątrz timeoutu // gameState.changeState("title"); // This line is moved inside the timeout }; var gameLoop = function gameLoop() { // Główne aktualizacje gry // LK automatycznie wywołuje update na obiektach dodanych do sceny (game) // Możemy dodać tutaj logikę globalną // Na przykład, aktualizacja UI, która nie jest automatycznie wywoływana na obiektach UI, chyba że UI jest DisplayObjectContainer i dodano je do sceny. // Aktualizuj UI niezależnie od stanu gry, bo jego widoczność i zawartość zależą od stanu/danych // Ale tylko jeśli UI zostało stworzone if (ui) { // UI update logic if needed (currently empty) // ui.update(); // Zakładając, że UI ma metodę update // Aktualizuj pasek zdrowia bossa (jeśli boss istnieje) if (boss) { // ui.updateBossHealth(boss.health, boss.maxHealth); // Ta linia była wcześniej, ale lepiej wywoływać ją po zmianie zdrowia bossa // Zaktualizuj UI HP Bossa w game.update, ale tylko gdy HP bossa > 0 (żeby nie migało przy śmierci) // UI HP Boss aktualizowane jest teraz w takeDamage i die Bossa. // Upewnijmy się, że jest aktualizowane też jeśli boss HP > 0. if (boss.health > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode) { ui.updateBossHealth(boss.health, boss.maxHealth); } else { // Jeśli boss martwy i nie jest to game over po czasie, ukryj pasek if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) { ui.updateBossHealth(0, 1); // Ukryj pasek } } } else { // Jeśli boss nie istnieje, ukryj pasek (chyba że to wygrana Boss+ przez czas) if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) { // Domyślne maxHP do ukrycia paska to 1 (uniknięcie dzielenia przez 0) ui.updateBossHealth(0, 1); } } // Aktualizuj serca gracza (jeśli gracz istnieje i UI istnieje) if (player) { ui.updateHearts(player.health, storage.maxHearts); } else if (gameState.currentState !== "game" && gameState.currentState !== "gameOver") { // Ukryj serca poza grą/game over ui.updateHearts(0, storage.maxHearts); } // Pozycjonowanie/widoczność elementów UI jest zarządzana przez gameState.positionElements() w metodach zmiany stanu. } // Logika specyficzna dla stanu gry if (gameState.currentState === "game") { if (player) { player.update(); // Aktualizacja gracza (ruch turlania, nietykalność, kolizja turlania z bossem) } if (boss) { boss.update(); // Aktualizacja bossa (ruch, cooldown, tworzenie/usuwanie ataków, kolizje ataków z graczem) } } // Logika game over else if (gameState.currentState === "gameOver") { // Tutaj można dodać logikę game over, np. animację, możliwość restartu // Aktualizacja player i boss nadal jest wywoływana, ale ich metody update powinny sprawdzać stan gry if (player) { player.update(); } // Nadal aktualizuj gracza (np. animacja śmierci) if (boss) { boss.update(); } // Nadal aktualizuj bossa (np. animacja śmierci) // Można dodać logikę przycisków restartu, powrotu do menu itp. } // Logika grill screen else if (gameState.currentState === "grillScreen") { // Tutaj można dodać logikę grill screen, np. animację, możliwość przejścia dalej if (player) { player.update(); } // Nadal aktualizuj gracza (jeśli widoczny) if (boss) { boss.update(); } // Nadal aktualizuj bossa (jeśli widoczny) // Można dodać logikę przycisków przejścia dalej, powrotu do menu itp. } // Rysowanie sceny (wykonywane automatycznie przez LK po gameLoop) }; // Inicjalizacja gry LK.init(gameContainer, { onInit: function onInit() { storage = LK.localStorage; // Initialize storage here after LK.init // Load or set maxHearts from storage if needed, default to 5 storage.maxHearts = storage.maxHearts || 5; // Default max hearts // Determine game mode (Boss+ or Standard) from storage or default isNewBossPlusMode = storage.newBossPlusMode === true; // Default to false if not in storage createGame(); // Call the createGame function to set up the initial game state and objects // Game loop is set up by LK.init to call gameLoop function repeatedly (e.g., 60 times per second) } });
===================================================================
--- original.js
+++ change.js
@@ -1,17 +1,597 @@
/****
-* Plugins
+* Classes
****/
-var tween = LK.import("@upit/tween.v1");
-var storage = LK.import("@upit/storage.v1");
+// End gameState
+/****
+* Player class
+****/
+var Player = Container.expand(function () {
+ var self = Container.call(this);
+ // Attach player graphic (depends on asset)
+ self.playerGraphics = self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.health = 5; // Default player health (will be set by storage.maxHearts in gameState.startGame)
+ self.speed = 8; // Player movement speed
+ self.rollSpeed = 12; // Player roll speed
+ self.rolling = false;
+ self.rollDuration = 30; // Roll duration in frames (0.5 seconds at 60fps)
+ self.rollTimer = 0;
+ self.rollDirectionX = 0;
+ self.rollDirectionY = 0;
+ self.invulnerable = false; // Player invulnerability flag
+ self.invulnerabilityDuration = 60; // Invulnerability duration after rolling/getting hit
+ self.invulnerabilityTimer = 0;
+ self.dead = false;
+ // Create player roll animation (depends on assets)
+ self.createRollAnim = function () {
+ var frames = [LK.getAsset('roll0', {}), LK.getAsset('roll1', {}), LK.getAsset('roll2', {}), LK.getAsset('roll3', {}), LK.getAsset('roll4', {})];
+ var rollAnim = new SpriteAnimation({
+ frames: frames,
+ frameDuration: 60,
+ // Roll animation speed
+ loop: true,
+ // Roll animation loops
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 0,
+ // Relative position
+ y: 0 // Relative position
+ });
+ return rollAnim;
+ };
+ self.rollAnim = null; // Will hold the current roll animation object
+ self.startRoll = function () {
+ if (self.rolling || self.dead || gameState.currentState !== "game") {
+ return; // Cannot roll if already rolling, dead, or not in game state
+ }
+ self.rolling = true;
+ self.rollTimer = self.rollDuration;
+ self.invulnerable = true; // Invulnerable during roll
+ self.invulnerabilityTimer = self.invulnerabilityDuration;
+ // Capture current movement direction for roll
+ self.rollDirectionX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0);
+ self.rollDirectionY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0);
+ // Ensure there's a direction to roll, default if no input
+ if (self.rollDirectionX === 0 && self.rollDirectionY === 0) {
+ // Default roll direction (e.g., last movement direction, or down)
+ // For simplicity, let's just prevent roll if no direction pressed.
+ // Or set a default if needed: self.rollDirectionY = 1; // Example: roll down
+ self.rolling = false; // Cancel roll if no direction input
+ self.invulnerable = false;
+ self.invulnerabilityTimer = 0;
+ return; // Exit if no roll direction
+ }
+ // Normalize roll direction vector (optional, but good for consistent speed)
+ var rollMagnitude = Math.sqrt(self.rollDirectionX * self.rollDirectionX + self.rollDirectionY * self.rollDirectionY);
+ if (rollMagnitude > 0) {
+ self.rollDirectionX /= rollMagnitude;
+ self.rollDirectionY /= rollMagnitude;
+ }
+ // Remove idle graphic, add roll animation
+ if (self.playerGraphics && self.playerGraphics.parent === self) {
+ self.playerGraphics.parent.removeChild(self.playerGraphics);
+ } else if (self.playerGraphics && self.playerGraphics.parent) {
+ // Fallback
+ self.playerGraphics.parent.removeChild(self.playerGraphics);
+ }
+ self.playerGraphics = null;
+ self.rollAnim = self.addChild(self.createRollAnim()); // Add roll animation as child
+ self.rollAnim.play(); // Start roll animation
+ };
+ self.takeDamage = function (amount) {
+ if (self.dead || self.invulnerable || gameState.currentState !== "game") {
+ return; // Cannot take damage if dead, invulnerable, or not in game state
+ }
+ self.health -= amount;
+ self.health = Math.max(0, self.health); // Health cannot go below zero
+ LK.getSound('playerHit').play(); // Play player hit sound
+ LK.effects.flashObject(self, 0xFF0000, 300); // Flash effect on hit
+ self.invulnerable = true; // Become invulnerable after taking damage
+ // Use a separate timer for hit invulnerability if needed, or just reuse roll timer logic
+ // For simplicity, reuse invulnerabilityTimer:
+ self.invulnerabilityTimer = self.invulnerabilityDuration; // Invulnerable for a short period
+ // Update UI hearts immediately
+ // ui.updateHearts(self.health, storage.maxHearts); // This is called in game.update -> ui.updateHearts
+ if (self.health <= 0) {
+ self.die(); // Player dies
+ }
+ };
+ self.die = function () {
+ if (self.dead) {
+ return; // Already dead
+ }
+ self.dead = true;
+ // Stop all movement/actions
+ self.rolling = false;
+ self.invulnerable = false; // No longer invulnerable once dead
+ // Stop/remove roll animation if active
+ if (self.rollAnim) {
+ self.rollAnim.stop();
+ if (self.rollAnim.parent === self) {
+ self.rollAnim.parent.removeChild(self.rollAnim);
+ } else if (self.rollAnim.parent) {
+ // Fallback
+ self.rollAnim.parent.removeChild(self.rollAnim);
+ }
+ self.rollAnim.destroy();
+ self.rollAnim = null;
+ }
+ // Add player death animation or graphic (if any)
+ // For simplicity, just hide or remove the player graphic
+ if (self.playerGraphics && self.playerGraphics.parent === self) {
+ self.playerGraphics.parent.removeChild(self.playerGraphics);
+ } else if (self.playerGraphics && self.playerGraphics.parent) {
+ // Fallback
+ self.playerGraphics.parent.removeChild(self.playerGraphics);
+ }
+ self.playerGraphics = null; // Clear reference
+ // Trigger game over state after a short delay or death animation
+ LK.setTimeout(function () {
+ if (gameState.currentState === "game") {
+ // Only transition if still in game state
+ gameState.gameOver(true); // Call game over function with death reason
+ }
+ }, 1000); // Delay game over screen by 1 second
+ };
+ self.update = function () {
+ // Update only if in game state
+ if (gameState.currentState !== "game") {
+ // Stop rolling and remove animation if state changes
+ if (self.rolling) {
+ self.rolling = false;
+ if (self.rollAnim) {
+ self.rollAnim.stop();
+ if (self.rollAnim.parent === self) {
+ self.rollAnim.parent.removeChild(self.rollAnim);
+ } else if (self.rollAnim.parent) {
+ // Fallback
+ self.rollAnim.parent.removeChild(self.rollAnim);
+ }
+ self.rollAnim.destroy();
+ self.rollAnim = null;
+ }
+ // Add back idle graphic if it was removed
+ if (!self.playerGraphics) {
+ self.playerGraphics = self.addChild(self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 0,
+ y: 0 // Relative position
+ }));
+ }
+ }
+ return; // Exit update if not in game state
+ }
+ // Do not update if player is dead
+ if (self.dead) {
+ return;
+ }
+ // --- Rolling logic ---
+ if (self.rolling) {
+ self.x += self.rollDirectionX * self.rollSpeed;
+ self.y += self.rollDirectionY * self.rollSpeed;
+ self.rollTimer--;
+ if (self.rollTimer <= 0) {
+ self.rolling = false;
+ // Stop and remove roll animation
+ if (self.rollAnim) {
+ self.rollAnim.stop();
+ if (self.rollAnim.parent === self) {
+ self.rollAnim.parent.removeChild(self.rollAnim);
+ } else if (self.rollAnim.parent) {
+ // Fallback
+ self.rollAnim.parent.removeChild(self.rollAnim);
+ }
+ self.rollAnim.destroy();
+ self.rollAnim = null;
+ }
+ // Add back idle graphic
+ if (!self.playerGraphics) {
+ self.playerGraphics = self.addChild(self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 0,
+ y: 0 // Relative position
+ }));
+ }
+ }
+ } else {
+ // --- Movement logic (only when not rolling) ---
+ var moveX = (LK.isDown("left") ? -1 : 0) + (LK.isDown("right") ? 1 : 0);
+ var moveY = (LK.isDown("up") ? -1 : 0) + (LK.isDown("down") ? 1 : 0);
+ var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY);
+ if (moveMagnitude > 0) {
+ moveX /= moveMagnitude;
+ moveY /= moveMagnitude;
+ self.x += moveX * self.speed;
+ self.y += moveY * self.speed;
+ }
+ // No specific idle animation in this example, uses static player graphic
+ }
+ // --- Invulnerability timer ---
+ if (self.invulnerable) {
+ self.invulnerabilityTimer--;
+ if (self.invulnerabilityTimer <= 0) {
+ self.invulnerable = false;
+ }
+ // Optional: add visual effect for invulnerability (e.g., alpha blinking)
+ self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1; // Simple blinking effect
+ } else {
+ self.alpha = 1; // Fully visible when not invulnerable
+ }
+ // --- Boundary checks (Arena borders) ---
+ // Assuming arena borders are at 100px from edge, map 2048x2732
+ var halfWidth = self.width * self.scaleX / 2;
+ var halfHeight = self.height * self.scaleY / 2;
+ var minX = 100 + halfWidth;
+ var maxX = 2048 - 100 - halfWidth;
+ var minY = 100 + halfHeight;
+ var maxY = 2732 - 100 - halfHeight;
+ self.x = Math.max(minX, Math.min(self.x, maxX));
+ self.y = Math.max(minY, Math.min(self.y, maxY));
+ // --- Collision with Boss (only when not invulnerable) ---
+ // Boss collision check is handled in player.update(), NOT boss.update()
+ // It should only happen if player is NOT invulnerable
+ // This collision check is handled in game.update -> player.update()
+ // Let's add the check here if player.update is responsible for it.
+ // Check collision with Boss if boss exists, is not dead, and player is not invulnerable
+ if (boss && !boss.dead && !self.invulnerable) {
+ var dx_b = boss.x - self.x;
+ var dy_b = boss.y - self.y;
+ var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b);
+ var playerRadius_b = self.width / 2;
+ var bossRadius_b = boss.width * boss.scaleX / 2; // Use scaled boss width
+ if (distance_b < playerRadius_b + bossRadius_b) {
+ // Collision detected with Boss body
+ self.takeDamage(1); // Player takes 1 damage
+ }
+ }
+ // Note: Collision with Boss *attacks* is handled in boss.update()
+ };
+ return self;
+});
/****
-* Classes
+* Initialize Game
****/
-// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
-// Klasy z stworzylo zloto.txt (bardziej zaawansowana walka z bossem)
+// End Player class
+/****
+* UI object
+****/
+var game = new LK.Game({
+ backgroundColor: 0x000000
+});
+
+/****
+* Game Code
+****/
+/****
+* Global variables
+****/
+var player = null;
+var boss = null;
+var ui = null;
+var storage = null;
+var isNewBossPlusMode = false;
+var game = null;
+var gameContainer = "gameContainer"; // ID elementu HTML, w którym będzie wyświetlana gra
+var gameOverReasonIsDeath = true; // true jeśli game over z powodu śmierci, false jeśli z powodu czasu w Boss+
+/****
+* Game state manager
+****/
+var gameState = function () {
+ var self = {};
+ self.currentState = "loading"; // Initial state
+ self.changeState = function (newState) {
+ console.log("Changing state from", self.currentState, "to", newState); // Log state changes
+ self.currentState = newState;
+ // Manage visibility of game elements based on state
+ // These position/visibility methods should be in gameState or UI, not individual element updates
+ self.positionElements(); // Call positionElements on state change
+ // Handle specific state transitions
+ if (self.currentState === "title") {
+ // Clear any game elements from previous attempts if necessary
+ if (game) {
+ game.removeChildren(); // Remove all children from the game container
+ // Do not destroy game here, it's handled in createGame
+ }
+ // Ensure player and boss are null when returning to title
+ player = null;
+ boss = null;
+ ui = null; // UI will be recreated in createGame if returning from game over
+ // Ensure attacks are cleared
+ if (boss && boss.attacks) {
+ boss.attacks.forEach(function (attack) {
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy();
+ }
+ });
+ boss.attacks = [];
+ }
+ // If boss object is destroyed, self.attacks check might fail, add a check for that
+ // Display title screen elements
+ // In this simple example, we just show the start button via positionElements
+ } else if (self.currentState === "game") {
+ // Start the game loop when entering game state
+ // The gameLoop is already running based on LK.init settings (60fps)
+ // Player and Boss are created just before changing to 'game' state
+ // Reset player/boss health or properties if necessary (or do it in their constructors)
+ if (player) {
+ player.health = storage.maxHearts; // Reset player health
+ // ui.updateHearts(player.health, storage.maxHearts); // Update UI immediately
+ }
+ if (boss) {
+ boss.health = boss.maxHealth; // Reset boss health
+ // ui.updateBossHealth(boss.health, boss.maxHealth); // Update UI immediately
+ boss.attackCooldown = 60; // Initial delay before first boss attack (1 second at 60fps)
+ boss.stunned = false; // Ensure boss is not stunned
+ boss.repositioning = false; // Ensure boss is not repositioning
+ boss.dead = false; // Ensure boss is not dead
+ boss.phase = 1; // Reset boss phase
+ boss.attackSpeedMultiplier = 1; // Reset attack speed multiplier
+ boss.attacks = []; // Clear any lingering attacks
+ // Reset boss graphic state if needed (e.g., tint)
+ boss.tint = 0xFFFFFF; // Reset tint
+ }
+ } else if (self.currentState === "gameOver") {
+ // Stop game loop? LK manages this, or we can stop updates manually in gameLoop
+ // In this example, gameLoop checks gameState.currentState
+ // Display game over screen elements (handled by positionElements)
+ // Stop boss attacks explicitly if boss exists and has a method for it
+ if (boss) {
+ boss.attackCooldown = 9999; // Prevent new attacks
+ // Ensure existing attacks are cleaned up in boss.update when state changes
+ }
+ // Stop player movement, etc. (handled by player.update checking gameState.currentState)
+ // Clean up game elements from screen? gameState.positionElements might hide them.
+ } else if (self.currentState === "grillScreen") {
+ // Similar cleanup/display logic for grill screen
+ if (boss) {
+ boss.attackCooldown = 9999; // Prevent new attacks
+ }
+ // Ensure existing attacks are cleaned up
+ if (boss && boss.attacks) {
+ boss.attacks.forEach(function (attack) {
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy();
+ }
+ });
+ boss.attacks = [];
+ }
+ // Hide player and boss (handled by positionElements)
+ }
+ };
+ self.positionElements = function () {
+ // This function manages the visibility and potentially position of game elements based on the current state
+ // It should be called whenever the state changes.
+ var startButton = LK.getObjectById("startButton"); // Przykład pobierania przycisku
+ var bossHpBar = LK.getObjectById("bossHpBar");
+ var playerHearts = LK.getObjectById("playerHearts"); // Zakładając, że UI ma kontener na serca
+ var gameOverMessage = LK.getObjectById("gameOverMessage"); // Przykład wiadomości game over
+ var grillMenu = LK.getObjectById("grillMenu"); // Przykład menu grill screen
+ // Visibility logic
+ if (startButton) {
+ startButton.visible = self.currentState === "title";
+ }
+ // Pasek HP bossa widoczny tylko w stanie "game" (lub game over po czasie w Boss+)
+ if (bossHpBar) {
+ bossHpBar.visible = self.currentState === "game" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode;
+ }
+ // Serca gracza widoczne tylko w stanie "game"
+ if (playerHearts) {
+ playerHearts.visible = self.currentState === "game";
+ }
+ if (gameOverMessage) {
+ gameOverMessage.visible = self.currentState === "gameOver" && gameOverReasonIsDeath;
+ } // Game over tylko ze śmierci
+ if (grillMenu) {
+ grillMenu.visible = self.currentState === "grillScreen" || self.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode;
+ } // Menu grill screen lub game over po czasie
+ // Show player and boss only in game state
+ if (player) {
+ player.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen";
+ } // Pokaż gracza w game, game over, grill screen
+ if (boss) {
+ boss.visible = self.currentState === "game" || self.currentState === "gameOver" || self.currentState === "grillScreen";
+ } // Pokaż bossa w game, game over, grill screen
+ };
+ self.gameOver = function () {
+ var reasonIsDeath = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+ gameOverReasonIsDeath = reasonIsDeath; // Zapisz przyczynę game over
+ self.changeState("gameOver");
+ // Wyświetl wiadomość Game Over lub Grill Screen w zależności od przyczyny i trybu (zajmie się tym positionElements)
+ // Tutaj można dodać logikę czyszczenia obiektów, zatrzymania muzyki itp.
+ LK.getSound('playerHit').stop(); // Stop player hit sound if it's playing
+ };
+ self.showGrillScreen = function () {
+ self.changeState("grillScreen");
+ // Logic for showing the grill screen
+ };
+ // Metody zmiany stanu dla przycisków
+ self.startGame = function () {
+ var newBossPlus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
+ isNewBossPlusMode = newBossPlus; // Set the mode
+ storage.newBossPlusMode = isNewBossPlusMode; // Save mode to storage
+ // Wyczyść poprzednią grę i stwórz nową instancję
+ createGame(); // This function now contains a setTimeout delay
+ // Change state to game is now inside the setTimeout in createGame()
+ // self.changeState("game"); // MOVED INSIDE TIMEOUT IN createGame
+ };
+ self.returnToTitle = function () {
+ // Clean up game elements before returning to title
+ if (game) {
+ game.removeChildren(); // Remove all display objects from the game container
+ // Destroy player, boss, ui objects explicitly to clear references and updates
+ if (player && player.destroy && !player.destroyed) {
+ player.destroy();
+ }
+ if (boss && boss.destroy && !boss.destroyed) {
+ boss.destroy();
+ }
+ if (ui && ui.destroy && !ui.destroyed) {
+ ui.destroy();
+ }
+ player = null;
+ boss = null;
+ ui = null;
+ // game.destroy(); // Do not destroy game container itself, it's reused
+ }
+ // Clean up any lingering attacks if Boss object was null
+ if (boss && boss.attacks) {
+ // This check is redundant if boss is null, but safe
+ boss.attacks.forEach(function (attack) {
+ if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
+ attack.visual.destroy();
+ }
+ });
+ boss.attacks = [];
+ }
+ self.changeState("title");
+ };
+ return self;
+}();
+// End Player class
+/****
+* UI object
+****/
+var UI = function () {
+ var self = {};
+ // Create UI elements (depend on assets/shapes)
+ self.bossHpBar = LK.getAsset('bossHpbar', {});
+ self.bossHpBar.x = 1024 - self.bossHpBar.width / 2; // Center the bar
+ self.bossHpBar.y = 50; // Position near top
+ self.bossHpBar.visible = false; // Hide initially
+ self.playerHearts = new DisplayObjectContainer(); // Container for player hearts
+ // Hearts will be added as children to this container in updateHearts
+ self.playerHearts.x = 100; // Position the hearts container
+ self.playerHearts.y = 50;
+ self.playerHearts.visible = false; // Hide initially
+ // Create start button (depends on asset/shape)
+ self.startButton = LK.getShape('button_bg', {}); // Example shape button
+ self.startButton.x = 1024; // Center the button
+ self.startButton.y = 1366; // Position it
+ self.startButton.anchorX = 0.5;
+ self.startButton.anchorY = 0.5;
+ self.startButton.visible = false; // Hide initially
+ // Add text to start button (depends on font/text rendering)
+ self.startButtonText = new LK.Text({
+ text: "Start Game",
+ color: 0x000000,
+ size: 40,
+ font: "Arial",
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.startButton.addChild(self.startButtonText); // Add text as child of button
+ // Add event listener to start button
+ self.startButton.on("click", function () {
+ if (gameState.currentState === "title") {
+ gameState.startGame(); // Start game in standard mode
+ }
+ });
+ // Create Boss+ button (depends on asset/shape)
+ self.newBossPlusButton = LK.getShape('button_bg', {}); // Example shape button
+ self.newBossPlusButton.x = 1024; // Center
+ self.newBossPlusButton.y = 1500; // Position below start
+ self.newBossPlusButton.anchorX = 0.5;
+ self.newBossPlusButton.anchorY = 0.5;
+ self.newBossPlusButton.visible = false;
+ // Add text to Boss+ button
+ self.newBossPlusButtonText = new LK.Text({
+ text: "NEW BOSS+",
+ color: 0x000000,
+ size: 40,
+ font: "Arial",
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.newBossPlusButton.addChild(self.newBossPlusButtonText);
+ // Add event listener to Boss+ button
+ self.newBossPlusButton.on("click", function () {
+ if (gameState.currentState === "title") {
+ gameState.startGame(true); // Start game in Boss+ mode
+ }
+ });
+ // Create Game Over message (depends on text rendering)
+ self.gameOverMessage = new LK.Text({
+ text: "Game Over!",
+ color: 0xFF0000,
+ size: 80,
+ font: "Arial",
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 1024,
+ // Center X
+ y: 1366,
+ // Center Y
+ visible: false // Hide initially
+ });
+ // Create Grill Screen menu (depends on shape)
+ self.grillMenu = LK.getShape('grillMenu', {});
+ self.grillMenu.x = 1024;
+ self.grillMenu.y = 1366;
+ self.grillMenu.anchorX = 0.5;
+ self.grillMenu.anchorY = 0.5;
+ self.grillMenu.visible = false; // Hide initially
+ // Add buttons/text to Grill Screen if needed
+ // Add all UI elements to the game container (depth/layering handled by addChild order or setChildIndex)
+ // Order matters for layering: background first, then game objects, then UI on top
+ // Assuming UI should be on top of everything else in the 'game' container
+ // Add UI elements AFTER adding player, boss, etc. in createGame
+ // Or add them here and manage z-index
+ // Let's add them here and assume later added elements are on top by default or managed by setChildIndex
+ self.updateBossHealth = function (currentHp, maxHp) {
+ // Ensure maxHp is at least 1 to prevent division by zero
+ var safeMaxHp = Math.max(1, maxHp);
+ // Calculate width based on health percentage
+ var hpPercentage = currentHp / safeMaxHp;
+ // Assuming original bossHpBar width was 800
+ self.bossHpBar.scaleX = hpPercentage;
+ // Adjust position if anchor is not 0,0 to keep the bar filling from left
+ // If anchorX is 0.5, the bar scales from the center. Need to adjust x.
+ self.bossHpBar.x = 1024 - self.bossHpBar.width * self.bossHpBar.scaleX / 2;
+ };
+ self.updateHearts = function (currentHearts, maxHearts) {
+ // Clear existing heart graphics
+ self.playerHearts.removeChildren();
+ // Add heart graphics based on currentHearts
+ var heartSize = 40; // Match heart asset size
+ var padding = 10; // Spacing between hearts
+ for (var i = 0; i < maxHearts; i++) {
+ var heartGraphic = LK.getShape('heart', {}); // Use heart shape asset
+ // Position hearts horizontally within the container
+ heartGraphic.x = i * (heartSize + padding);
+ heartGraphic.y = 0; // All on the same vertical level
+ // Tint heart based on current health
+ if (i < currentHearts) {
+ heartGraphic.tint = 0xFF0000; // Full heart color (Red)
+ } else {
+ heartGraphic.tint = 0x888888; // Empty heart color (Grayish)
+ }
+ self.playerHearts.addChild(heartGraphic); // Add heart to the container
+ }
+ // Position the container is done in gameState.positionElements or createGame
+ };
+ // Add UI elements to game container in createGame after player/boss
+ // This is handled in the modified createGame function
+ self.update = function () {
+ // UI elements are mostly passive and updated by boss/player health changes
+ // No continuous update needed for UI elements themselves in this example,
+ // but UI object could have update logic for animations, etc.
+ };
+ return self;
+}(); // End UI object
+/****
+* Boss class
+****/
var Boss = Container.expand(function () {
var self = Container.call(this);
+ // Definicje list klatek dla fireballi - Zdefiniowane raz na początku klasy Boss
+ var circleFrames = [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})];
+ var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})];
// *** MODYFIKACJA: Dodaj grafikę bossIdle jako DZIECKO obiektu bossa (self) ***
// Używamy attachAsset, który już dodaje jako dziecko i ustawia pozycję relatywną na podstawie anchor.
self.bossGraphics = self.attachAsset('bossIdle', {
// Dziecko self
@@ -159,29 +739,30 @@
}
// Ustaw właściwy czas odnowienia dla następnego ataku
self.attackCooldown = (90 + Math.floor(Math.random() * 60)) * self.attackSpeedMultiplier; // 1.5-2.5 seconds * multiplier
};
- // *** START Zmodyfikowana funkcja createAttack - przyjmuje frameDurationMs i activationFrame jako argumenty ***
+ // Zmodyfikowana funkcja createAttack - NIE nadpisuje lokalnie spriteAnim.update (jak w poprzedniej próbie)
// Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
- // Ta funkcja tworzy obiekt ataku logicznego i jego wizualizację (SpriteAnimation)
- // Zmodyfikowana funkcja createAttack - zawiera ZMIENIONĄ KOLEJNOŚĆ operacji add/remove w update klatek
- // Kolejność argumentów: x, y, duration (ms), activationFrame (index klatki), frameDurationMs (ms), framesList (opcjonalne)
- // Ta funkcja tworzy obiekt ataku logicznego i jego wizualizację (SpriteAnimation)
+ // Logika aktywacji kolizji (isActive) jest w Boss.update
self.createAttack = function (x, y, duration, activationFrame, frameDurationMs, framesList) {
- // Lista klatek animacji. Domyślna lista (np. dla szarży), jeśli framesList nie jest podane.
+ // Użyj przekazanej framesList, domyślną oryginalną jeśli nie przekazana
var frames = framesList || [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})];
+ // console.log("CreateAttack called at:", x, y, " Frames array:", frames); // Debug log - można usunąć po naprawie
+ // if (!frames || frames.length === 0 || frames.some(f => f === null || typeof f === 'undefined' || !f.texture)) { // Debug check - można usunąć po naprawie
+ // console.error("Frames array is empty or contains invalid assets! Attack creation might fail."); // Debug log - można usunąć po naprawie
+ // }
var spriteAnim = game.addChild(new SpriteAnimation({
+ // Wizualizacja fireballa jest dodawana do kontenera 'game'
frames: frames,
frameDuration: frameDurationMs,
+ // Użyj wartości przekazanej do konstruktora SpriteAnimation
loop: false,
+ // Animacja nie zapętla się
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y
}));
- // NATYCHMIAST ustaw pierwszą klatkę żeby nie było pustki:
- spriteAnim.removeChildren();
- spriteAnim.addChild(spriteAnim.frames[0]);
spriteAnim.scaleX = 1.6; // Ręczne skalowanie wizualizacji
spriteAnim.scaleY = 1.6;
var attack = {
// Obiekt logiczny ataku
@@ -192,58 +773,16 @@
visual: spriteAnim,
// Referencja do wizualizacji (SpriteAnimation)
lifeTime: Math.floor(duration * (60 / 1000)),
// Czas życia w klatkach gry (przy 60fps)
- isActive: false // Flaga aktywności kolizji, domyślnie false
+ isActive: false,
+ // Flaga aktywności kolizji, domyślnie false
+ activationFrame: activationFrame // Przechowuj activationFrame w obiekcie ataku
};
- // Metoda update dla tej konkretnej animacji spriteAnim (obiektu visual)
- spriteAnim.update = function () {
- // Sprawdź, czy animacja nadal gra i ma klatki
- if (!this.playing || this.frames.length === 0) {
- return;
- }
- this.frameTimer++;
- // Sprawdź, czy minął czas na przejście do następnej klatki
- if (this.frameTimer >= this.frameDuration / (1000 / 60)) {
- this.frameTimer = 0;
- var previousFrameSprite = null; // Będziemy przechowywać poprzednią klatkę do usunięcia
- // Jeśli obecna klatka istnieje, zapisz referencję i usuń ją PRZED zmianą currentFrame
- // *** Zmieniona logika: Usuń obecną klatkę PO dodaniu nowej ***
- // Zamiast usuwać tutaj, zrobimy to na końcu tej sekcji.
- // this.removeChildren(); // Stara linia - USUNIĘTA/ZMIENIONA
- this.currentFrame++; // Przejdź do następnej klatki
- // Obsługa końca animacji (jeśli nie zapętlona)
- if (this.currentFrame >= this.frames.length) {
- // Zostań na ostatniej klatce jeśli nie zapętlona
- this.currentFrame = this.frames.length - 1;
- this.playing = false;
- }
- // *** MODYFIKACJA KOLEJNOŚCI: Najpierw dodaj nową klatkę, POTEM usuń poprzednią ***
- // Dodaj nową obecną klatkę do wyświetlenia
- var nextFrameSprite = this.frames[this.currentFrame];
- // Sprawdź, czy nowa klatka istnieje i różni się od poprzedniej, zanim dodasz/usuniesz
- // Dodajemy tylko jeśli nowa klatka istnieje
- if (nextFrameSprite) {
- this.addChild(nextFrameSprite); // Dodaj nową klatkę
- }
- // Usuń poprzednią klatkę (jeśli istniała, była dzieckiem, różni się od nowej i nadal jest dzieckiem)
- // Sprawdzamy parent, bo w międzyczasie sprite mógł zostać usunięty inaczej
- if (previousFrameSprite && previousFrameSprite.parent === this && previousFrameSprite !== nextFrameSprite) {
- this.removeChild(previousFrameSprite); // Usuń starą klatkę
- }
- // *** Aktywuj kolizję OD PODANEJ activationFrame WZWYŻ ***
- if (this.currentFrame >= activationFrame) {
- attack.isActive = true;
- }
- }
- };
+ // USUNIĘTO: Lokalna definicja funkcji spriteAnim.update
self.attacks.push(attack); // Dodaj obiekt ataku logicznego do tablicy bossa
};
- // *** END Zmodyfikowana funkcja createAttack ***
- // *** START Zmodyfikowana funkcja circleAttack ***
- // Ta funkcja tworzy wiele pocisków w kole wokół bossa
- // Ta funkcja tworzy wiele pocisków w kole wokół bossa Z MAŁYM OPÓŹNIENIEM między nimi
- // Ta funkcja tworzy wiele pocisków w kole wokół bossa Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO
+ // Zmodyfikowana funkcja circleAttack - Z pętlą setTimeout delay (jak w poprzedniej próbie, która dała wiele pocisków)
self.circleAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku
var center = {
// Ustaw środek koła na pozycji bossa
@@ -252,39 +791,35 @@
};
var count = isNewBossPlusMode ? 12 : 8; // Liczba pocisków w ataku (więcej w New Boss+)
var radius = 300; // Promień koła, na którym rozmieszczone są pociski
var attackLifeTime = isNewBossPlusMode ? 2000 : 3000; // Czas życia pocisku w ms (krótszy w New Boss+?)
- // Lista klatek animacji dla tego ataku (pociski koła)
- var circleFrames = [LK.getAsset('fireball0', {}), LK.getAsset('fireball00', {}), LK.getAsset('fireball01', {}), LK.getAsset('fireball02', {}), LK.getAsset('fireball03', {}), LK.getAsset('fireball04', {}), LK.getAsset('fireball05', {}), LK.getAsset('fireball06', {}), LK.getAsset('fireball07', {}), LK.getAsset('fireball08', {}), LK.getAsset('fireball09', {}), LK.getAsset('fireball1', {}), LK.getAsset('fireball10', {}), LK.getAsset('fireball11', {}), LK.getAsset('fireball12', {}), LK.getAsset('fireball13', {}), LK.getAsset('fireball14', {})];
- // Pętla tworząca wiele pocisków rozmieszczonych w kole Z OPÓŹNIENIEM DLA KAŻDEGO
+ // circleFrames array jest zdefiniowane raz na początku klasy Boss
+ // Pętla tworząca wiele pocisków rozmieszczonych w kole Z MINIMALNYM OPÓŹNIENIEM DLA KAŻDEGO
for (var i = 0; i < count; i++) {
// Iteruj tyle razy, ile ma być pocisków (count)
var angle = i / count * Math.PI * 2; // Oblicz kąt dla bieżącego pocisku (równo rozmieszczone na okręgu)
var posX = center.x + Math.cos(angle) * radius; // Oblicz współrzędną X na okręgu
var posY = center.y + Math.sin(angle) * radius; // Oblicz współrzędną Y na okręgu
- // *** MODIFIED: Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku, w tym pierwszego ***
- // Opóźnienie dla i-tego pocisku będzie wynosić 20ms + i * 20ms
+ // Dodaj minimalne opóźnienie (np. 20ms) DO KAŻDEGO pocisku
var delay = i * 20 + 20; // Zapewnij, że nawet pierwszy pocisk ma opóźnienie
// Użyj setTimeout do stworzenia każdego pocisku po małym opóźnieniu
- LK.setTimeout(function (x, y, frames) {
- // Używamy closure do "zamknięcia" wartości x, y i frames dla każdego timeoutu
+ LK.setTimeout(function (x, y) {
+ // Używamy closure do "zamknięcia" wartości x, y (frames jest globalne/dostępne)
return function () {
// Sprawdź czy boss nadal istnieje i gra jest w stanie 'game' zanim stworzysz atak po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
// Wywołaj funkcję createAttack dla tego pocisku z zapamiętaną pozycją i parametrami
// Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 8 (aktywacja od klatki fireball08)
- // frameDurationMs = 250ms (prędkość animacji - można dostosować jeśli nadal miga, ale skupmy się na migraniu)
- // framesList = circleFrames (lista klatek dla tego ataku)
- self.createAttack(x, y, attackLifeTime, 8, 120, frames); // Używamy 250ms, które wcześniej wydawało się zwalniać migotanie
+ // frameDurationMs = 250ms (przykład prędkości)
+ // framesList = circleFrames (używamy zmiennej zdefiniowanej poza funkcją)
+ self.createAttack(x, y, attackLifeTime, 8, 250, circleFrames); // Używamy 250ms dla frameDurationMs
}
};
- }(posX, posY, circleFrames), delay); // Przekaż aktualne posX, posY i circleFrames do closure
+ }(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
};
- // *** END Zmodyfikowana funkcja circleAttack ***
- // *** START Zmodyfikowana funkcja lineAttack ***
- // Ta funkcja tworzy wiele pocisków w linii od bossa do gracza
+ // *** START Zmodyfikowana funkcja lineAttack - Używa synchronicznej pętli i wywołuje nową createAttack ***
self.lineAttack = function () {
LK.getSound('bossAttack').play(); // Odtwórz dźwięk ataku
// Tworzy linię ataków
if (!player) {
@@ -294,25 +829,26 @@
var targetY = player.y;
var count = isNewBossPlusMode ? 8 : 5; // Liczba pocisków w linii
var attackLifeTime = isNewBossPlusMode ? 1500 : 2000; // Czas życia pocisku w ms
var delayBetweenAttacks = isNewBossPlusMode ? 100 : 200; // Opóźnienie między pociskami w ms
- // Lista klatek animacji dla tego ataku (pociski linii)
- var lineFrames = [LK.getAsset('fireball2', {}), LK.getAsset('fireball3', {}), LK.getAsset('fireball4', {}), LK.getAsset('fireball5', {}), LK.getAsset('fireball6', {}), LK.getAsset('fireball7', {}), LK.getAsset('fireball8', {}), LK.getAsset('fireball9', {}), LK.getAsset('fireball15', {}), LK.getAsset('fireball16', {})];
- // Pętla tworząca pociski w linii z opóźnieniem
+ // lineFrames array jest zdefiniowane raz na początku klasy
+ // Pętla tworząca pociski w linii z opóźnieniem (jak w souls5.txt)
for (var i = 0; i < count; i++) {
- var t = i / (count - 1); // Współczynnik interpolacji (0 do 1)
+ var t = i / (count - 1); // Współczynnik interpolacji
var posX = self.x + (targetX - self.x) * t; // Interpolowana pozycja X
var posY = self.y + (targetY - self.y) * t; // Interpolowana pozycja Y
var delay = i * delayBetweenAttacks * self.attackSpeedMultiplier; // Opóźnienie przed stworzeniem pocisku, skalowane
LK.setTimeout(function (x, y) {
- // Używamy closure do "zamknięcia" wartości x i y dla każdego timeoutu
+ // Używamy closure do przekazania pozycji
return function () {
// Sprawdź warunki przed stworzeniem ataku po opóźnieniu
if (self && !self.dead && gameState.currentState === "game") {
- // *** MODYFIKACJA: Wywołaj createAttack z activationFrame = 3, frameDurationMs = 100 i listą klatek dla linii ***
- // Ustaw frameDuration na 100ms dla tego ataku (przykład innej prędkości)
- // Indeks 3 odpowiada 'fireball5' na liście lineFrames [fireball2 (0), fireball3 (1), fireball4 (2), fireball5 (3), ...]
- self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // *** MODIFIED: Added 3, 100, lineFrames ***
+ // Wywołaj funkcję createAttack
+ // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
+ // activationFrame = 3 (indeks 'fireball5' na liście lineFrames)
+ // frameDurationMs = 100ms (przykład prędkości)
+ // framesList = lineFrames (używamy zmiennej zdefiniowanej poza funkcją)
+ self.createAttack(x, y, attackLifeTime, 3, 100, lineFrames); // Przekaż parametry
}
};
}(posX, posY), delay); // Przekaż aktualne posX, posY do closure
}
@@ -325,9 +861,9 @@
// Upewnij się, że gracz istnieje
if (!player) {
return;
}
- // Obsługa grafiki bossa podczas szarży (pozostaje bez zmian od poprzedniej poprawki - pokazuje grafikę idle)
+ // Obsługa grafiki bossa podczas szarży (pokazuje grafikę idle)
if (self.bossAttackAnim) {
self.bossAttackAnim.stop();
if (self.bossAttackAnim.parent === self) {
self.bossAttackAnim.parent.removeChild(self.bossAttackAnim);
@@ -357,1872 +893,295 @@
dy /= distance;
}
var startX = self.x; // Pozycja początkowa szarży
var startY = self.y;
- var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży w jednostkach gry
+ var chargeDistance = isNewBossPlusMode ? 700 : 500; // Dystans szarży
var chargeDuration = isNewBossPlusMode ?
- // Czas trwania szarży w ms
+ // Czas trwania szarży
600 : 800;
- var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków tworzonych wzdłuż ścieżki szarży
- var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku w ms
+ var attacksOnPathCount = isNewBossPlusMode ? 8 : 5; // Liczba pocisków wzdłuż ścieżki
+ var attackLifeTime = isNewBossPlusMode ? 1000 : 1500; // Czas życia pocisku
// Tween (animacja ruchu) bossa podczas szarży
- // Ten tween modyfikuje pozycję obiektu self (boss)
tween(self, {
x: self.x + dx * chargeDistance,
- // Celowa pozycja X
- y: self.y + dy * chargeDistance // Celowa pozycja Y
+ y: self.y + dy * chargeDistance
}, {
duration: chargeDuration * self.attackSpeedMultiplier,
- // Czas trwania szarży, skalowany
+ // Czas trwania, skalowany
easing: tween.easeIn,
- // Krzywa animacji (przyspieszenie)
+ // Krzywa animacji
onFinish: function onFinish() {
- // Funkcja wywoływana po zakończeniu tweenu szarży
+ // Po zakończeniu szarży
if (self && !self.dead && gameState.currentState === "game") {
- // Sprawdź warunki
- // Faza repozycji - boss zatrzymuje ruch i po chwili wraca
+ // Faza repozycji
self.repositioning = true;
LK.setTimeout(function () {
if (self && !self.dead) {
- self.repositioning = false; // Zakończ stan repozycji
+ self.repositioning = false;
}
- }, 500 * self.attackSpeedMultiplier); // Czas repozycji, skalowany
+ }, 500 * self.attackSpeedMultiplier);
// Tween powrotu do pozycji startowej
- // Ten tween modyfikuje pozycję self (boss) - grafika jako dziecko podąży za nim.
tween(self, {
x: startX,
- // Powrót do pozycji X startowej
- y: startY // Powrót do pozycji Y startowej
+ y: startY
}, {
duration: 1000 * self.attackSpeedMultiplier,
- // Czas powrotu, skalowany
- easing: tween.easeOut // Krzywa animacji (zwalnianie)
+ easing: tween.easeOut
});
// Utworzenie ataków (fireballi) wzdłuż ścieżki szarży
- // Fireballe ze szarży używają domyślnej listy klatek z createAttack (bo framesList nie jest podane)
+ // Fireballe ze szarży używają domyślnej listy klatek z createAttack (circleFrames)
for (var i = 0; i < attacksOnPathCount; i++) {
- var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji (0 do 1)
- var currentChargeX = self.x; // Aktualna pozycja X bossa (koniec szarży)
- var currentChargeY = self.y; // Aktualna pozycja Y bossa (koniec szarży)
- // Interpolujemy pozycję pocisku pomiędzy początkiem szarży (startX, startY) a końcem (currentChargeX, currentChargeY)
- var posX = startX + (currentChargeX - startX) * t;
- var posY = startY + (currentChargeY - startY) * t;
- // Utwórz atak (fireball) w obliczonej pozycji
- // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList (opcjonalne)
+ var t = i / (attacksOnPathCount - 1); // Współczynnik interpolacji
+ var currentChargeX = self.x; // Aktualna pozycja bossa (koniec szarży)
+ var currentChargeY = self.y;
+ var posX = startX + (currentChargeX - startX) * t; // Interpolowana pozycja X
+ var posY = startY + (currentChargeY - startY) * t; // Interpolowana pozycja Y
+ // Utwórz atak
+ // Kolejność argumentów createAttack: x, y, duration(ms), activationFrame(index), frameDurationMs(ms), framesList
// activationFrame = 4 (domyślna klatka aktywacji dla tych pocisków)
- // frameDurationMs = 140ms (domyślna prędkość animacji dla tych pocisków)
- self.createAttack(posX, posY, attackLifeTime, 4, 140); // *** MODIFIED: Added 4, 140 ***
+ // frameDurationMs = 140ms (domyślna prędkość)
+ // framesList = circleFrames (używamy listy klatek ataku koła)
+ self.createAttack(posX, posY, attackLifeTime, 4, 140, circleFrames); // Przekaż parametry
}
}
}
});
};
- // *** END Zmodyfikowana funkcja chargeAttack ***
self.takeDamage = function (amount) {
// Funkcja obsługująca otrzymywanie obrażeń przez bossa
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ń
+ self.health = Math.max(0, self.health); // Upewnij się, że zdrowie nie spadnie poniżej zera
LK.effects.flashObject(self, 0xFFFFFF, 200); // Efekt błysku
- // Przejście fazy bossa przy 50% zdrowia (może być wyłączone w New Boss+?)
if (!isNewBossPlusMode) {
- // Tylko w standardowym trybie
+ // Przejście fazy bossa przy 50% zdrowia tylko w standardowym trybie
if (self.health <= self.maxHealth / 2 && self.phase === 1) {
self.phase = 2; // Zmień fazę na 2
self.speed += 2; // Boss staje się szybszy (ruch)
- self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej (mnożnik < 1, np. 0.8 oznacza 80% oryginalnego cooldownu)
- // Wizualne przejście fazy (np. zmiana koloru)
+ self.attackSpeedMultiplier *= 0.8; // Boss atakuje nieco szybciej
tween(self, {
- tint: 0xFF3300 // Zmień odcień na pomarańczowy/czerwony
+ tint: 0xFF3300 // Pomarańczowy/czerwony odcień
}, {
duration: 1000,
- easing: tween.easeInOut // Krzywa animacji
+ easing: tween.easeInOut
});
}
}
- // Sprawdzenie, czy boss został pokonany
if (self.health <= 0) {
self.die(); // Wywołaj funkcję śmierci
}
- // Aktualizacja paska zdrowia jest w game.update -> ui.updateBossHealth
+ // ui.updateBossHealth jest w game.update
};
self.die = function () {
// Funkcja obsługująca śmierć bossa
if (self.dead || gameState.currentState !== "game") {
- // Boss umiera tylko w stanie gry
return;
}
self.dead = true; // Ustaw flagę śmierci
- // 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
+ // Dźwięk zwycięstwa tylko dla STANDARDOWEGO trybu
+ LK.getSound('victory').play();
}
// Wyczyść pozostałe ataki przy śmierci bossa
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy(); // Zniszcz wizualizację ataku
+ attack.visual.destroy();
}
});
- self.attacks = []; // Wyczyść tablicę ataków
+ self.attacks = []; // Wyczyść tablicę ataków logicznych
// Animacja śmierci bossa (zanikanie i powiększanie)
tween(self, {
alpha: 0,
- // Zanik do przezroczystości
scaleX: 2,
- // Podwój skalowanie w poziomie
- scaleY: 2 // Podwój skalowanie w pionie
+ scaleY: 2
}, {
duration: 2000,
- // Czas trwania animacji śmierci (w ms)
easing: tween.easeOut,
- // Krzywa animacji (zwalnianie)
onFinish: function onFinish() {
- // Funkcja wywoływana po zakończeniu animacji śmierci
- // Sprawdź, czy boss nadal istnieje przed zniszczeniem (zabezpieczenie)
+ // Po zakończeniu animacji śmierci
if (self && self.destroy && !self.destroyed) {
self.destroy(); // Zniszcz obiekt bossa
}
- // Zwiększ licznik pokonanych bossów
- storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów (pobierz z local storage lub zacznij od 0)
- // !!! Po pokonaniu bossa ZAWSZE przechodzimy do Grill Screena (niezależnie od trybu) !!!
- gameState.showGrillScreen(); // <--- Zmieniono z gameOver na showGrillScreen
+ storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; // Zwiększ licznik pokonanych bossów
+ // ZAWSZE przechodzimy do Grill Screena
+ gameState.showGrillScreen();
}
});
- // Ataki zostaną wyczyszczone na początku die() lub w gameState.showGrillScreen clean up
};
self.update = function () {
- // Główna metoda aktualizacji bossa (wywoływana w game.update)
+ // Główna metoda aktualizacji bossa
// Aktualizuj tylko w stanie gry
if (gameState.currentState !== "game") {
- // Jeśli zmieniono stan gry i boss nie umiera, wyczyść pozostałe ataki
- // (Zabezpieczenie na wypadek zmiany stanu gry bez śmierci bossa)
if (!self.dead && self.attacks) {
+ // Jeśli zmieniono stan i boss nie umiera, wyczyść ataki
self.attacks.forEach(function (attack) {
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy(); // Zniszcz wizualizację ataku
+ attack.visual.destroy();
}
});
- self.attacks = []; // Wyczyść tablicę ataków logicznych
+ self.attacks = [];
}
- return; // Zakończ aktualizację, jeśli nie jesteśmy w stanie gry
+ return;
}
- // Nie aktualizuj jeśli boss jest martwy i animacja śmierci działa (obiekt może być jeszcze widoczny)
- // Pozwalamy animacji śmierci działać niezależnie od reszty logiki update
+ // Nie aktualizuj jeśli boss jest martwy (pozwól animacji śmierci działać)
if (self.dead) {
return;
}
- // --- START NOWA LOGIKA RUCHU BOSS ---
- // Boss porusza się w kierunku gracza tylko jeśli:
- // - Nie jest martwy (już sprawdzone na początku update)
- // - Gra jest w stanie 'game' (już sprawdzone na początku update)
- // - Nie jest w trakcie "repozycji" po ataku szarży (flaga self.repositioning)
- // - Cooldown ataku jest wystarczająco długi (np. > 30 klatek), co oznacza, że boss nie jest w trakcie przygotowania do nowego ataku
- // - Gracz istnieje i nie jest martwy
- // Zakładamy, że 'chargeAttack' sam zarządza ruchem bossa podczas samej szarży (przez tween).
+ // --- LOGIKA RUCHU BOSS ---
+ // Poruszaj się w kierunku gracza jeśli spełnione warunki
if (!self.repositioning && self.attackCooldown > 30 && player && !player.dead) {
- var dx = player.x - self.x; // Różnica pozycji X między graczem a bossem
- var dy = player.y - self.y; // Różnica pozycji Y
- var distance = Math.sqrt(dx * dx + dy * dy); // Odległość między graczem a bossem
- var moveSpeed = self.speed; // Prędkość ruchu bossa
- // Boss porusza się tylko jeśli gracz jest dalej niż pewna minimalna odległość (np. 150 jednostek)
- // Zapobiega to "drganiom" bossa, gdy jest bardzo blisko gracza.
+ var dx = player.x - self.x;
+ var dy = player.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var moveSpeed = self.speed;
if (distance > 150) {
- var moveX = dx / distance * moveSpeed; // Komponent ruchu X w kierunku gracza
- var moveY = dy / distance * moveSpeed; // Komponent ruchu Y
- var nextX = self.x + moveX; // Przewidywana następna pozycja X bossa
- var nextY = self.y + moveY; // Przewidywana następna pozycja Y bossa
- // Ograniczenia areny (ściany na 100px marginesie, mapa 2048x2732)
- // Uwzględnij rozmiar bossa, aby jego środek nie wszedł w ścianę (zakładając anchor 0.5, 0.5)
- var halfWidth = self.width * self.scaleX / 2; // Połowa szerokości bossa (uwzględniając skalowanie)
- var halfHeight = self.height * self.scaleY / 2; // Połowa wysokości bossa
- var minX = 100 + halfWidth; // Minimalna pozycja X, aby uniknąć ściany z lewej
- var maxX = 2048 - 100 - halfWidth; // Maksymalna pozycja X, aby uniknąć ściany z prawej
- var minY = 100 + halfHeight; // Minimalna pozycja Y, aby uniknąć ściany z góry
- var maxY = 2732 - 100 - halfHeight; // Maksymalna pozycja Y, aby uniknąć ściany z dołu
- // Ustaw nową pozycję bossa, ograniczając ją do granic areny
+ // Poruszaj się tylko jeśli gracz jest dalej niż 150 jednostek
+ var moveX = dx / distance * moveSpeed;
+ var moveY = dy / distance * moveSpeed;
+ var nextX = self.x + moveX;
+ var nextY = self.y + moveY;
+ // Ograniczenia areny
+ var halfWidth = self.width * self.scaleX / 2;
+ var halfHeight = self.height * self.scaleY / 2;
+ var minX = 100 + halfWidth;
+ var maxX = 2048 - 100 - halfWidth;
+ var minY = 100 + halfHeight;
+ var maxY = 2732 - 100 - halfHeight;
self.x = Math.max(minX, Math.min(nextX, maxX));
self.y = Math.max(minY, Math.min(nextY, maxY));
- // --- DODANO: Zaktualizuj pozycję aktywnej grafiki, aby podążała za bossem ---
- // Ten komentarz był wcześniej. Ponieważ grafika jest teraz dzieckiem Bossa i jej pozycja relatywna to 0,0,
- // pozycja grafiki jest AUTOMATYCZNIE aktualizowana, gdy self.x i self.y się zmieniają.
- // Nie potrzebujemy tutaj ręcznej aktualizacji pozycji grafiki.
- // Kod poniżej jest usunięty, bo pozycja relatywna dziecka jest ustawiona na 0,0 przy dodawaniu.
- // if (self.bossGraphics && self.bossGraphics.parent === self) { self.bossGraphics.x = 0; self.bossGraphics.y = 0; }
- // if (self.bossAttackAnim && self.bossAttackAnim.parent === self) { self.bossAttackAnim.x = 0; self.bossAttackAnim.y = 0; }
+ // Grafika jako dziecko podąża automatycznie.
}
}
- // --- KONIEC NOWA LOGIKA RUCHU BOSS ---
- // --- START OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) ---
+ // --- KONIEC LOGIKA RUCHU BOSS ---
+ // --- OBSŁUGA ATAKÓW BOSS (Kolizje i Czas Życia Pocisków) ---
// Aktualizuj istniejące ataki bossa (pociski) i sprawdzaj kolizje z graczem
- // Iteruj od końca do początku tablicy self.attacks, aby bezpiecznie usuwać elementy (przy użyciu splice)
for (var i = self.attacks.length - 1; i >= 0; i--) {
- var attack = self.attacks[i]; // Pobierz bieżący obiekt ataku logicznego
- // Zmniejszaj czas życia ataku (w klatkach gry)
+ var attack = self.attacks[i];
+ // Aktualizuj flagę isActive na podstawie aktualnej klatki visual i przechowywanego activationFrame
+ // Sprawdź czy visual istnieje i ma currentFrame
+ if (attack.visual && typeof attack.visual.currentFrame !== 'undefined') {
+ if (attack.visual.currentFrame >= attack.activationFrame) {
+ attack.isActive = true; // Aktywuj kolizję
+ } else {
+ attack.isActive = false; // Kolizja nieaktywna
+ }
+ } else {
+ attack.isActive = false;
+ }
if (attack.lifeTime > 0) {
+ // Zmniejszaj czas życia
attack.lifeTime--;
}
- // Sprawdź kolizję ataku z graczem tylko jeśli:
- // - Flaga attack.isActive jest true (atak jest "groźny")
- // - Gracz istnieje i nie jest martwy
- // - Gracz nie jest nietykalny (np. po turlaniu)
- // - Wizualizacja ataku istnieje i nie została jeszcze zniszczona
+ // Sprawdź kolizję z graczem jeśli spełnione warunki
if (attack.isActive && player && !player.dead && !player.invulnerable && attack.visual && !attack.visual.destroyed) {
- var dx_p = player.x - attack.x; // Różnica pozycji X między graczem a atakiem
- var dy_p = player.y - attack.y; // Różnica pozycji Y
- var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p); // Odległość między graczem a atakiem
- // Użyj promieni do sprawdzenia kolizji (kolizja kołowa)
- var playerRadius_p = player.width / 2; // Promień gracza (zakładając, że gracz ma właściwość width)
- var attackRadius = attack.radius; // Promień ataku (zdefiniowany w createAttack)
+ var dx_p = player.x - attack.x;
+ var dy_p = player.y - attack.y;
+ var distance_p = Math.sqrt(dx_p * dx_p + dy_p * dy_p);
+ var playerRadius_p = player.width / 2;
+ var attackRadius = attack.radius;
if (distance_p < playerRadius_p + attackRadius) {
- // Wykryto kolizję! Gracz otrzymuje obrażenia.
- player.takeDamage(1); // Zadaj 1 punkt obrażeń graczowi
- // Opcjonalnie: zniszcz wizualizację ataku natychmiast po trafieniu gracza
+ player.takeDamage(1); // Gracz otrzymuje obrażenia
if (attack.visual && attack.visual.destroy) {
- attack.visual.destroy(); // Zniszcz sprite animacji
+ attack.visual.destroy();
}
- self.attacks.splice(i, 1); // Usuń obiekt ataku logicznego z tablicy
- // Można dodać 'break;' tutaj, jeśli chcesz, aby gracz mógł otrzymać obrażenia tylko od jednego pocisku bossa na klatkę.
- // W przeciwnym razie może otrzymać obrażenia od wielu pocisków w tej samej klatce, jeśli na siebie nałożą.
+ self.attacks.splice(i, 1); // Usuń trafiony atak
// break;
}
}
- // Usuń atak, jeśli skończył mu się czas życia LUB jego wizualizacja została zniszczona (np. po kolizji z graczem)
- if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroyed) {
- // Upewnij się, że wizualizacja jest zniszczona, jeśli jeszcze istnieje (na wypadek, gdyby skończył się tylko lifeTime)
+ // Usuń atak, jeśli skończył mu się czas życia LUB wizualizacja zniszczona
+ if (attack.lifeTime <= 0 || attack.visual && attack.visual.destroy || attack.visual && attack.visual.destroyed) {
+ // Dodano trzeci warunek dla pewności
if (attack.visual && attack.visual.destroy && !attack.visual.destroyed) {
- attack.visual.destroy(); // Zniszcz sprite animacji
+ attack.visual.destroy();
}
- self.attacks.splice(i, 1); // Usuń obiekt ataku logicznego z tablicy self.attacks
+ self.attacks.splice(i, 1);
}
}
// --- KONIEC OBSŁUGA ATAKÓW BOSS ---
- // Zmniejszaj cooldown ataku w każdej klatce
if (self.attackCooldown > 0) {
+ // Zmniejszaj cooldown
self.attackCooldown--;
}
- // Sprawdź, czy nadszedł czas na rozpoczęcie nowego wzorca ataku
- // Warunki: cooldown ataku <= 0, boss żyje, stan gry to "game", boss nie jest w trakcie repozycji po szarży
+ // Sprawdź, czy nadszedł czas na nowy atak
if (self.attackCooldown <= 0 && !self.repositioning) {
- // Wywołanie startAttackPattern sprawdzi resztę warunków (dead, state) wewnątrz siebie
- self.startAttackPattern(); // Rozpocznij nowy wzorzec ataku
+ self.startAttackPattern();
}
};
return self; // Zwróć instancję obiektu Boss
-});
-var Player = Container.expand(function () {
- var self = Container.call(this);
- // Zamiast var playerGraphics = ...
- self.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 (NIE UŻYWANE OBECNIE W UPDATE?)
- 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 (frames)
- 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 (NIE UŻYWANE?)
- self.rollAnimationInterval = null; // Store interval ID for roll animation
- self.hasRolledThroughBossThisRoll = false; // Track if boss was hit during this roll
- // Clear all roll-related timeouts and intervals
- self.clearRollTimeouts = function () {
- if (self.rollTimeoutId) {
- LK.clearTimeout(self.rollTimeoutId);
- self.rollTimeoutId = null;
- }
- if (self.rollAnimationInterval) {
- LK.clearInterval(self.rollAnimationInterval);
- self.rollAnimationInterval = null;
- }
- };
- // Roll mechanic
- self.roll = function (direction) {
- if (!self.rolling && self.rollCooldown <= 0 && !self.dead) {
- self.rolling = true;
- self.rollDirection = direction;
- self.rollCooldown = 45;
- self.invulnerable = true;
- self.invulnerabilityFrames = 30;
- self.hasRolledThroughBossThisRoll = false;
- // Ukryj normalną grafikę gracza
- if (self.playerGraphics && !self.playerGraphics.destroyed) {
- self.playerGraphics.visible = false;
- }
- // --- Roll Animation (turlanie) ---
- var rollFrames = ['roll', 'roll0', 'roll1', 'roll2'];
- var currentFrame = 0;
- var rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 0,
- y: 0
- }));
- var rollAnimationInterval = LK.setInterval(function () {
- if (rollAnimationSprite && rollAnimationSprite.destroy) {
- rollAnimationSprite.destroy();
- }
- currentFrame = (currentFrame + 1) % rollFrames.length;
- rollAnimationSprite = self.addChild(LK.getAsset(rollFrames[currentFrame], {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 0,
- y: 0
- }));
- }, 70); // co 70ms zmienia klatkę podczas turlania
- if (self.rollTimeoutId) {
- LK.clearTimeout(self.rollTimeoutId);
- }
- self.rollTimeoutId = LK.setTimeout(function () {
- self.rolling = false;
- // --- Po rollu zakończ turlanie ---
- if (rollAnimationInterval) {
- LK.clearInterval(rollAnimationInterval);
- }
- if (rollAnimationSprite && rollAnimationSprite.destroy) {
- rollAnimationSprite.destroy();
- }
- // --- Stand Up Animation (wstawanie + machnięcie mieczem) ---
- var standUpFrames = ['roll3', 'roll4'];
- var standUpFrame = 0;
- var standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 0,
- y: 0
- }));
- var standUpInterval = LK.setInterval(function () {
- if (standUpSprite && standUpSprite.destroy) {
- standUpSprite.destroy();
- }
- standUpFrame++;
- if (standUpFrame < standUpFrames.length) {
- standUpSprite = self.addChild(LK.getAsset(standUpFrames[standUpFrame], {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 0,
- y: 0
- }));
- } else {
- // Koniec animacji stand-up
- LK.clearInterval(standUpInterval);
- if (standUpSprite && standUpSprite.destroy) {
- standUpSprite.destroy();
- }
- // Przywróć normalną grafikę gracza
- if (self.playerGraphics && !self.playerGraphics.destroyed) {
- self.playerGraphics.visible = true;
- }
- }
- }, 100); // 100ms między klatkami wstawania
- self.rollTimeoutId = null;
- }, self.rollDuration);
- }
- };
- // 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.health = 0; // Ensure health doesn't go below 0 visually
- self.die();
- return; // Zakończ, jeśli gracz umarł
- }
- // Set brief invulnerability after hit (if not rolling)
- // Nietykalność po trafieniu jest teraz KRÓTSZA niż turlanie
- if (!self.rolling) {
- self.invulnerable = true;
- self.invulnerabilityFrames = 30; // Krótsza nietykalność po trafieniu (np. 0.5s)
- }
- }
- };
- // Die method
- self.die = function () {
- if (self.dead) {
- // Zapobiegaj wielokrotnemu wywołaniu
- 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 || storage.maxHearts < 5) {
- // Upewnij się, że startujemy od min 5
- storage.maxHearts = 5;
- }
- if (storage.maxHearts < 10) {
- storage.maxHearts = storage.maxHearts + 1;
- }
- self.clearRollTimeouts(); // Anuluj ewentualne trwające turlanie
- // Death animation
- tween(self, {
- alpha: 0,
- scaleX: 2,
- scaleY: 2
- }, {
- duration: 1000,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- // Zniszcz obiekt gracza PO zakończeniu animacji
- if (self && self.destroy && !self.destroyed) {
- self.destroy();
- }
- // Set death reason flag to true (player died)
- gameOverReasonIsDeath = true;
- // Show game over screen - gameState zarządza przejściem
- gameState.gameOver(true); // Przekaż true (śmierć gracza)
- }
- });
- };
- // Update method called every frame
- self.update = function () {
- // Aktualizuj tylko w stanie gry
- if (gameState.currentState !== "game") {
- // Wyczyść timery, jeśli stan gry się zmienił
- if (self.rolling) {
- self.rolling = false;
- } // Przerwij turlanie
- self.clearRollTimeouts();
- return;
- }
- // Nie aktualizuj jeśli gracz jest martwy (ale pozwól animacji śmierci działać)
- if (self.dead) {
- return;
- }
- // Handle roll cooldown
- if (self.rollCooldown > 0) {
- self.rollCooldown--;
- }
- // Handle invulnerability frames (zarówno z turlania, jak i z otrzymania obrażeń)
- if (self.invulnerable && self.invulnerabilityFrames > 0) {
- self.invulnerabilityFrames--;
- // Blinking effect during invulnerability
- // Miga szybciej
- self.alpha = self.invulnerabilityFrames % 4 > 1 ? 0.3 : 1;
- if (self.invulnerabilityFrames <= 0) {
- self.invulnerable = false;
- self.alpha = 1; // Restore full opacity when invulnerability ends
- }
- } else if (self.invulnerable && self.invulnerabilityFrames <= 0) {
- // Upewnij się, że stan jest spójny jeśli klatki się skończyły
- self.invulnerable = false;
- self.alpha = 1;
- }
- // Handle movement during roll
- if (self.rolling) {
- var rollDx = self.rollDirection.x * self.rollSpeed;
- var rollDy = self.rollDirection.y * self.rollSpeed;
- var nextX = self.x + rollDx;
- var nextY = self.y + rollDy;
- // Ograniczenia areny (ściany na 100px marginesie, mapa 2048x2732)
- // Uwzględnij rozmiar gracza (zakładając anchor 0.5, 0.5)
- var halfWidth = self.width / 2;
- var halfHeight = self.height / 2;
- var minX = 100 + halfWidth;
- var maxX = 2048 - 100 - halfWidth;
- var minY = 100 + halfHeight;
- var maxY = 2732 - 100 - halfHeight;
- self.x = Math.max(minX, Math.min(nextX, maxX));
- self.y = Math.max(minY, Math.min(nextY, maxY));
- // Sprawdź kolizję turlania z bossem
- // 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_b = self.x - boss.x;
- var dy_b = self.y - boss.y;
- var distance_b = Math.sqrt(dx_b * dx_b + dy_b * dy_b);
- // 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_b < playerCollisionRadius + bossCollisionRadius) {
- // Kolizja wykryta podczas turlania
- boss.takeDamage(10); // ZADAJ 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
- }
- }
- } else {
- // --- Podstawowy ruch gracza (jeśli nie turla się) ---
- // Ta część była nieobecna, dodajmy prosty ruch oparty na wejściu (jeśli LK go dostarcza)
- // Zakładając, że mamy dostęp do stanu klawiszy/joysticka np. przez LK.controls
- // To jest PRZYKŁAD - musisz dostosować do API LK
- /*
- var moveX = 0;
- var moveY = 0;
- if (LK.controls.left) moveX -= 1;
- if (LK.controls.right) moveX += 1;
- if (LK.controls.up) moveY -= 1;
- if (LK.controls.down) moveY += 1;
- if (moveX !== 0 || moveY !== 0) {
- // Normalizuj wektor ruchu, jeśli poruszasz się po przekątnej
- var moveMagnitude = Math.sqrt(moveX * moveX + moveY * moveY);
- var normalizedX = moveX / moveMagnitude;
- var normalizedY = moveY / moveMagnitude;
- var nextX = self.x + normalizedX * self.speed; // Użyj self.speed
- var nextY = self.y + normalizedY * self.speed;
- // Ograniczenia areny (jak w turlaniu)
- var halfWidth = self.width / 2;
- var halfHeight = self.height / 2;
- var minX = 100 + halfWidth;
- var maxX = 2048 - 100 - halfWidth;
- var minY = 100 + halfHeight;
- var maxY = 2732 - 100 - halfHeight;
- self.x = Math.max(minX, Math.min(nextX, maxX));
- self.y = Math.max(minY, Math.min(nextY, maxY));
- }
- */
- // Jeśli nie masz łatwego dostępu do stanu klawiszy, gracz będzie się ruszał tylko podczas turlania.
- }
- };
- 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 SpriteAnimation = Container.expand(function (options) {
- var self = Container.call(this);
- // Initialize with default values
- options = options || {};
- self.frames = options.frames || [];
- self.frameDuration = options.frameDuration || 100; // Default 100ms per frame
- self.loop = options.loop !== undefined ? options.loop : true;
- self.currentFrame = 0;
- self.frameTimer = 0;
- self.playing = true;
- // Set initial position and anchor if provided
- if (options.x !== undefined) {
- self.x = options.x;
- }
- if (options.y !== undefined) {
- self.y = options.y;
- }
- // Handle anchor options
- if (options.anchorX !== undefined || options.anchorY !== undefined) {
- var anchorX = options.anchorX !== undefined ? options.anchorX : 0;
- var anchorY = options.anchorY !== undefined ? options.anchorY : 0;
- // Apply anchor to all frames
- self.frames.forEach(function (frame) {
- frame.anchor.set(anchorX, anchorY);
- });
- }
- // Add the first frame to display initially
- if (self.frames.length > 0) {
- self.removeChildren(); // zabezpieczenie
- self.addChild(self.frames[self.currentFrame]);
- }
- // Animation update method
- self.update = function () {
- if (!self.playing || self.frames.length === 0) {
- return;
- }
- self.frameTimer++;
- if (self.frameTimer >= self.frameDuration / (1000 / 60)) {
- self.frameTimer = 0;
- if (self.children.length > 0) {
- self.removeChild(self.children[0]); // Usuwamy tylko 1 aktualną klatkę
- }
- self.currentFrame++;
- if (self.currentFrame >= self.frames.length) {
- if (self.loop) {
- self.currentFrame = 0;
- } else {
- self.currentFrame = self.frames.length - 1;
- self.playing = false;
- }
- }
- self.addChild(self.frames[self.currentFrame]); // Dodajemy nową klatkę
- }
- };
- // Method to stop animation
- self.stop = function () {
- self.playing = false;
- };
- // Method to start/resume animation
- self.play = function () {
- self.playing = true;
- };
- // Method to set a specific frame
- self.gotoFrame = function (frameIndex) {
- if (frameIndex >= 0 && frameIndex < self.frames.length) {
- self.removeChildren();
- self.currentFrame = frameIndex;
- self.addChild(self.frames[self.currentFrame]);
- }
- };
- 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) {
- // Sprawdź poprawność danych wejściowych
- current = Math.max(0, current || 0);
- max = Math.max(1, max || 1); // Max musi być co najmniej 1
- // Usuń istniejące serca tylko jeśli liczba się zmieniła lub nie ma serc
- if (self.hearts.length !== max) {
- while (self.hearts.length > 0) {
- var oldHeart = self.hearts.pop();
- if (oldHeart && oldHeart.destroy && !oldHeart.destroyed) {
- oldHeart.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 + 25; // Dodano +25 aby wycentrować lepiej (bo anchorX = 0.5)
- self.heartContainer.y = 100; // Pozycja pionowa
- } else {
- // Zaktualizuj tylko kolory istniejących serc
- for (var j = 0; j < self.hearts.length; j++) {
- if (self.hearts[j]) {
- self.hearts[j].tint = j < current ? 0xFF0000 : 0x555555;
- }
- }
- }
- };
- // Aktualizuje wizualizację paska zdrowia bossa
- self.updateBossHealth = function (current, max) {
- // Sprawdź poprawność danych
- current = Math.max(0, current || 0);
- max = Math.max(1, max || 1); // Max musi być co najmniej 1
- // 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
- // Pokaż jeśli (stan gry lub game over) ORAZ (boss istnieje i żyje LUB jesteśmy w stanie game over z powodu czasu w Boss+)
- var shouldBeVisible = (gameState.currentState === "game" || gameState.currentState === "gameOver") && (boss && !boss.dead && current > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode);
- self.bossHealthBarContainer.alpha = shouldBeVisible ? 1 : 0;
- }
- // Upewnij się, że pasek zdrowia tła też jest widoczny/ukryty tak samo jak kontener
- 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);
- self.messageTimeout = null;
- }
- // Zaplanuj zanikanie po czasie, jeśli duration > 0
- if (duration && duration > 0) {
- self.messageTimeout = LK.setTimeout(function () {
- tween(self.messageText, {
- alpha: 0 // Zaniknij do przezroczystości 0
- }, {
- duration: 500,
- // Czas trwania zanikania
- onFinish: function onFinish() {
- self.messageTimeout = null;
- } // Reset ID
- });
- }, duration);
- } else {
- // If duration is 0 or not provided, message stays visible
- self.messageText.alpha = 1;
- }
- };
- // 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 () {
- // Upewnij się, że storage.totalDeaths jest liczbą
- var deaths = storage.totalDeaths || 0;
- self.deathsText.setText("Deaths: " + deaths);
- };
- // Aktualizuje wyświetlanie czasu timera
- self.updateTimerDisplay = function (seconds) {
- // Upewnij się, że sekundy są liczbą >= 0
- seconds = Math.max(0, seconds || 0);
- 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) {
- // Pozycje stałe (niezależne od stanu)
- self.deathsText.x = 2048 - 50;
- self.deathsText.y = 50;
- self.timerText.x = 50;
- self.timerText.y = 50;
- self.heartContainer.y = 100; // Pozycja pionowa serc
- self.bossHealthBarContainer.x = 2048 / 2;
- self.bossHealthBarContainer.y = 150; // Pasek HP bossa poniżej timera/serc
- // Resetuj widoczność przed ustawieniem dla danego stanu
- self.titleText.alpha = 0;
- self.messageText.alpha = 0;
- self.tutorialText.alpha = 0;
- self.timerText.alpha = 0;
- self.heartContainer.alpha = 0;
- self.bossHealthBarContainer.alpha = 0; // Zostanie włączony przez updateBossHealth jeśli trzeba
- self.deathsText.alpha = 1; // Licznik śmierci zawsze widoczny
- switch (state) {
- case "title":
- // Pozycje dla ekranu tytułowego
- self.titleText.x = 2048 / 2;
- self.titleText.y = 800;
- self.titleText.alpha = 1; // Pokaż tytuł
- self.messageText.x = 2048 / 2;
- self.messageText.y = 1000; // "Tap to Start" (ustawiane przez showMessage)
- // self.messageText.alpha = 1; // Ustawiane przez showMessage
- self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 1200; // "Swipe to Roll..." (ustawiane przez showTutorial)
- // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial
- break;
- case "game":
- // Pozycje dla stanu gry (walka z bossem)
- // self.titleText.alpha = 0; // Ustawione domyślnie
- self.messageText.x = 2048 / 2;
- self.messageText.y = 1500; // Komunikaty w trakcie gry (np. przyspieszenie bossa)
- // self.messageText.alpha = 0; // Ustawiane przez showMessage
- self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 200; // Tutorial w trakcie gry ("Swipe to roll away!")
- // self.tutorialText.alpha = 1; // Ustawiane przez showTutorial/hideTutorial
- // Pokaż serca i timer podczas gry
- self.heartContainer.alpha = 1;
- self.timerText.alpha = 1;
- // Pasek zdrowia bossa jest zarządzany przez updateBossHealth
- break;
- case "grillMenu":
- // Nowy stan dla Grill Screena
- // self.titleText.alpha = 0; // Ustawione domyślnie
- self.messageText.x = 2048 / 2;
- self.messageText.y = 500; // Pozycja dla komunikatów "Rest in peace..."
- // self.messageText.alpha = 0; // Ustawiane przez showMessage
- // self.tutorialText.alpha = 0; // Ustawione domyślnie
- break;
- case "gameOver":
- // Pozycje dla ekranu Game Over
- self.titleText.x = 2048 / 2;
- self.titleText.y = 800; // Tytuł "YOU DIED" lub komunikat zwycięstwa Boss+
- // self.titleText.alpha = 1; // Ustawiane w gameState.gameOver
- self.messageText.x = 2048 / 2;
- self.messageText.y = 1000; // Pusty lub inny komunikat
- // self.messageText.alpha = 0; // Ustawiane w gameState.gameOver przez showMessage
- self.tutorialText.x = 2048 / 2;
- self.tutorialText.y = 1200; // Pusty
- // self.tutorialText.alpha = 0; // Ustawiane w gameState.gameOver przez showTutorial
- // Widoczność paska HP bossa zarządzana przez updateBossHealth
- break;
- case "intro": // Pozycje dla ekranów intro/tutoriali
- case "fakeTutorial":
- case "realTutorial":
- // Większość elementów ukryta (ustawione domyślnie wyżej)
- break;
- }
- };
- return self;
-});
-
+})(); // End Boss class
/****
-* Initialize Game
+* Game creation and loop
****/
-var game = new LK.Game({
- backgroundColor: 0x111111 // Ciemne tło domyślne
-});
-
-/****
-* Game Code
-****/
-// Globalny kontener na elementy scen intro/tutoriali i Grill Screena (do łatwego czyszczenia)
-// Komentarze o assetach pominięte dla zwięzłości
-var currentSceneElements = new Container();
-// Zmienne gry
-var player;
-var boss;
-var ui;
-var walls = []; // Ściany areny bossa
-// Zmienna do przechowywania aktywnego tła sceny
-var currentBackground = null;
-// Flaga do śledzenia, czy jesteśmy w trybie New Boss+
-var isNewBossPlusMode = false;
-// Flaga do śledzenia przyczyny Game Over (true = gracz zginął, false = czas minął)
-var gameOverReasonIsDeath = false;
-// Funkcja do czyszczenia elementów z kontenera currentSceneElements (nie usuwa UI ani tła)
-function clearScene() {
- while (currentSceneElements.children.length > 0) {
- var child = currentSceneElements.children[0];
- if (child && child.destroy) {
- child.destroy(); // Użyj destroy jeśli dostępne
- } else if (child && child.parent) {
- child.parent.removeChild(child); // Fallback
- } else {
- // Jeśli obiekt nie ma ani destroy ani parent, usuń go z tablicy (choć to nie powinno się zdarzyć)
- currentSceneElements.children.shift();
- }
+// Global variables for game objects
+// Already defined at the beginning: var player, boss, ui, game, gameContainer, etc.
+var createGame = function createGame() {
+ // Upewnij się, że poprzednia instancja gry jest posprzątana
+ if (game) {
+ game.destroy();
+ game = null;
}
- // Usuń też dzieci bezpośrednio z 'game', które mogły zostać dodane poza 'currentSceneElements' w scenach,
- // ale UWAŻAJ, aby nie usunąć stałych elementów jak UI, walls, currentSceneElements sam w sobie.
- // Lepsze podejście: zawsze dodawaj elementy specyficzne dla sceny do currentSceneElements.
-}
-// Obiekt zarządzający stanami gry
-var gameState = {
- currentState: "title",
- // Początkowy stan gry
- gameDuration: 120,
- // Domyślny czas walki z bossem w sekundach (2 minuty)
- remainingTime: 0,
- // Pozostały czas
- gameTimerInterval: null,
- // ID interwału timera
- fakeTutorialTimerId: null,
- // ID timera przechodzącego do prawdziwego tutorialu
- bossSpeedIncreased: false,
- // Flaga przyspieszenia bossa
- touchStart: {
- x: 0,
- y: 0
- },
- // Początek gestu
- touchEnd: {
- x: 0,
- y: 0
- },
- // Koniec gestu
- // Inicjalizacja gry (wywoływana raz na początku)
- init: function init() {
- // Resetuj stan przy każdym uruchomieniu
- storage.totalDeaths = 0;
- storage.maxHearts = 5; // Zaczynaj zawsze z 5 sercami
- isNewBossPlusMode = false;
- gameOverReasonIsDeath = false;
- game.setBackgroundColor(0x111111); // Ustaw domyślny kolor tła
- this.createWalls(); // Stwórz ściany areny
- ui = game.addChild(new UI()); // Stwórz UI
- // Zainicjalizuj UI dla ekranu tytułowego
- ui.updateDeathsCounter();
- ui.updateHearts(storage.maxHearts, storage.maxHearts); // Pokaż początkowe serca
- ui.positionElements("title"); // Pozycjonuj elementy UI dla tytułu
- game.addChild(currentSceneElements); // Dodaj kontener na elementy scen
- // Rozpocznij muzykę w tle
- LK.playMusic('bgMusic', {
- fade: {
- start: 0,
- end: 0.3,
- duration: 1000
- }
- });
- this.showTitleScreen(); // Rozpocznij od ekranu tytułowego
- },
- // Tworzy/odświeża ściany areny bossa
- createWalls: function createWalls() {
- walls.forEach(function (wall) {
- if (wall && wall.destroy) {
- wall.destroy();
- }
- });
- walls = [];
- // Użyj getAsset zamiast new Shape dla ścian, jeśli 'wall' i 'floor' to assety
- var leftWall = game.addChild(LK.getAsset('wall', {
- anchorX: 0,
- anchorY: 0,
- x: 0,
- y: 0
- }));
- walls.push(leftWall);
- var rightWall = game.addChild(LK.getAsset('wall', {
- anchorX: 1,
- anchorY: 0,
- x: 2048,
- y: 0
- })); // AnchorX=1 dla prawej ściany
- rightWall.x = 2048; // Poprawka pozycji prawej ściany
- walls.push(rightWall);
- var topWall = game.addChild(LK.getAsset('floor', {
- anchorX: 0,
- anchorY: 0,
- x: 0,
- y: 0
- }));
- walls.push(topWall);
- var bottomWall = game.addChild(LK.getAsset('floor', {
- anchorX: 0,
- anchorY: 1,
- x: 0,
- y: 2732
- })); // AnchorY=1 dla dolnej ściany
- bottomWall.y = 2732; // Poprawka pozycji dolnej ściany
- walls.push(bottomWall);
- // Ustaw ściany na spodzie (niski zIndex)
- walls.forEach(function (wall) {
- game.setChildIndex(wall, 0);
- });
- },
- // ---------- Metody przejścia między stanami ----------
- showTitleScreen: function showTitleScreen() {
- // Zatrzymaj timery poprzedniego stanu
- if (this.fakeTutorialTimerId) {
- LK.clearTimeout(this.fakeTutorialTimerId);
- }
- this.fakeTutorialTimerId = null;
- if (this.gameTimerInterval) {
- LK.clearInterval(this.gameTimerInterval);
- }
- this.gameTimerInterval = null;
- clearScene(); // Wyczyść elementy poprzedniej sceny (Grill, GameOver, itp.)
- // Usuń stare tło, jeśli istnieje
- if (currentBackground) {
- tween.stop(currentBackground); // Zatrzymaj animacje tła
- currentBackground.destroy();
- currentBackground = null;
- }
- this.currentState = "title";
- game.setBackgroundColor(0x1a1a1a); // Ciemne tło bazowe dla tytułu
- // Dodaj tło ekranu tytułowego
- currentBackground = LK.getAsset('titleBg', {
- anchorX: 0,
- anchorY: 0,
- x: 0,
- y: 0
- });
- game.addChildAt(currentBackground, 0); // Dodaj na spód
- // Ukryj gracza/bossa jeśli istnieją
- if (player && player.destroy) {
- player.destroy();
- }
- player = null;
- if (boss && boss.destroy) {
- boss.destroy();
- }
- boss = null;
- // Pokaż ściany (jako część tła menu)
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 1;
- }
- });
- // Ustaw UI dla tytułu
- ui.positionElements("title");
- ui.titleText.setText("Welcome Unchosen");
- ui.showMessage("Tap to Start", 0);
- ui.showTutorial("Swipe to Roll - Death is Progress");
- // Resetuj stan gestu
- this.touchStart = {
- x: 0,
- y: 0
- };
- this.touchEnd = {
- x: 0,
- y: 0
- };
- // Przycisk testowy do Grill Menu (jeśli nadal potrzebny)
- var grillMenuTestButton = new Container();
- grillMenuTestButton.interactive = true;
- grillMenuTestButton.x = 2048 / 2;
- grillMenuTestButton.y = 1600;
- currentSceneElements.addChild(grillMenuTestButton);
- var grillButtonBg = LK.getAsset('button_bg', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- grillMenuTestButton.addChild(grillButtonBg);
- var grillButtonText = new Text2('Go to Grill Menu', {
- size: 50,
- fill: 0xFFFFFF
- });
- grillButtonText.anchor.set(0.5, 0.5);
- grillMenuTestButton.addChild(grillButtonText);
- grillMenuTestButton.down = function () {
- gameState.showGrillScreen();
- };
- // --- Tap anywhere on title screen to start the intro ---
- game.on('down', function () {
- if (gameState.currentState === 'title') {
- gameState.showIntro();
- }
- });
- },
- showIntro: function showIntro() {
- if (this.fakeTutorialTimerId) {
- LK.clearTimeout(this.fakeTutorialTimerId);
- }
- this.fakeTutorialTimerId = null; // Wyczyść timer
- clearScene(); // Wyczyść elementy tytułu
- if (currentBackground) {
- tween.stop(currentBackground);
- currentBackground.destroy();
- currentBackground = null;
- } // Usuń stare tło
- this.currentState = "intro";
- game.setBackgroundColor(0x111111);
- // Dodaj tło intro i animację zoom
- currentBackground = LK.getAsset('introBg', {
- anchorX: 0.5,
- anchorY: 0.4,
- x: 1000,
- y: 1000,
- scaleX: 1,
- scaleY: 1
- });
- game.addChildAt(currentBackground, 0);
- tween(currentBackground, {
- scaleX: 1.4,
- scaleY: 1.4
- }, {
- duration: 22000,
- easing: tween.linear,
- repeat: Infinity,
- yoyo: true
- });
- // Ukryj ściany na czas intro
- if (player && player.destroy) {
- player.destroy();
- }
- player = null; // Upewnij się, że nie ma gracza/bossa
- if (boss && boss.destroy) {
- boss.destroy();
- }
- boss = null;
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 0;
- }
- }); // Ukryj ściany
- ui.positionElements("intro"); // Ustaw UI dla intro (głównie ukrywa elementy)
- // --- Teksty intro z timerami ---
- // Funkcja do wyświetlania tekstu z fade-in, fade-out i kontynuacją
- function showIntroText(text, delay, onComplete) {
- var introText = new Text2(text, {
- size: 90,
- fill: 0xFFFFFF,
- align: 'center',
- wordWrap: true,
- wordWrapWidth: 1800
- });
- introText.anchor.set(0.5, 0.5);
- introText.x = 2048 / 2;
- introText.y = 2232 / 2 - 400;
- introText.alpha = 0;
- currentSceneElements.addChild(introText);
- LK.setTimeout(function () {
- tween(introText, {
- alpha: 1
- }, {
- duration: 1000,
- easing: tween.easeInOut
- });
- LK.setTimeout(function () {
- tween(introText, {
- alpha: 0
- }, {
- duration: 1000,
- easing: tween.easeInOut
- });
- LK.setTimeout(function () {
- try {
- if (introText && introText.destroy) {
- introText.destroy();
- }
- } catch (e) {}
- if (onComplete) {
- onComplete();
- }
- }, 1000); // czas na fade-out
- }, 3000); // czas wyświetlania
- }, delay);
- }
- // --- Startujemy sekwencję intro ---
- showIntroText('From the creators of FromSoftware...', 2000, function () {
- showIntroText('...I have no idea. I don’t know them.', 0, function () {
- showIntroText('Silas GameStudio...', 0, function () {
- showIntroText('Still looking for funding.', 0, function () {
- showIntroText('Oh... and I don’t even exist.', 0, function () {
- // <-- NOWY TEKST
- // Po ostatnim tekście przechodzimy do howToScene
- LK.setTimeout(function () {
- if (typeof gameState.showFakeTutorial === 'function') {
- gameState.showFakeTutorial();
- } else {
- console.error("Error: gameState.showFakeTutorial is not defined");
- gameState.showTitleScreen(); // Fallback to title screen
- }
- }, 1000);
- });
- });
- });
- });
- });
- },
- showFakeTutorial: function showFakeTutorial() {
- clearScene(); // Wyczyść tekst intro
- if (this.fakeTutorialTimerId) {
- LK.clearTimeout(this.fakeTutorialTimerId);
- }
- this.fakeTutorialTimerId = null; // Wyczyść stary timer
- this.currentState = "fakeTutorial";
- // Zachowaj tło intro? Jeśli tak, usuń game.setBackgroundColor i nie niszcz currentBackground
- // game.setBackgroundColor(0x000000); // Czarne tło - usunięte, aby zachować tło intro
- ui.positionElements("fakeTutorial"); // Ustaw UI
- // Tekst fałszywego tutorialu
- var instructionText = new Text2('Press LPM to block.. or don’t press.', {
- size: 100,
- fill: 0xFFFFFF,
- align: 'center',
- wordWrap: true,
- wordWrapWidth: 1800,
- dropShadow: true,
- dropShadowColor: 0x000000,
- // czarny cień
- dropShadowDistance: 3,
- // odległość cienia
- dropShadowBlur: 4,
- // lekkie rozmycie cienia
- dropShadowAngle: Math.PI / 4 // kąt cienia
- });
- instructionText.anchor.set(0.5, 0.5);
- instructionText.x = 2048 / 2;
- instructionText.y = 2732 / 2;
- currentSceneElements.addChild(instructionText);
- // Timer przechodzący do prawdziwego tutorialu
- this.fakeTutorialTimerId = LK.setTimeout(function () {
- try {
- if (instructionText && instructionText.destroy) {
- instructionText.destroy();
- }
- } catch (e) {}
- gameState.fakeTutorialTimerId = null; // Zresetuj ID
- // *** POPRAWKA: Sprawdzenie przed wywołaniem ***
- if (gameState && typeof gameState.showRealTutorial === 'function') {
- gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
- } else {
- console.error("Error: gameState.showRealTutorial is not a function in fakeTutorial timeout!");
- gameState.showTitleScreen(); // Awaryjny powrót do tytułu
- }
- } /*.bind(this)*/, 6000); // 6 sekund
- },
- // *** NOWA FUNKCJA: Prawdziwy Tutorial (oddzielona od startGame) ***
- showRealTutorial: function showRealTutorial() {
- if (this.fakeTutorialTimerId) {
- LK.clearTimeout(this.fakeTutorialTimerId);
- }
- this.fakeTutorialTimerId = null; // Wyczyść timer fake tutorialu
- clearScene(); // Wyczyść elementy fake tutorialu/fake game over
- // Zatrzymaj animację tła intro i usuń je
- if (currentBackground) {
- tween.stop(currentBackground);
- currentBackground.destroy();
- currentBackground = null;
- }
- this.currentState = "realTutorial";
- game.setBackgroundColor(0x111111); // Ciemne tło
- // Pokaż ściany areny
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 1;
- }
- });
- ui.positionElements("realTutorial"); // Ustaw UI (głównie ukrywa elementy gry)
- // Tekst prawdziwego tutorialu
- var tutorialTitle = new Text2('HOW TO PLAY', {
- size: 100,
- fill: 0xFFFFFF
- });
- tutorialTitle.anchor.set(0.5, 0.5);
- tutorialTitle.x = 2048 / 2;
- tutorialTitle.y = 600;
- currentSceneElements.addChild(tutorialTitle);
- var tutorialDesc = new Text2('Swipe the screen to ROLL.\nRoll THROUGH the boss attacks to deal damage.\nSurvive!', {
- size: 60,
- fill: 0xFFFFFF,
- align: 'center',
- wordWrap: true,
- wordWrapWidth: 1600
- });
- tutorialDesc.anchor.set(0.5, 0.5);
- tutorialDesc.x = 2048 / 2;
- tutorialDesc.y = 1000;
- currentSceneElements.addChild(tutorialDesc);
- // Przycisk "Let's Roll!" do rozpoczęcia gry
- var startButton = new Container();
- startButton.interactive = true;
- startButton.x = 2048 / 2;
- startButton.y = 1500;
- currentSceneElements.addChild(startButton);
- var startButtonBg = LK.getAsset('button_bg', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- startButton.addChild(startButtonBg);
- var startButtonText = new Text2("Let's Roll!", {
- size: 50,
- fill: 0xFFFFFF
- });
- startButtonText.anchor.set(0.5, 0.5);
- startButton.addChild(startButtonText);
- startButton.down = function () {
- // clearScene(); // Niekonieczne, startGame to zrobi
- gameState.startGame(); // Rozpocznij grę
- };
- },
- // 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
- }
- clearScene(); // Wyczyść ekran fałszywego tutorialu
- // Ustaw stan tymczasowo (można by dodać dedykowany stan 'fakeGameOver')
- this.currentState = "fakeGameOver"; // Zmieniono z "gameOver"
- // Zatrzymaj animację tła intro, ale nie usuwaj go jeszcze
- if (currentBackground) {
- tween.stop(currentBackground);
- }
- ui.positionElements("gameOver"); // Użyj pozycjonowania gameOver dla tekstów "YOU DIED"
- // 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 = 600;
- currentSceneElements.addChild(diedText);
- // Wyświetl wyjaśnienie
- var explanationText = new Text2("Did you check the title of the game?", {
- size: 70,
- fill: 0xFFFFFF,
- align: 'center',
- wordWrap: true,
- wordWrapWidth: 1800,
- dropShadow: true,
- dropShadowColor: 0x000000,
- // cień czarny
- dropShadowDistance: 3,
- // odległość cienia
- dropShadowBlur: 4,
- // lekkie rozmycie cienia
- dropShadowAngle: Math.PI / 4 // kąt padania cienia (45 stopni)
- });
- explanationText.anchor.set(0.5, 0.5);
- explanationText.x = 2048 / 2;
- explanationText.y = 800;
- 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', {
- size: 50,
- fill: 0xFFFFFF
- }); // Zmieniono tekst i rozmiar
- buttonText.anchor.set(0.5, 0.5);
- howToPlayButtonContainer.addChild(buttonText);
- // Akcja przycisku: Przejdź do prawdziwego tutorialu
- howToPlayButtonContainer.down = function () {
- // *** POPRAWKA: Sprawdzenie przed wywołaniem ***
- if (gameState && typeof gameState.showRealTutorial === 'function') {
- gameState.showRealTutorial(); // Przejdź do prawdziwego tutorialu
- } else {
- console.error("Error: gameState.showRealTutorial is not a function in fake input handler!");
- gameState.showTitleScreen(); // Awaryjny powrót do tytułu
- }
- };
- },
- // Przejście do stanu gry (walka z bossem) - wywoływane z Prawdziwego Tutorialu
- startGame: function startGame() {
- // Wyczyść timery z poprzednich stanów
- if (this.fakeTutorialTimerId) {
- LK.clearTimeout(this.fakeTutorialTimerId);
- }
- this.fakeTutorialTimerId = null;
- if (this.gameTimerInterval) {
- LK.clearInterval(this.gameTimerInterval);
- }
- this.gameTimerInterval = null;
- clearScene(); // Wyczyść elementy tutorialu
- // Usuń tło tutorialu/intro jeśli istnieje
- if (currentBackground) {
- tween.stop(currentBackground);
- currentBackground.destroy();
- currentBackground = null;
- }
- this.currentState = "game"; // Ustaw stan na "game"
- game.setBackgroundColor(0x111111);
- // Stwórz gracza (jeśli nie istnieje)
- if (player && player.destroy) {
- player.destroy();
- } // Zniszcz starego gracza
- player = game.addChild(new Player()); // Stwórz nowego gracza
- player.health = storage.maxHearts || 5; // Ustaw zdrowie gracza
- // Resetuj stan gracza
- player.rolling = false;
- player.invulnerable = false;
- player.invulnerabilityFrames = 0;
- player.rollCooldown = 0;
- if (player && typeof player.clearRollTimeouts === 'function') {
- player.clearRollTimeouts();
- }
- player.hasRolledThroughBossThisRoll = false;
- player.x = 2048 / 2;
- player.y = 2732 / 2 + 400; // Pozycja startowa gracza
- player.alpha = 1; // Upewnij się, że jest widoczny
- // Stwórz bossa (jeśli nie istnieje)
- if (boss && boss.destroy) {
- boss.destroy();
- } // Zniszcz starego bossa
- boss = game.addChild(new Boss()); // Stwórz nowego bossa
- boss.x = 2048 / 2;
- boss.y = 2732 / 2 - 400; // Pozycja startowa bossa
- boss.alpha = 1; // Upewnij się, że jest widoczny
- // Resetuj stan bossa
- boss.attackCooldown = 90; // Daj graczowi chwilę przed pierwszym atakiem (ok 1.5s)
- boss.attacks = [];
- boss.attackSpeedMultiplier = 1;
- boss.repositioning = false;
- boss.dead = false; // Upewnij się, że nie jest martwy
- boss.phase = 1; // Zacznij od fazy 1
- boss.tint = 0xFFFFFF; // Resetuj kolor (na wypadek przejścia fazy w poprzedniej grze)
- boss.scale.set(1, 1); // Resetuj skalę (na wypadek animacji śmierci)
- // Ustaw parametry bossa i gry zależnie od trybu
- if (isNewBossPlusMode) {
- boss.maxHealth = 2000;
- boss.health = boss.maxHealth;
- this.gameDuration = 600; // 10 minut
- boss.attackSpeedMultiplier = 0.6; // Szybsze ataki od początku
- boss.speed = 7; // Szybszy ruch
- console.log("Starting NEW BOSS+ mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
- } else {
- boss.maxHealth = 200; // Standardowy boss HP
- boss.health = boss.maxHealth;
- this.gameDuration = 120; // 2 minuty
- boss.speed = 5; // Standardowa prędkość
- boss.attackSpeedMultiplier = 1; // Standardowa prędkość ataków
- console.log("Starting STANDARD mode. HP:", boss.maxHealth, "Time:", this.gameDuration);
- }
- // Pokaż ściany areny
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 1;
- }
- });
- // Ustaw UI dla stanu gry
- ui.positionElements("game");
- ui.updateHearts(player.health, storage.maxHearts);
- ui.showTutorial("Swipe to Roll!"); // Wyświetl krótki tutorial
- ui.updateBossHealth(boss.health, boss.maxHealth); // Pokaż pasek HP bossa
- // --- Timer walki z bossem ---
- this.remainingTime = this.gameDuration; // Ustaw początkowy czas
- ui.updateTimerDisplay(this.remainingTime); // Wyświetl czas
- // Rozpocznij interwał timera
- if (this.gameTimerInterval) {
- LK.clearInterval(this.gameTimerInterval);
- } // Wyczyść stary timer
- this.bossSpeedIncreased = false; // Resetuj flagę przyspieszenia bossa
- this.gameTimerInterval = LK.setInterval(function () {
- // Aktualizuj tylko w stanie gry
- if (gameState.currentState === "game") {
- gameState.remainingTime--;
- ui.updateTimerDisplay(gameState.remainingTime);
- // --- Przyspieszenie bossa - TYLKO W STANDARDOWYM TRYBIE ---
- if (!isNewBossPlusMode) {
- var accelerationThreshold = gameState.gameDuration - 60; // Przyspieszenie na 60s przed końcem
- if (gameState.remainingTime <= accelerationThreshold && !gameState.bossSpeedIncreased) {
- gameState.bossSpeedIncreased = true;
- if (boss && !boss.dead) {
- // Sprawdź czy boss nadal żyje
- boss.attackSpeedMultiplier *= 0.7; // Przyspiesz ataki
- boss.speed += 1; // Lekko przyspiesz ruch
- ui.showMessage("Boss attacks faster!", 2000); // Komunikat
- }
- }
- }
- // --- Koniec Przyspieszenia bossa ---
- // Sprawdź koniec gry (czas minął)
- if (gameState.remainingTime <= 0) {
- LK.clearInterval(gameState.gameTimerInterval); // Zatrzymaj timer
- gameState.gameTimerInterval = null;
- if (isNewBossPlusMode) {
- // Czas minął w Boss+ -> Zwycięstwo przez przetrwanie
- gameOverReasonIsDeath = false; // Przyczyna: czas
- gameState.gameOver(false); // Przejdź do ekranu końca gry (specjalny komunikat)
- } else {
- // Czas minął w standardowym trybie -> Przejdź do Grill Menu
- // Zakładamy, że przetrwanie 2 minut w standardowym trybie to też "zwycięstwo"
- gameState.showGrillScreen();
- }
- }
- } else {
- // Jeśli stan gry się zmienił, zatrzymaj timer
- if (gameState.gameTimerInterval) {
- LK.clearInterval(gameState.gameTimerInterval);
- }
- gameState.gameTimerInterval = null;
- }
- } /*.bind(this)*/, 1000); // Interwał co 1 sekundę
- // Ukryj tutorial po chwili
- LK.setTimeout(function () {
- if (gameState.currentState === "game") {
- // Sprawdź czy nadal jesteśmy w grze
- ui.hideTutorial();
- }
- // Rozpoczęcie ataków bossa jest teraz obsługiwane przez Boss.update i jego cooldown
- }, 3000); // 3 sekundy opóźnienia
- },
- // victory() - nieużywane, logika w timerze i boss.die()
- showGrillScreen: function showGrillScreen() {
- // Wyczyść timery
- if (this.gameTimerInterval) {
- LK.clearInterval(this.gameTimerInterval);
- }
- this.gameTimerInterval = null;
- if (this.fakeTutorialTimerId) {
- LK.clearTimeout(this.fakeTutorialTimerId);
- }
- this.fakeTutorialTimerId = null;
- this.bossSpeedIncreased = false; // Reset flagi
- clearScene(); // Wyczyść elementy gry (ataki, itp.)
- // Zniszcz gracza i bossa jeśli jeszcze istnieją
- if (player && player.destroy) {
- player.destroy();
- }
- player = null;
- if (boss && boss.destroy) {
- boss.destroy();
- }
- boss = null; // Boss powinien być już zniszczony przez die(), ale na wszelki wypadek
- this.currentState = "grillMenu";
- game.setBackgroundColor(0x333333); // Tło grilla
- // Usuń tło gry
- if (currentBackground) {
- tween.stop(currentBackground);
- currentBackground.destroy();
- currentBackground = null;
- }
- // Dodaj tło dla ekranu Grilla
- currentBackground = LK.getAsset('grillMenu', {
- anchorX: 0.5,
- anchorY: 0.5,
- x: 2048 / 2,
- y: 2732 / 2
- });
- game.addChildAt(currentBackground, 0); // Na spód
- // Ukryj ściany
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 0;
- }
- });
- // Ustaw UI dla Grill Menu
- ui.positionElements("grillMenu");
- ui.updateBossHealth(0, 1); // Ukryj pasek HP bossa
- ui.updateHearts(0, storage.maxHearts); // Ukryj serca gracza
- // --- Przyciski na ekranie Grilla ---
- var buttonYStart = 1000;
- var buttonYOffset = 150;
- // Przycisk "Rest"
- var restButton = new Container();
- restButton.interactive = true;
- restButton.x = 2048 / 2;
- restButton.y = buttonYStart;
- currentSceneElements.addChild(restButton);
- 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);
- restButton.down = function () {
- ui.showMessage("Rest in peace...", 2000);
- LK.setTimeout(function () {
- ui.showMessage("Thank you for playing!", 3000);
- }, 2500);
- LK.setTimeout(function () {
- // clearScene(); // Niekonieczne, showTitleScreen to zrobi
- gameState.showTitleScreen(); // Wróć do tytułu
- }, 6000); // Opóźnienie
- };
- // Przycisk "Upgrade Roll" (obecnie bez funkcji)
- var upgradeButton = new Container();
- upgradeButton.interactive = true;
- upgradeButton.x = 2048 / 2;
- upgradeButton.y = buttonYStart + buttonYOffset;
- currentSceneElements.addChild(upgradeButton);
- 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);
- upgradeButton.down = function () {
- ui.showMessage("Not implemented yet!", 2000);
- }; // Zmieniono komunikat
- // Przycisk "New Boss+"
- var newBossButton = new Container();
- newBossButton.interactive = true;
- newBossButton.x = 2048 / 2;
- newBossButton.y = buttonYStart + buttonYOffset * 2;
- currentSceneElements.addChild(newBossButton);
- 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);
- newBossButton.down = function () {
- // clearScene(); // Niekonieczne, startGame to zrobi
- isNewBossPlusMode = true; // Ustaw flagę
- gameState.startGame(); // Rozpocznij grę w trybie Boss+
- };
- },
- // Przejście do stanu Game Over
- // isDeath: true (gracz zginął), false (czas minął w Boss+)
- gameOver: function gameOver(isDeath) {
- this.currentState = "gameOver"; // Ustaw stan
- gameOverReasonIsDeath = isDeath; // Zapisz przyczynę
- // Zatrzymaj timer gry
- if (this.gameTimerInterval) {
- LK.clearInterval(this.gameTimerInterval);
- }
- this.gameTimerInterval = null;
- this.bossSpeedIncreased = false; // Reset flagi przyspieszenia
- // Zatrzymaj animację tła (jeśli istnieje, ale nie powinno w tym stanie)
- if (currentBackground) {
- tween.stop(currentBackground);
- }
- game.setBackgroundColor(0x000000); // Czarne tło
- // Ukryj/zniszcz gracza i bossa (powinny być już zniszczone przez swoje metody die)
- if (player && player.alpha !== 0) {
- player.alpha = 0;
- } // Ukryj, jeśli nie zniknął
- if (boss && boss.alpha !== 0) {
- boss.alpha = 0;
- }
- // Ukryj ściany
- walls.forEach(function (wall) {
- if (wall) {
- wall.alpha = 0;
- }
- });
- // Ustaw UI dla ekranu game over
- ui.positionElements("gameOver");
- // Logika komunikatu Game Over
- var gameOverMessage = "YOU DIED"; // Domyślny komunikat
- if (!isDeath) {
- // Jeśli czas minął (tylko w Boss+)
- gameOverMessage = "YOU SURVIVED... for now."; // Zmieniono komunikat zwycięstwa Boss+
- }
- // Wyświetl komunikat i tytuł
- ui.titleText.setText(gameOverMessage);
- ui.titleText.alpha = 1; // Pokaż tytuł
- ui.showMessage("", 0); // Wyczyść ewentualny poprzedni komunikat
- ui.showTutorial(""); // Ukryj tutorial
- // Pasek zdrowia bossa jest zarządzany przez updateBossHealth w pętli gry
- // W tym stanie updateBossHealth pokaże pasek tylko jeśli !isDeath && isNewBossPlusMode
- // Zaplanuj restart gry po opóźnieniu
- LK.setTimeout(function () {
- // Wyczyść scenę przed restartem
- clearScene();
- // Zniszcz gracza/bossa jeśli jakimś cudem przetrwali
- if (player && player.destroy) {
- player.destroy();
- }
- player = null;
- if (boss && boss.destroy) {
- boss.destroy();
- }
- boss = null;
- // Wyczyść ataki bossa (na wszelki wypadek) - teraz robione w Boss.die / Boss.update
- // if (boss && boss.attacks) boss.attacks = [];
- // Usuń tło game over (czarne)
- if (currentBackground) {
- currentBackground.destroy();
- }
- currentBackground = null;
- // Resetuj UI do stanu "przed grą" (np. jak w tytule, ale bez tekstów)
- // Pozycjonowanie jest ok, ale trzeba wyczyścić teksty
- ui.titleText.setText("");
- ui.titleText.alpha = 0;
- ui.messageText.setText("");
- ui.messageText.alpha = 0;
- ui.tutorialText.setText("");
- ui.tutorialText.alpha = 0;
- ui.updateDeathsCounter(); // Zaktualizuj licznik śmierci
- ui.updateBossHealth(0, 1); // Ukryj pasek HP
- ui.updateHearts(0, storage.maxHearts); // Ukryj serca
- // Flaga isNewBossPlusMode pozostaje niezmieniona (jeśli zginąłeś w Boss+, restartujesz w Boss+)
- // gameOverReasonIsDeath zostanie zresetowana przy następnym wywołaniu gameOver
- // Restartuj walkę (startGame użyje flagi isNewBossPlusMode)
- gameState.startGame();
- }, 4000); // Zwiększono opóźnienie do 4 sekund przed restartem
- },
- // Obsługa gestów dotykowych/myszy
- processTouchGesture: function processTouchGesture() {
- // --- Obsługa inputu w stanie fakeTutorial ---
- if (this.currentState === "fakeTutorial" && this.fakeTutorialTimerId) {
- this.handleFakeTutorialInput(); // Wywołaj fałszywą śmierć
- return; // Zakończ przetwarzanie gestu
- }
- var dx = this.touchEnd.x - this.touchStart.x;
- var dy = this.touchEnd.y - this.touchStart.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- // Minimalny dystans dla swipe (turlania)
- var swipeThreshold = 50;
- // --- Obsługa Tapnięcia ---
- if (distance < swipeThreshold) {
- // Sprawdź, czy tapnięcie było na interaktywnym obiekcie (przycisku)
- // Zakładamy, że LK.Game obsługuje to przez przekazanie 'obj' do game.down/up
- // Jeśli tapnięcie NIE było na przycisku:
- if (this.currentState === "title") {
- // Sprawdź czy tapnięcie nie było na przycisku Grill Menu
- // Prosty sposób: załóżmy, że jeśli obj nie jest zdefiniowany w game.up, to kliknięto tło
- // (Wymaga sprawdzenia, jak LK.Game przekazuje 'obj')
- // Na razie zakładamy, że tapnięcie w tło przechodzi do intro
- this.showIntro();
- return;
- }
- // W innych stanach (gameOver, victory, grillMenu) tapnięcie w tło nic nie robi
- // Obsługa przycisków dzieje się przez ich własne handlery .down/.up
- return;
- }
- // --- Obsługa Swipe (Turlania) ---
- // Działa tylko w stanie gry i gdy gracz żyje
- if (this.currentState === "game" && player && !player.dead) {
- // Normalizuj kierunek gestu
- var direction = {
- x: 0,
- y: 0
- };
- if (distance > 0) {
- // Unikaj dzielenia przez zero
- direction.x = dx / distance;
- direction.y = dy / distance;
- }
- // Wykonaj turlanie gracza
- player.roll(direction);
- }
- // W innych stanach swipe jest ignorowany
- }
+ game = new DisplayObjectContainer(); // Stwórz główny kontener gry
+ LK.add(game); // Dodaj kontener gry do frameworka LK
+ // Stwórz i dodaj podstawowe elementy tła od razu (nie zależą od assetów fireballi)
+ gameState.createWalls(); // Tworzy i dodaje ściany
+ gameState.createFloor(); // Tworzy i dodaje podłogę
+ // *** MODYFIKACJA: Dodaj opóźnienie setTimeout przed tworzeniem elementów zależnych od assetów ***
+ // Daje to assetom czas na załadowanie po odpaleniu onInit przez LK.init
+ LK.setTimeout(function () {
+ // *** Kod z oryginalnego createGame, który tworzył elementy zależne od assetów, PRZENIESIONY tutaj ***
+ // Stwórz i dodaj gracza (zależy od assetów)
+ player = new Player(); // Klasa Player prawdopodobnie używa assetów
+ game.addChild(player);
+ // Stwórz i dodaj bossa (mocno zależy od assetów dla grafiki i ataków)
+ boss = new Boss(); // Klasa Boss używa wielu assetów
+ game.addChild(boss);
+ // Stwórz i dodaj elementy UI (niektóre elementy UI mogą używać assetów)
+ ui = new UI(); // Klasa UI prawdopodobnie używa assetów (serca, przyciski, itp.)
+ game.addChild(ui);
+ // Add UI elements to the game container (order matters for layering)
+ // Assuming UI should be on top of game elements
+ game.addChild(ui.bossHpBar);
+ game.addChild(ui.playerHearts);
+ game.addChild(ui.startButton); // Start button is visible in title state
+ game.addChild(ui.newBossPlusButton); // Boss+ button is visible in title state
+ game.addChild(ui.gameOverMessage); // Game over message is hidden initially
+ game.addChild(ui.grillMenu); // Grill menu is hidden initially
+ // Start the game state (enables updates, attacks, etc.)
+ // This was the last line in the original createGame
+ gameState.changeState("title"); // Rozpocznij grę w stanie title
+ console.log("Opóźnione kroki tworzenia gry wykonane."); // Opcjonalny log do potwierdzenia odpalenia timeoutu
+ }, 500); // *** CZAS OPÓŹNIENIA (np. 500ms) - Dostosuj w razie potrzeby ***
+ // To jest opóźnienie heurystyczne. Lepszym rozwiązaniem byłby właściwy mechanizm preładowania, jeśli dostępny.
+ // Oryginalne linie kodu po ustawieniach createGame i przed gameState.changeState są teraz wewnątrz timeoutu
+ // gameState.changeState("title"); // This line is moved inside the timeout
};
-// --- Obsługa inputu ---
-game.down = function (x, y, obj) {
- // Rejestruj początek dotyku/kliknięcia
- // Stany, w których śledzimy gesty lub kliknięcia przycisków:
- var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver"];
- if (trackStates.indexOf(gameState.currentState) !== -1) {
- gameState.touchStart.x = x;
- gameState.touchStart.y = y;
- gameState.touchEnd.x = x; // Reset end point
- gameState.touchEnd.y = y;
- }
- // Obsługa kliknięć przycisków jest robiona przez ich własne handlery .down
-};
-game.up = function (x, y, obj) {
- // Rejestruj koniec dotyku/kliknięcia i przetwarzaj gest
- var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver"];
- if (trackStates.indexOf(gameState.currentState) !== -1) {
- gameState.touchEnd.x = x;
- gameState.touchEnd.y = y;
- // Przetwórz gest (sprawdzi czy to tap czy swipe i podejmie akcję)
- // Sprawdź, czy 'obj' istnieje - jeśli tak, to było kliknięcie na interaktywnym elemencie,
- // którego logikę obsługuje jego własny handler .down/.up, więc nie rób nic więcej.
- // Jeśli 'obj' nie istnieje, to było kliknięcie/swipe w tło.
- game.up = function (x, y, obj) {
- if (trackStates.indexOf(gameState.currentState) !== -1) {
- gameState.touchEnd.x = x;
- gameState.touchEnd.y = y;
- // WAŻNE: Wywołuj zawsze processTouchGesture(), bez if (!obj)!
- gameState.processTouchGesture();
- }
- };
- }
- // Obsługa puszczenia przycisków (.up) może być dodana tutaj, jeśli potrzebna
-};
-game.move = function (x, y, obj) {
- // Śledź ruch palca/myszy dla swipe
- var trackStates = ["title", "game", "fakeTutorial", "fakeGameOver", "realTutorial", "grillMenu", "gameOver"];
- if (trackStates.indexOf(gameState.currentState) !== -1) {
- gameState.touchEnd.x = x; // Aktualizuj końcową pozycję na bieżąco
- gameState.touchEnd.y = y;
- }
-};
-// --- Główna pętla aktualizacji gry ---
-game.update = function () {
- // Aktualizuj UI niezależnie od stanu gry (ale tylko jeśli UI istnieje)
+var gameLoop = function gameLoop() {
+ // Główne aktualizacje gry
+ // LK automatycznie wywołuje update na obiektach dodanych do sceny (game)
+ // Możemy dodać tutaj logikę globalną
+ // Na przykład, aktualizacja UI, która nie jest automatycznie wywoływana na obiektach UI, chyba że UI jest DisplayObjectContainer i dodano je do sceny.
+ // Aktualizuj UI niezależnie od stanu gry, bo jego widoczność i zawartość zależą od stanu/danych
+ // Ale tylko jeśli UI zostało stworzone
if (ui) {
- ui.updateDeathsCounter();
- // Aktualizuj pasek zdrowia bossa (jeśli boss istnieje i UI istnieje)
+ // UI update logic if needed (currently empty)
+ // ui.update(); // Zakładając, że UI ma metodę update
+ // Aktualizuj pasek zdrowia bossa (jeśli boss istnieje)
if (boss) {
- // Upewnij się, że maxHealth jest sensowne
- var maxHp = boss.maxHealth > 0 ? boss.maxHealth : 1;
- ui.updateBossHealth(boss.health, maxHp);
+ // ui.updateBossHealth(boss.health, boss.maxHealth); // Ta linia była wcześniej, ale lepiej wywoływać ją po zmianie zdrowia bossa
+ // Zaktualizuj UI HP Bossa w game.update, ale tylko gdy HP bossa > 0 (żeby nie migało przy śmierci)
+ // UI HP Boss aktualizowane jest teraz w takeDamage i die Bossa.
+ // Upewnijmy się, że jest aktualizowane też jeśli boss HP > 0.
+ if (boss.health > 0 || gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode) {
+ ui.updateBossHealth(boss.health, boss.maxHealth);
+ } else {
+ // Jeśli boss martwy i nie jest to game over po czasie, ukryj pasek
+ if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
+ ui.updateBossHealth(0, 1); // Ukryj pasek
+ }
+ }
} else {
// Jeśli boss nie istnieje, ukryj pasek (chyba że to wygrana Boss+ przez czas)
if (!(gameState.currentState === "gameOver" && !gameOverReasonIsDeath && isNewBossPlusMode)) {
// Domyślne maxHP do ukrycia paska to 1 (uniknięcie dzielenia przez 0)
@@ -2245,11 +1204,42 @@
}
if (boss) {
boss.update(); // Aktualizacja bossa (ruch, cooldown, tworzenie/usuwanie ataków, kolizje ataków z graczem)
}
- // Sprawdzanie kolizji i zmiany stanów (gameOver, grillScreen) są teraz wewnątrz metod update/die gracza i bossa.
}
- // Logika dla innych stanów (np. animacje w intro, obsługa przycisków) jest zarządzana przez ich własne funkcje i handlery.
+ // Logika game over
+ else if (gameState.currentState === "gameOver") {
+ // Tutaj można dodać logikę game over, np. animację, możliwość restartu
+ // Aktualizacja player i boss nadal jest wywoływana, ale ich metody update powinny sprawdzać stan gry
+ if (player) {
+ player.update();
+ } // Nadal aktualizuj gracza (np. animacja śmierci)
+ if (boss) {
+ boss.update();
+ } // Nadal aktualizuj bossa (np. animacja śmierci)
+ // Można dodać logikę przycisków restartu, powrotu do menu itp.
+ }
+ // Logika grill screen
+ else if (gameState.currentState === "grillScreen") {
+ // Tutaj można dodać logikę grill screen, np. animację, możliwość przejścia dalej
+ if (player) {
+ player.update();
+ } // Nadal aktualizuj gracza (jeśli widoczny)
+ if (boss) {
+ boss.update();
+ } // Nadal aktualizuj bossa (jeśli widoczny)
+ // Można dodać logikę przycisków przejścia dalej, powrotu do menu itp.
+ }
+ // Rysowanie sceny (wykonywane automatycznie przez LK po gameLoop)
};
-// --- Rozpoczęcie gry ---
-// Inicjalizuj obiekt gameState, który zajmie się resztą.
-gameState.init();
\ No newline at end of file
+// Inicjalizacja gry
+LK.init(gameContainer, {
+ onInit: function onInit() {
+ storage = LK.localStorage; // Initialize storage here after LK.init
+ // Load or set maxHearts from storage if needed, default to 5
+ storage.maxHearts = storage.maxHearts || 5; // Default max hearts
+ // Determine game mode (Boss+ or Standard) from storage or default
+ isNewBossPlusMode = storage.newBossPlusMode === true; // Default to false if not in storage
+ createGame(); // Call the createGame function to set up the initial game state and objects
+ // Game loop is set up by LK.init to call gameLoop function repeatedly (e.g., 60 times per second)
+ }
+});
\ No newline at end of file