Хуже, чем были раньше, но лучше, чем ничего. Плавность переходов имеется, и на том спасибо
275 lines
11 KiB
C#
275 lines
11 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 WallRunningMechanic _wallRunningMechanic;
|
|
private WallLeanMechanic _wallLeanMechanic;
|
|
private GroundMovementSystem _groundMovement;
|
|
private AirMovementSystem _airMovement;
|
|
|
|
public bool IsSliding => _slideMechanic.IsSliding;
|
|
public bool IsWallRunning => _wallRunningMechanic.IsWallRunning;
|
|
|
|
private float abilityBlockTimer;
|
|
private bool isAbilitiesBlocked;
|
|
|
|
private CharacterInputs _lastInputs;
|
|
|
|
public void SetPosition(Vector3 position) => motor.SetPosition(position);
|
|
public void SetRotation(Quaternion rotation) => motor.SetRotation(rotation);
|
|
|
|
[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
|
|
// );
|
|
_wallRunningMechanic = new WallRunningMechanic();
|
|
// Настройка параметров WallRunningMechanic (можно вынести в конфиг позже)
|
|
_wallRunningMechanic.MaxWallRunTime = 1.75f;
|
|
_wallRunningMechanic.WallRunSpeed = 15.0f;
|
|
_wallRunningMechanic.WallRunAcceleration = 25.0f;
|
|
_wallRunningMechanic.WallRunGravityScale = 0.25f;
|
|
_wallRunningMechanic.WallDetachImpulse = 7.0f;
|
|
_wallRunningMechanic.WallContactAngleThreshold = 45.0f;
|
|
|
|
_wallLeanMechanic = new WallLeanMechanic(
|
|
_characterConfig.leanAngle,
|
|
_characterConfig.leanThreshold,
|
|
_characterConfig.leanSmoothSpeed
|
|
);
|
|
_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.SetModel(characterTransform);
|
|
_wallRunningMechanic.Initialize(motor, _inputHandler);
|
|
}
|
|
|
|
// Текущий режим камеры для адаптации поведения
|
|
private CameraMode _currentCameraMode = CameraMode.FirstPerson;
|
|
|
|
public void SetInputs(ref CharacterInputs inputs)
|
|
{
|
|
_lastInputs = inputs;
|
|
// Обновляем таймеры механик
|
|
_slideMechanic.TickCooldown(Time.deltaTime);
|
|
// _wallClimbMechanic.TickCooldown(Time.deltaTime);
|
|
_wallRunningMechanic.Tick(Time.deltaTime);
|
|
|
|
// Если активен слайд - игнорируем весь остальной ввод
|
|
if (_slideMechanic.IsSliding)
|
|
{
|
|
_inputHandler.MoveInputVector = Vector3.zero;
|
|
_inputHandler.LookInputVector = Vector3.one;
|
|
_inputHandler.IsSprinting = false;
|
|
_groundMovement.RequestJump(inputs.JumpPressed);
|
|
return;
|
|
}
|
|
|
|
_currentCameraMode = inputs.CameraMode;
|
|
|
|
if (isAbilitiesBlocked)
|
|
{
|
|
abilityBlockTimer -= Time.deltaTime;
|
|
if (abilityBlockTimer <= 0)
|
|
{
|
|
isAbilitiesBlocked = false;
|
|
}
|
|
else
|
|
{
|
|
inputs.SprintPressed = false;
|
|
inputs.SlidePressed = false;
|
|
}
|
|
}
|
|
|
|
// Обработка слайда
|
|
_slideMechanic.RequestSlide(inputs.SlidePressed);
|
|
|
|
// Обработка остального ввода
|
|
_inputHandler.ProcessInputs(ref inputs, motor);
|
|
|
|
// Передаём запрос прыжка
|
|
_groundMovement.RequestJump(inputs.JumpPressed);
|
|
}
|
|
|
|
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
|
{
|
|
if (_slideMechanic.IsSliding || _wallRunningMechanic.IsWallRunning) return;
|
|
|
|
switch (_currentCameraMode)
|
|
{
|
|
case CameraMode.FirstPerson when _inputHandler.ShouldApplyCameraRotation:
|
|
currentRotation = _inputHandler.FirstPersonCameraRotation;
|
|
break;
|
|
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:
|
|
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;
|
|
}
|
|
|
|
// === WALL RUNNING: обработка состояния ===
|
|
if (_wallRunningMechanic.IsWallRunning)
|
|
{
|
|
// 🔄 Передаём актуальные данные для walljump
|
|
if (_groundMovement.JumpRequested) // или _lastInputs.JumpPressed — зависит от логики
|
|
{
|
|
_wallRunningMechanic.PerformWallJump(_lastInputs, _inputHandler.FirstPersonCameraRotation);
|
|
_groundMovement.CancelJumpRequest(); // сброс, чтобы не сработало дважды
|
|
}
|
|
else
|
|
{
|
|
_wallRunningMechanic.HandleWallRun(ref currentVelocity, _lastInputs, deltaTime);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Если персонаж находится на земле
|
|
if (motor.GroundingStatus.IsStableOnGround)
|
|
{
|
|
_groundMovement.UpdateVelocity(ref currentVelocity, _inputHandler, motor, deltaTime);
|
|
}
|
|
// Если персонаж не на земле — стандартное воздушное движение
|
|
else
|
|
{
|
|
_airMovement.UpdateVelocity(ref currentVelocity, deltaTime);
|
|
_groundMovement.CancelJumpRequest();
|
|
}
|
|
}
|
|
|
|
public void BlockAbilities(float duration)
|
|
{
|
|
isAbilitiesBlocked = true;
|
|
abilityBlockTimer = duration;
|
|
}
|
|
|
|
public void BeforeCharacterUpdate(float deltaTime)
|
|
{
|
|
}
|
|
|
|
public void PostGroundingUpdate(float deltaTime)
|
|
{
|
|
if (_slideMechanic.IsSliding && !motor.GroundingStatus.IsStableOnGround)
|
|
{
|
|
_slideMechanic.EndSlide();
|
|
}
|
|
}
|
|
|
|
public void AfterCharacterUpdate(float deltaTime)
|
|
{
|
|
_wallLeanMechanic.Update(
|
|
_wallRunningMechanic.IsWallRunning,
|
|
_wallRunningMechanic.GetWallNormal(),
|
|
motor.CharacterForward
|
|
);
|
|
}
|
|
|
|
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 (_wallRunningMechanic.TryStartWallRun(hitNormal, hitPoint))
|
|
{
|
|
}
|
|
}
|
|
|
|
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
|
|
Vector3 atCharacterPosition,
|
|
Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
|
|
{
|
|
}
|
|
|
|
public void OnDiscreteCollisionDetected(Collider hitCollider)
|
|
{
|
|
}
|
|
}
|
|
} |