level 1-3 updates, bug fixes etc
- улучшил структуру уровня 1-3 - пофиксил баг с застреванием персонажа на уровне 1-3 - пофиксил баг с прилипанием игрока к платформе сбоку - изменил логику платформ - добавил больше динамичности в уровень 1-3
This commit is contained in:
parent
9bcf2cfd16
commit
bd57b74d8b
@ -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
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
200452
Assets/_Recovery/0 (7).unity
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/_Recovery/0 (7).unity.meta
Normal file
7
Assets/_Recovery/0 (7).unity.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f7875b4be4235949939aacb210b9d88
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
x
Reference in New Issue
Block a user