using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Cysharp.Threading.Tasks; using System.Threading; public class RadarView : MonoBehaviour { [SerializeField] private RadarOrigin origin; [SerializeField] private float radarRadius = 100f; [SerializeField] private bool showDebugGizmos = true; [Header("Icon Prefabs")] [SerializeField] private GameObject enemyShipPrefab; [SerializeField] private GameObject projectilePrefab; [SerializeField] private GameObject destroyedShipPrefab; [Header("Scan Settings")] [SerializeField] private float scanWidth = 30f; [Header("Fade Settings")] [SerializeField] private float iconFadeOutSpeed = 1f; [SerializeField] private float detectionCooldown = 2f; [Header("Destroyed Ship Blink Settings")] [SerializeField] private int blinkCount = 5; [SerializeField] private float blinkDuration = 0.2f; [Header("Scan Animation")] [SerializeField] private List radarSlides; [SerializeField] private float anglePerSecond = 180; [SerializeField] private Image image; private DangersModel dangersModel; private RectTransform radarRectTransform; private float currentScanAngle = 0; // Храним активные иконки private List activeIcons = new List(); // Храним таймеры последнего обнаружения private Dictionary lastDetectionTime = new Dictionary(); // Отслеживаем уничтоженные корабли для мигания private Dictionary blinkingShips = new Dictionary(); private void Awake() { EnemyShip.ShipDefeated += OnShipDefeated; } public void Initialize(RadarOrigin radarOrigin, DangersModel model) { origin = radarOrigin; dangersModel = model; radarRectTransform = GetComponent(); if (radarRectTransform == null) { Debug.LogWarning("RadarView должен быть на объекте с RectTransform!"); } if (dangersModel != null) { dangersModel.OnUpdate += OnDangersModelUpdated; } } private void OnDangersModelUpdated(DangersModel model) { // Обновляем списки } private void OnShipDefeated(EnemyShip ship) { if (ship == null) return; // Запускаем мигание для уничтоженного корабля BlinkDestroyedShipIcon(ship).Forget(); } private async UniTaskVoid BlinkDestroyedShipIcon(EnemyShip ship) { // Создаем иконку для уничтоженного корабля в текущей позиции if (ship != null && ship.transform != null) { // Отменяем предыдущее мигание если оно было if (blinkingShips.TryGetValue(ship, out CancellationTokenSource existingCts)) { existingCts.Cancel(); existingCts.Dispose(); blinkingShips.Remove(ship); } // Создаем иконку для уничтоженного корабля RadarIcon icon = CreateIcon(ship.transform, IconType.DestroyedShip); if (icon != null) { // Создаем новый токен отмены var blinkCts = new CancellationTokenSource(); blinkingShips[ship] = blinkCts; try { // Выполняем мигание await PerformBlink(icon, blinkCts.Token); // После мигания начинаем затухание if (icon != null && !icon.IsDestroyed) { icon.StartFadeOut(); } } catch (System.OperationCanceledException) { // Мигание отменено - все равно начинаем затухание if (icon != null && !icon.IsDestroyed) { icon.StartFadeOut(); } } finally { if (blinkingShips.ContainsKey(ship)) blinkingShips.Remove(ship); blinkCts.Dispose(); } } } } private async UniTask PerformBlink(RadarIcon icon, CancellationToken cancellationToken) { for (int i = 0; i < blinkCount; i++) { if (cancellationToken.IsCancellationRequested) break; // Исчезаем icon.SetAlpha(0f); await UniTask.Delay((int)(blinkDuration * 1000), cancellationToken: cancellationToken); if (cancellationToken.IsCancellationRequested) break; // Появляемся icon.SetAlpha(1f); await UniTask.Delay((int)(blinkDuration * 1000), cancellationToken: cancellationToken); } } private void Update() { if (origin == null || dangersModel == null) return; // Обновляем угол сканирования currentScanAngle = (currentScanAngle + anglePerSecond * Time.deltaTime) % 360; // Обновляем спрайт сканирования if (radarSlides != null && radarSlides.Count > 0 && image != null) { int slideIndex = (int)(currentScanAngle / (360f / radarSlides.Count)); slideIndex = Mathf.Clamp(slideIndex, 0, radarSlides.Count - 1); image.sprite = radarSlides[slideIndex]; } // Проверяем объекты на обнаружение CheckForDetections(); // Обновляем существующие иконки (затухание) UpdateExistingIcons(); } private void CheckForDetections() { // Проверяем вражеские корабли foreach (var ship in dangersModel.AliveShips) { if (ship != null && ship.HealthComponent != null && ship.HealthComponent.Alive) { CheckAndCreateIcon(ship.transform, IconType.EnemyShip); } } // Проверяем снаряды foreach (var projectile in dangersModel.Projectiles) { if (projectile != null) { CheckAndCreateIcon(projectile.transform, IconType.Projectile); } } } private void CheckAndCreateIcon(Transform target, IconType iconType) { if (target == null) return; // Проверяем, находится ли цель в зоне сканирования прямо сейчас if (IsInScanZone(target)) { // Проверяем кулдаун для кораблей (для снарядов кулдауна нет) bool useCooldown = iconType != IconType.Projectile; if (!useCooldown || !lastDetectionTime.ContainsKey(target) || Time.time - lastDetectionTime[target] >= detectionCooldown) { // Создаем иконку CreateIcon(target, iconType); if (useCooldown) { lastDetectionTime[target] = Time.time; } } } } private bool IsInScanZone(Transform target) { if (origin == null || target == null) return false; // Получаем угол до цели Vector3 directionToTarget = target.position - origin.transform.position; float angleToTarget = Vector3.SignedAngle(origin.transform.forward, directionToTarget, Vector3.up); // Проверяем дистанцию float distance = directionToTarget.magnitude; if (distance > origin.Radius) return false; // Проверяем угол float halfWidth = scanWidth / 2; float normalizedScanAngle = NormalizeAngle(currentScanAngle); float normalizedTargetAngle = NormalizeAngle(angleToTarget); float angleDifference = Mathf.DeltaAngle(normalizedScanAngle, normalizedTargetAngle); return Mathf.Abs(angleDifference) <= halfWidth; } private RadarIcon CreateIcon(Transform target, IconType iconType) { // Выбираем префаб в зависимости от типа иконки GameObject selectedPrefab = null; switch (iconType) { case IconType.EnemyShip: selectedPrefab = enemyShipPrefab; break; case IconType.Projectile: selectedPrefab = projectilePrefab; break; case IconType.DestroyedShip: selectedPrefab = destroyedShipPrefab; break; } if (selectedPrefab == null || target == null) return null; GameObject iconObject = Instantiate(selectedPrefab, transform); RadarIcon icon = iconObject.GetComponent(); if (icon == null) { icon = iconObject.AddComponent(); } // Получаем позицию для радара (фиксируем в момент обнаружения) Vector3 radarData = origin.TransformOnRadar(target); Vector2 fixedPosition = new Vector2(radarData.x, radarData.y) * radarRadius; float fixedRotation = -radarData.z; // Для уничтоженных кораблей не начинаем затухание сразу bool startFadeImmediately = (iconType != IconType.DestroyedShip); icon.Initialize(fixedPosition, fixedRotation, iconFadeOutSpeed, startFadeImmediately); activeIcons.Add(icon); return icon; } private void UpdateExistingIcons() { // Обновляем все иконки и удаляем уничтоженные for (int i = activeIcons.Count - 1; i >= 0; i--) { if (activeIcons[i] == null || activeIcons[i].IsDestroyed) { activeIcons.RemoveAt(i); } else { activeIcons[i].UpdateLife(); } } } private float NormalizeAngle(float angle) { angle = angle % 360; if (angle > 180) angle -= 360; if (angle < -180) angle += 360; return angle; } private void OnDestroy() { EnemyShip.ShipDefeated -= OnShipDefeated; if (dangersModel != null) { dangersModel.OnUpdate -= OnDangersModelUpdated; } // Отменяем все мигания foreach (var cts in blinkingShips.Values) { cts.Cancel(); cts.Dispose(); } blinkingShips.Clear(); // Уничтожаем все иконки foreach (var icon in activeIcons) { if (icon != null) Destroy(icon.gameObject); } activeIcons.Clear(); lastDetectionTime.Clear(); } private void OnDrawGizmos() { if (!showDebugGizmos || origin == null) return; Gizmos.color = Color.white; Gizmos.DrawWireSphere(origin.transform.position, origin.Radius); // Визуализируем зону сканирования Vector3 center = origin.transform.position; Vector3 forward = origin.transform.forward; float halfWidth = scanWidth / 2; float minAngle = currentScanAngle - halfWidth; float maxAngle = currentScanAngle + halfWidth; Vector3 leftBoundary = Quaternion.Euler(0, minAngle, 0) * forward; Vector3 rightBoundary = Quaternion.Euler(0, maxAngle, 0) * forward; Gizmos.color = new Color(1, 1, 0, 0.5f); Gizmos.DrawRay(center, leftBoundary * origin.Radius); Gizmos.DrawRay(center, rightBoundary * origin.Radius); } private enum IconType { EnemyShip, Projectile, DestroyedShip } } // Обновленный компонент для иконки радара public class RadarIcon : MonoBehaviour { private RectTransform rectTransform; private CanvasGroup canvasGroup; private float fadeOutSpeed = 1f; private float currentAlpha = 1f; private bool isDestroyed = false; private bool isFading = false; public bool IsDestroyed => isDestroyed; public void Initialize(Vector2 position, float rotation, float fadeSpeed, bool startFadeImmediately = true) { rectTransform = GetComponent(); if (rectTransform == null) { rectTransform = gameObject.AddComponent(); } // Используем CanvasGroup для управления прозрачностью всего объекта canvasGroup = GetComponent(); if (canvasGroup == null) { canvasGroup = gameObject.AddComponent(); } // Устанавливаем позицию и поворот rectTransform.anchoredPosition = position; rectTransform.localRotation = Quaternion.Euler(0, 0, rotation); // Настраиваем затухание fadeOutSpeed = fadeSpeed; currentAlpha = 1f; canvasGroup.alpha = 1f; // Начинаем затухание только если нужно isFading = startFadeImmediately; isDestroyed = false; } public void UpdateLife() { if (isDestroyed) return; // Только если иконка сейчас затухает if (isFading) { // Уменьшаем прозрачность currentAlpha -= fadeOutSpeed * Time.deltaTime; if (currentAlpha <= 0) { DestroyIcon(); return; } // Обновляем прозрачность SetAlpha(currentAlpha); } } public void StartFadeOut() { if (!isDestroyed && !isFading) { isFading = true; } } public void SetAlpha(float alpha) { if (canvasGroup != null && !isDestroyed) { currentAlpha = Mathf.Clamp01(alpha); canvasGroup.alpha = currentAlpha; } } public void DestroyIcon() { if (!isDestroyed) { isDestroyed = true; Destroy(gameObject); } } }