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
|
|||
|
}
|
|||
|
}
|