450 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Sprite> radarSlides;
[SerializeField] private float anglePerSecond = 180;
[SerializeField] private Image image;
private DangersModel dangersModel;
private RectTransform radarRectTransform;
private float currentScanAngle = 0;
// Храним активные иконки
private List<RadarIcon> activeIcons = new List<RadarIcon>();
// Храним таймеры последнего обнаружения
private Dictionary<Transform, float> lastDetectionTime = new Dictionary<Transform, float>();
// Отслеживаем уничтоженные корабли для мигания
private Dictionary<EnemyShip, CancellationTokenSource> blinkingShips = new Dictionary<EnemyShip, CancellationTokenSource>();
private void Awake()
{
EnemyShip.ShipDefeated += OnShipDefeated;
}
public void Initialize(RadarOrigin radarOrigin, DangersModel model)
{
origin = radarOrigin;
dangersModel = model;
radarRectTransform = GetComponent<RectTransform>();
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<RadarIcon>();
if (icon == null)
{
icon = iconObject.AddComponent<RadarIcon>();
}
// Получаем позицию для радара (фиксируем в момент обнаружения)
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<RectTransform>();
if (rectTransform == null)
{
rectTransform = gameObject.AddComponent<RectTransform>();
}
// Используем CanvasGroup для управления прозрачностью всего объекта
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
{
canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
// Устанавливаем позицию и поворот
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);
}
}
}