using System; using System.Collections; using UnityEngine; public class GameManager : MonoBehaviour { public static event System.Action OnInviteSent; public static event System.Action OnReady; public event System.Action OnInputModeChanged; private IBotAI bot; public Transform shipPlacementCameraPosition; public Transform mainMenuCameraPosition; public Transform gameplayCameraPosition; [Tooltip("Центр вращения камеры во время расстановки кораблей (пустой GO у поля игрока)")] [SerializeField] private Transform shipPlacementPivotPoint; /// Исходная мировая позиция cameraPivot — восстанавливается вне режима расстановки. private Vector3 _defaultPivotPosition; public bool isReady = false; // Готов ли игрок (расставил ли корабли) public GameManager opponentManager; // Противник (если есть) public CannonBattery battery; public CannonBattery enemyBattery; public GameState currentState; public UIManager uiManager; public GamePole playerField; public GamePole enemyField; public GameObject targetCursorPrefab; private GameObject targetCursorInstance; public bool isPlayerTurn = false; public int playerShips = 0; public int enemyShips = 0; public int playerId = 1; // 1 - первый игрок, 2 - второй игрок и т.д. public Camera playerCamera; public Transform cameraPivot; public int currentBotLevel = 0; public EnemyManager enemyManager; public FortInitializer fortInitializer; [Header("Время бездействия")] [SerializeField] float inactionTimer = 360f; [SerializeField] float mainMenuVideoIdleDelay = 30f; public float timerBackToMenu = 0; private bool idleVideoRequested; [SerializeField] private ShipPlacementTimer shipPlacementTimer; private bool autoStartBattlePending; [Header("Таймер бездействия в бою")] [SerializeField] private float battleIdleTimeout = 45f; private float battleIdleTimer = 0f; private bool battlePauseShown = false; /// True после EndGame() — блокирует таймер паузы и вызов StartPlayerTurn() из BotTurn(). private bool isGameEnded; public enum InputMode { UI, Settings, ShipPlacement, Battle } private bool paidFroFriend = false; public InputMode currentInputMode = InputMode.UI; [Header("Настройки бота")] public bool useManualDifficulty = false; public BotDifficulty manualDifficulty = BotDifficulty.Level1; [Header("Конфиг уровней (режимы игры)")] [SerializeField] private BossLevelConfig bossLevelConfig; public enum BotDifficulty { Level1, Level2, Level3, Level4, Level5, Level6, } public GameMode gameMode = GameMode.PlayerVsBot; public BotDifficulty botDifficulty = BotDifficulty.Level1; public float cameraMoveDuration = 1.0f; // Время перемещения в секундах void Start() { // Запоминаем исходную позицию пивота (центр карты) if (cameraPivot != null) _defaultPivotPosition = cameraPivot.position; var player = GetComponentInParent(); if (player != null) playerId = player.PlayerId; SetState(GameState.MainMenu); } private void Update() { timerBackToMenu += Time.deltaTime; bool idleVideoPlaying = IdleMainMenuVideoController.Instance != null && IdleMainMenuVideoController.Instance.IsPlaying(); if (currentState == GameState.MainMenu) { if (!idleVideoPlaying && timerBackToMenu >= mainMenuVideoIdleDelay) { IdleMainMenuVideoController.Instance?.TryStartVideo(); idleVideoRequested = true; } } else { idleVideoRequested = false; if (timerBackToMenu >= inactionTimer) { ReturnToMainMenu(); timerBackToMenu = 0; } } // --- Таймер бездействия во время боя --- if (currentState == GameState.Playing && !battlePauseShown && !isGameEnded) { battleIdleTimer += Time.deltaTime; if (battleIdleTimer >= battleIdleTimeout) { ShowBattlePause(); } } } private void OnEnable() { In.ButtonEvent += ResetTimer; if (shipPlacementTimer != null) shipPlacementTimer.TimerExpired += HandleShipPlacementTimeout; } private void OnDisable() { In.ButtonEvent -= ResetTimer; if (shipPlacementTimer != null) shipPlacementTimer.TimerExpired -= HandleShipPlacementTimeout; } private void ResetTimer() { ResetIdleTimer(); IdleMainMenuVideoController.Instance?.StopVideo(); } public void ResetIdleTimer() { timerBackToMenu = 0; idleVideoRequested = false; } // ===== Таймер бездействия в бою ===== public void ResetBattleIdleTimer() { battleIdleTimer = 0f; } public void ShowBattlePause() { if (battlePauseShown) return; battlePauseShown = true; SetInputMode(InputMode.UI); uiManager.ShowOverlay("BattlePause", true, hideOtherOverlays: true); } public void HideBattlePause() { if (!battlePauseShown) return; battlePauseShown = false; battleIdleTimer = 0f; uiManager.ShowOverlay("BattlePause", false); // Восстанавливаем режим ввода: если ход игрока — Battle, иначе UI SetInputMode(isPlayerTurn ? InputMode.Battle : InputMode.UI); } void SetPlayerReady(bool ready) { isReady = ready; if (gameMode == GameMode.PlayerVsBot) { uiManager.SetupPlayButtons(ready); } else { var rez = isReady & opponentManager.isReady; uiManager.SetupPlayButtons(rez); opponentManager.uiManager.SetupPlayButtons(rez); } OnReady?.Invoke(playerId, ready, gameMode); TryStartBattleAfterPlacementTimeout(); } public void AddShipPlayer() { playerShips++; if (playerShips == playerField.ShipCount) { SetPlayerReady(true); } } public void SetInputMode(InputMode newMode) { currentInputMode = newMode; OnInputModeChanged?.Invoke(newMode); if (newMode == InputMode.ShipPlacement && targetCursorInstance == null) InitTargetCursor(true); } public void ShipDestroyed(bool isPlayer) { if (isPlayer) { enemyManager.TriggerEventReaction(DialogueEventType.PlayerShipDestroyed); playerShips--; } else { enemyManager.TriggerEventReaction(DialogueEventType.BotShipDestroyed); enemyShips--; } CheckVictoryCondition(); } private void CheckVictoryCondition() { BlinkController.StartBlinkATimes(playerId,5,0.2f); BlinkController.StartBlinkCTimes(5,0.2f); if (playerShips <= 0) { Debug.Log("Игрок проиграл!"); BlinkController.LEDRedAll(playerId); // Красный LED при поражении (статический) enemyManager.TriggerEndGameReaction(false); EndGame(); } else if (enemyShips <= 0) { Debug.Log("Игрок победил!"); BlinkController.SetLEDBaseCustomColor(playerId, 255, 215, 0); // Устанавливаем золотой как базовый enemyManager.TriggerEndGameReaction(true); EndGame(); } } public void ProcessShotResult(bool hit, bool isPlayer, bool wasShipDestroyed = false) { if (wasShipDestroyed) { BlinkController.StartBlinkATimes(playerId,3,0.2f); // Мигание LED 3 раза при потоплении if (!isPlayer) { // Бот потопил корабль игрока - красный 3 раза BlinkController.BlinkLEDRed(playerId, 3, 0.4f); } else { // Игрок потопил корабль противника - зеленый 3 раза BlinkController.BlinkLEDGreen(playerId, 3, 0.4f); } ShipDestroyed(!isPlayer); return; } if (!isPlayer) { if (hit) { BlinkController.StartBlinkATimes(playerId,1); BlinkController.BlinkLEDRed(playerId, 1, 0.6f); enemyManager.TriggerEventReaction(DialogueEventType.BotHit); } else { SoundManager.Instance.PlayMiss(playerId); enemyManager.TriggerEventReaction(DialogueEventType.BotMiss); } } else { if (hit) { BlinkController.StartBlinkATimes(playerId,1); BlinkController.BlinkLEDGreen(playerId, 1, 0.6f); enemyManager.TriggerEventReaction(DialogueEventType.PlayerHit); } else { SoundManager.Instance.PlayMiss(playerId); enemyManager.TriggerEventReaction(DialogueEventType.PlayerMiss); } } } public void SetState(GameState newState) { currentState = newState; // Сброс таймера бездействия в бою при выходе из боя if (newState != GameState.Playing) { battleIdleTimer = 0f; battlePauseShown = false; } if (newState != GameState.ShipPlacement) { autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); } // UI: просто показываем панель по имени state (без switch) uiManager.ShowById(newState.ToString()); // LED подсветка в зависимости от состояния if (newState == GameState.MainMenu) { // В главном меню: канал 1 - волна, канал 2 - радуга BlinkController.LEDWave(1, playerId); // Канал 1 -> волна (команда 8111) BlinkController.LEDRainbow(2, playerId); // Канал 2 -> радуга (команда 8209) } // Камера — оставляем switch только для 3 точек switch (newState) { case GameState.MainMenu: MoveCameraTo(mainMenuCameraPosition); break; case GameState.ShipPlacement: MoveCameraTo(shipPlacementCameraPosition); break; case GameState.Playing: MoveCameraTo(gameplayCameraPosition); break; } } public void StartShipPlacementCountdown() { autoStartBattlePending = false; shipPlacementTimer?.StartTimer(); } private BotDifficulty GetDifficultyFromLevel() { if (useManualDifficulty) { return manualDifficulty; } int level = Mathf.Clamp(currentBotLevel, 0, 4); Debug.Log("шанс на победу : " + DifficultyManager.GetChance()); if (level == 4) // дошел до 5го уровня { // Система шанса на победу if (DifficultyManager.GetChance() <= 0) { // Счётчик НЕ сбрасываем — он сбросится только после победы игрока. // Пока не победил — каждая игра остаётся шанс-игрой (Level5). Debug.Log("Шанс на победу! (сбросится при победе)"); return BotDifficulty.Level5; // даем шанс победить } else { DifficultyManager.DecreaseChance(); return BotDifficulty.Level6; // почти непобедимый } } return (BotDifficulty)level; } public void PrepareForBattle() { if (playerShips != playerField.ShipCount) { playerField.ArrangeShip(); playerShips = playerField.ShipCount; } if (targetCursorInstance != null) { Destroy(targetCursorInstance); targetCursorInstance = null; } SetInputMode(InputMode.Battle); InitTargetCursor(); // Устанавливаем синий цвет LED при входе в бой BlinkController.SetLEDBaseColor(playerId, 5); // 5 = синий } public void StartBattle() { Debug.Log("Игровой этап начат"); autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); if (gameMode == GameMode.PlayerVsPlayer) { if (!isReady || opponentManager == null || !opponentManager.isReady) { Debug.Log("Ожидаем готовности оппонента..."); return; } PrepareForBattle(); opponentManager.PrepareForBattle(); SetState(GameState.Playing); opponentManager.SetState(GameState.Playing); // копируем корабли на поля if (!enemyField.IsReady) { enemyField.CopyShipsFrom(opponentManager.playerField, true); enemyShips = opponentManager.playerField.ShipCount; } if (!opponentManager.enemyField.IsReady) { opponentManager.enemyField.CopyShipsFrom(playerField, true); opponentManager.enemyShips = playerField.ShipCount; } paidFroFriend = false; opponentManager.paidFroFriend = false; opponentManager.uiManager.ShowTimer(false); isReady = false; opponentManager.isReady = false; } else // бот { PrepareForBattle(); enemyShips = enemyField.ShipCount; enemyField.ArrangeShip(true); SetState(GameState.Playing); } StartPlayerTurn(); } public void InitTargetCursor(bool isPlacingShips = false) { if (targetCursorInstance != null) return; targetCursorInstance = Instantiate(targetCursorPrefab); TargetCursor cursorScript = targetCursorInstance.GetComponent(); cursorScript.Initialize(this, playerId, isPlacingShips); targetCursorInstance.SetActive(isPlacingShips); // по дефолту скрыт } public void EndGame() { isGameEnded = true; SetInputMode(InputMode.UI); autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); StartCoroutine(EndGameSequence()); } private IEnumerator EndGameSequence() { bool isWin = enemyShips <= 0; bool isBotGame = (gameMode == GameMode.PlayerVsBot); // Задержка перед показом GameOver экрана yield return new WaitForSeconds(0.5f); // Определяем приз ДО повышения уровня Prize pendingPrize = null; bool isAtFinalBoss = (currentBotLevel >= 4); if (gameMode == GameMode.PlayerVsBot && isWin) { Prize priz = PrizeSerializer.GetPrizes()[currentBotLevel]; Debug.Log("Приз :" + priz); bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать"; if (settingEnabled && priz.PrizeType != PrizeType.None) pendingPrize = priz; // Повышаем уровень бота ПЕРЕД выдачей приза. if (currentBotLevel < 4) currentBotLevel++; else DifficultyManager.ResetChance(); } // Показываем GameOver; если есть приз и не финальный босс — кнопка "Меню" станет "Забрать приз" bool showClaimPrize = (pendingPrize != null && !isAtFinalBoss); uiManager.ShowGameOverMenu(isWin, isBotGame, showClaimPrize ? pendingPrize : null); // Финальный босс — приз выдаём сразу как раньше (кнопки "Продолжить" нет) if (pendingPrize != null && isAtFinalBoss) { yield return new WaitForSeconds(1.5f); if (pendingPrize.PrizeType == PrizeType.Special) { uiManager.ShowOverlayAnimated("QRCode", 0f); yield break; } if (pendingPrize.PrizeType == PrizeType.Showcase) { uiManager.ShowOverlayAnimated("PrizeSelection", 0f); yield break; } uiManager.ShowOverlayAnimated("PrizeReminder", 0f); PrizeSystem.GivePrize(pendingPrize, playerId); } } // ============= buttons ========================================================================== public void OnReplayPressed() // по идеее функции разные так как нужна проверка на баланс $ { if (gameMode == GameMode.PlayerVsBot) { StartBotGame(isReplay: true); // скидка за реванш обрабатывается в CreditSystem.PayBotGame } else { int price = CreditSystem.PlayerGamePrice; if (CreditSystem.IsSharedBalance()) { // Общий пул — платим ровно цену один раз CreditSystem.Pay(price, 1); } else if (CreditSystem.GetPlayerCredits(opponentManager.playerId) >= price) { CreditSystem.Pay(price, playerId); CreditSystem.Pay(price, opponentManager.playerId); } else { paidFroFriend = true; CreditSystem.Pay(price * 2, playerId); } opponentManager.StartPlayerGame(); StartPlayerGame(); } } public void OnContinuePressed() { if (gameMode == GameMode.PlayerVsBot) { // Возврат кредита за победу только в платном режиме (следующий уровень бесплатный) bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!isFreeMode) { CreditSystem.AddCredit(CreditSystem.BotGamePrice, playerId); } StartBotGame(); } } public void MenuGame(bool endGame = false) { bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!endGame && !isFreeMode) { if (gameMode == GameMode.PlayerVsPlayer) { int price = CreditSystem.PlayerGamePrice; if (CreditSystem.IsSharedBalance()) { // Общий пул — возвращаем ровно цену один раз CreditSystem.AddCredit(price, 1); } else if (paidFroFriend) CreditSystem.AddCredit(price * 2, playerId); else if (opponentManager.paidFroFriend) CreditSystem.AddCredit(price * 2, opponentManager.playerId); else { CreditSystem.AddCredit(price, playerId); CreditSystem.AddCredit(price, opponentManager.playerId); } opponentManager.ReturnToMainMenu(); } else { // Бот: рефанд только если игрок ещё не выиграл ни разу (вышел с первой игры) if (currentBotLevel == 0) CreditSystem.AddCredit(CreditSystem.BotGamePrice, playerId); } } if (gameMode == GameMode.PlayerVsPlayer) opponentManager.ReturnToMainMenu(); ReturnToMainMenu(); } public void ClearPole() { if (playerField != null) { playerField.ClearPole(); playerShips = 0; SetPlayerReady(false); Debug.Log("Поле очищено"); } } public void ArrangeGame() { if (playerField != null) { playerField.ArrangeShip(); playerShips = playerField.ShipCount; SetPlayerReady(true); Debug.Log("Поле очищено и корабли расставлены заново"); } else { Debug.LogWarning("GamePole не назначен в GameManager"); } } public void OnPlayVsPlayerClicked(int playerId) // мистр кастыль { bool freeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!freeMode) { int opponentId = playerId == 1 ? 2 : 1; int price = CreditSystem.PlayerGamePrice; bool canStart; if (CreditSystem.IsSharedBalance()) { // Единый пул — достаточно одной цены canStart = CreditSystem.GetPlayerCredits(1) >= price; } else { int myCoins = CreditSystem.GetPlayerCredits(playerId); int opponentCoins = CreditSystem.GetPlayerCredits(opponentId); canStart = (myCoins >= price && opponentCoins >= price) || myCoins >= price * 2; } if (!canStart) { uiManager?.ShowOverlay("InsufficientFunds", true, hideOtherOverlays: false); return; } } int oppId = playerId == 1 ? 2 : 1; OnInviteSent?.Invoke(playerId, oppId); } public void AcceptInvite() { int price = CreditSystem.PlayerGamePrice; if (CreditSystem.IsSharedBalance()) { // Общий пул — платим ровно цену один раз CreditSystem.Pay(price, 1); } else if (CreditSystem.GetPlayerCredits(playerId) >= price) { CreditSystem.Pay(price, playerId); CreditSystem.Pay(price, opponentManager.playerId); } else { opponentManager.paidFroFriend = true; CreditSystem.Pay(price * 2, opponentManager.playerId); } StartPlayerGame(); opponentManager.StartPlayerGame(); } public void DeclineInvite() { uiManager.HideInvite(); } public void StartPlayerGame() { // PvP всегда использует классический режим 10x10 playerField.Configure(10, 10); PrepareNewGame(); Debug.Log("Начинаем игру с игроком"); gameMode = GameMode.PlayerVsPlayer; if (playerId == 1) { SoundManager.Instance?.SetPvPMode(true); } SetState(GameState.ShipPlacement); SetInputMode(InputMode.UI); uiManager.ShowOverlay("PlacementChoice", true, hideOtherOverlays: true); SetInputMode(InputMode.UI); fortInitializer.FortSwitch(3); enemyManager.SetEnemyPlayer(); } public void StartBotGame(bool isReplay = false) { // Сбрасываем скидку Виндисты — начинается новая игра VindistaController.Instance?.SetDiscountMode(false, playerId); // Если есть оппонент, который ждёт в главном меню - закрываем его приглашение if (opponentManager != null && opponentManager.currentState == GameState.MainMenu) { opponentManager.DeclineInvite(); } // PayBotGame сам считает скидку если isReplay=true и она включена в настройках if (!CreditSystem.PayBotGame(isReplay, playerId)) return; // Конфигурируем поле под текущий режим игры и уровень босса ConfigureFields(); PrepareNewGame(); // Рифы применяем ПОСЛЕ ClearPole (чтобы массивы не были обнулены) ApplyModeReefs(); Debug.Log("Начинаем игру с ботом"); gameMode = GameMode.PlayerVsBot; if (playerId == 1) SoundManager.Instance?.SetPvPMode(false); SetState(GameState.ShipPlacement); SetInputMode(InputMode.UI); // Затем показать overlay выбора расстановки uiManager.ShowOverlay("PlacementChoice", true, hideOtherOverlays: true); InitBot(); } //========================================================================================== /// /// Читает режим игры из настроек и конфигурирует оба поля (размер + кол-во кораблей). /// Вызывается перед PrepareNewGame() в StartBotGame. /// private void ConfigureFields() { string mode = SettingsManager.instance?.GetSettingValue("Режим игры") ?? "Классика"; int fieldSize = 10; int shipCount = 10; if (mode != "Классика" && bossLevelConfig != null) { var data = bossLevelConfig.GetLevel(currentBotLevel); if (mode == "Растущие поля") { fieldSize = data.fieldSize; shipCount = data.shipCountGame1; } else if (mode == "Рифы") { fieldSize = 10; // поле всегда 10x10 shipCount = data.shipCountGame2; // отдельное кол-во кораблей } } playerField.Configure(fieldSize, shipCount); enemyField.Configure(fieldSize, shipCount); Debug.Log($"[GameManager] Режим игры: '{mode}', поле: {fieldSize}x{fieldSize}, кораблей: {shipCount}"); } /// /// Применяет случайные рифы для режима "Рифы". /// Вызывается ПОСЛЕ PrepareNewGame() (после ClearPole), ДО расстановки кораблей. /// private void ApplyModeReefs() { if (bossLevelConfig == null) return; string mode = SettingsManager.instance?.GetSettingValue("Режим игры") ?? "Классика"; if (mode != "Рифы") return; var data = bossLevelConfig.GetLevel(currentBotLevel); playerField.ApplyRandomReefs(data.reefs); enemyField.ApplyRandomReefs(data.reefs); Debug.Log($"[GameManager] Рифы применены для уровня {currentBotLevel}"); } private void InitBot() { BotDifficulty selectedDifficulty = GetDifficultyFromLevel(); Debug.Log("Выбран бот: " + selectedDifficulty); switch (selectedDifficulty) { case BotDifficulty.Level1: bot = new EasyBotAI(); enemyManager.SetEnemy(0); fortInitializer.FortSwitch(0); break; case BotDifficulty.Level2: bot = new NormalBotAI(); enemyManager.SetEnemy(1); fortInitializer.FortSwitch(1); break; case BotDifficulty.Level3: bot = new HardBotAI(); enemyManager.SetEnemy(2); fortInitializer.FortSwitch(2); break; case BotDifficulty.Level4: bot = new VeryHardBotAI(); enemyManager.SetEnemy(3); fortInitializer.FortSwitch(3); break; case BotDifficulty.Level5: bot = new CrazyBotAI(); enemyManager.SetEnemy(4); fortInitializer.FortSwitch(4); break; case BotDifficulty.Level6: bot = new UltimateBotAI(); enemyManager.SetEnemy(4); fortInitializer.FortSwitch(4); break; default: bot = new EasyBotAI(); enemyManager.SetEnemy(0); fortInitializer.FortSwitch(0); break; } } // ходы public void StartPlayerTurn() { isPlayerTurn = true; uiManager.ShowTimer(true); if (targetCursorInstance != null) targetCursorInstance.SetActive(true); SetInputMode(InputMode.Battle); } public void EndPlayerTurn() { isPlayerTurn = false; uiManager.ShowTimer(false); if (targetCursorInstance != null) targetCursorInstance.SetActive(false); if (gameMode == GameMode.PlayerVsPlayer) { opponentManager.StartPlayerTurn(); } else { // enemyManager.ShowNextDialogue(); StartCoroutine(BotTurn()); } } public IEnumerator BotTurn() { bool hit; do { yield return new WaitForSeconds(1f); Vector2Int target = bot.MakeMove(playerField); if (target.x < 0 || target.y < 0) { Debug.LogWarning("Боту нечего стрелять, пропускаем ход"); break; // или EndGame, если нет ходов } bool shotComplete = false; hit = false; enemyBattery.FireShot(playerField, target.x, target.y, (wasHit, wasShipDestroyed) => { hit = wasHit; shotComplete = true; ProcessShotResult(wasHit,false, wasShipDestroyed); // if (hit) SoundManager.Instance.PlayBotHit(playerId); // else SoundManager.Instance.PlayBotMiss(playerId); // if(wasShipDestroyed) SoundManager.Instance.PlayPlayerShipDestroyed(playerId); }); yield return new WaitUntil(() => shotComplete); } while (hit); // Не восстанавливаем ход игрока, если игра уже завершена (EndGame был вызван из колбэка выстрела) if (!isGameEnded) { // Если пауза активна — ждём её снятия, иначе StartPlayerTurn() перезатрёт InputMode.UI if (battlePauseShown) yield return new WaitUntil(() => !battlePauseShown); StartPlayerTurn(); } } public void MoveCameraTo(Transform target) { if (playerCamera != null && target != null) { StopAllCoroutines(); StartCoroutine(MoveCameraSmoothly(target)); } } private IEnumerator MoveCameraSmoothly(Transform target) { Transform cam = playerCamera.transform; // Отсоединяем камеру от пивота на время перемещения — // иначе поворот пивота кнопками во время полёта камеры сдвигает её конечную позицию cam.SetParent(null); Vector3 startPos = cam.position; Quaternion startRot = cam.rotation; Vector3 endPos = target.position; Quaternion endRot = target.rotation; float elapsed = 0f; while (elapsed < cameraMoveDuration) { float t = elapsed / cameraMoveDuration; cam.position = Vector3.Lerp(startPos, endPos, t); cam.rotation = Quaternion.Slerp(startRot, endRot, t); elapsed += Time.deltaTime; yield return null; } cam.position = endPos; cam.rotation = endRot; // Сбрасываем пивот и возвращаем камеру обратно как дочерний объект if (cameraPivot != null) { cameraPivot.rotation = Quaternion.identity; // Перемещаем пивот: во время расстановки кораблей — к полю игрока, // в остальных состояниях — обратно на дефолтную позицию bool isShipPlacement = (target == shipPlacementCameraPosition); if (isShipPlacement && shipPlacementPivotPoint != null) cameraPivot.position = shipPlacementPivotPoint.position; else cameraPivot.position = _defaultPivotPosition; // worldPositionStays: true — локальные координаты пересчитаются под новый пивот cam.SetParent(cameraPivot, true); // Фиксируем новый "дом" и синхронизируем квадрант курсора var camCtrl = cameraPivot.GetComponent(); if (camCtrl != null) { camCtrl.CaptureHomeNow(); camCtrl.TargetCursor?.SwitchButton(cameraPivot.rotation.eulerAngles); } } } /// /// Сбрасывает игровое состояние - очищает поля, удаляет курсор, сбрасывает переменные /// private void ResetGameState() { Debug.Log("Сброс состояния игры..."); // 1. Сбрасываем игровые поля if (playerField != null) { playerField.ClearPole(); // очистка своего поля (удаляем корабли и сбрасываем массивы) } if (enemyField != null) { enemyField.ClearPole(); // очистка вражеского поля } // 2. Удаляем курсор if (targetCursorInstance != null) { Destroy(targetCursorInstance); targetCursorInstance = null; } // 3. Сбрасываем переменные playerShips = 0; enemyShips = 0; SetPlayerReady(false); isPlayerTurn = false; SetInputMode(InputMode.UI); autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); Debug.Log("Сброс завершён"); } /// /// Подготовка к новой игре - сбрасывает состояние и переводит в состояние расстановки /// private void PrepareNewGame() { isGameEnded = false; ResetGameState(); // Состояние будет установлено в вызывающем методе (StartPlayerGame или StartBotGame) } /// /// Возврат в главное меню - сбрасывает состояние и показывает главное меню /// public void ReturnToMainMenu() { // Сбрасываем скидку Виндисты VindistaController.Instance?.SetDiscountMode(false, playerId); paidFroFriend = false; currentBotLevel = 0; ResetGameState(); if (playerId == 1) SoundManager.Instance?.SetPvPMode(false); idleVideoRequested = false; SetState(GameState.MainMenu); } private void HandleShipPlacementTimeout() { autoStartBattlePending = true; uiManager.HideAllOverlays(); if (playerShips != playerField.ShipCount) { playerField.ArrangeShip(); playerShips = playerField.ShipCount; } SetPlayerReady(true); TryStartBattleAfterPlacementTimeout(); } private void TryStartBattleAfterPlacementTimeout() { if (!autoStartBattlePending || currentState != GameState.ShipPlacement || !isReady) return; if (gameMode == GameMode.PlayerVsPlayer) { if (opponentManager == null || !opponentManager.isReady) return; autoStartBattlePending = false; opponentManager.autoStartBattlePending = false; StartBattle(); return; } autoStartBattlePending = false; StartBattle(); } }