92 lines
4.4 KiB
C#
92 lines
4.4 KiB
C#
using KinematicCharacterController;
|
|
using UnityEngine;
|
|
|
|
namespace Code.Gameplay.Character.Controller.Abilities
|
|
{
|
|
/*
|
|
* Механика залазания на стены
|
|
* Обрабатывает столкновения и применяет вертикальный импульс при ударе о стену с достаточной скоростью
|
|
*/
|
|
public class WallClimbMechanic
|
|
{
|
|
// Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену
|
|
private readonly float _wallClimbSpeedFactor;
|
|
// Минимальная горизонтальная скорость для начала залазания на стену
|
|
private readonly float _minWallClimbSpeed;
|
|
// Пороговый косинус стены
|
|
public float WallNormalThreshold { get; }
|
|
|
|
private bool _hasClimbedThisWallSession;
|
|
private float _wallClimbCooldownTimer;
|
|
private readonly float _wallClimbCooldownDuration = 1.5f;
|
|
|
|
public bool IsClimbingActive { get; private set; }
|
|
|
|
// Конструктор механики залазания на стену
|
|
public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed, float wallDetectionAngleThreshold)
|
|
{
|
|
_wallClimbSpeedFactor = wallClimbSpeedFactor;
|
|
_minWallClimbSpeed = minWallClimbSpeed;
|
|
// Преобразуем угол в косинус порога: если |dot| > cos(threshold), то это НЕ стена
|
|
WallNormalThreshold = Mathf.Cos(Mathf.Deg2Rad * wallDetectionAngleThreshold);
|
|
}
|
|
|
|
// Обработка столкновения при движении (вызывается при ударе о препятствие)
|
|
public bool TryClimbWall(KinematicCharacterMotor motor, Vector3 hitNormal)
|
|
{
|
|
// Проверяем, что это вертикальная или почти вертикальная поверхность (стена)
|
|
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
|
|
|
|
if (Mathf.Abs(dotWithUp) > WallNormalThreshold)
|
|
{
|
|
// Сбрасываем сессию, если коснулись пола/потолка → выход из "режима стены"
|
|
_hasClimbedThisWallSession = false;
|
|
return false;
|
|
}
|
|
|
|
// Запрещаем повторное залазание в рамках одной сессии
|
|
if (_hasClimbedThisWallSession || _wallClimbCooldownTimer > 0f)
|
|
return false;
|
|
|
|
// Получаем текущую скорость персонажа
|
|
var currentVelocity = motor.Velocity;
|
|
|
|
// Проекция скорости на направление к стене (отрицательная, если летим в стену)
|
|
var velocityIntoWall = Vector3.Dot(currentVelocity, -hitNormal);
|
|
|
|
// Если движемся в стену с достаточной скоростью
|
|
if (velocityIntoWall > _minWallClimbSpeed)
|
|
{
|
|
_wallClimbCooldownTimer = _wallClimbCooldownDuration;
|
|
// Преобразуем часть горизонтальной скорости в вертикальную
|
|
var verticalBoost = velocityIntoWall * _wallClimbSpeedFactor;
|
|
|
|
// Добавляем вертикальный импульс через BaseVelocity
|
|
motor.BaseVelocity += motor.CharacterUp * verticalBoost;
|
|
|
|
// Предотвращаем заземление и повторное применение в этом кадре
|
|
motor.ForceUnground();
|
|
_hasClimbedThisWallSession = true;
|
|
IsClimbingActive = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void ResetWallClimbSession()
|
|
{
|
|
_hasClimbedThisWallSession = false;
|
|
IsClimbingActive = false;
|
|
}
|
|
|
|
public void TickCooldown(float deltaTime)
|
|
{
|
|
if (_wallClimbCooldownTimer > 0f)
|
|
{
|
|
_wallClimbCooldownTimer -= deltaTime;
|
|
}
|
|
}
|
|
}
|
|
} |