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); } } }