using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using RR.Game2D.Core; namespace RR.Game2D.Entities.Platforms { public class HidingTriggeringPlatform : MonoBehaviour { [Header("Настройки движения платформы")] [SerializeField] private float moveSpeed = 3f; [SerializeField] private float pathLength = 5f; [SerializeField] private Direction2D moveDirection = Direction2D.Right; [Header("Настройки матрицы объектов")] [SerializeField] private int matrixWidth = 5; [SerializeField] private int matrixHeight = 3; [Header("Прикрепление игрока")] [SerializeField] private bool attachPlayer = true; [Tooltip("Минимальный угол нормали контакта к вертикали для прикрепления (0.7 ≈ 45°)")] [SerializeField, Range(0f, 1f)] private float normalThreshold = 0.7f; private Vector3 _startPosition; private Vector3 _endPosition; private List _platformChildren; private readonly List _hiddenChildren = new (); private Rigidbody2D _attachedPlayerRb; private void Awake() { _startPosition = transform.position; _endPosition = moveDirection switch { Direction2D.Down => _startPosition + Vector3.down * pathLength, Direction2D.Left => _startPosition + Vector3.left * pathLength, Direction2D.Right => _startPosition + Vector3.right * pathLength, Direction2D.Up => _startPosition + Vector3.up * pathLength, _ => _startPosition }; // Собираем все дочерние объекты платформы _platformChildren = new List(); for (int i = 0; i < transform.childCount; i++) { _platformChildren.Add(transform.GetChild(i)); } } private void FixedUpdate() { // Если игрок прикреплён к платформе, корректируем его 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); } public void Move() { StopAllCoroutines(); StartCoroutine(MovePlatformWithHiding(_endPosition, true)); } public void MoveBack() { StopAllCoroutines(); StartCoroutine(MovePlatformWithHiding(_startPosition, false)); } private IEnumerator MovePlatformWithHiding(Vector3 targetPosition, bool isMovingForward) { float distanceTraveled = 0f; int lastUnitTraveled = 0; Vector3 previousPosition = transform.position; while (transform.position != targetPosition) { transform.position = Vector3.MoveTowards( transform.position, targetPosition, Time.deltaTime * moveSpeed); // Вычисляем пройденную дистанцию distanceTraveled += Vector3.Distance(previousPosition, transform.position); previousPosition = transform.position; // Проверяем, прошли ли мы еще один юнит int currentUnit = Mathf.FloorToInt(distanceTraveled); if (currentUnit > lastUnitTraveled) { if (isMovingForward) { HideNextChild(); } else { ShowNextChild(); } lastUnitTraveled = currentUnit; } yield return null; } transform.position = targetPosition; } /// /// Скрывает следующий крайний ряд или колонку в направлении движения /// private void HideNextChild() { // Находим видимые объекты var visibleChildren = _platformChildren.Where(c => c.gameObject.activeSelf).ToList(); if (visibleChildren.Count == 0) return; // Определяем, скрываем ряд или колонку в зависимости от направления List childrenToHide = moveDirection switch { Direction2D.Right => GetRightmostColumn(visibleChildren), Direction2D.Left => GetLeftmostColumn(visibleChildren), Direction2D.Up => GetTopmostRow(visibleChildren), Direction2D.Down => GetBottommostRow(visibleChildren), _ => new List() }; // Скрываем все объекты в ряду/колонке foreach (var child in childrenToHide) { child.gameObject.SetActive(false); _hiddenChildren.Add(child); } } /// /// Показывает следующий скрытый ряд или колонку (при движении назад) /// private void ShowNextChild() { if (_hiddenChildren.Count == 0) return; // Определяем размер ряда/колонки в зависимости от направления int countToShow = moveDirection is Direction2D.Right or Direction2D.Left ? matrixHeight : matrixWidth; // Показываем последние скрытые объекты (один ряд/колонку) countToShow = Mathf.Min(countToShow, _hiddenChildren.Count); for (int i = 0; i < countToShow; i++) { int lastIndex = _hiddenChildren.Count - 1; Transform childToShow = _hiddenChildren[lastIndex]; childToShow.gameObject.SetActive(true); _hiddenChildren.RemoveAt(lastIndex); } } /// /// Получает самую правую колонку объектов /// private static List GetRightmostColumn(List children) { if (children.Count == 0) return new List(); // Находим максимальное X float maxX = children.Max(c => c.position.x); // Возвращаем все объекты с этим X (колонку) return children.Where(c => Mathf.Approximately(c.position.x, maxX)).ToList(); } /// /// Получает самую левую колонку объектов /// private static List GetLeftmostColumn(List children) { if (children.Count == 0) return new List(); float minX = children.Min(c => c.position.x); return children.Where(c => Mathf.Approximately(c.position.x, minX)).ToList(); } /// /// Получает самый верхний ряд объектов /// private static List GetTopmostRow(List children) { if (children.Count == 0) return new List(); float maxY = children.Max(c => c.position.y); return children.Where(c => Mathf.Approximately(c.position.y, maxY)).ToList(); } /// /// Получает самый нижний ряд объектов /// private static List GetBottommostRow(List children) { if (children.Count == 0) return new List(); float minY = children.Min(c => c.position.y); return children.Where(c => Mathf.Approximately(c.position.y, minY)).ToList(); } } }