using System; using System.IO; using UnityEngine; using UnityEngine.SceneManagement; using RR.Game2D.Behaviour; using RR.Game2D.Entities; namespace RR.Game2D.Core { [DefaultExecutionOrder(-1000)] public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } [Header("Пауза")] public bool IsPaused { get; private set; } public event Action PauseStateChanged; [Header("Базовые настройки игры")] [SerializeField] private int maxLives = 3; [Header("Информация для игры")] public int World { get; private set; } = 1; public int Stage { get; private set; } = 1; public int StagesByWorld { get; private set; } = 5; public int Lives { get; set; } = 3; public int Coins { get; private set; } = 0; [Header("Игрок")] [SerializeField] private GameObject playerPrefab; [SerializeField] private string playerPrefabResourcePath = "Prefabs/Player"; // Путь в Resources для загрузки префаба игрока, если не назначен в инспекторе public GameObject PlayerObj { get; private set; } private bool _shouldSpawnPlayer; private void OnEnable() => SceneManager.sceneLoaded += OnSceneLoaded; private void OnDisable() => SceneManager.sceneLoaded -= OnSceneLoaded; public event Action PlayerSpawned; public event Action CoinsChanged; public event Action LivesChanged; //--------------------------- // Singleton //--------------------------- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Bootstrap() { if (Instance == null) { var go = new GameObject(nameof(GameManager)); Instance = go.AddComponent(); DontDestroyOnLoad(go); } } private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); if (SceneManager.GetActiveScene().name != "MainMenu") _shouldSpawnPlayer = true; } private void OnDestroy() { if (Instance == this) Instance = null; } //--------------------------- // //--------------------------- //private void Start() //{ // //Application.targetFrameRate = 60; // //LoadScene("MainMenu", false); // //NewGame(); // if (SceneManager.GetActiveScene().name != "MainMenu") // { // TrySpawnPlayerForActiveScene(); // } //} /// /// Поиск префаба игрока. /// Если не найдет префаб из инспектора, ищет в "Resources" /// /// Префаб игрока private GameObject ResolvePlayerPrefab() { if (playerPrefab != null) return playerPrefab; GameObject loaded = Resources.Load(playerPrefabResourcePath); if (loaded == null) { Debug.LogError( $"GameManager: playerPrefab is null. Resources.Load('{playerPrefabResourcePath}') вернул null. " + $"Ожидается файл: Assets/Resources/{playerPrefabResourcePath}.prefab (точное совпадение регистра)."); } return loaded; } /// /// Начало новой игры. /// Загружает первый уровень. /// Устанавливает жизни на 3, монеты на 0 /// public void NewGame() { Lives = maxLives; LivesChanged?.Invoke(Lives); Coins = 0; CoinsChanged?.Invoke(Coins); LoadLevel(1, 1); } // Можно заменить на другое поведение /// /// Окончание игры. /// Загружает сцену главного меню /// private void GameOver() // => LoadScene("MainMenu", false); { LoadScene("MainMenu", false); Lives = maxLives; LivesChanged?.Invoke(Lives); } // -- /// /// Загрузка уровня по данным. /// Загружает уровень по номерам мира и уровня /// /// Номер мира /// Номер уровня public void LoadLevel(int world, int stage) { this.World = world; this.Stage = stage; string sceneName = $"{world}-{stage}"; if (SceneInBuildSettingsExists(sceneName)) { LoadScene(sceneName); } else { //NewGame(); // Можно заменить на другое поведение print("WIN"); LoadScene("MainMenu", false); // -- } } /// /// Загрузка сцены по названию. /// Вызывает одноименный метод в SceneManager. /// Может загружать сцены с UI и без. /// В сценах без UI игрок не спавнится /// /// Строковое название сцены /// Использовать ли UI public void LoadScene(string sceneName, bool useUI = true) { _shouldSpawnPlayer = true; SceneManager.LoadScene(sceneName); if (useUI) { SceneManager.LoadScene("Level UI", LoadSceneMode.Additive); } else _shouldSpawnPlayer = false; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { string currentSceneName = SceneManager.GetActiveScene().name; if (!currentSceneName.Contains("-")) return; TrySpawnPlayerForActiveScene(); if (currentSceneName == $"{World}-{Stage}") return; string[] levelInfo = currentSceneName.Split("-"); World = Convert.ToInt32(levelInfo[0]); Stage = Convert.ToInt32(levelInfo[1]); } /// /// Метод для спавна игрока. /// Ищет первый спавнпоинт, спавнит игрока на его позиции. /// Если на карте есть объект игрока, удаляет его и спавнит свой /// private void TrySpawnPlayerForActiveScene() { if (!_shouldSpawnPlayer) return; PlayerSpawn spawn = FindFirstObjectByType(); Vector3 pos = spawn ? spawn.transform.position : new Vector3(0, 1.3f, 0); Quaternion rot = spawn ? spawn.transform.rotation : Quaternion.identity; PlayerObj = GameObject.FindGameObjectWithTag("Player"); if (PlayerObj != null) Destroy(PlayerObj); var prefab = ResolvePlayerPrefab(); if (prefab == null) return; PlayerObj = Instantiate(prefab, pos, rot); SceneManager.MoveGameObjectToScene(PlayerObj, SceneManager.GetActiveScene()); PlayerSpawned?.Invoke(PlayerObj); } // Новый метод: респавн игрока без перезагрузки сцены public void RespawnPlayer() { // отнимем жизнь (оставляем текущее поведение); монеты не сбрасываем Lives--; LivesChanged?.Invoke(Lives); if (Lives <= 0) { GameOver(); return; } // Определяем дефолтные позицию и направление из PlayerSpawn в сцене PlayerSpawn spawn = FindFirstObjectByType(); Vector3 defaultPos = spawn ? spawn.transform.position : new Vector3(0, 1.3f, 0); Vector2 defaultForward = spawn ? spawn.defaultForward : Vector2.right; // Если есть активный чекпоинт — берём из него; иначе дефолт Vector3 respawnPos = defaultPos; Vector2 respawnForward = defaultForward; if (CheckpointSystem.Instance != null) { respawnPos = CheckpointSystem.Instance.GetSpawnPosition(defaultPos); respawnForward = CheckpointSystem.Instance.GetSpawnForward(defaultForward); if (CheckpointSystem.Instance.Active != null) { respawnPos = CheckpointSystem.Instance.Active.GetSpawnPosition(); respawnForward = CheckpointSystem.Instance.Active.GetForward(); } } // Если потеряли ссылку — попробуем найти игрока в сцене if (PlayerObj == null) PlayerObj = GameObject.FindGameObjectWithTag("Player"); if (PlayerObj == null) { // Создадим нового игрока, если объект был уничтожен var prefab = ResolvePlayerPrefab(); if (prefab == null) return; PlayerObj = Instantiate(prefab, respawnPos, Quaternion.identity); SceneManager.MoveGameObjectToScene(PlayerObj, SceneManager.GetActiveScene()); PlayerSpawned?.Invoke(PlayerObj); } else { // Остановим движение и телепортируем var rb = PlayerObj.GetComponent(); if (rb != null) { rb.linearVelocity = Vector2.zero; rb.angularVelocity = 0f; } PlayerObj.transform.position = respawnPos; } // Повернём в нужную сторону (по X) if (PlayerObj != null) { if (respawnForward.x != 0) { PlayerObj.transform.localEulerAngles = respawnForward.x >= 0 ? Vector3.zero : new Vector3(0, 180f, 0); } // Восстановим здоровье var hp = PlayerObj.GetComponent(); if (hp != null) hp.ResetHealth(); } } /// /// Метод переход на следующий уровень. /// Может загружать уровни и миры в прямой зависимости от /// Количества уровней на мир(StagesByWorld) /// public void NextLevel() { if (Stage >= StagesByWorld) { World++; Stage = 1; } else Stage++; LoadLevel(World, Stage); } /// /// Метод сброса уровня. /// При сбросе также сбрасывает монеты и отнимает жизни /// public void ResetLevel() { Lives--; LivesChanged?.Invoke(Lives); Coins = 0; CoinsChanged?.Invoke(Coins); //print($"Lives: {lives}"); if (Lives > 0) LoadLevel(World, Stage); else GameOver(); } /// /// Метод добавления монеты /// public void AddCoin() { Coins++; // Можно заменить на другое поведение if (Coins >= 100) { Coins = 0; AddLife(); } // -- CoinsChanged?.Invoke(Coins); } /// /// Метод паузы игры. /// Ставит на паузу все SFX и саму игру /// /// Ставить ли на паузу public void SetPause(bool pause) { if (IsPaused == pause) return; IsPaused = pause; Time.timeScale = pause ? 0f : 1f; AudioListener.pause = pause; PauseStateChanged?.Invoke(IsPaused); } // Можно заменить на другое поведение /// /// Метод переключателя паузы /// public void TogglePause() => SetPause(!IsPaused); // -- // Можно заменить на другое поведение /// /// Метод для добавления жизни. /// private void AddLife() { Lives++; LivesChanged?.Invoke(Lives); } // -- /// /// Метод проверки сцены на существование в настройках билда /// /// Строковое название сцены /// private static bool SceneInBuildSettingsExists(string sceneName) { var count = SceneManager.sceneCountInBuildSettings; for (var i = 0; i < count; i++) { var path = SceneUtility.GetScenePathByBuildIndex(i); if (Path.GetFileNameWithoutExtension(path) == sceneName) return true; } return false; } } }