Загрузка данных
using UnityEngine;
public class SoftVehicleCamera : MonoBehaviour
{
[Header("Ссылки")]
[SerializeField] private Transform target;
[SerializeField] private Rigidbody vehicleRigidbody;
[SerializeField] private Transform gun;
[Header("Дистанция и Позиция")]
[SerializeField] private float distance = 5.0f;
[SerializeField] private float height = 1.5f;
[Header("Мягкость и Сглаживание")]
[SerializeField] private float rotationSmoothTime = 0.3f;
[SerializeField] private float angleThreshold = 15.0f;
[Header("Вращение мыши")]
[SerializeField] private float mouseSensitivity = 3.0f;
[SerializeField] private float minPitch = -30f;
[SerializeField] private float maxPitch = 60f;
[Header("Смещение прицела")]
[SerializeField] private float aimOffsetSensitivity = 3.0f;
[SerializeField] private float maxAimOffsetDegrees = 10f;
private float horizontalAngle = 0f;
private float verticalAngle = 15f;
private float currentDistance = 5f;
private float smoothHorizontalVelocity;
private Vector3 smoothPositionVelocity;
private bool followGunMode = false;
private Vector2 lockedTargetOffsetAngles = Vector2.zero;
private float baseHorizontalAngle = 0f;
private float baseVerticalAngle = 0f;
void Start()
{
if (target == null) return;
Vector3 direction = (transform.position - target.position).normalized;
horizontalAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;
verticalAngle = Mathf.Asin(direction.y) * Mathf.Rad2Deg;
verticalAngle = Mathf.Clamp(verticalAngle, minPitch, maxPitch);
currentDistance = Vector3.Distance(transform.position, target.position);
}
void LateUpdate()
{
if (!target) return;
if (followGunMode && gun != null)
{
HandleFollowGunMode();
// Сохраняем текущие углы камеры
UpdateCameraAnglesFromPosition();
}
else
{
HandleManualMode();
}
}
void HandleManualMode()
{
// Ввод с мыши
if (Input.GetMouseButton(0))
{
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * 10f;
horizontalAngle += mouseX;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * 10f;
verticalAngle = Mathf.Clamp(verticalAngle - mouseY, minPitch, maxPitch);
}
// УБРАНО: Автоматическое следование за машиной при движении
// Камера больше не поворачивается автоматически
// Позиция камеры
Quaternion rotation = Quaternion.Euler(verticalAngle, horizontalAngle, 0f);
Vector3 direction = rotation * Vector3.back;
Vector3 desiredPosition = target.position + direction * currentDistance;
desiredPosition.y = target.position.y + height + Mathf.Sin(verticalAngle * Mathf.Deg2Rad) * currentDistance * 0.3f;
transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref smoothPositionVelocity, 0.08f);
transform.LookAt(target.position + Vector3.up * 0.5f);
}
void HandleFollowGunMode()
{
Vector3 gunForward = gun.forward;
Vector3 gunPosition = gun.position;
if (PantsirRadarSystem.IsAnyTargetLocked && PantsirRadarSystem.LockedTargetPosition.HasValue)
{
Vector3 lockedTargetPos = PantsirRadarSystem.LockedTargetPosition.Value;
Vector3 directionToTarget = (lockedTargetPos - gunPosition).normalized;
baseHorizontalAngle = Mathf.Atan2(directionToTarget.x, directionToTarget.z) * Mathf.Rad2Deg;
baseVerticalAngle = Mathf.Asin(directionToTarget.y) * Mathf.Rad2Deg;
baseVerticalAngle = Mathf.Clamp(baseVerticalAngle, minPitch, maxPitch);
if (Input.GetMouseButton(0))
{
float mouseX = Input.GetAxis("Mouse X") * aimOffsetSensitivity;
float mouseY = Input.GetAxis("Mouse Y") * aimOffsetSensitivity;
lockedTargetOffsetAngles += new Vector2(mouseX, mouseY);
lockedTargetOffsetAngles = Vector2.ClampMagnitude(lockedTargetOffsetAngles, maxAimOffsetDegrees);
}
float finalHorizontalAngle = baseHorizontalAngle + lockedTargetOffsetAngles.x;
float finalVerticalAngle = Mathf.Clamp(
baseVerticalAngle + lockedTargetOffsetAngles.y,
minPitch,
maxPitch
);
PantsirRadarSystem.AimOffset = lockedTargetOffsetAngles;
Quaternion gunRotation = Quaternion.Euler(finalVerticalAngle, finalHorizontalAngle, 0f);
Vector3 cameraDirection = gunRotation * Vector3.back;
Vector3 desiredPosition = gunPosition + cameraDirection * distance;
desiredPosition.y = gunPosition.y + height + Mathf.Sin(finalVerticalAngle * Mathf.Deg2Rad) * distance * 0.3f;
transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref smoothPositionVelocity, 0.08f);
Vector3 aimPoint = lockedTargetPos;
Quaternion offsetRotation = Quaternion.Euler(
lockedTargetOffsetAngles.y,
lockedTargetOffsetAngles.x,
0f
);
Vector3 offsetDirection = offsetRotation * directionToTarget;
aimPoint = gunPosition + offsetDirection * Vector3.Distance(gunPosition, lockedTargetPos);
transform.LookAt(aimPoint);
PantsirRadarSystem.AimPoint = aimPoint;
}
else
{
if (Input.GetMouseButton(0))
{
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * 10f;
horizontalAngle += mouseX;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * 10f;
verticalAngle = Mathf.Clamp(verticalAngle - mouseY, minPitch, maxPitch);
}
Vector3 desiredPosition = gunPosition - gunForward * distance + Vector3.up * height;
transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref smoothPositionVelocity, 0.08f);
transform.LookAt(gunPosition + gunForward * 1000f);
}
}
// Метод для сохранения текущих углов камеры
private void UpdateCameraAnglesFromPosition()
{
Vector3 directionFromTarget = (transform.position - target.position).normalized;
horizontalAngle = Mathf.Atan2(directionFromTarget.x, directionFromTarget.z) * Mathf.Rad2Deg;
verticalAngle = Mathf.Asin(directionFromTarget.y) * Mathf.Rad2Deg;
verticalAngle = Mathf.Clamp(verticalAngle, minPitch, maxPitch);
currentDistance = Vector3.Distance(transform.position, target.position);
}
public void SetFollowMode(bool enable)
{
followGunMode = enable;
lockedTargetOffsetAngles = Vector2.zero;
PantsirRadarSystem.AimOffset = Vector2.zero;
}
public void RotateToTarget(Vector3 targetPosition)
{
lockedTargetOffsetAngles = Vector2.zero;
if (gun != null)
{
Vector3 directionToTarget = (targetPosition - gun.position).normalized;
baseHorizontalAngle = Mathf.Atan2(directionToTarget.x, directionToTarget.z) * Mathf.Rad2Deg;
baseVerticalAngle = Mathf.Asin(directionToTarget.y) * Mathf.Rad2Deg;
baseVerticalAngle = Mathf.Clamp(baseVerticalAngle, minPitch, maxPitch);
Vector3 gunPosition = gun.position;
Quaternion rotation = Quaternion.Euler(baseVerticalAngle, baseHorizontalAngle, 0f);
Vector3 cameraDirection = rotation * Vector3.back;
transform.position = gunPosition + cameraDirection * distance;
transform.position += Vector3.up * height;
transform.LookAt(targetPosition);
}
else
{
transform.LookAt(targetPosition);
}
PantsirRadarSystem.AimOffset = Vector2.zero;
}
public void ResetAimOffset()
{
lockedTargetOffsetAngles = Vector2.zero;
PantsirRadarSystem.AimOffset = Vector2.zero;
}
public Vector3 GetAimPoint()
{
Ray ray = new Ray(transform.position, transform.forward);
float maxDistance = 10000f;
RaycastHit hit;
if (Physics.Raycast(ray, out hit, maxDistance))
{
return hit.point;
}
return transform.position + transform.forward * maxDistance;
}
public bool IsInFollowMode()
{
return followGunMode;
}
}
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class PantsirRadarSystem : MonoBehaviour
{
[Header("Ссылки на объекты")]
public Transform turret;
public Transform searchAntenna;
public Camera worldSpaceCamera;
public Transform gun;
[Header("Настройки РЛС")]
public float radarRange = 15000f;
public float antennaRotationSpeed = 90f;
public float scanHalfWidth = 5f;
public float targetTimeout = 4f;
[Header("Настройки Наведения")]
public float turretMaxSpeed = 45f;
public float gunMaxSpeed = 30f;
[Header("Ограничения орудия")]
public float minElevationAngle = 0f;
public float maxElevationAngle = 85f;
[Header("Интерфейс (UI)")]
public RectTransform radarBlipPrefab;
public RectTransform radarCenterPoint;
public float radarUIRadius = 150f;
public RectTransform lockFrameUI;
public GameObject lockMarkerPrefab;
public TextMeshProUGUI distanceText;
public TextMeshProUGUI closureSpeedText;
// Статическое свойство для камеры
public static bool IsAnyTargetLocked { get; private set; }
public static Vector3? LockedTargetPosition { get; private set; }
public static Vector3 AimPoint { get; set; }
public static Vector2 AimOffset { get; set; }
// Логика и списки
private float currentAntennaAngle = 0f;
private List<RadarTargetData> detectedTargets = new List<RadarTargetData>();
private int lockedTargetIndex = -1;
private RadarTargetData lockedTarget = null;
private GameObject currentLockMarkerInstance;
private Rigidbody myRigidbody;
// Для управления камерой
private SoftVehicleCamera cameraScript;
private bool isFirstFrameOfLock = false;
// Список заблокированных целей
private List<Transform> blockedTargets = new List<Transform>();
public float targetBlockTime = 10f;
public class RadarTargetData
{
public Transform targetTransform;
public Rigidbody targetRigidbody;
public RectTransform blipUI;
public float lastDetectedTime;
public float azimuth;
public float distance;
}
void Start()
{
myRigidbody = GetComponentInParent<Rigidbody>();
cameraScript = FindObjectOfType<SoftVehicleCamera>();
AimOffset = Vector2.zero;
IsAnyTargetLocked = false;
LockedTargetPosition = null;
AimPoint = Vector3.zero;
}
void FixedUpdate()
{
RotateSearchAntenna();
ScanForAirEnemies();
UpdateRadarUI();
HandleTurretTracking();
UpdateLockTexts();
UpdateLockedTargetPosition();
}
void UpdateLockedTargetPosition()
{
if (lockedTarget != null && lockedTarget.targetTransform != null)
{
LockedTargetPosition = lockedTarget.targetTransform.position;
IsAnyTargetLocked = true;
}
else
{
LockedTargetPosition = null;
IsAnyTargetLocked = false;
}
}
void RotateSearchAntenna()
{
currentAntennaAngle += antennaRotationSpeed * Time.fixedDeltaTime;
currentAntennaAngle %= 360f;
searchAntenna.rotation = Quaternion.Euler(0f, currentAntennaAngle, 0f);
}
void ScanForAirEnemies()
{
GameObject[] enemies = GameObject.FindGameObjectsWithTag("AirEnemy");
Vector3 antennaForward = searchAntenna.forward;
foreach (GameObject enemy in enemies)
{
if (IsTargetBlocked(enemy.transform)) continue;
Vector3 toTarget = enemy.transform.position - searchAntenna.position;
float distance = toTarget.magnitude;
if (distance > radarRange) continue;
Vector3 toTargetXZ = new Vector3(toTarget.x, 0f, toTarget.z).normalized;
Vector3 antennaForwardXZ = new Vector3(antennaForward.x, 0f, antennaForward.z).normalized;
float angleToTarget = Vector3.Angle(antennaForwardXZ, toTargetXZ);
if (angleToTarget <= scanHalfWidth)
{
UpdateOrAddTarget(enemy, distance, toTargetXZ);
}
}
CleanupOldTargets();
}
// Метод для срыва захвата с цели
public void LoseTarget(Transform targetTransform)
{
if (targetTransform == null) return;
// Если это текущая захваченная цель - сбрасываем захват
if (lockedTarget != null && lockedTarget.targetTransform == targetTransform)
{
// Сбрасываем захват
lockedTarget = null;
lockedTargetIndex = -1;
isFirstFrameOfLock = false;
AimOffset = Vector2.zero;
if (currentLockMarkerInstance != null)
{
Destroy(currentLockMarkerInstance);
currentLockMarkerInstance = null;
}
// ВОЗВРАЩАЕМ КАМЕРУ В ОБЫЧНЫЙ РЕЖИМ
if (cameraScript != null)
{
cameraScript.SetFollowMode(false);
cameraScript.ResetAimOffset();
}
}
// Добавляем цель в список заблокированных
if (!blockedTargets.Contains(targetTransform))
{
blockedTargets.Add(targetTransform);
StartCoroutine(UnblockTargetAfterDelay(targetTransform, targetBlockTime));
}
// Убираем цель из списка обнаруженных
RadarTargetData targetToRemove = detectedTargets.Find(t => t.targetTransform == targetTransform);
if (targetToRemove != null)
{
if (targetToRemove.blipUI != null) Destroy(targetToRemove.blipUI.gameObject);
detectedTargets.Remove(targetToRemove);
}
}
// Корутина для разблокировки цели
private System.Collections.IEnumerator UnblockTargetAfterDelay(Transform targetTransform, float delay)
{
yield return new WaitForSeconds(delay);
if (targetTransform != null)
{
blockedTargets.Remove(targetTransform);
Debug.Log($"Цель {targetTransform.name} снова доступна для захвата");
}
}
// Метод для проверки, заблокирована ли цель
public bool IsTargetBlocked(Transform targetTransform)
{
return blockedTargets.Contains(targetTransform);
}
// Метод для мгновенной разблокировки всех целей
public void UnblockAllTargets()
{
blockedTargets.Clear();
Debug.Log("Все цели разблокированы");
}
void UpdateOrAddTarget(GameObject enemy, float distance, Vector3 toTargetXZ)
{
RadarTargetData existing = detectedTargets.Find(t => t.targetTransform == enemy.transform);
float azimuth = Mathf.Atan2(toTargetXZ.x, toTargetXZ.z) * Mathf.Rad2Deg;
if (azimuth < 0) azimuth += 360f;
if (existing != null)
{
existing.lastDetectedTime = Time.time;
existing.distance = distance;
existing.azimuth = azimuth;
}
else
{
RadarTargetData newData = new RadarTargetData
{
targetTransform = enemy.transform,
targetRigidbody = enemy.GetComponent<Rigidbody>(),
lastDetectedTime = Time.time,
distance = distance,
azimuth = azimuth
};
if (radarBlipPrefab != null && radarCenterPoint != null)
{
newData.blipUI = Instantiate(radarBlipPrefab, radarCenterPoint);
}
detectedTargets.Add(newData);
}
}
void UpdateRadarUI()
{
foreach (var target in detectedTargets)
{
if (target.blipUI == null) continue;
float angleRad = (90f - target.azimuth) * Mathf.Deg2Rad;
float normalizedDist = target.distance / radarRange;
float currentRadius = normalizedDist * radarUIRadius;
float x = Mathf.Cos(angleRad) * currentRadius;
float y = Mathf.Sin(angleRad) * currentRadius;
target.blipUI.localPosition = new Vector3(x, y, 0f);
CanvasGroup cg = target.blipUI.GetComponent<CanvasGroup>();
if (cg != null)
{
float timePassed = Time.time - target.lastDetectedTime;
cg.alpha = Mathf.Clamp01(1f - (timePassed / targetTimeout));
}
}
}
void HandleTurretTracking()
{
Vector3 aimPoint;
bool hasTarget = lockedTarget != null && lockedTarget.targetTransform != null;
// Всегда следуем за камерой, независимо от захвата
if (cameraScript != null)
{
aimPoint = cameraScript.GetAimPoint();
AimPoint = aimPoint;
}
else
{
aimPoint = transform.position + transform.forward * 1000f;
AimPoint = aimPoint;
}
// Вращаем башню к точке прицеливания камеры
Vector3 directionToTargetLocal = turret.InverseTransformDirection(aimPoint - turret.position);
float targetAzimuthAngle = Mathf.Atan2(directionToTargetLocal.x, directionToTargetLocal.z) * Mathf.Rad2Deg;
Quaternion targetTurretLocalRotation = turret.localRotation * Quaternion.Euler(0f, targetAzimuthAngle, 0f);
turret.localRotation = Quaternion.RotateTowards(
turret.localRotation,
targetTurretLocalRotation,
turretMaxSpeed * Time.fixedDeltaTime
);
// Вращаем орудие к точке прицеливания камеры
if (gun != null)
{
Vector3 directionToTargetTurretLocal = turret.InverseTransformDirection(aimPoint - gun.position);
float targetElevationAngle = Mathf.Atan2(directionToTargetTurretLocal.y, directionToTargetTurretLocal.z) * Mathf.Rad2Deg;
targetElevationAngle = Mathf.Clamp(targetElevationAngle, -minElevationAngle, maxElevationAngle);
Quaternion targetGunLocalRotation = Quaternion.Euler(-targetElevationAngle, 0f, 0f);
gun.localRotation = Quaternion.RotateTowards(
gun.localRotation,
targetGunLocalRotation,
gunMaxSpeed * Time.fixedDeltaTime
);
}
// Обновляем UI рамку захвата
if (lockFrameUI != null)
{
if (hasTarget && worldSpaceCamera != null && lockedTarget.targetTransform != null)
{
Vector3 screenPoint = worldSpaceCamera.WorldToScreenPoint(lockedTarget.targetTransform.position);
if (screenPoint.z > 0)
{
lockFrameUI.gameObject.SetActive(true);
lockFrameUI.position = screenPoint;
}
else
{
lockFrameUI.gameObject.SetActive(false);
}
}
else
{
lockFrameUI.gameObject.SetActive(false);
}
}
}
void UpdateLockTexts()
{
if (lockedTarget == null || lockedTarget.targetTransform == null)
{
if (distanceText) distanceText.text = "РАДИУС: ИСК";
if (closureSpeedText) closureSpeedText.text = "СБЛИЖЕНИЕ: ---";
return;
}
float dist = Vector3.Distance(transform.position, lockedTarget.targetTransform.position);
if (distanceText) distanceText.text = $"ДАЛЬНОСТЬ: {(dist / 1000f):F2} км";
Vector3 myVelocity = myRigidbody != null ? myRigidbody.linearVelocity : Vector3.zero;
Vector3 targetVelocity = lockedTarget.targetRigidbody != null ? lockedTarget.targetRigidbody.linearVelocity : Vector3.zero;
Vector3 relativeVelocity = myVelocity - targetVelocity;
Vector3 directionToTarget = (lockedTarget.targetTransform.position - transform.position).normalized;
float closureSpeed = Vector3.Dot(relativeVelocity, directionToTarget);
if (closureSpeedText)
{
float closureSpeedKMH = closureSpeed * 3.6f;
closureSpeedText.text = $"СБЛИЖЕНИЕ: {closureSpeedKMH:F0} км/ч";
}
}
public void Button_LockTarget()
{
if (detectedTargets.Count == 0) return;
RadarTargetData targetToLock = null;
foreach (var target in detectedTargets)
{
if (!IsTargetBlocked(target.targetTransform))
{
targetToLock = target;
break;
}
}
if (targetToLock == null)
{
Debug.Log("Нет доступных целей для захвата");
return;
}
lockedTargetIndex = detectedTargets.IndexOf(targetToLock);
lockedTarget = targetToLock;
isFirstFrameOfLock = false;
AimOffset = Vector2.zero;
if (cameraScript != null && lockedTarget.targetTransform != null)
{
cameraScript.RotateToTarget(lockedTarget.targetTransform.position);
cameraScript.SetFollowMode(true);
}
}
public void Button_NextTarget()
{
if (detectedTargets.Count <= 1) return;
int startIndex = lockedTargetIndex;
int nextIndex = (lockedTargetIndex + 1) % detectedTargets.Count;
while (nextIndex != startIndex)
{
if (!IsTargetBlocked(detectedTargets[nextIndex].targetTransform))
{
if (currentLockMarkerInstance != null) Destroy(currentLockMarkerInstance);
lockedTargetIndex = nextIndex;
lockedTarget = detectedTargets[lockedTargetIndex];
isFirstFrameOfLock = false;
AimOffset = Vector2.zero;
if (cameraScript != null && lockedTarget.targetTransform != null)
{
cameraScript.RotateToTarget(lockedTarget.targetTransform.position);
cameraScript.SetFollowMode(true);
}
return;
}
nextIndex = (nextIndex + 1) % detectedTargets.Count;
}
Debug.Log("Нет других доступных целей");
}
public void Button_ClearLock()
{
lockedTarget = null;
lockedTargetIndex = -1;
isFirstFrameOfLock = false;
if (currentLockMarkerInstance != null)
{
Destroy(currentLockMarkerInstance);
currentLockMarkerInstance = null;
}
// НЕ ВЫЗЫВАЕМ SetFollowMode(false)!
// Камера остаётся в текущем положении
}
void CleanupOldTargets()
{
// Очищаем заблокированные цели
for (int i = blockedTargets.Count - 1; i >= 0; i--)
{
if (blockedTargets[i] == null)
{
blockedTargets.RemoveAt(i);
}
}
for (int i = detectedTargets.Count - 1; i >= 0; i--)
{
var target = detectedTargets[i];
if (target.targetTransform == null || (Time.time - target.lastDetectedTime) > targetTimeout)
{
if (target.blipUI != null) Destroy(target.blipUI.gameObject);
if (lockedTarget == target)
{
lockedTarget = null;
lockedTargetIndex = -1;
isFirstFrameOfLock = false;
if (currentLockMarkerInstance != null) Destroy(currentLockMarkerInstance);
// НЕ ВЫЗЫВАЕМ SetFollowMode(false)!
}
detectedTargets.RemoveAt(i);
}
}
}
}