283 lines
14 KiB
C#
283 lines
14 KiB
C#
using Code.Configs;
|
||
using Code.Gameplay.Camera;
|
||
using Code.Gameplay.Character.Controller.Abilities;
|
||
using Code.Gameplay.Character.Controller.Input;
|
||
using Code.Gameplay.Character.Controller.Movement;
|
||
using Code.Gameplay.Character.Core;
|
||
using KinematicCharacterController;
|
||
using Reflex.Attributes;
|
||
using UnityEngine;
|
||
|
||
namespace Code.Gameplay.Character.Controller
|
||
{
|
||
/*
|
||
* Скрипт, отвечающий за передвижение персонажа
|
||
* Реализует физическое движение персонажа с использованием Kinematic Character Controller
|
||
* Обрабатывает ввод пользователя, применяет движение и вращение с учетом поверхности
|
||
*
|
||
* Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM
|
||
*/
|
||
public class CustomCharacterController : MonoBehaviour, ICharacterController
|
||
{
|
||
[Inject] private CharacterConfig _characterConfig; // Конфиг персонажа через DI
|
||
|
||
[SerializeField] private KinematicCharacterMotor motor; // Движок физического перемещения персонажа
|
||
|
||
[SerializeField] private Transform characterTransform; // Трансформ персонажа (с точкой отслеживания камеры)
|
||
|
||
// Подсистемы
|
||
private CharacterInputHandler _inputHandler;
|
||
private SlideMechanic _slideMechanic;
|
||
private WallClimbMechanic _wallClimbMechanic;
|
||
private WallLeanMechanic _wallLeanMechanic;
|
||
private GroundMovementSystem _groundMovement;
|
||
private AirMovementSystem _airMovement;
|
||
|
||
public void SetPosition(Vector3 position) => motor.SetPosition(position);
|
||
public void SetRotation(Quaternion rotation) => motor.SetRotation(rotation);
|
||
|
||
// Инициализация контроллера
|
||
// private void Awake()
|
||
// {
|
||
// // Инициализация подсистем с параметрами из конфига
|
||
// _inputHandler = new CharacterInputHandler(
|
||
// _characterConfig.sprintAngleThreshold
|
||
// );
|
||
// _slideMechanic = new SlideMechanic(
|
||
// _characterConfig.slideSpeed,
|
||
// _characterConfig.slideDuration,
|
||
// _characterConfig.slideCooldown
|
||
// );
|
||
// _wallClimbMechanic = new WallClimbMechanic(
|
||
// _characterConfig.wallClimbSpeedFactor,
|
||
// _characterConfig.minWallClimbSpeed,
|
||
// _characterConfig.wallDetectionAngleThreshold
|
||
// );
|
||
// _wallLeanMechanic = new WallLeanMechanic(
|
||
// _characterConfig.maxWallLeanAngle,
|
||
// _characterConfig.wallLeanSharpness,
|
||
// _characterConfig.wallLeanHeightScale
|
||
// );
|
||
// _groundMovement = new GroundMovementSystem(
|
||
// _characterConfig.maxStableMoveSpeed,
|
||
// _characterConfig.sprintMoveSpeed,
|
||
// _characterConfig.stableMovementSharpness,
|
||
// _characterConfig.jumpSpeed
|
||
// );
|
||
// _airMovement = new AirMovementSystem(_characterConfig.gravity);
|
||
// }
|
||
[Inject]
|
||
private void Construct(CharacterConfig characterConfig)
|
||
{
|
||
_characterConfig = characterConfig;
|
||
// Инициализация подсистем с параметрами из конфига
|
||
_inputHandler = new CharacterInputHandler(
|
||
_characterConfig.sprintAngleThreshold
|
||
);
|
||
_slideMechanic = new SlideMechanic(
|
||
_characterConfig.slideSpeed,
|
||
_characterConfig.slideDuration,
|
||
_characterConfig.slideCooldown
|
||
);
|
||
_wallClimbMechanic = new WallClimbMechanic(
|
||
_characterConfig.wallClimbSpeedFactor,
|
||
_characterConfig.minWallClimbSpeed,
|
||
_characterConfig.wallDetectionAngleThreshold
|
||
);
|
||
_wallLeanMechanic = new WallLeanMechanic(
|
||
_characterConfig.maxWallLeanAngle,
|
||
_characterConfig.wallLeanSharpness,
|
||
_characterConfig.wallLeanHeightScale
|
||
);
|
||
_groundMovement = new GroundMovementSystem(
|
||
_characterConfig.maxStableMoveSpeed,
|
||
_characterConfig.sprintMoveSpeed,
|
||
_characterConfig.stableMovementSharpness,
|
||
_characterConfig.jumpSpeed
|
||
);
|
||
_airMovement = new AirMovementSystem(_characterConfig.gravity);
|
||
|
||
|
||
motor.CharacterController = this; // Регистрация этого контроллера в движке
|
||
_inputHandler.FirstPersonCameraRotation =
|
||
motor.Transform.rotation; // Начальное вращение совпадает с персонажемс
|
||
_wallLeanMechanic.SetCharacterTransform(characterTransform);
|
||
}
|
||
// Текущий режим камеры для адаптации поведения
|
||
private CameraMode _currentCameraMode = CameraMode.FirstPerson;
|
||
|
||
// Инициализация контроллера
|
||
// private void Start()
|
||
// {
|
||
// motor.CharacterController = this; // Регистрация этого контроллера в движке
|
||
// _inputHandler.FirstPersonCameraRotation =
|
||
// motor.Transform.rotation; // Начальное вращение совпадает с персонажемс
|
||
// _wallLeanMechanic.SetCharacterTransform(characterTransform);
|
||
// }
|
||
|
||
// Обработка и преобразование входных данных от персонажа
|
||
public void SetInputs(ref CharacterInputs inputs)
|
||
{
|
||
// Обновляем таймер перезарядки слайда
|
||
_slideMechanic.TickCooldown(Time.deltaTime);
|
||
_wallClimbMechanic.TickCooldown(Time.deltaTime);
|
||
|
||
// Если активен слайд - игнорируем весь остальной ввод
|
||
if (_slideMechanic.IsSliding)
|
||
{
|
||
// Во время слайда не обрабатываем движение, прыжки и спринт
|
||
_inputHandler.MoveInputVector = Vector3.zero;
|
||
_inputHandler.LookInputVector = Vector3.one;
|
||
_inputHandler.IsSprinting = false;
|
||
_groundMovement.RequestJump(inputs.JumpPressed);
|
||
return;
|
||
}
|
||
|
||
// Сохраняем текущий режим камеры
|
||
_currentCameraMode = inputs.CameraMode;
|
||
|
||
// Обработка слайда
|
||
_slideMechanic.RequestSlide(inputs.SlidePressed);
|
||
|
||
// Обработка остального ввода
|
||
_inputHandler.ProcessInputs(ref inputs, motor);
|
||
|
||
// Передаём запрос прыжка напрямую в систему движения по земле
|
||
_groundMovement.RequestJump(inputs.JumpPressed);
|
||
}
|
||
|
||
// Обновление вращения персонажа на основе входных данных
|
||
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
||
{
|
||
if (_slideMechanic.IsSliding || _wallClimbMechanic.IsClimbingActive) return;
|
||
|
||
switch (_currentCameraMode)
|
||
{
|
||
// В FirstPerson режиме применяем вращение камеры напрямую к персонажу (только горизонталь)
|
||
case CameraMode.FirstPerson when _inputHandler.ShouldApplyCameraRotation:
|
||
// Немедленно применяем горизонтальное вращение камеры к персонажу
|
||
currentRotation = _inputHandler.FirstPersonCameraRotation;
|
||
break;
|
||
// В ThirdPerson режиме плавно поворачиваем персонаж в направлении движения
|
||
case CameraMode.ThirdPerson:
|
||
{
|
||
// Вращение применяется только если есть направление движения и разрешена резкость вращения
|
||
if (_inputHandler.LookInputVector.sqrMagnitude > 0 && _characterConfig.orientationSharpness > 0)
|
||
{
|
||
// Плавная интерполяция текущего направления к целевому с экспоненциальным сглаживанием
|
||
var smoothedLookInputDirection = Vector3.Slerp(
|
||
motor.CharacterForward,
|
||
_inputHandler.LookInputVector,
|
||
1f - Mathf.Exp(-_characterConfig.orientationSharpness * deltaTime)
|
||
).normalized;
|
||
|
||
// Установка нового вращения смотря в сглаженном направлении
|
||
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, motor.CharacterUp);
|
||
}
|
||
|
||
break;
|
||
}
|
||
// Заготовка на будущее (если понадобится)
|
||
case CameraMode.Static:
|
||
case CameraMode.Cinematic:
|
||
case CameraMode.Bogdan: // Скорее всего юзлес, но пускай будет
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Обновление скорости движения персонажа
|
||
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
||
{
|
||
// Если во время слайда запрошен прыжок — прерываем слайд
|
||
if (_slideMechanic.IsSliding && _groundMovement.JumpRequested)
|
||
{
|
||
_slideMechanic.EndSlide();
|
||
}
|
||
|
||
// Проверяем запрос слайда и запускаем его если можно
|
||
if (!_slideMechanic.IsSliding)
|
||
{
|
||
_slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation,
|
||
_inputHandler.LookInputVector);
|
||
}
|
||
// Если активен слайд - обрабатываем отдельно
|
||
else
|
||
{
|
||
_slideMechanic.HandleSlide(ref currentVelocity, deltaTime, motor);
|
||
return;
|
||
}
|
||
|
||
// Если персонаж находится на земле
|
||
if (motor.GroundingStatus.IsStableOnGround)
|
||
{
|
||
_groundMovement.UpdateVelocity(ref currentVelocity, _inputHandler, motor, deltaTime);
|
||
}
|
||
// Если персонаж не находится на земле
|
||
else
|
||
{
|
||
_airMovement.UpdateVelocity(ref currentVelocity, deltaTime);
|
||
// Отменяем прыжок (фикс проблемы с двойным прыжком после приземления)
|
||
_groundMovement.CancelJumpRequest();
|
||
}
|
||
}
|
||
|
||
// Метод вызывается перед обновлением персонажа (можно использовать для подготовки)
|
||
public void BeforeCharacterUpdate(float deltaTime)
|
||
{
|
||
}
|
||
|
||
// Метод вызывается после обработки столкновения с землей
|
||
public void PostGroundingUpdate(float deltaTime)
|
||
{
|
||
// Если во время слайда персонаж оторвался от земли - заканчиваем слайд
|
||
if (_slideMechanic.IsSliding && !motor.GroundingStatus.IsStableOnGround)
|
||
{
|
||
_slideMechanic.EndSlide();
|
||
}
|
||
|
||
if (motor.GroundingStatus.IsStableOnGround)
|
||
{
|
||
_wallClimbMechanic.ResetWallClimbSession();
|
||
}
|
||
}
|
||
|
||
// Метод вызывается после обновления персонажа
|
||
public void AfterCharacterUpdate(float deltaTime)
|
||
{
|
||
_wallLeanMechanic.UpdateLean(motor, deltaTime);
|
||
}
|
||
|
||
// Проверка валидности коллайдера для обработки столкновений
|
||
public bool IsColliderValidForCollisions(Collider coll)
|
||
{
|
||
return true; // Все коллайдеры считаются валидными для столкновений
|
||
}
|
||
|
||
// Обработка столкновения с землей (вызывается при приземлении)
|
||
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
||
ref HitStabilityReport hitStabilityReport)
|
||
{
|
||
}
|
||
|
||
// Обработка столкновения при движении (вызывается при ударе о препятствие)
|
||
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
||
ref HitStabilityReport hitStabilityReport)
|
||
{
|
||
if (_wallClimbMechanic.TryClimbWall(motor, hitNormal))
|
||
_wallLeanMechanic.OnWallContact(motor, hitNormal, motor.TransientPosition.y);
|
||
}
|
||
|
||
// Обработка отчета о стабильности столкновения
|
||
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
||
Vector3 atCharacterPosition,
|
||
Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
|
||
{
|
||
}
|
||
|
||
// Обработка дискретных столкновений (для триггеров и специальных коллайдеров)
|
||
public void OnDiscreteCollisionDetected(Collider hitCollider)
|
||
{
|
||
}
|
||
}
|
||
} |