using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; public class GamePole : MonoBehaviour { public GameManager gameManager; public GameObject eLiters, eNums, ePole; public GameObject[] ShipMass; private List placedShips = new List(); public bool IsReady { get; private set; } = false; GameObject[] Liters; GameObject[] Nums; public GameObject[,] Pole; public int LengPole = 10; public int[,] PoleShot; // 0 - не стрелял 1 - промах 2 - ранил 3 - уничтожен public int[,] FreePole; // поле для растоновки кораблей public Material hitMaterial; private GameObject ghostShip = null; private int ghostShipIndex = -1; private int currentShipIndex = 0; private int shipCaunt = 10; /// Количество кораблей в текущей конфигурации. public int ShipCount => shipCaunt; /// Масштаб одной клетки (и кораблей) относительно базового 10x10. public float CellScale => _cellScale; /// Текущий масштаб клетки и кораблей (вычисляется в CreatePole). private float _cellScale = 1f; /// Позиции рифов текущей игры — восстанавливаются после каждого ClearPole. private readonly List _savedReefs = new List(); /// Группы (прямоугольники) рифов для повторного спавна объектов. private readonly List _savedReefGroups = new List(); /// Конфиг рифа для каждой группы — параллельный список к _savedReefGroups. private readonly List _savedReefConfigs = new List(); /// Spawned 3D-объекты рифов — уничтожаются при Configure и пересоздаются в RestoreReefs. private readonly List _spawnedReefObjects = new List(); public GameObject[] missEffectPrefabs; // промах void Awake() { PoleShot = new int[LengPole, LengPole]; FreePole = new int[LengPole, LengPole]; if (gameManager == null) gameManager = GetComponentInParent(); } void Start() { CreatePole(); } public Cell GetCell(int x, int y) { // Проверяем, что поле уже создано if (Pole == null) return null; // Проверяем корректность координат if (x < 0 || x >= LengPole || y < 0 || y >= LengPole) return null; GameObject cellObject = Pole[x, y]; if (cellObject == null) return null; return cellObject.GetComponent(); } public List GetPlacedShips() { return placedShips; } void CreatePole() { // 95% от размера классического 10x10, чтобы оставался небольшой отступ _cellScale = 9.5f / LengPole; float cellScale = _cellScale; float XX = cellScale; float ZZ = -cellScale; Liters = new GameObject[LengPole]; Nums = new GameObject[LengPole]; for (int i = 0; i < LengPole; i++) { Liters[i] = Instantiate(eLiters, transform); Liters[i].transform.localPosition = new Vector3(XX, 0, 0); Liters[i].transform.localScale = Vector3.one * cellScale; Liters[i].GetComponent().Index = i; XX += cellScale; Nums[i] = Instantiate(eNums, transform); Nums[i].transform.localPosition = new Vector3(0, 0, ZZ); Nums[i].transform.localScale = Vector3.one * cellScale; Nums[i].GetComponent().Index = i; ZZ -= cellScale; } XX = cellScale; ZZ = -cellScale; Pole = new GameObject[LengPole, LengPole]; for (int Z = 0; Z < LengPole; Z++) { for (int X = 0; X < LengPole; X++) { Pole[X, Z] = Instantiate(ePole, transform); Pole[X, Z].transform.localPosition = new Vector3(XX, 0, ZZ); Pole[X, Z].transform.localScale = Vector3.one * cellScale; XX += cellScale; } XX = cellScale; ZZ -= cellScale; } } public void Configure(int fieldSize, int shipCount) { // Сброс всех данных рифов при старте новой игры _savedReefs.Clear(); _savedReefGroups.Clear(); _savedReefConfigs.Clear(); foreach (var o in _spawnedReefObjects) if (o != null) Destroy(o); _spawnedReefObjects.Clear(); bool needRebuild = Pole == null || LengPole != fieldSize; if (needRebuild) { DestroyGrid(); LengPole = fieldSize; PoleShot = new int[LengPole, LengPole]; FreePole = new int[LengPole, LengPole]; CreatePole(); } else { // Тот же размер — просто сбрасываем массивы (ClearPole сделает это ещё раз, не страшно) PoleShot = new int[LengPole, LengPole]; FreePole = new int[LengPole, LengPole]; } shipCaunt = (ShipMass != null) ? Mathf.Min(shipCount, ShipMass.Length) : shipCount; currentShipIndex = 0; IsReady = false; // placedShips — НЕ трогаем здесь; ClearPole() (внутри PrepareNewGame) уничтожит их корректно } private void DestroyGrid() { if (Liters != null) { foreach (var o in Liters) if (o != null) Destroy(o); Liters = null; } if (Nums != null) { foreach (var o in Nums) if (o != null) Destroy(o); Nums = null; } if (Pole != null) { foreach (var o in Pole) if (o != null) Destroy(o); Pole = null; } } /// /// Размещает рифы согласно массиву ReefEntry[]. /// Каждый entry — тип рифа (ReefConfig) и сколько штук поставить. /// Вызывать ПОСЛЕ ClearPole(), ДО расстановки кораблей. /// public void ApplyRandomReefs(BossLevelConfig.ReefEntry[] reefs) { _savedReefs.Clear(); _savedReefGroups.Clear(); _savedReefConfigs.Clear(); foreach (var o in _spawnedReefObjects) if (o != null) Destroy(o); _spawnedReefObjects.Clear(); if (reefs == null) return; foreach (var entry in reefs) { if (entry?.config == null) continue; int w = Mathf.Max(1, entry.config.groupWidth); int h = Mathf.Max(1, entry.config.groupHeight); // Риф крупнее поля — разместить невозможно if (LengPole < w || LengPole < h) { Debug.LogWarning($"[GamePole] Риф '{entry.config.name}': размер группы {w}x{h} больше поля {LengPole}x{LengPole}."); continue; } // Собираем все стартовые позиции, где группа сейчас помещается var validPositions = new List(); for (int rx = 0; rx <= LengPole - w; rx++) { for (int ry = 0; ry <= LengPole - h; ry++) { bool ok = true; for (int dx = 0; dx < w && ok; dx++) for (int dy = 0; dy < h && ok; dy++) if (FreePole[rx + dx, ry + dy] != 0) ok = false; if (ok) validPositions.Add(new Vector2Int(rx, ry)); } } // Перемешиваем — каждая игра уникальна Shuffle(validPositions); int placed = 0; foreach (var pos in validPositions) { if (placed >= entry.count) break; // Перепроверяем: предыдущий размещённый риф мог занять эту позицию bool canPlace = true; for (int dx = 0; dx < w && canPlace; dx++) for (int dy = 0; dy < h && canPlace; dy++) if (FreePole[pos.x + dx, pos.y + dy] != 0) canPlace = false; if (!canPlace) continue; // Размещаем группу var group = new RectInt(pos.x, pos.y, w, h); _savedReefGroups.Add(group); _savedReefConfigs.Add(entry.config); for (int dx = 0; dx < w; dx++) for (int dy = 0; dy < h; dy++) { int cx = pos.x + dx, cy = pos.y + dy; _savedReefs.Add(new Vector2Int(cx, cy)); FreePole[cx, cy] = 3; PoleShot[cx, cy] = 1; Pole[cx, cy]?.GetComponent()?.ShowReef(); } if (entry.config.prefab != null) SpawnReefObject(group, entry.config); placed++; } if (placed < entry.count) Debug.LogWarning($"[GamePole] Риф '{entry.config.name}': размещено {placed}/{entry.count} — " + $"поле слишком занято для групп {w}x{h}. Уменьши count или размер групп."); } } /// /// Спавнит 3D-объект рифа в центре группы клеток. /// Масштаб умножается на _cellScale для пропорции с полем. /// private void SpawnReefObject(RectInt group, ReefConfig config) { int x2 = Mathf.Min(group.x + group.width - 1, LengPole - 1); int y2 = Mathf.Min(group.y + group.height - 1, LengPole - 1); // Центр между первой и последней клеткой группы Vector3 topLeft = Pole[group.x, group.y].transform.localPosition; Vector3 bottomRight = Pole[x2, y2].transform.localPosition; Vector3 center = (topLeft + bottomRight) * 0.5f; center.y = config.yOffset; var obj = Instantiate(config.prefab, transform); obj.transform.localPosition = center; obj.transform.localScale = config.scale * _cellScale; float rotY = config.randomRotationY ? Random.Range(0f, 360f) : config.fixedRotationY; obj.transform.localRotation = Quaternion.Euler(0f, rotY, 0f); _spawnedReefObjects.Add(obj); } /// /// Восстанавливает рифы (клетки + 3D-объекты) после ClearPole. /// Вызывается автоматически в конце ClearPole. /// private void RestoreReefs() { // Уничтожаем старые объекты перед пересозданием foreach (var o in _spawnedReefObjects) if (o != null) Destroy(o); _spawnedReefObjects.Clear(); // Восстанавливаем клетки foreach (var pos in _savedReefs) { FreePole[pos.x, pos.y] = 3; PoleShot[pos.x, pos.y] = 1; Pole[pos.x, pos.y]?.GetComponent()?.ShowReef(); } // Пересоздаём 3D-объекты — каждая группа знает свой конфиг for (int i = 0; i < _savedReefGroups.Count; i++) { var cfg = _savedReefConfigs[i]; if (cfg?.prefab != null) SpawnReefObject(_savedReefGroups[i], cfg); } } public List<(int x, int y)> GetAvailableShots() // получаем доступные для выстрела поля { var available = new List<(int, int)>(); for (int x = 0; x < LengPole; x++) { for (int y = 0; y < LengPole; y++) { if (PoleShot[x, y] == 0) available.Add((x, y)); } } return available; } public int[] SearchWreckedShip() // ищем корабли ваокруг { for (int x = 0; x < LengPole; x++) { for (int y = 0; y < LengPole; y++) { if (PoleShot[x, y] == 2) { // Проверяем клетки вокруг if (HasFreeAdjacent( x, y)) return new int[] { x, y }; } } } return null; } private bool HasFreeAdjacent( int x, int y) { int[,] dirs = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } }; foreach (var i in new int[] { 0, 1, 2, 3 }) { int dx = x + dirs[i, 0]; int dy = y + dirs[i, 1]; if (dx >= 0 && dx < LengPole && dy >= 0 && dy < LengPole) { if (PoleShot[dx, dy] == 0) return true; } } return false; } public bool Shot(int x, int y) { if (PoleShot[x, y] != 0) return true; // затычка bool hit = Pole[x, y].GetComponent().hit(); if (hit) { var cell = Pole[x, y].GetComponent(); if (cell != null && cell.deck != null) { PoleShot[x, y] = 2; gameManager.playerCamera.GetComponent().Shake(0.3f, 0.2f); var ship = cell.deck.GetComponentInParent(); if (ship != null && ship.IsDestroyed()) { HitAroundShip(ship); ship.Destroy(); bool isPlayer = this == gameManager.playerField; // gameManager.ShipDestroyed(isPlayer); } } } else { PoleShot[x, y] = 1; if (missEffectPrefabs != null && missEffectPrefabs.Length > 0) { Vector3 spawnPos = Pole[x, y].transform.position; // выбираем случайный элемент GameObject prefab = missEffectPrefabs[Random.Range(0, missEffectPrefabs.Length)]; if (prefab != null) { GameObject effect = Instantiate(prefab, spawnPos, Quaternion.identity); // Автоматически уничтожаем через 2 секунды Destroy(effect, 2f); } } if (EffectManager.Instance != null) { Vector3 basePos = Pole[x, y].transform.position; // Случайное смещение: по Y от 5 до 10, по X от -1 до 1 float randomY = Random.Range(7f, 14f); float randomX = Random.Range(-2f, 2f); Vector3 spawnPos = basePos + new Vector3(randomX, randomY, 0f); GameObject textFx = EffectManager.Instance.PlayMissText(spawnPos); if (textFx != null) { // Случайный масштаб для разнообразия float scale = Random.Range(3f, 4f); textFx.transform.localScale *= scale; } } } return hit; } // обвдим корабль void HitAroundShip(Ship ship) { foreach (var coord in ship.deckCoords) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int nx = coord.x + dx; int ny = coord.y + dy; if (nx >= 0 && nx < LengPole && ny >= 0 && ny < LengPole) { if (dx == 0 && dy == 0) { PoleShot[nx, ny] = 3; // корабль убит continue; } // Риф — не закрашиваем, чтобы не накладывались спрайты if (FreePole[nx, ny] == 3) continue; var cell = Pole[nx, ny].GetComponent(); if (cell != null) { PoleShot[nx, ny] = 1; cell.hit(); } } } } } } public void ClearPole() { ClearGhost(); foreach (var ship in placedShips) { if (ship != null) Destroy(ship); } placedShips.Clear(); currentShipIndex = 0; IsReady = false; for (int x = 0; x < LengPole; x++) { for (int y = 0; y < LengPole; y++) { if (Pole[x, y] != null) { var cell = Pole[x, y].GetComponent(); if (cell != null) { cell.deck = null; cell.HideAll(); } } FreePole[x, y] = 0; PoleShot[x, y] = 0; } } RestoreReefs(); // рифы восстанавливаются сразу после очистки } // проверяемс свободны ли клетки bool CanPlaceShip(int[,] field, int start_x, int start_y, int length, Direction dir) { int dx = 0, dy = 0; switch (dir) { case Direction.Left: dx = -1; break; case Direction.Right: dx = 1; break; case Direction.Up: dy = -1; break; case Direction.Down: dy = 1; break; } for (int i = 0; i < length; i++) { int x = start_x + dx * i; int y = start_y + dy * i; if (x < 0 || x >= LengPole || y < 0 || y >= LengPole) return false; if (field[x, y] != 0) return false; } return true; } public bool PlaceShipManual(int x, int y, Direction direction) { if (currentShipIndex >= shipCaunt) return false; GameObject shipPrefab = ShipMass[currentShipIndex]; int size = shipPrefab.GetComponent().decks.Count; if (!CanPlaceShip(FreePole, x, y, size, direction)) return false; GameObject ship = Instantiate(shipPrefab, transform); ship.SetActive(true); ship.transform.localScale *= _cellScale; switch (direction) { case Direction.Left: ship.transform.localRotation = Quaternion.Euler(0, 0, 0); break; case Direction.Right: ship.transform.localRotation = Quaternion.Euler(0, 180, 0); break; case Direction.Up: ship.transform.localRotation = Quaternion.Euler(0, 90, 0); break; case Direction.Down: ship.transform.localRotation = Quaternion.Euler(0, 270, 0); break; } Vector3 basePos = Pole[x, y].transform.localPosition; ship.transform.localPosition = new Vector3(basePos.x, basePos.y - 0.1f, basePos.z); int end_x = x + (direction == Direction.Left ? -size + 1 : direction == Direction.Right ? size - 1 : 0); int end_y = y + (direction == Direction.Up ? -size + 1 : direction == Direction.Down ? size - 1 : 0); PlaceShip(FreePole, ship, x, y, end_x, end_y); placedShips.Add(ship); ClearGhost(); // убираем призрак currentShipIndex++; return true; } public void ShowShipGhost(int x, int y, Direction direction) { if(currentShipIndex >= shipCaunt) return; int size = ShipMass[currentShipIndex].GetComponent().decks.Count; if (!CanPlaceShip(FreePole, x, y, size, direction)) { if (ghostShip != null) ghostShip.SetActive(false); // просто скрываем return; } // Если уже создан призрак нужного типа — просто обновляем позицию if (ghostShip != null && ghostShipIndex == currentShipIndex) { ghostShip.SetActive(true); // вдруг был скрыт } else { if (ghostShip != null) Destroy(ghostShip); ghostShip = Instantiate(ShipMass[currentShipIndex], transform); ghostShipIndex = currentShipIndex; ghostShip.transform.localScale *= _cellScale; foreach (Collider col in ghostShip.GetComponentsInChildren()) col.enabled = false; foreach (Renderer renderer in ghostShip.GetComponentsInChildren()) renderer.material.color = new Color(1f, 1f, 1f, 0.3f); // полупрозрачный } // Поворот switch (direction) { case Direction.Left: ghostShip.transform.localRotation = Quaternion.Euler(0, 0, 0); break; case Direction.Right: ghostShip.transform.localRotation = Quaternion.Euler(0, 180, 0); break; case Direction.Up: ghostShip.transform.localRotation = Quaternion.Euler(0, 90, 0); break; case Direction.Down: ghostShip.transform.localRotation = Quaternion.Euler(0, 270, 0); break; } Vector3 basePos = Pole[x, y].transform.localPosition; ghostShip.transform.localPosition = new Vector3(basePos.x, basePos.y - 0.1f, basePos.z); } public void ClearGhost() { if (ghostShip != null) { Destroy(ghostShip); ghostShip = null; ghostShipIndex = -1; } } // раставляем корабли public void ArrangeShip(bool hideShips = false) { ClearPole(); int count = Mathf.Min(shipCaunt, ShipMass != null ? ShipMass.Length : 0); for (int i = 0; i < count; i++) { GameObject shipPrefab = ShipMass[i]; GameObject ship = Instantiate(shipPrefab); ship.transform.SetParent(this.transform, false); ship.SetActive(true); ship.transform.localScale *= _cellScale; placedShips.Add(ship); if (hideShips) { foreach (Renderer r in ship.GetComponentsInChildren()) r.enabled = false; } int quantity = ship.GetComponent().decks.Count; // Генерируем список всех возможных позиций List<(int x, int y, Direction dir)> possiblePlacements = new(); for (int x = 0; x < LengPole; x++) { for (int y = 0; y < LengPole; y++) { foreach (Direction direction in System.Enum.GetValues(typeof(Direction))) { if (CanPlaceShip(FreePole, x, y, quantity, direction)) possiblePlacements.Add((x, y, direction)); } } } // Если нет ни одного подходящего места if (possiblePlacements.Count == 0) { Debug.LogWarning("Не удалось разместить корабль, перезапускаем расстановку."); ArrangeShip(hideShips); continue; } // Перемешиваем возможные позиции Shuffle(possiblePlacements); // Берем первую подходящую var (start_x, start_y, dir) = possiblePlacements[0]; switch (dir) { case Direction.Left: ship.transform.localRotation = Quaternion.Euler(0, 0, 0); break; case Direction.Right: ship.transform.localRotation = Quaternion.Euler(0, 180, 0); break; case Direction.Up: ship.transform.localRotation = Quaternion.Euler(0, 90, 0); break; case Direction.Down: ship.transform.localRotation = Quaternion.Euler(0, 270, 0); break; } Vector3 basePos = Pole[start_x, start_y].transform.localPosition; ship.transform.localPosition = new Vector3(basePos.x, basePos.y - 0.1f, basePos.z); int end_x = start_x + (dir == Direction.Left ? -quantity + 1 : dir == Direction.Right ? quantity - 1 : 0); int end_y = start_y + (dir == Direction.Up ? -quantity + 1 : dir == Direction.Down ? quantity - 1 : 0); PlaceShip(FreePole, ship, start_x, start_y, end_x, end_y); } currentShipIndex = count; // все корабли расставлены — ручная расстановка заблокирована IsReady = true; } void Shuffle(List list) { for (int i = list.Count - 1; i > 0; i--) { int j = Random.Range(0, i + 1); (list[i], list[j]) = (list[j], list[i]); } } // тут раставляются корабли void PlaceShip(int[,] FreePole, GameObject ship, int start_x, int start_y, int end_x, int end_y) { var decks = ship.GetComponent().decks; int deckIndex = 0; if (start_x != end_x) { int step = start_x < end_x ? 1 : -1; for (; start_x != end_x + step; start_x += step) { SetDeckAndAround(FreePole, start_x, start_y, decks[deckIndex]); ship.GetComponent().AddDeckCoord(start_x, start_y); deckIndex++; } } else { int step = start_y < end_y ? 1 : -1; for (; start_y != end_y + step; start_y += step) { SetDeckAndAround(FreePole, start_x, start_y, decks[deckIndex]); ship.GetComponent().AddDeckCoord(start_x, start_y); deckIndex++; } } } // тут устанавливаем палубы и свободное поле делаем не свободным void SetDeckAndAround(int[,] FreePole, int x, int y, GameObject deck) { FreePole[x, y] = 1; // Привязка палубы к клетке Pole[x, y].GetComponent().deck = deck; // TODU надо придумать что-то поумнее deck.SetActive(true); for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int nx = x + dx; int ny = y + dy; if (nx >= 0 && nx < LengPole && ny >= 0 && ny < LengPole && FreePole[nx, ny] == 0) { FreePole[nx, ny] = 2; } } } } // тут копируем корабли public void CopyShipsFrom(GamePole sourceField, bool hideShips = true) { ClearPole(); foreach (var originalShip in sourceField.placedShips) { GameObject copy = Instantiate(originalShip); // Делаем копию дочерним объектом текущего поля copy.transform.SetParent(this.transform, false); // Копируем ЛОКАЛЬНЫЕ координаты, чтобы корабли встали на сетку поля copy.transform.localPosition = originalShip.transform.localPosition; copy.transform.localRotation = originalShip.transform.localRotation; if (hideShips) { foreach (Renderer r in copy.GetComponentsInChildren()) r.enabled = false; } placedShips.Add(copy); var ship = copy.GetComponent(); var sourceShip = originalShip.GetComponent(); for (int i = 0; i < sourceShip.deckCoords.Count; i++) { var coord = sourceShip.deckCoords[i]; Pole[coord.x, coord.y].GetComponent().deck = ship.decks[i]; ship.AddDeckCoord(coord.x, coord.y); } } IsReady = true; } }