using System.Linq; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; namespace RR.Common.UI { public class LevelSelectModal : MonoBehaviour { [Header("Управление")] [SerializeField] private InputActionReference close; [Header("UI")] [SerializeField] private CanvasGroup canvasGroup; [SerializeField] private Transform content; // Grid parent [SerializeField] private LevelButton levelButtonPrefab; [SerializeField] private Button closeButton; [SerializeField] private float fadeDuration = 0.15f; private readonly List _levelsPool = new(); void Awake() { if (closeButton) closeButton.onClick.AddListener(Close); gameObject.SetActive(false); } // ==== ПУБЛИЧНЫЙ API ==== /// /// Метод для открытия модального окна /// public void Open() { BuildIfNeeded(); gameObject.SetActive(true); StopAllCoroutines(); StartCoroutine(Fade(0f, 1f, fadeDuration)); } /// /// Метод для закрытия модального окна /// public void Close() { StopAllCoroutines(); StartCoroutine(Fade(1f, 0f, fadeDuration, () => gameObject.SetActive(false))); } // ==== ВНУТРЕННЕЕ ==== /// /// Метод для сборки кнопок уровней /// void BuildIfNeeded() { // Очистим/пересоберём (можно кэшировать, но пересборка надёжнее при изменениях Build Settings) foreach (var b in _levelsPool) if (b) Destroy(b.gameObject); _levelsPool.Clear(); var levels = EnumerateLevelScenesInBuild() .OrderBy(e => e.world) .ThenBy(e => e.stage) .ToList(); foreach (var lv in levels) { var btn = Instantiate(levelButtonPrefab, content); btn.Setup(lv.name, lv.world, lv.stage); if (lv.world == 1) btn.SetColor(Color.green); else if (lv.world == 2) btn.SetColor(Color.blue); _levelsPool.Add(btn); } } struct LevelEntry { public string name; public int world; public int stage; } /// /// Метод для перебора уровней в игре /// /// LevelEntry - текущий мир при переборе IEnumerable EnumerateLevelScenesInBuild() { int n = SceneManager.sceneCountInBuildSettings; for (int i = 0; i < n; i++) { string path = SceneUtility.GetScenePathByBuildIndex(i); string name = System.IO.Path.GetFileNameWithoutExtension(path); // фильтр "только уровни" по формату имени W-S: var parts = name.Split('-'); if (parts.Length != 2) continue; if (!int.TryParse(parts[0], out int world)) continue; if (!int.TryParse(parts[1], out int stage)) continue; // Исключим служебные сцены, даже если вдруг совпадут по формату (на всякий случай) if (name.Equals("Level UI", System.StringComparison.OrdinalIgnoreCase)) continue; if (name.Equals("MainMenu", System.StringComparison.OrdinalIgnoreCase)) continue; yield return new LevelEntry { name = name, world = world, stage = stage }; } } /// /// Метод для плавного перехода (затухания) /// /// Начальная прозрачность /// Конечная прозрачнеость /// Время перехода /// Действие при выполнении перехода IEnumerator Fade(float from, float to, float time, System.Action onDone = null) { if (!canvasGroup) { onDone?.Invoke(); yield break; } float t = 0f; canvasGroup.alpha = from; canvasGroup.blocksRaycasts = true; while (t < time) { t += Time.unscaledDeltaTime; canvasGroup.alpha = Mathf.Lerp(from, to, t / time); yield return null; } canvasGroup.alpha = to; canvasGroup.blocksRaycasts = to > 0.99f; onDone?.Invoke(); } void Update() { // ESC закрыть if (gameObject.activeSelf && close.action.WasPressedThisFrame()) Close(); } } }