using System.Collections.Generic; using UnityEngine; using Zenject; namespace DanroJump.Gameplay { /// /// Выбирает активный этап сложности по максимальной высоте игрока или Y-координате генерации. /// public sealed class GameplayDifficultyProgression : MonoBehaviour, IGameplayDifficultyProgression { [SerializeField] private JumpPlayerController player; [SerializeField] private bool progressionEnabled = true; [SerializeField] private List stages = new(); /// /// Показывает, есть ли хотя бы один настроенный этап сложности. /// public bool HasStages => progressionEnabled && stages != null && stages.Exists(stage => stage != null); /// /// Начальная Y-координата генерации по первому этапу. /// public float InitialSpawnY => GetInitialSpawnY(); /// /// Прогресс игрока внутри текущего этапа по максимальной высоте забега. /// public float CurrentProgress => CalculateProgress(player != null ? player.HighestY : 0f); /// /// Имя текущего этапа игрока для HUD и editor overlay. /// public string CurrentStageName => GetStage(player != null ? player.HighestY : 0f)?.StageName ?? string.Empty; /// /// Получает игрока из SceneContext, чтобы UI и диагностические окна видели текущий этап забега. /// [Inject] public void Construct(JumpPlayerController injectedPlayer) { player = injectedPlayer; } /// /// Выбирает prefab платформы для указанной высоты по настройкам активного этапа. /// public GameObject GetPlatformPrefab(float worldY, System.Random random) { var stage = GetStage(worldY); return stage != null ? stage.GetPlatformPrefab(random) : null; } /// /// Возвращает расстояние между платформами для указанной высоты. /// public float GetVerticalSpacing(float worldY) { var stage = GetStage(worldY); if (stage == null) { return 0f; } return stage.GetVerticalSpacing(worldY); } /// /// Возвращает распределение платформ по ширине уровня для указанной высоты. /// public float GetHorizontalDistribution(float worldY) { var stage = GetStage(worldY); return stage != null ? stage.GetHorizontalDistribution() : 0f; } /// /// Создает профиль контента активного этапа: collectibles, equipment и enemies. /// public IPlatformContentSource CreateContentSource(float worldY) { var stage = GetStage(worldY); if (stage == null) { return null; } return stage.CreateContentSource(worldY); } /// /// Пробует выбрать процедурного врага для указанной высоты из активного ScriptableObject-этапа. /// public bool TryGetProceduralEnemyPrefab(float worldY, System.Random random, out GameObject prefab) { var stage = GetStage(worldY); if (stage == null) { prefab = null; return false; } return stage.TryGetProceduralEnemyPrefab(worldY, random, out prefab); } /// /// Возвращает имя этапа для диагностики и overlay-окна. /// public string GetStageName(float worldY) { return GetStage(worldY)?.StageName ?? string.Empty; } /// /// Возвращает прогресс внутри текущего этапа в диапазоне 0..1. /// public float GetStageProgress(float worldY) { var stage = GetStage(worldY); return stage != null ? stage.CalculateProgress(worldY) : 0f; } private float CalculateProgress(float worldY) { var stage = GetStage(worldY); return stage != null ? stage.CalculateProgress(worldY) : 0f; } /// /// Возвращает стартовую высоту генерации из первого доступного этапа. /// private float GetInitialSpawnY() { var firstStage = GetFirstStage(); return firstStage != null ? firstStage.StartY : 0f; } /// /// Находит первый этап по минимальной стартовой высоте. /// private GameplayDifficultyStage GetFirstStage() { if (stages == null || stages.Count == 0) { return null; } GameplayDifficultyStage firstStage = null; foreach (var stage in stages) { if (stage == null) { continue; } if (firstStage == null || stage.StartY < firstStage.StartY) { firstStage = stage; } } return firstStage; } /// /// Находит последний этап, стартовая высота которого уже пройдена. /// private GameplayDifficultyStage GetStage(float worldY) { if (!progressionEnabled || stages == null || stages.Count == 0) { return null; } GameplayDifficultyStage currentStage = null; foreach (var stage in stages) { if (stage == null || worldY < stage.StartY) { continue; } if (currentStage == null || stage.StartY >= currentStage.StartY) { currentStage = stage; } } // Последний достигнутый этап намеренно продолжается бесконечно. // verticalLength управляет только прогрессом кривых, а не выключением этапа. return currentStage; } } /// /// Интерфейс чтения прогрессии сложности для gameplay-систем и диагностических окон. /// public interface IGameplayDifficultyProgression { float CurrentProgress { get; } string CurrentStageName { get; } bool HasStages { get; } float InitialSpawnY { get; } GameObject GetPlatformPrefab(float worldY, System.Random random); float GetVerticalSpacing(float worldY); float GetHorizontalDistribution(float worldY); IPlatformContentSource CreateContentSource(float worldY); bool TryGetProceduralEnemyPrefab(float worldY, System.Random random, out GameObject prefab); string GetStageName(float worldY); float GetStageProgress(float worldY); } }