310 lines
10 KiB
C#
310 lines
10 KiB
C#
using System.Collections.Generic;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
public class RadarView : MonoBehaviour
|
||
{
|
||
[SerializeField] private RadarOrigin origin;
|
||
[SerializeField] private GameObject prefab;
|
||
[SerializeField] private float radarRadius = 100f;
|
||
[SerializeField] private bool showDebugGizmos = true;
|
||
|
||
[Header("Scan Settings")]
|
||
[SerializeField] private float scanWidth = 30f; // Ширина луча сканирования (градусы)
|
||
|
||
[Header("Fade Settings")]
|
||
[SerializeField] private float iconFadeOutSpeed = 1f; // Скорость затухания иконок
|
||
|
||
// Храним активные иконки
|
||
private List<RadarIcon> activeIcons = new List<RadarIcon>();
|
||
|
||
// Храним время последнего обнаружения для каждого объекта
|
||
private Dictionary<Transform, float> lastDetectionTime = new Dictionary<Transform, float>();
|
||
|
||
// Задержка между обнаружениями одного и того же объекта (чтобы не спавнить пачками)
|
||
[SerializeField] private float detectionCooldown = 2f;
|
||
|
||
private DangersModel dangersModel;
|
||
private RectTransform radarRectTransform;
|
||
|
||
[SerializeField] private List<Sprite> radarSlides;
|
||
[SerializeField] private float anglePerSecond = 180;
|
||
private float currentScanAngle = 0;
|
||
[SerializeField] private Image image;
|
||
|
||
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 Update()
|
||
{
|
||
if (origin == null || dangersModel == null) return;
|
||
|
||
// Обновляем угол сканирования
|
||
currentScanAngle = (currentScanAngle + anglePerSecond * Time.deltaTime) % 360;
|
||
|
||
// Обновляем спрайт сканирования
|
||
int slideIndex = (int)(currentScanAngle / (360 / radarSlides.Count));
|
||
image.sprite = radarSlides[slideIndex];
|
||
|
||
// Проверяем все живые объекты на обнаружение
|
||
CheckForDetections();
|
||
|
||
// Обновляем существующие иконки (уменьшаем таймеры жизни)
|
||
UpdateExistingIcons();
|
||
}
|
||
|
||
private void CheckForDetections()
|
||
{
|
||
// Проверяем корабли
|
||
foreach (var ship in dangersModel.AliveShips)
|
||
{
|
||
if (ship != null && ship.HealthComponent.Alive)
|
||
{
|
||
CheckAndCreateIcon(ship.transform);
|
||
}
|
||
}
|
||
|
||
// Проверяем снаряды
|
||
foreach (var projectile in dangersModel.Projectiles)
|
||
{
|
||
if (projectile != null)
|
||
{
|
||
CheckAndCreateIcon(projectile.transform);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void CheckAndCreateIcon(Transform target)
|
||
{
|
||
if (target == null) return;
|
||
|
||
// Проверяем, находится ли цель в зоне сканирования
|
||
if (IsInScanZone(target))
|
||
{
|
||
// Проверяем, не создавали ли мы недавно иконку для этого объекта
|
||
if (!lastDetectionTime.ContainsKey(target) ||
|
||
Time.time - lastDetectionTime[target] >= detectionCooldown)
|
||
{
|
||
// Создаем новую иконку
|
||
CreateIcon(target);
|
||
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 void CreateIcon(Transform target)
|
||
{
|
||
if (prefab == null || target == null) return;
|
||
|
||
GameObject iconObject = Instantiate(prefab, 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;
|
||
|
||
// Определяем цвет в зависимости от типа цели
|
||
Color iconColor = Color.white;
|
||
if (target.GetComponent<AbstractProjectile>() != null)
|
||
{
|
||
iconColor = Color.red;
|
||
}
|
||
else if (target.GetComponent<EnemyShip>() != null)
|
||
{
|
||
iconColor = Color.yellow;
|
||
}
|
||
|
||
icon.Initialize(fixedPosition, fixedRotation, iconFadeOutSpeed, iconColor);
|
||
activeIcons.Add(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()
|
||
{
|
||
if (dangersModel != null)
|
||
{
|
||
dangersModel.OnUpdate -= OnDangersModelUpdated;
|
||
}
|
||
|
||
// Уничтожаем все иконки
|
||
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);
|
||
|
||
// Визуализируем зону сканирования
|
||
if (Application.isPlaying)
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Компонент для отдельной иконки на радаре
|
||
public class RadarIcon : MonoBehaviour
|
||
{
|
||
private RectTransform rectTransform;
|
||
private Image image;
|
||
private float fadeOutSpeed = 1f;
|
||
private float currentAlpha = 1f;
|
||
private bool isDestroyed = false;
|
||
|
||
public bool IsDestroyed => isDestroyed;
|
||
|
||
public void Initialize(Vector2 position, float rotation, float fadeSpeed, Color color)
|
||
{
|
||
rectTransform = GetComponent<RectTransform>();
|
||
if (rectTransform == null)
|
||
{
|
||
rectTransform = gameObject.AddComponent<RectTransform>();
|
||
}
|
||
|
||
image = GetComponent<Image>();
|
||
if (image == null)
|
||
{
|
||
image = gameObject.AddComponent<Image>();
|
||
}
|
||
|
||
// Устанавливаем позицию и поворот
|
||
rectTransform.anchoredPosition = position;
|
||
rectTransform.localRotation = Quaternion.Euler(0, 0, rotation);
|
||
|
||
// Настраиваем цвет
|
||
fadeOutSpeed = fadeSpeed;
|
||
color.a = 1f;
|
||
image.color = color;
|
||
|
||
// Настраиваем размер иконки
|
||
//rectTransform.sizeDelta = new Vector2(20, 20);
|
||
|
||
currentAlpha = 1f;
|
||
isDestroyed = false;
|
||
}
|
||
|
||
public void UpdateLife()
|
||
{
|
||
if (isDestroyed) return;
|
||
|
||
// Уменьшаем прозрачность
|
||
currentAlpha -= fadeOutSpeed * Time.deltaTime;
|
||
|
||
if (currentAlpha <= 0)
|
||
{
|
||
// Иконка полностью исчезла - уничтожаем
|
||
isDestroyed = true;
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
|
||
// Обновляем цвет
|
||
Color color = image.color;
|
||
color.a = currentAlpha;
|
||
image.color = color;
|
||
|
||
//// Плавно уменьшаем размер
|
||
//float scale = Mathf.Lerp(1f, 0f, 1f - currentAlpha);
|
||
//rectTransform.localScale = Vector3.one * scale;
|
||
}
|
||
|
||
public void SetColor(Color color)
|
||
{
|
||
if (image != null)
|
||
{
|
||
color.a = currentAlpha;
|
||
image.color = color;
|
||
}
|
||
}
|
||
} |