using UnityEngine; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } [Header("Game Settings")] public int lives = 3; public int score = 0; [Header("Prefabs")] public Ball ballPrefab; public DestructibleBrick brickPrefab; public Paddle paddlePrefab; public MultiBallBrick multiBallBrickPrefab; public GameObject splitBallPrefab; // Префаб дочернего мяча [Header("Field Configuration")] public int rows = 5; public int columns = 8; public float brickSpacing = 0.2f; public Vector2 brickOffset = new Vector2(0, 4); [Header("Special Bricks")] [Range(0, 10)] public int multiBallBricksCount = 3; // Количество специальных блоков на поле [Header("Scale Settings")] [Range(0.1f, 3.0f)] public float brickScale = 1.0f; [Range(0.1f, 3.0f)] public float ballScale = 1.0f; [Range(0.1f, 3.0f)] public float paddleScale = 1.0f; [Header("Wall Settings")] public float wallThickness = 0.5f; public float wallHeight = 10f; public Color wallColor = Color.gray; // Properties for UI access public int DestroyedBricks => destroyedBricks; public int TotalBricks => totalBricks; public int UsedLives => usedLives; public int InitialLives => initialLives; private Ball currentBall; private Paddle paddle; private GameObject leftWall; private GameObject rightWall; private GameObject topWall; private int totalBricks; private int destroyedBricks; private int initialLives; private int usedLives; private int activeBallsCount = 0; void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } void Start() { InitializeGame(); } void InitializeGame() { score = 0; destroyedBricks = 0; usedLives = 0; initialLives = lives; totalBricks = rows * columns; CreateWalls(); // Создаем платформу с заданным масштабом paddle = Instantiate(paddlePrefab, new Vector3(0, -4, 0), Quaternion.identity); paddle.transform.localScale = new Vector3(paddleScale, paddleScale, 1f); SpawnNewBall(); CreateBricks(); } void CreateBricks() { Vector2 brickSize = brickPrefab.GetComponent().bounds.size; // Применяем масштаб к размеру блока brickSize *= brickScale; // Создаем список всех возможных позиций Vector2[,] positions = new Vector2[rows, columns]; for (int row = 0; row < rows; row++) { for (int col = 0; col < columns; col++) { positions[row, col] = new Vector2( (-columns/2f + col) * (brickSize.x + brickSpacing) + brickSize.x/2f, brickOffset.y - row * (brickSize.y + brickSpacing) ); } } // Размещаем специальные блоки случайным образом int specialBricksPlaced = 0; while (specialBricksPlaced < multiBallBricksCount) { int randomRow = Random.Range(0, rows); int randomCol = Random.Range(0, columns); if (positions[randomRow, randomCol] == Vector2.zero) continue; // Создаем специальный блок Brick specialBrick = Instantiate(multiBallBrickPrefab, positions[randomRow, randomCol], Quaternion.identity); specialBrick.transform.localScale = new Vector3(brickScale, brickScale, 1f); // Помечаем позицию как использованную positions[randomRow, randomCol] = Vector2.zero; specialBricksPlaced++; } // Заполняем оставшиеся позиции обычными блоками for (int row = 0; row < rows; row++) { // Количество жизней зависит от строки (сверху вниз) // Максимум 15 жизней для самого верхнего ряда int rowHitPoints = Mathf.Min(15, rows - row); // Очки за блок зависят от количества жизней int rowPoints = rowHitPoints * 100; for (int col = 0; col < columns; col++) { // Пропускаем использованные позиции if (positions[row, col] == Vector2.zero) continue; DestructibleBrick brick = Instantiate(brickPrefab, positions[row, col], Quaternion.identity); brick.transform.localScale = new Vector3(brickScale, brickScale, 1f); brick.hitPoints = rowHitPoints; brick.points = rowPoints; } } } public SplitBall CreateSplitBall(Vector3 position, Vector2 velocity, float scale) { GameObject splitBallObj = Instantiate(splitBallPrefab.gameObject, position, Quaternion.identity); SplitBall splitBall = splitBallObj.GetComponent(); if (splitBall != null) { splitBall.Initialize(velocity, scale); activeBallsCount++; // Увеличиваем счетчик при создании нового мяча Debug.Log($"Split ball created. Active balls: {activeBallsCount}"); } return splitBall; } public void SpawnNewBall() { // Создаем новый мяч только если нет других активных мячей if (activeBallsCount <= 0) { // Создаем мяч над платформой с учетом масштабов float paddleHeight = paddle.GetComponent().bounds.size.y; float ballHeight = ballPrefab.GetComponent().bounds.size.y * ballScale; Vector3 spawnPosition = paddle.transform.position + Vector3.up * (paddleHeight/2 + ballHeight/2 + 0.1f); GameObject ballObj = Instantiate(ballPrefab.gameObject, spawnPosition, Quaternion.identity); currentBall = ballObj.GetComponent(); if (currentBall != null) { currentBall.transform.localScale = Vector3.one * ballScale; activeBallsCount++; // Увеличиваем счетчик при создании основного мяча Debug.Log($"Main ball created. Active balls: {activeBallsCount}"); } } else { Debug.Log($"Not spawning main ball - there are still {activeBallsCount} active balls"); } } // Перегрузка для обратной совместимости public void LoseBall() { LoseBall(null); } public void LoseBall(Ball ball) { if (ball != null) { if (ball == currentBall) { currentBall = null; } Destroy(ball.gameObject); activeBallsCount--; Debug.Log($"Ball lost. Remaining active balls: {activeBallsCount}"); } else { // Если мяч не указан, считаем что это основной мяч if (currentBall != null) { Destroy(currentBall.gameObject); currentBall = null; activeBallsCount--; Debug.Log($"Main ball lost. Remaining active balls: {activeBallsCount}"); } } // Проверяем, остались ли еще мячи if (activeBallsCount <= 0) { lives--; usedLives++; UpdateUI(); Debug.Log($"No balls remaining. Lives left: {lives}"); if (lives <= 0) { GameOver(); } else { SpawnNewBall(); } } } public void BrickDestroyed() { destroyedBricks++; CheckWinCondition(); } public void AddScore(int points) { score += points; } public void CheckWinCondition() { if (destroyedBricks >= totalBricks) // Check if this was the last brick { WinGame(); } } void GameOver() { Debug.Log("Game Over! Final Score: " + score); // Implement game over UI and restart functionality Invoke("RestartGame", 2f); } void WinGame() { Debug.Log("You Win! Score: " + score); // Implement win UI and next level or restart functionality Invoke("RestartGame", 2f); } void RestartGame() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } void CreateWalls() { // Calculate wall positions based on camera viewport float screenHalfWidth = Camera.main.orthographicSize * Camera.main.aspect; float screenHalfHeight = Camera.main.orthographicSize; float wallX = screenHalfWidth + (wallThickness / 2f); float wallY = screenHalfHeight + (wallThickness / 2f); // Create left wall leftWall = CreateWall(-wallX, 0, wallThickness, wallHeight, "LeftWall"); // Create right wall rightWall = CreateWall(wallX, 0, wallThickness, wallHeight, "RightWall"); // Create top wall topWall = CreateWall(0, wallY, screenHalfWidth * 2, wallThickness, "TopWall"); } GameObject CreateWall(float xPosition, float yPosition, float width, float height, string name) { GameObject wall = new GameObject(name); wall.transform.position = new Vector3(xPosition, yPosition, 0); // Add BoxCollider2D BoxCollider2D collider = wall.AddComponent(); collider.size = new Vector2(width, height); // Add SpriteRenderer to make the wall visible SpriteRenderer renderer = wall.AddComponent(); renderer.sprite = CreateWallSprite(); renderer.color = wallColor; // Scale the wall to match the collider wall.transform.localScale = new Vector3(width, height, 1); // Add physics material for proper bouncing collider.sharedMaterial = new PhysicsMaterial2D { bounciness = 1f, friction = 0f }; return wall; } Sprite CreateWallSprite() { // Create a 1x1 white texture that we can tint Texture2D texture = new Texture2D(1, 1); texture.SetPixel(0, 0, Color.white); texture.Apply(); return Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f)); } // Handle Telegram back button public void HandleBackButton() { // Pause the game Time.timeScale = 0; // Here you could show a pause menu or exit confirmation // For now, we'll just resume the game Time.timeScale = 1; } private void UpdateUI() { // Implement UI update logic here } }