level 1-3 updates, bug fixes etc

- улучшил структуру уровня 1-3
- пофиксил баг с застреванием персонажа на уровне 1-3
- пофиксил баг с прилипанием игрока к платформе сбоку
- изменил логику платформ
- добавил больше динамичности в уровень 1-3
This commit is contained in:
Данил Ярош 2025-11-28 16:48:31 +03:00
parent 9bcf2cfd16
commit bd57b74d8b
9 changed files with 236267 additions and 254 deletions

View File

@ -2698,7 +2698,7 @@ Rigidbody2D:
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_Interpolate: 0
m_Interpolate: 1
m_SleepingMode: 1
m_CollisionDetection: 1
m_Constraints: 4

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
using System.Linq;
using UnityEngine;
using UnityEngine;
using UnityEngine.InputSystem;
using RR.Common.Interfaces;
@ -35,6 +34,12 @@ namespace RR.Game2D.Behaviour
[SerializeField] private float groundCheckRadius = 0.05f;
[SerializeField] private LayerMask groundMask;
[Header("Антизастревание")]
[SerializeField] private float stuckThreshold = 0.3f; // Время до срабатывания (сек)
[SerializeField] private float stuckSpeedThreshold = 0.5f; // Макс скорость для "застревания"
[SerializeField] private float unstuckForce = 15f; // Сила выталкивания вверх
[SerializeField] private float unstuckCooldown = 0.5f; // Задержка между выталкиваниями
[Header("Аниматор [Опционально]")]
[SerializeField] private Animator animator; // оставить в инспекторе на всякий случай
@ -56,6 +61,10 @@ namespace RR.Game2D.Behaviour
private bool _jumpPressed, _jumpReleased, _jumpHold;
private float _fallSpeedYDampingChangeThreshold;
// Антизастревание
private float _stuckTime;
private float _unstuckCooldownTimer;
// Аниматор
private static readonly int Jump = Animator.StringToHash("Jump");
@ -80,7 +89,7 @@ namespace RR.Game2D.Behaviour
{
rb = GetComponent<Rigidbody2D>();
//col = GetComponent<Collider2D>();
rb.freezeRotation = true;
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
_fallSpeedYDampingChangeThreshold = CameraManager.instance._fallSpeedYDampingChangeThreshold;
@ -152,6 +161,9 @@ namespace RR.Game2D.Behaviour
rb.linearVelocity = newVelocity;
_jumpPressed = _jumpReleased = _jumpHold = false;
// Система антизастревания
CheckAndHandleStuck();
}
private void OnTriggerEnter2D(Collider2D collision)
@ -186,6 +198,110 @@ namespace RR.Game2D.Behaviour
animator.SetTrigger(Death);
}
/// <summary>
/// Проверяет, застрял ли игрок, и выталкивает его если нужно
/// </summary>
private void CheckAndHandleStuck()
{
// Уменьшаем таймер кулдауна
if (_unstuckCooldownTimer > 0)
{
_unstuckCooldownTimer -= Time.fixedDeltaTime;
return;
}
// Проверяем условия застревания:
// 1. Игрок пытается двигаться (есть ввод)
// 2. Скорость по X близка к нулю (не движется)
// 3. Касается чего-то (земли или стены)
bool tryingToMove = Mathf.Abs(_inputX) > 0.1f;
bool notMoving = Mathf.Abs(rb.linearVelocity.x) < stuckSpeedThreshold;
bool hasCollision = IsGrounded || CheckWallContact();
if (tryingToMove && notMoving && hasCollision)
{
// Накапливаем время застревания
_stuckTime += Time.fixedDeltaTime;
// Если застряли достаточно долго - выталкиваем
if (_stuckTime >= stuckThreshold)
{
UnstuckPlayer();
_stuckTime = 0f;
_unstuckCooldownTimer = unstuckCooldown;
}
}
else
{
// Игрок двигается нормально - сбрасываем счетчик
_stuckTime = 0f;
}
}
/// <summary>
/// Проверяет, касается ли игрок стены с боков
/// </summary>
private bool CheckWallContact()
{
// Проверяем коллизии с боков
Vector2 boxSize = new Vector2(0.1f, 0.5f);
Vector2 leftCheck = (Vector2)transform.position + Vector2.left * 0.3f;
Vector2 rightCheck = (Vector2)transform.position + Vector2.right * 0.3f;
bool leftWall = Physics2D.OverlapBox(leftCheck, boxSize, 0f, groundMask);
bool rightWall = Physics2D.OverlapBox(rightCheck, boxSize, 0f, groundMask);
return leftWall || rightWall;
}
/// <summary>
/// Выталкивает игрока из застревания
/// </summary>
private void UnstuckPlayer()
{
// Пробуем найти свободную позицию выше текущей
Vector3 freePosition = FindFreePosition();
if (freePosition != Vector3.zero)
{
// Телепортируем в свободную позицию
transform.position = freePosition;
}
// Добавляем импульс вверх для гарантии
Vector2 unstuckVelocity = rb.linearVelocity;
unstuckVelocity.y = unstuckForce;
rb.linearVelocity = unstuckVelocity;
Debug.Log($"[PlayerController] Unstuck player at {transform.position}");
}
/// <summary>
/// Ищет ближайшую свободную позицию над игроком
/// </summary>
private Vector3 FindFreePosition()
{
// Проверяем позиции в<><D0B2>ше текущей с шагом 0.1 единиц
float checkDistance = 0.05f;
int maxAttempts = 10;
for (int i = 1; i <= maxAttempts; i++)
{
Vector3 testPosition = transform.position + Vector3.up * (checkDistance * i);
// Проверяем, свободна ли эта позиция
Collider2D hit = Physics2D.OverlapCircle(testPosition, groundCheckRadius, groundMask);
if (hit == null)
{
return testPosition;
}
}
// Если не нашли свободную позицию, возвращаем небольшое смещение вверх
return transform.position + Vector3.up * 0.1f;
}
/// <summary>
/// Метод для проверки соприкосновения с землей
/// </summary>

View File

@ -1,3 +1,4 @@
using System.Linq;
using UnityEngine;
using RR.Game2D.Core;
@ -13,6 +14,8 @@ namespace RR.Game2D.Entities.Platforms
[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;
@ -41,14 +44,53 @@ namespace RR.Game2D.Entities.Platforms
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
other.transform.SetParent(transform);
// Проверяем, что игрок стоит на верхней части платформы
if (IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
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);
}
// Если игрок не прикреплён, но теперь на верхней части - прикрепляем
else if (other.transform.parent != transform && IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
other.transform.SetParent(null);
if (other.transform.parent == transform)
{
other.transform.SetParent(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);
}
}
}

View File

@ -1,5 +1,5 @@
using System;
using System.Collections;
using System.Collections;
using System.Linq;
using RR.Game2D.Core;
using UnityEngine;
@ -14,19 +14,19 @@ namespace RR.Game2D.Entities.Platforms
[Header("Прикрепление игрока")]
[SerializeField] private bool attachPlayer = true;
[Tooltip("Минимальный угол нормали контакта к вертикали для прикрепления (0.7 ≈ 45°)")]
[SerializeField, Range(0f, 1f)] private float normalThreshold = 0.7f;
private Vector3 _startPosition;
private void Awake()
{
_startPosition = transform.position;
}
private void OnCollisionEnter2D(Collision2D other)
{
if (!other.gameObject.CompareTag("Player")) return;
if (attachPlayer) other.transform.SetParent(transform);
// Проверяем, что игрок стоит на верхней части платформы
if (attachPlayer && IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
Vector3 shiftVector = shiftDirection switch
{
@ -41,11 +41,31 @@ namespace RR.Game2D.Entities.Platforms
StartCoroutine(ShiftPlatform(shiftVector));
}
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);
}
// Если игрок не прикреплён, но теперь на верхней части - прикрепляем
else if (other.transform.parent != transform && IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (!other.gameObject.CompareTag("Player")) return;
if (attachPlayer) other.transform.SetParent(null);
if (attachPlayer && other.transform.parent == transform)
{
other.transform.SetParent(null);
}
Vector3 shiftVector = shiftDirection switch
{
@ -59,6 +79,19 @@ namespace RR.Game2D.Entities.Platforms
StopAllCoroutines();
StartCoroutine(ShiftPlatform(shiftVector));
}
/// <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);
}
private IEnumerator ShiftPlatform(Vector3 shiftVector)
{

View File

@ -1,4 +1,5 @@
using UnityEngine;
using System.Linq;
using UnityEngine;
namespace RR.Game2D.Entities.Platforms
{
@ -11,35 +12,39 @@ namespace RR.Game2D.Entities.Platforms
[Header("Прикрепление игрока")]
[SerializeField] private bool attachPlayer = true;
[Tooltip("Минимальный угол нормали контакта к вертикали для прикрепления (0.7 ≈ 45°)")]
[SerializeField, Range(0f, 1f)] private float normalThreshold = 0.7f;
[Header("Задержка перед началом движения (в секундах)")]
[SerializeField] private float startDelay = 0;
[SerializeField] private float startDelay;
private int _currentTargetIndex;
private float _delayTimer;
private bool _started;
private bool _isWaiting;
private void Start()
{
_delayTimer = Mathf.Max(0f, startDelay);
_started = _delayTimer <= 0f;
// Если задержка задана, начинаем с ожидания
if (!(startDelay > 0f)) return;
_isWaiting = true;
_delayTimer = startDelay;
}
private void FixedUpdate()
{
if (coordinates == null || coordinates.Length == 0) return;
// Если ещё идёт задержка старта, уменьшаем таймер и ожидаем
if (!_started)
// Если платформа в режиме ожидания, уменьшаем таймер
if (_isWaiting)
{
_delayTimer -= Time.fixedDeltaTime;
if (_delayTimer <= 0f)
{
_started = true;
_isWaiting = false;
}
else
{
return; // пока не стартовали, не двигаемся
return; // пока ждём, не двигаемся
}
}
@ -51,33 +56,83 @@ namespace RR.Game2D.Entities.Platforms
// Проверяем, достигли ли целевой точки
if (!(Vector3.Distance(transform.position, targetPoint) < 0.01f)) return;
if (loop)
{
// Режим зацикливания: переходим к следующей точке по кругу
_currentTargetIndex = (_currentTargetIndex + 1) % coordinates.Length;
if (_currentTargetIndex == 0) _started = false;
// Если вернулись в начальную точку (индекс 0), активируем задержку
if (_currentTargetIndex != 0 || !(startDelay > 0f)) return;
}
else
{
_currentTargetIndex++;
if (_currentTargetIndex < coordinates.Length) return;
// Возвращаемся в начальную точку
transform.position = coordinates[0];
_currentTargetIndex = 0;
// Активируем задержку перед следующим циклом
if (!(startDelay > 0f)) return;
}
_isWaiting = true;
_delayTimer = startDelay;
}
private void OnCollisionEnter2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
other.transform.SetParent(transform);
// Проверяем, что игрок стоит на верхней части платформы
if (IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
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);
}
// Если игрок не прикреплён, но теперь на верхней части - прикрепляем
else if (other.transform.parent != transform && IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
other.transform.SetParent(null);
if (other.transform.parent == transform)
{
other.transform.SetParent(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);
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections;
using System.Linq;
using UnityEngine;
using RR.Game2D.Core;
@ -14,6 +15,8 @@ namespace RR.Game2D.Entities.Platforms
[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;
@ -36,14 +39,53 @@ namespace RR.Game2D.Entities.Platforms
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
other.transform.SetParent(transform);
// Проверяем, что игрок стоит на верхней части платформы
if (IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
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);
}
// Если игрок не прикреплён, но теперь на верхней части - прикрепляем
else if (other.transform.parent != transform && IsPlayerOnTop(other))
{
other.transform.SetParent(transform);
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (!attachPlayer) return;
if (!other.gameObject.CompareTag("Player")) return;
other.transform.SetParent(null);
if (other.transform.parent == transform)
{
other.transform.SetParent(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()

200452
Assets/_Recovery/0 (7).unity Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8f7875b4be4235949939aacb210b9d88
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: