Files
CourierOld/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallLeanMechanic.cs
T

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;
}
}
}