Подробный список изменений: - В JumpPlayerController.cs в методе CopyWrappedVisuals возвращено мировое позиционирование и вращение клонов с оффсетом (вместо локальных). Это полностью исправило баг двойной трансформации поворота родительского объекта, из-за которого клон лежал на боку. - В OneWayPlatformCollider.cs добавлено отслеживание позиции платформы. В FixedUpdate, если позиция платформы изменилась по сравнению с предыдущим кадром, вызывается UpdateCachedTopY. Это решило проблему ложного срабатывания коллизии при прыжке сквозь перенесенные вверх платформы.
200 lines
7.6 KiB
C#
200 lines
7.6 KiB
C#
using UnityEngine;
|
||
using Zenject;
|
||
|
||
namespace DanroJump.Gameplay
|
||
{
|
||
/// <summary>
|
||
/// Кастомная one-way логика платформы.
|
||
/// Игнорирует столкновение, если GroundCheck игрока ниже верхней границы платформы.
|
||
/// </summary>
|
||
[DisallowMultipleComponent]
|
||
[RequireComponent(typeof(Collider2D))]
|
||
public sealed class OneWayPlatformCollider : MonoBehaviour
|
||
{
|
||
[SerializeField] private JumpPlayerController player;
|
||
[SerializeField] private float topBoundaryPadding;
|
||
[SerializeField] private float landingVelocityThreshold = 0.05f;
|
||
[SerializeField] private float crossingGraceDistance = 0.8f;
|
||
|
||
private Collider2D platformCollider;
|
||
private BoxCollider2D boxPlatformCollider;
|
||
private Collider2D playerCollider;
|
||
private float cachedTopY;
|
||
private Vector3 lastPosition;
|
||
private bool isIgnoringPlayerCollision;
|
||
|
||
/// <summary>
|
||
/// Y-координата верхней границы коллайдера платформы (кэшированная).
|
||
/// </summary>
|
||
public float TopY => cachedTopY;
|
||
|
||
/// <summary>
|
||
/// Вычисляет и сохраняет Y-координату верхней границы платформы.
|
||
/// </summary>
|
||
public void UpdateCachedTopY()
|
||
{
|
||
if (boxPlatformCollider != null)
|
||
{
|
||
var localTop = boxPlatformCollider.offset + Vector2.up * (boxPlatformCollider.size.y * 0.5f);
|
||
cachedTopY = boxPlatformCollider.transform.TransformPoint(localTop).y;
|
||
}
|
||
else
|
||
{
|
||
cachedTopY = platformCollider != null ? platformCollider.bounds.max.y : transform.position.y;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает игрока через DI, чтобы не искать его по сцене на каждой платформе.
|
||
/// </summary>
|
||
[Inject]
|
||
public void Construct(JumpPlayerController injectedPlayer)
|
||
{
|
||
player = injectedPlayer;
|
||
ResolvePlayerCollider();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Подготавливает Collider2D и отключает стандартный PlatformEffector2D, если он остался на prefab.
|
||
/// </summary>
|
||
private void Awake()
|
||
{
|
||
platformCollider = GetComponent<Collider2D>();
|
||
boxPlatformCollider = platformCollider as BoxCollider2D;
|
||
platformCollider.usedByEffector = false;
|
||
|
||
if (TryGetComponent(out PlatformEffector2D platformEffector))
|
||
{
|
||
platformEffector.enabled = false;
|
||
}
|
||
|
||
UpdateCachedTopY();
|
||
lastPosition = transform.position;
|
||
RefreshColliderState();
|
||
}
|
||
|
||
/// <summary>
|
||
/// После повторного включения сразу синхронизирует IgnoreCollision, не ожидая следующего FixedUpdate.
|
||
/// </summary>
|
||
private void OnEnable()
|
||
{
|
||
UpdateCachedTopY();
|
||
lastPosition = transform.position;
|
||
RefreshColliderState();
|
||
}
|
||
|
||
/// <summary>
|
||
/// При выключении платформы обязательно возвращает столкновение с игроком.
|
||
/// </summary>
|
||
private void OnDisable()
|
||
{
|
||
SetPlayerCollisionIgnored(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Синхронизирует состояние IgnoreCollision с текущей позицией GroundCheck игрока.
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Обновляет режим столкновения между платформой и игроком.
|
||
/// </summary>
|
||
private void RefreshColliderState()
|
||
{
|
||
if (playerCollider == null && !ResolvePlayerCollider())
|
||
{
|
||
return;
|
||
}
|
||
|
||
SetPlayerCollisionIgnored(!ShouldCollideWith(player));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, должен ли игрок столкнуться с платформой в текущем физическом шаге.
|
||
/// </summary>
|
||
/// <param name="targetPlayer">Игрок, для которого проверяется столкновение.</param>
|
||
/// <returns>True, если игрок падает сверху или только что пересек верх платформы сверху вниз.</returns>
|
||
public bool ShouldCollideWith(JumpPlayerController targetPlayer)
|
||
{
|
||
if (targetPlayer == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var topY = cachedTopY + topBoundaryPadding;
|
||
|
||
if (targetPlayer.GroundCheckY >= topY)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// Grace-зона закрывает быстрые кадры, когда GroundCheck успел проскочить немного ниже верхней границы.
|
||
var crossedFromAbove = targetPlayer.PreviousGroundCheckY >= topY &&
|
||
targetPlayer.GroundCheckY >= topY - crossingGraceDistance;
|
||
return crossedFromAbove;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возвращает true, если ссылка на игрока уже получена.
|
||
/// </summary>
|
||
private bool ResolvePlayer()
|
||
{
|
||
return player != null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Кэширует коллайдер игрока для Physics2D.IgnoreCollision.
|
||
/// </summary>
|
||
private bool ResolvePlayerCollider()
|
||
{
|
||
if (player == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return player.TryGetComponent(out playerCollider);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Включает или выключает физическое столкновение пары игрок-платформа.
|
||
/// </summary>
|
||
private void SetPlayerCollisionIgnored(bool shouldIgnore)
|
||
{
|
||
if (platformCollider == null || playerCollider == null || isIgnoringPlayerCollision == shouldIgnore)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Physics2D.IgnoreCollision(platformCollider, playerCollider, shouldIgnore);
|
||
isIgnoringPlayerCollision = shouldIgnore;
|
||
}
|
||
|
||
}
|
||
}
|