using UnityEngine; using Zenject; namespace DanroJump.Gameplay { /// /// Кастомная one-way логика платформы. /// Игнорирует столкновение, если нижняя точка игрока ниже верхней границы платформы. /// [DisallowMultipleComponent] [RequireComponent(typeof(Collider2D))] public sealed class OneWayPlatformCollider : MonoBehaviour { [SerializeField] private JumpPlayerController player; [SerializeField] private float topBoundaryPadding; [SerializeField] private float landingVelocityThreshold = 0.05f; private Collider2D platformCollider; private Collider2D playerCollider; private float cachedTopY; private Vector3 lastPosition; private bool isIgnoringPlayerCollision; /// /// Y-координата верхней границы коллайдера платформы (кэшированная). /// public float TopY => cachedTopY; /// /// Вычисляет и сохраняет Y-координату верхней границы платформы. /// public void UpdateCachedTopY() { cachedTopY = platformCollider != null ? platformCollider.bounds.max.y : transform.position.y; } /// /// Получает игрока через DI, чтобы не искать его по сцене на каждой платформе. /// [Inject] public void Construct(JumpPlayerController injectedPlayer) { player = injectedPlayer; ResolvePlayerCollider(); } /// /// Подготавливает Collider2D и отключает стандартный PlatformEffector2D, если он остался на prefab. /// private void Awake() { platformCollider = GetComponent(); platformCollider.usedByEffector = false; if (TryGetComponent(out PlatformEffector2D platformEffector)) { platformEffector.enabled = false; } UpdateCachedTopY(); lastPosition = transform.position; RefreshColliderState(); } /// /// После повторного включения сразу синхронизирует IgnoreCollision, не ожидая следующего FixedUpdate. /// private void OnEnable() { UpdateCachedTopY(); lastPosition = transform.position; RefreshColliderState(); } /// /// При выключении платформы обязательно возвращает столкновение с игроком. /// private void OnDisable() { SetPlayerCollisionIgnored(false); } /// /// Синхронизирует состояние IgnoreCollision с текущей позицией GroundCheck игрока. /// private void FixedUpdate() { if (player == null && !ResolvePlayer()) { return; } // Если платформа переместилась (например, была рециклирована), принудительно обновляем кэш if (transform.position != lastPosition) { UpdateCachedTopY(); lastPosition = transform.position; } // Оптимизация: если игрок взлетает вверх, коллизия гарантированно игнорируется. // Это избавляет от выполнения сложных вычислений расстояний для всех платформ. var isFalling = player.VerticalVelocity <= landingVelocityThreshold || player.PreviousVerticalVelocity <= landingVelocityThreshold; if (!isFalling) { SetPlayerCollisionIgnored(true); return; } RefreshColliderState(); } /// /// Обновляет режим столкновения между платформой и игроком. /// private void RefreshColliderState() { if (playerCollider == null && !ResolvePlayerCollider()) { return; } SetPlayerCollisionIgnored(!ShouldCollideWith(player)); } /// /// Проверяет, должен ли игрок столкнуться с платформой в текущем физическом шаге. /// /// Игрок, для которого проверяется столкновение. /// True, если нижняя точка игрока не ниже верхней границы платформы. public bool ShouldCollideWith(JumpPlayerController targetPlayer) { if (targetPlayer == null) { return false; } var topY = cachedTopY + Mathf.Max(0f, topBoundaryPadding); var playerLowestY = playerCollider != null ? playerCollider.bounds.min.y : targetPlayer.GroundCheckY; return playerLowestY >= topY; } /// /// Возвращает true, если ссылка на игрока уже получена. /// private bool ResolvePlayer() { return player != null; } /// /// Кэширует коллайдер игрока для Physics2D.IgnoreCollision. /// private bool ResolvePlayerCollider() { if (player == null) { return false; } return player.TryGetComponent(out playerCollider); } /// /// Включает или выключает физическое столкновение пары игрок-платформа. /// private void SetPlayerCollisionIgnored(bool shouldIgnore) { if (platformCollider == null || playerCollider == null || isIgnoringPlayerCollision == shouldIgnore) { return; } Physics2D.IgnoreCollision(platformCollider, playerCollider, shouldIgnore); isIgnoringPlayerCollision = shouldIgnore; } } }