using System.Linq;
using UnityEngine;
namespace RR.Game2D.Entities.Platforms
{
public class TrajectoryPlatform : MonoBehaviour
{
[Header("Настройки движения платформы")]
[SerializeField] private float moveSpeed = 1f;
[SerializeField] private Vector3[] coordinates;
[SerializeField] private bool loop = true;
[Header("Прикрепление игрока")]
[SerializeField] private bool attachPlayer = true;
[Tooltip("Минимальный угол нормали контакта к вертикали для прикрепления (0.7 ≈ 45°)")]
[SerializeField, Range(0f, 1f)] private float normalThreshold = 0.7f;
[Header("Задержка перед началом движения (в секундах)")]
[SerializeField] private float startDelay;
private int _currentTargetIndex;
private float _delayTimer;
private bool _isWaiting;
private Rigidbody2D _attachedPlayerRb;
private void Start()
{
// Если задержка задана, начинаем с ожидания
if (!(startDelay > 0f)) return;
_isWaiting = true;
_delayTimer = startDelay;
}
private void FixedUpdate()
{
if (coordinates == null || coordinates.Length == 0) return;
// Если платформа в режиме ожидания, уменьшаем таймер
if (_isWaiting)
{
_delayTimer -= Time.fixedDeltaTime;
if (_delayTimer <= 0f)
{
_isWaiting = false;
}
else
{
return; // пока ждём, не двигаемся
}
}
// Определяем целевую точку
Vector3 targetPoint = coordinates[_currentTargetIndex];
// Двигаем платформу к целевой точке
transform.position = Vector3.MoveTowards(transform.position, targetPoint, moveSpeed * Time.fixedDeltaTime);
// Проверяем, достигли ли целевой точки
if (!(Vector3.Distance(transform.position, targetPoint) < 0.01f)) return;
if (loop)
{
// Режим зацикливания: переходим к следующей точке по кругу
_currentTargetIndex = (_currentTargetIndex + 1) % coordinates.Length;
// Если вернулись в начальную точку (индекс 0), активируем задержку
if (_currentTargetIndex == 0 && startDelay > 0f)
{
_isWaiting = true;
_delayTimer = startDelay;
}
}
else
{
_currentTargetIndex++;
if (_currentTargetIndex >= coordinates.Length)
{
// Возвращаемся в начальную точку
transform.position = coordinates[0];
_currentTargetIndex = 0;
// Активируем задержку перед следующим циклом
if (startDelay > 0f)
{
_isWaiting = true;
_delayTimer = startDelay;
}
}
}
// Если игрок прикреплён к платформе, корректируем его velocity
if (!_attachedPlayerRb) return;
// Конвертируем мировую скорость игрока в локальную относительно платформы
Vector2 worldVelocity = _attachedPlayerRb.linearVelocity;
Vector2 localVelocity = transform.InverseTransformDirection(worldVelocity);
// Устанавливаем обратно, чтобы избежать конфликта с движением платформы
_attachedPlayerRb.linearVelocity = transform.TransformDirection(localVelocity);
}
private void OnCollisionEnter2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
// Проверяем, что игрок стоит на верхней части платформы
if (!IsPlayerOnTop(other)) return;
other.transform.SetParent(transform);
_attachedPlayerRb = other.rigidbody;
}
private void OnCollisionStay2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
// Если игрок уже прикреплён, но больше не на верхней части - отсоединяем
if (other.transform.parent == transform && !IsPlayerOnTop(other))
{
other.transform.SetParent(null);
_attachedPlayerRb = null;
}
// Если игрок не прикреплён, но теперь на верхней части - прикрепляем
else if (other.transform.parent != transform && IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
_attachedPlayerRb = other.rigidbody;
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
if (other.transform.parent != transform) return;
other.transform.SetParent(null);
_attachedPlayerRb = null;
}
///
/// Проверяет, находится ли игрок на верхней части платформы
///
/// Данные о столкновении
/// True, если игрок стоит сверху
private bool IsPlayerOnTop(Collision2D collision)
{
// Проверяем нормали всех контактных точек
// Нормаль направлена от другого объекта (игрока) к платформе,
// поэтому если игрок сверху, нормаль будет направлена вниз
return collision.contacts.Any(contact => Vector2.Dot(contact.normal, Vector2.down) > normalThreshold);
}
}
}