Danro-Jump/Assets/Scripts/Runtime/Gameplay/OneWayPlatformCollider.cs
Danil Iarosh 1f6884c5ac Build gameplay systems and service settings
Подробные изменения:
- Добавлены и доработаны модальные окна главного меню, сервисные настройки, шаблоны UI Toolkit и единый scene-flow сервис для переходов между сценами.
- Расширена gameplay-архитектура: сессия забега, HUD, спавн контента платформ, collectible-предметы, оборудование, хрупкие и движущиеся платформы.
- Добавлена база противников: типы спавна, патрулирование, процедурный спавнер, правила размещения через ScriptableObject-сложности и убийство врагов прыжком сверху.
- Исправлены смерть и respawn игрока, финальный GameOver, отскоки от врагов, пружин и boost-предметов, а также проход сквозь врагов при усиленном подъеме.
- Проведен рефакторинг взаимодействия систем: общий landing context, единые правила player-contact, NonLazy gameplay session, fail-fast installer и централизованный выбор точек спавна.
- Обновлены сцены, prefab-ассеты, difficulty stages, ресурсы главного меню, настройки проекта, roadmap и аппаратная COM-интеграция.
- Добавлены ассеты летучей мыши, врагов, collectibles, TextMesh Pro, ProjectContext и gameplay UI.

Проверки:
- dotnet build .\\Assembly-CSharp.csproj --no-restore
- Unity validation ключевых gameplay/UI/scene-flow скриптов
- Unity Console после force refresh без ошибок
2026-05-27 16:34:51 +03:00

178 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 bool isIgnoringPlayerCollision;
/// <summary>
/// Y-координата верхней границы коллайдера платформы.
/// </summary>
public float TopY
{
get
{
if (boxPlatformCollider != null)
{
var localTop = boxPlatformCollider.offset + Vector2.up * (boxPlatformCollider.size.y * 0.5f);
return boxPlatformCollider.transform.TransformPoint(localTop).y;
}
return 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;
}
RefreshColliderState();
}
/// <summary>
/// После повторного включения сразу синхронизирует IgnoreCollision, не ожидая следующего FixedUpdate.
/// </summary>
private void OnEnable()
{
RefreshColliderState();
}
/// <summary>
/// При выключении платформы обязательно возвращает столкновение с игроком.
/// </summary>
private void OnDisable()
{
SetPlayerCollisionIgnored(false);
}
/// <summary>
/// Синхронизирует состояние IgnoreCollision с текущей позицией GroundCheck игрока.
/// </summary>
private void FixedUpdate()
{
RefreshColliderState();
}
/// <summary>
/// Обновляет режим столкновения между платформой и игроком.
/// </summary>
private void RefreshColliderState()
{
if (player == null && !ResolvePlayer())
{
return;
}
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 = TopY + topBoundaryPadding;
var isFalling = targetPlayer.VerticalVelocity <= landingVelocityThreshold ||
targetPlayer.PreviousVerticalVelocity <= landingVelocityThreshold;
if (!isFalling)
{
return false;
}
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;
}
}
}