267 lines
11 KiB
C#
267 lines
11 KiB
C#
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<Transform> _platformChildren;
|
||
private readonly List<Transform> _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<Transform>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, находится ли игрок на верхней части платформы
|
||
/// </summary>
|
||
/// <param name="collision">Данные о столкновении</param>
|
||
/// <returns>True, если игрок стоит сверху</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Скрывает следующий крайний ряд или колонку в направлении движения
|
||
/// </summary>
|
||
private void HideNextChild()
|
||
{
|
||
// Находим видимые объекты
|
||
var visibleChildren = _platformChildren.Where(c => c.gameObject.activeSelf).ToList();
|
||
if (visibleChildren.Count == 0) return;
|
||
|
||
// Определяем, скрываем ряд или колонку в зависимости от направления
|
||
List<Transform> childrenToHide = moveDirection switch
|
||
{
|
||
Direction2D.Right => GetRightmostColumn(visibleChildren),
|
||
Direction2D.Left => GetLeftmostColumn(visibleChildren),
|
||
Direction2D.Up => GetTopmostRow(visibleChildren),
|
||
Direction2D.Down => GetBottommostRow(visibleChildren),
|
||
_ => new List<Transform>()
|
||
};
|
||
|
||
// Скрываем все объекты в ряду/колонке
|
||
foreach (var child in childrenToHide)
|
||
{
|
||
child.gameObject.SetActive(false);
|
||
_hiddenChildren.Add(child);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Показывает следующий скрытый ряд или колонку (при движении назад)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает самую правую колонку объектов
|
||
/// </summary>
|
||
private static List<Transform> GetRightmostColumn(List<Transform> children)
|
||
{
|
||
if (children.Count == 0) return new List<Transform>();
|
||
|
||
// Находим максимальное X
|
||
float maxX = children.Max(c => c.position.x);
|
||
|
||
// Возвращаем все объекты с этим X (колонку)
|
||
return children.Where(c => Mathf.Approximately(c.position.x, maxX)).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает самую левую колонку объектов
|
||
/// </summary>
|
||
private static List<Transform> GetLeftmostColumn(List<Transform> children)
|
||
{
|
||
if (children.Count == 0) return new List<Transform>();
|
||
|
||
float minX = children.Min(c => c.position.x);
|
||
return children.Where(c => Mathf.Approximately(c.position.x, minX)).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает самый верхний ряд объектов
|
||
/// </summary>
|
||
private static List<Transform> GetTopmostRow(List<Transform> children)
|
||
{
|
||
if (children.Count == 0) return new List<Transform>();
|
||
|
||
float maxY = children.Max(c => c.position.y);
|
||
return children.Where(c => Mathf.Approximately(c.position.y, maxY)).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает самый нижний ряд объектов
|
||
/// </summary>
|
||
private static List<Transform> GetBottommostRow(List<Transform> children)
|
||
{
|
||
if (children.Count == 0) return new List<Transform>();
|
||
|
||
float minY = children.Min(c => c.position.y);
|
||
return children.Where(c => Mathf.Approximately(c.position.y, minY)).ToList();
|
||
}
|
||
}
|
||
}
|
||
|