using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using UnityEngine.InputSystem; 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; 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); btn.SetColor(Color.green); levelsPool.Add(btn); } } struct LevelEntry { public string name; public int world; public int stage; } 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 w)) continue; if (!int.TryParse(parts[1], out int s)) 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 = w, stage = s }; } } System.Collections.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(); } }