149 lines
5.7 KiB
C#
149 lines
5.7 KiB
C#
using UnityEngine;
|
|
using KinematicCharacterController;
|
|
|
|
namespace Code.Gameplay.Character.Controller.Abilities
|
|
{
|
|
/*
|
|
* Механика наклона персонажа при контакте со стеной
|
|
* Управляет визуальным наклоном модели в зависимости от высоты подъёма по стене
|
|
*/
|
|
public class WallLeanMechanic
|
|
{
|
|
private readonly float _maxLeanAngle;
|
|
private readonly float _leanSharpness;
|
|
private readonly float _heightScale;
|
|
|
|
private Transform _characterTransform;
|
|
private Vector3 _lastWallNormal;
|
|
private float _lastWallContactHeight;
|
|
private float _leanFactor;
|
|
private float _leanSign;
|
|
private Vector3 _leanDirection;
|
|
|
|
private Vector3 _lastWallContactPoint;
|
|
private bool _isLeaningOnWall;
|
|
private const float WallProximityThreshold = 0.6f;
|
|
|
|
public WallLeanMechanic(float maxLeanAngle, float leanSharpness, float heightScale)
|
|
{
|
|
_maxLeanAngle = maxLeanAngle;
|
|
_leanSharpness = leanSharpness;
|
|
_heightScale = heightScale;
|
|
}
|
|
|
|
// Назначаем трансформ визуальной модели один раз
|
|
public void SetCharacterTransform(Transform transform)
|
|
{
|
|
_characterTransform = transform;
|
|
}
|
|
|
|
// Вызывается из OnMovementHit
|
|
public void OnWallContact(KinematicCharacterMotor motor, Vector3 wallNormal, float contactHeight)
|
|
{
|
|
// Только если это действительно стена (не пол/потолок)
|
|
var dotWithUp = Vector3.Dot(wallNormal, motor.CharacterUp);
|
|
if (Mathf.Abs(dotWithUp) > 0.7f) // ~45°, можно настроить
|
|
return;
|
|
|
|
_lastWallNormal = wallNormal;
|
|
_lastWallContactHeight = contactHeight;
|
|
_lastWallContactPoint = motor.TransientPosition;
|
|
|
|
// Пересчитываем направление наклона
|
|
_leanDirection = -_lastWallNormal;
|
|
_leanDirection = Vector3.ProjectOnPlane(_leanDirection, motor.CharacterUp).normalized;
|
|
|
|
_leanSign = 1;
|
|
|
|
// what the fuck
|
|
if (_leanDirection.x != 0)
|
|
{
|
|
switch (_leanDirection.x)
|
|
{
|
|
case -1 when motor.CharacterRight.x > 0.1f:
|
|
case 1 when motor.CharacterRight.x > 0.1f:
|
|
_leanSign = -1;
|
|
break;
|
|
case -1 when Mathf.Abs(motor.CharacterRight.x) < 0.1f:
|
|
case 1 when Mathf.Abs(motor.CharacterRight.x) < 0.1f:
|
|
_leanDirection = new Vector3(0, 0, -1);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (_leanDirection.z)
|
|
{
|
|
case 1 when Mathf.Abs(motor.CharacterRight.z) < 0.1f:
|
|
_leanSign = -1;
|
|
break;
|
|
case -1 when motor.CharacterRight.z < -0.1f:
|
|
case 1 when motor.CharacterRight.z > 0.1f:
|
|
_leanDirection = new Vector3(-1, 0, 0);
|
|
break;
|
|
case -1 when motor.CharacterRight.z > 0.1f:
|
|
case 1 when motor.CharacterRight.z < -0.1f:
|
|
_leanDirection = new Vector3(1, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
_isLeaningOnWall = true;
|
|
}
|
|
|
|
// Обновление каждый кадр
|
|
public void UpdateLean(KinematicCharacterMotor motor, float deltaTime)
|
|
{
|
|
if (!_characterTransform) return;
|
|
|
|
var currentPosition = motor.TransientPosition;
|
|
|
|
var isStillNearWall = false;
|
|
if (_isLeaningOnWall && _lastWallNormal != Vector3.zero)
|
|
{
|
|
var toWall = currentPosition - _lastWallContactPoint;
|
|
var distanceToWallPlane = Mathf.Abs(Vector3.Dot(toWall, _lastWallNormal));
|
|
isStillNearWall = distanceToWallPlane <= WallProximityThreshold;
|
|
}
|
|
|
|
if (_isLeaningOnWall)
|
|
{
|
|
var isBelowContactHeight = currentPosition.y < _lastWallContactHeight;
|
|
|
|
if (!isStillNearWall || isBelowContactHeight)
|
|
{
|
|
_isLeaningOnWall = false;
|
|
}
|
|
else
|
|
{
|
|
var heightDelta = currentPosition.y - _lastWallContactHeight;
|
|
var targetLeanFactor = Mathf.Clamp01(heightDelta * _heightScale);
|
|
_leanFactor = Mathf.Lerp(_leanFactor, targetLeanFactor, 1f - Mathf.Exp(-_leanSharpness * deltaTime));
|
|
}
|
|
}
|
|
|
|
if (!_isLeaningOnWall)
|
|
{
|
|
_leanFactor = Mathf.Lerp(_leanFactor, 0f, 1f - Mathf.Exp(-_leanSharpness * deltaTime));
|
|
}
|
|
|
|
ApplyLeanRotation(motor);
|
|
}
|
|
|
|
private void ApplyLeanRotation(KinematicCharacterMotor motor)
|
|
{
|
|
if (_leanFactor <= 0.01f)
|
|
{
|
|
_characterTransform.localRotation = Quaternion.identity;
|
|
return;
|
|
}
|
|
|
|
if (_leanDirection.sqrMagnitude < 0.01f) return;
|
|
|
|
var leanAngle = _leanSign * _leanFactor * _maxLeanAngle;
|
|
var leanAxis = Vector3.Cross(motor.CharacterUp, _leanDirection);
|
|
var leanRotation = Quaternion.AngleAxis(leanAngle, leanAxis);
|
|
_characterTransform.localRotation = leanRotation;
|
|
}
|
|
}
|
|
} |