355 lines
11 KiB
C#
355 lines
11 KiB
C#
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<SpriteRenderer>().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<SplitBall>();
|
||
|
||
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<SpriteRenderer>().bounds.size.y;
|
||
float ballHeight = ballPrefab.GetComponent<SpriteRenderer>().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<Ball>();
|
||
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<BoxCollider2D>();
|
||
collider.size = new Vector2(width, height);
|
||
|
||
// Add SpriteRenderer to make the wall visible
|
||
SpriteRenderer renderer = wall.AddComponent<SpriteRenderer>();
|
||
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
|
||
}
|
||
}
|