From 5bf468673fcdc61a26c0327353859c2df75ab564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C5=A1a=20Ceviel?= <39222425+MrTorex@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:16:24 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=97=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=D0=B0,=20=D1=84=D0=B8=D0=BA=D1=81=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B3=D0=B0=20=D1=81=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=BC=20=D1=81=D0=BB=D0=B0=D0=B9=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Теперь всё поделено по логическим классам, чтобы было помодульно, а также если выполняется запрос на слайд в момент, когда слайд не может быть выполнен, запрос отменяется --- .../Code/Gameplay/Character/Camera.meta | 3 + .../Character/{ => Camera}/CharacterCamera.cs | 18 +- .../{ => Camera}/CharacterCamera.cs.meta | 0 .../Code/Gameplay/Character/Character.cs | 118 ----- .../Code/Gameplay/Character/Controller.meta | 3 + .../Character/Controller/Abilities.meta | 3 + .../Controller/Abilities/SlideMechanic.cs | 122 +++++ .../Abilities/SlideMechanic.cs.meta | 3 + .../Controller/Abilities/WallClimbMechanic.cs | 51 ++ .../Abilities/WallClimbMechanic.cs.meta | 3 + .../Controller/CustomCharacterController.cs | 213 +++++++++ .../CustomCharacterController.cs.meta | 0 .../Gameplay/Character/Controller/Input.meta | 3 + .../Controller/Input/CharacterInputHandler.cs | 115 +++++ .../Input/CharacterInputHandler.cs.meta | 3 + .../Character/Controller/Movement.meta | 3 + .../Controller/Movement/AirMovementSystem.cs | 26 + .../Movement/AirMovementSystem.cs.meta | 3 + .../Movement/GroundMovementSystem.cs | 87 ++++ .../Movement/GroundMovementSystem.cs.meta | 3 + .../Assets/Code/Gameplay/Character/Core.meta | 3 + .../Gameplay/Character/Core/CameraMode.cs | 17 + .../Character/Core/CameraMode.cs.meta | 3 + .../Code/Gameplay/Character/Core/Character.cs | 117 +++++ .../Character/{ => Core}/Character.cs.meta | 0 .../Character/Core/CharacterInputs.cs | 20 + .../Character/Core/CharacterInputs.cs.meta | 3 + .../Character/CustomCharacterController.cs | 443 ------------------ 28 files changed, 809 insertions(+), 577 deletions(-) create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Camera.meta rename Game/CourierOld/Assets/Code/Gameplay/Character/{ => Camera}/CharacterCamera.cs (94%) rename Game/CourierOld/Assets/Code/Gameplay/Character/{ => Camera}/CharacterCamera.cs.meta (100%) delete mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Character.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs rename Game/CourierOld/Assets/Code/Gameplay/Character/{ => Controller}/CustomCharacterController.cs.meta (100%) create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Core.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs.meta create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs rename Game/CourierOld/Assets/Code/Gameplay/Character/{ => Core}/Character.cs.meta (100%) create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs create mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs.meta delete mode 100644 Game/CourierOld/Assets/Code/Gameplay/Character/CustomCharacterController.cs diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Camera.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera.meta new file mode 100644 index 0000000..4293bf3 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b36ec6a31fb74d608232394fd5df657c +timeCreated: 1761570832 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/CharacterCamera.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs similarity index 94% rename from Game/CourierOld/Assets/Code/Gameplay/Character/CharacterCamera.cs rename to Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs index b54bf61..9278075 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/CharacterCamera.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs @@ -1,23 +1,9 @@ using System; +using Code.Gameplay.Character.Core; using UnityEngine; -namespace Code.Gameplay.Character +namespace Code.Gameplay.Character.Camera { - /* - * Перечисление режимов работы камеры - * Static - статичная камера без следования за целью - * FirstPerson - камера от первого лица (встроена в модель персонажа) - * ThirdPerson - камера от третьего лица (следует за персонажем на расстоянии) - * Cinematic - кинематографическая камера для скриптовых сцен - */ - public enum CameraMode - { - Static, - FirstPerson, - ThirdPerson, - Cinematic - } - /* * Скрипт, отвечающий за поведение камеры при передвижении * Поддерживает как 3rd-person, так и 1st-person режимы камеры diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/CharacterCamera.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs.meta similarity index 100% rename from Game/CourierOld/Assets/Code/Gameplay/Character/CharacterCamera.cs.meta rename to Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs.meta diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Character.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Character.cs deleted file mode 100644 index f55d67f..0000000 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Character.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Code.Gameplay.Character; -using Code.Input; -using Reflex.Attributes; -using UnityEngine; - -/* - * Главный скрипт персонажа, объединяющий камеру и контроллер персонажа - * Обрабатывает ввод пользователя и распределяет его между компонентами - * Синхронизирует состояние камеры и персонажа - * Управляет переключением режимов камеры через Input System - */ -public class Character : MonoBehaviour -{ - [SerializeField] - private CharacterCamera characterCamera; // Компонент камеры персонажа - [SerializeField] - private CustomCharacterController customCharacterController; // Компонент управления персонажем - [SerializeField] - private Transform cameraFollowPoint; // Точка следования камеры (уровень глаз для FirstPerson) - - [Inject] - private IInputService _inputService; // Сервис ввода через DI - - // Флаг для отслеживания переключения режима камеры (чтобы избежать многократных срабатываний) - private bool _cameraModeToggleHandled; - - // Инициализация персонажа - private void Start() - { - // Настройка камеры для следования за точкой камеры - characterCamera.SetFollowTransform(cameraFollowPoint); - - // Включение Gameplay мапа ввода - _inputService.EnableMap(ActionMappings.Gameplay); - } - - // Обработка ввода для камеры (вращение и зум) - private void HandleCameraInput() - { - // Получаем данные ввода из Input System - var lookInput = _inputService.LookInput; - var scrollInput = _inputService.ZoomInput; - - // Передаем данные в камеру - characterCamera.UpdateWithInput(Time.deltaTime, scrollInput, - new Vector3(lookInput.x, lookInput.y, 0)); - } - - // Обработка ввода для движения персонажа - private void HandleCharacterInput() - { - // Создаем структуру ввода с полной информацией о камере - var inputs = new CharacterInputs(); - - // Получаем данные движения из Input System - var moveInput = _inputService.MoveInput; - inputs.MoveAxisForward = moveInput.y; // Vertical ось (W/S или Up/Down) - inputs.MoveAxisRight = moveInput.x; // Horizontal ось (A/D или Left/Right) - inputs.JumpPressed = _inputService.JumpPressed; // Прыжки (Space) - inputs.SprintPressed = _inputService.SprintPressed; // Спринт (Shift) - inputs.SlidePressed = _inputService.SlidePressed; // Слайд (Ctrl) - - // Передаем информацию о камере для адаптивного поведения - inputs.CameraRotation = characterCamera.transform.rotation; - inputs.CameraMode = characterCamera.CurrentMode; - inputs.CameraPlanarDirection = characterCamera.PlanarDirection; - - // Передаем ввод в контроллер персонажа - customCharacterController.SetInputs(ref inputs); - } - - // Обработка переключения режима камеры через Input System - private void HandleCameraModeInput() - { - // Получаем состояние переключения режима камеры из Input System - var togglePressed = _inputService.ToggleCameraModePressed; - - switch (togglePressed) - { - // Обрабатываем нажатие только один раз при первом обнаружении - case true when !_cameraModeToggleHandled: - ToggleCameraMode(); - _cameraModeToggleHandled = true; - break; - // Сбрасываем флаг когда кнопка отпущена - case false: - _cameraModeToggleHandled = false; - break; - } - } - - // Метод для переключения режимов камеры - private void ToggleCameraMode() - { - var currentMode = characterCamera.CurrentMode; - var newMode = currentMode == CameraMode.FirstPerson ? - CameraMode.ThirdPerson : CameraMode.FirstPerson; - - characterCamera.SetCameraMode(newMode); - } - - // Обновление логики каждый кадр - private void Update() - { - // Обработка ввода для движения в Update (физика) - HandleCharacterInput(); - - // Обработка переключения режима камеры - HandleCameraModeInput(); - } - - // Обновление после всех вычислений (идеально для камеры) - private void LateUpdate() - { - // Обработка ввода для камеры в LateUpdate (после движения персонажа) - HandleCameraInput(); - } -} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller.meta new file mode 100644 index 0000000..072516c --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fb497e2e267044a7a35f80b47c88c2c9 +timeCreated: 1761570825 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities.meta new file mode 100644 index 0000000..e682207 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d3be946108b44b33b99d2e3e4a75d47f +timeCreated: 1761571338 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs new file mode 100644 index 0000000..2af81c9 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs @@ -0,0 +1,122 @@ +using KinematicCharacterController; +using UnityEngine; +using Code.Gameplay.Character.Core; + +namespace Code.Gameplay.Character.Controller.Abilities +{ + /* + * Механика слайда для персонажа + * Отвечает за управление состоянием, таймерами и скоростью слайда + */ + public class SlideMechanic + { + // Скорость слайда + private readonly float _slideSpeed; + // Длительность слайда в секундах + private readonly float _slideDuration; + // Время перезарядки слайда + private readonly float _slideCooldown; + + // Таймер слайда + private float _slideTimer; + // Таймер перезарядки слайда + private float _slideCooldownTimer; + // Направление слайда + private Vector3 _slideDirection; + // Флаг запроса слайда + private bool _slideRequested; + + // Свойство-флаг, активен ли слайд + public bool IsSliding { get; private set; } + + // Конструктор механики слайда + public SlideMechanic(float slideSpeed, float slideDuration, float slideCooldown) + { + _slideSpeed = slideSpeed; + _slideDuration = slideDuration; + _slideCooldown = slideCooldown; + } + + // Обновляем таймер перезарядки слайда + public void TickCooldown(float deltaTime) + { + if (_slideCooldownTimer > 0f) + { + _slideCooldownTimer -= deltaTime; + } + } + + // Обработка слайда + public void RequestSlide(bool slidePressed) + { + if (slidePressed && !_slideRequested && _slideCooldownTimer <= 0f) + { + _slideRequested = true; + } + } + + // Начало слайда + public void TryStartSlide(KinematicCharacterMotor motor, + CameraMode currentCameraMode, + Quaternion firstPersonCameraRotation, + Vector3 lookInputVector) + { + // Проверяем условия для старта слайда + if (!_slideRequested || !motor.GroundingStatus.IsStableOnGround || _slideCooldownTimer > 0f) + { + _slideRequested = false; + return; + } + + IsSliding = true; + _slideTimer = _slideDuration; + + // НАПРАВЛЕНИЕ СЛАЙДА - ВСЕГДА ВПЕРЕД ПО КАМЕРЕ + // Используем направление камеры, но проецируем на горизонтальную плоскость + var slideDir = currentCameraMode == CameraMode.FirstPerson ? + firstPersonCameraRotation * Vector3.forward : + lookInputVector; + + // Проецируем на горизонтальную плоскость и нормализуем + _slideDirection = Vector3.ProjectOnPlane(slideDir, motor.CharacterUp).normalized; + + // Если направление нулевое (персонаж стоит на месте), используем направление вперед + if (_slideDirection.sqrMagnitude < 0.01f) + { + _slideDirection = motor.CharacterForward; + } + + // Устанавливаем перезарядку + _slideCooldownTimer = _slideCooldown; + _slideRequested = false; + } + + // Обработка слайда + public void HandleSlide(ref Vector3 currentVelocity, float deltaTime, KinematicCharacterMotor motor) + { + // Уменьшаем таймер слайда + _slideTimer -= deltaTime; + + // Если время слайда вышло, персонаж в воздухе - заканчиваем слайд + if (_slideTimer <= 0f || !motor.GroundingStatus.IsStableOnGround) + { + EndSlide(); + return; + } + + // Устанавливаем скорость слайда вперед + // Используем прямую установку скорости вместо сложных вычислений + currentVelocity = _slideDirection * _slideSpeed; + + // Добавляем небольшую вертикальную компоненту для лучшего скольжения по неровностям + currentVelocity += motor.CharacterUp * 2f; + } + + // Завершение слайда + public void EndSlide() + { + IsSliding = false; + _slideTimer = 0f; + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs.meta new file mode 100644 index 0000000..0520e3a --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/SlideMechanic.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5904fa03346e4751967d57500ab3d1cf +timeCreated: 1761571347 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs new file mode 100644 index 0000000..decac53 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs @@ -0,0 +1,51 @@ +using KinematicCharacterController; +using UnityEngine; + +namespace Code.Gameplay.Character.Controller.Abilities +{ + /* + * Механика залазания на стены + * Обрабатывает столкновения и применяет вертикальный импульс при ударе о стену с достаточной скоростью + */ + public class WallClimbMechanic + { + // Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену + private readonly float _wallClimbSpeedFactor; + // Минимальная горизонтальная скорость для начала залазания на стену + private readonly float _minWallClimbSpeed; + + // Конструктор механики залазания на стену + public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed) + { + _wallClimbSpeedFactor = wallClimbSpeedFactor; + _minWallClimbSpeed = minWallClimbSpeed; + } + + // Обработка столкновения при движении (вызывается при ударе о препятствие) + public void OnMovementHit(KinematicCharacterMotor motor, Vector3 hitNormal) + { + // Проверяем, что это вертикальная или почти вертикальная поверхность (стена) + var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp); + if (Mathf.Abs(dotWithUp) > 0.1f) return; // Не стена — это пол или потолок + + // Получаем текущую скорость персонажа + var currentVelocity = motor.Velocity; + + // Проекция скорости на направление к стене (отрицательная, если летим в стену) + var velocityIntoWall = Vector3.Dot(currentVelocity, -hitNormal); + + // Если движемся в стену с достаточной скоростью + if (velocityIntoWall > _minWallClimbSpeed) + { + // Преобразуем часть горизонтальной скорости в вертикальную + var verticalBoost = velocityIntoWall * _wallClimbSpeedFactor; + + // Добавляем вертикальный импульс через BaseVelocity + motor.BaseVelocity += motor.CharacterUp * verticalBoost; + + // Предотвращаем заземление и повторное применение в этом кадре + motor.ForceUnground(); + } + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs.meta new file mode 100644 index 0000000..fdf7d59 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Abilities/WallClimbMechanic.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cfce19738ff34be286c953e33e515c46 +timeCreated: 1761571364 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs new file mode 100644 index 0000000..96b2763 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs @@ -0,0 +1,213 @@ +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 UnityEngine; + +namespace Code.Gameplay.Character.Controller +{ + /* + * Скрипт, отвечающий за передвижение персонажа + * Реализует физическое движение персонажа с использованием Kinematic Character Controller + * Обрабатывает ввод пользователя, применяет движение и вращение с учетом поверхности + * + * Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM + */ + public class CustomCharacterController : MonoBehaviour, ICharacterController + { + [SerializeField] + private KinematicCharacterMotor motor; // Движок физического перемещения персонажа + + [SerializeField] + private float + maxStableMoveSpeed = 8f, // Максимальная скорость движения по устойчивой поверхности + sprintMoveSpeed = 12f, // Максимальная скорость спринта + slideSpeed = 20f, // Скорость слайда + slideDuration = 0.8f, // Длительность слайда в секундах + slideCooldown = 1.5f, // Время перезарядки слайда + stableMovementSharpness = 15f, // Резкость изменения скорости движения (чем выше - тем резче) + orientationSharpness = 10f, // Резкость изменения вращения персонажа (чем выше - тем резче) + jumpSpeed = 10f, // Скорость прыжка + wallClimbSpeedFactor = 0.8f, // Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену + minWallClimbSpeed = 9f; // Минимальная горизонтальная скорость для начала залазания на стену + + [SerializeField] + private Vector3 gravity = new(0, -30f, 0); // Вектор, отвечающий за применение гравитации + + // Подсистемы + private readonly CharacterInputHandler _inputHandler; + private readonly SlideMechanic _slideMechanic; + private readonly WallClimbMechanic _wallClimbMechanic; + private readonly GroundMovementSystem _groundMovement; + private readonly AirMovementSystem _airMovement; + + // Конструктор без параметров, инициализирующий контроллер + public CustomCharacterController() + { + _inputHandler = new CharacterInputHandler(); + _slideMechanic = new SlideMechanic(slideSpeed, slideDuration, slideCooldown); + _wallClimbMechanic = new WallClimbMechanic(wallClimbSpeedFactor, minWallClimbSpeed); + _groundMovement = new GroundMovementSystem(maxStableMoveSpeed, sprintMoveSpeed, stableMovementSharpness, jumpSpeed); + _airMovement = new AirMovementSystem(gravity); + } + + // Текущий режим камеры для адаптации поведения + private CameraMode _currentCameraMode = CameraMode.ThirdPerson; + + // Инициализация контроллера + private void Start() + { + motor.CharacterController = this; // Регистрация этого контроллера в движке + _inputHandler.FirstPersonCameraRotation = motor.Transform.rotation; // Начальное вращение совпадает с персонажем + } + + // Обработка и преобразование входных данных от персонажа + public void SetInputs(ref CharacterInputs inputs) + { + // Обновляем таймер перезарядки слайда + _slideMechanic.TickCooldown(Time.deltaTime); + + // Если активен слайд - игнорируем весь остальной ввод + if (_slideMechanic.IsSliding) + { + // Во время слайда не обрабатываем движение, прыжки и спринт + _inputHandler.MoveInputVector = Vector3.zero; + _inputHandler.LookInputVector = Vector3.one; + _inputHandler.IsSprinting = false; + _groundMovement.CancelJumpRequest(); + 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) return; + + switch (_currentCameraMode) + { + // В FirstPerson режиме применяем вращение камеры напрямую к персонажу (только горизонталь) + case CameraMode.FirstPerson when _inputHandler.ShouldApplyCameraRotation: + // Немедленно применяем горизонтальное вращение камеры к персонажу + currentRotation = _inputHandler.FirstPersonCameraRotation; + break; + // В ThirdPerson режиме плавно поворачиваем персонаж в направлении движения + case CameraMode.ThirdPerson: + { + // Вращение применяется только если есть направление движения и разрешена резкость вращения + if (_inputHandler.LookInputVector.sqrMagnitude > 0 && orientationSharpness > 0) + { + // Плавная интерполяция текущего направления к целевому с экспоненциальным сглаживанием + var smoothedLookInputDirection = Vector3.Slerp( + motor.CharacterForward, + _inputHandler.LookInputVector, + 1f - Mathf.Exp(-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) + { + _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(); + } + } + + // Метод вызывается после обновления персонажа (можно использовать для пост-обработки) + public void AfterCharacterUpdate(float 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) + { + _wallClimbMechanic.OnMovementHit(motor, hitNormal); + } + + // Обработка отчета о стабильности столкновения + public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, + Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) + { + } + + // Обработка дискретных столкновений (для триггеров и специальных коллайдеров) + public void OnDiscreteCollisionDetected(Collider hitCollider) + { + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/CustomCharacterController.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs.meta similarity index 100% rename from Game/CourierOld/Assets/Code/Gameplay/Character/CustomCharacterController.cs.meta rename to Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs.meta diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input.meta new file mode 100644 index 0000000..afd584d --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e50ac1a55a4f4706b6385746eb9c6d1f +timeCreated: 1761571269 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs new file mode 100644 index 0000000..4e2f21c --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs @@ -0,0 +1,115 @@ +using UnityEngine; +using Code.Gameplay.Character.Core; +using KinematicCharacterController; + +namespace Code.Gameplay.Character.Controller.Input +{ + /* + * Обработчик входных данных для персонажа + * Преобразует сырые данные ввода в векторы движения и направления взгляда + */ + public class CharacterInputHandler + { + // Векторы движения и направления взгляда + public Vector3 MoveInputVector { get; set; } + public Vector3 LookInputVector { get; set; } + + // Флаг-свойство указывающий, что активен режим спринта + public bool IsSprinting { get; set; } + + // Вращение камеры в FirstPerson режиме для синхронизации (только горизонтальная компонента) + public Quaternion FirstPersonCameraRotation { get; set; } + + // Флаг-свойство указывающий, что нужно применить вращение камеры к персонажу + public bool ShouldApplyCameraRotation { get; private set; } + + // Обработка и преобразование входных данных от персонажа + public void ProcessInputs(ref CharacterInputs inputs, KinematicCharacterMotor motor) + { + // Сохраняем текущий режим камеры + var currentCameraMode = inputs.CameraMode; + + // Обработка спринта + IsSprinting = inputs is { SprintPressed: true, MoveAxisForward: > 0.1f }; // Спринт только при движении вперёд + + // Нормализация вектора движения и ограничение его величины до 1 + var moveInputVector = + Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0, inputs.MoveAxisForward), 1f); + + // Раздельная обработка в зависимости от режима камеры + if (currentCameraMode == CameraMode.FirstPerson) + { + HandleFirstPersonInputs(ref inputs, moveInputVector, motor); + } + else + { + HandleThirdPersonInputs(ref inputs, moveInputVector, motor); + } + } + + // Обработка ввода для FirstPerson режима + private void HandleFirstPersonInputs(ref CharacterInputs inputs, Vector3 moveInputVector, KinematicCharacterMotor motor) + { + // Сохраняем вращение камеры для применения в UpdateRotation (только горизонтальная компонента) + FirstPersonCameraRotation = ExtractHorizontalRotation(inputs.CameraRotation, motor); + ShouldApplyCameraRotation = true; + + // В FirstPerson режиме движение всегда относительно направления камеры + // Получаем направление камеры в горизонтальной плоскости + var cameraPlanarDirection = inputs.CameraPlanarDirection; + + // Создание вращения на основе направления камеры + var cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, motor.CharacterUp); + + // Преобразование локального ввода в мировые координаты относительно камеры + MoveInputVector = cameraPlanarRotation * moveInputVector; + + // В FirstPerson режиме направление взгляда всегда совпадает с направлением камеры (горизонтально) + LookInputVector = cameraPlanarDirection; + } + + // Обработка ввода для ThirdPerson режима + private void HandleThirdPersonInputs(ref CharacterInputs inputs, Vector3 moveInputVector, KinematicCharacterMotor motor) + { + // Сбрасываем флаг применения вращения камеры + ShouldApplyCameraRotation = false; + + // Получение направления камеры в горизонтальной плоскости (игнорируя наклон) + var cameraPlanarDirection = inputs.CameraPlanarDirection; + + // Резервный расчет направления, если основное направление параллельно вертикали + if (cameraPlanarDirection.sqrMagnitude == 0f) + { + cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, motor.CharacterUp); + } + + // Создание вращения на основе направления камеры + var cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, motor.CharacterUp); + + // Преобразование локального ввода в мировые координаты относительно камеры + MoveInputVector = cameraPlanarRotation * moveInputVector; + + // Направление взгляда совпадает с направлением движения (нормализованное) + LookInputVector = MoveInputVector.normalized; + } + + // Извлечение только горизонтальной компоненты вращения (игнорируя наклон камеры) + private static Quaternion ExtractHorizontalRotation(Quaternion cameraRotation, KinematicCharacterMotor motor) + { + // Получаем вектор направления камеры + var cameraForward = cameraRotation * Vector3.forward; + + // Проецируем на горизонтальную плоскость + var horizontalForward = Vector3.ProjectOnPlane(cameraForward, motor.CharacterUp).normalized; + + // Если проекция вырождена, используем текущее направление персонажа + if (horizontalForward.sqrMagnitude < 0.01f) + { + horizontalForward = motor.CharacterForward; + } + + // Создаем вращение только по горизонтали + return Quaternion.LookRotation(horizontalForward, motor.CharacterUp); + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs.meta new file mode 100644 index 0000000..c904140 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Input/CharacterInputHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 513b50847de24c619c380c15e084e932 +timeCreated: 1761571281 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement.meta new file mode 100644 index 0000000..4637a28 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b3dfd8f0dbeb4e8690e11049cb7e2f55 +timeCreated: 1761571298 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs new file mode 100644 index 0000000..71676cc --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs @@ -0,0 +1,26 @@ +using UnityEngine; + +namespace Code.Gameplay.Character.Controller.Movement +{ + /* + * Система движения персонажа в воздухе + */ + public class AirMovementSystem + { + // Вектор, отвечающий за применение гравитации + private readonly Vector3 _gravity; + + // Конструктор системы движения в воздухе + public AirMovementSystem(Vector3 gravity) + { + _gravity = gravity; + } + + // Обновление скорости движения персонажа в воздухе + public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) + { + // Применяем гравитацию + currentVelocity += _gravity * deltaTime; + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs.meta new file mode 100644 index 0000000..53aecf8 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/AirMovementSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5ea8841f02ff4e17ab8ebd8c7a949f6f +timeCreated: 1761571326 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs new file mode 100644 index 0000000..767c0b8 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs @@ -0,0 +1,87 @@ +using UnityEngine; +using Code.Gameplay.Character.Controller.Input; +using KinematicCharacterController; + +namespace Code.Gameplay.Character.Controller.Movement +{ + /* + * Система движения персонажа по устойчивой поверхности + */ + public class GroundMovementSystem + { + // Максимальная скорость движения по устойчивой поверхности + private readonly float _maxStableMoveSpeed; + // Максимальная скорость спринта + private readonly float _sprintMoveSpeed; + // Резкость изменения скорости движения (чем выше - тем резче) + private readonly float _stableMovementSharpness; + // Скорость прыжка + private readonly float _jumpSpeed; + // Флаг указывающий, что был запрошен прыжок + private bool _jumpRequested; + + // Конструктор системы движения на земле + public GroundMovementSystem(float maxStableMoveSpeed, float sprintMoveSpeed, float stableMovementSharpness, float jumpSpeed) + { + _maxStableMoveSpeed = maxStableMoveSpeed; + _sprintMoveSpeed = sprintMoveSpeed; + _stableMovementSharpness = stableMovementSharpness; + _jumpSpeed = jumpSpeed; + } + + // Метод для установки запроса на прыжок извне + public void RequestJump(bool jumpPressed) + { + if (jumpPressed) + _jumpRequested = true; + } + + // Метод для отмены запроса на прыжок извне + public void CancelJumpRequest() + { + _jumpRequested = false; + } + + // Обновление скорости движения персонажа на земле + public void UpdateVelocity( + ref Vector3 currentVelocity, + CharacterInputHandler inputHandler, + KinematicCharacterMotor motor, + float deltaTime) + { + // Сохранение текущей величины скорости + var currentVelocityMagnitude = currentVelocity.magnitude; + + // Получение нормали поверхности для корректного движения по наклонным плоскостям + var effectiveGroundNormal = motor.GroundingStatus.GroundNormal; + + // Переориентация текущей скорости вдоль поверхности + currentVelocity = motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) * + currentVelocityMagnitude; + + // Переориентация входного вектора движения вдоль поверхности + var inputRight = Vector3.Cross(inputHandler.MoveInputVector, motor.CharacterUp); + var reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * + inputHandler.MoveInputVector.magnitude; + + // Расчет целевой скорости движения с учетом ограничения максимальной скорости и спринта + var targetSpeed = inputHandler.IsSprinting ? _sprintMoveSpeed : _maxStableMoveSpeed; + var targetMovementVelocity = reorientedInput * targetSpeed; + + // Плавная интерполяция текущей скорости к целевой с экспоненциальным сглаживанием + currentVelocity = Vector3.Lerp( + currentVelocity, + targetMovementVelocity, + 1f - Mathf.Exp(-_stableMovementSharpness * deltaTime) + ); + + // Если запрошен прыжок + if (_jumpRequested) + { + currentVelocity += motor.CharacterUp * _jumpSpeed - Vector3.Project(currentVelocity, motor.CharacterUp); + _jumpRequested = false; // Сброс сразу после применения + motor.ForceUnground(); + } + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs.meta new file mode 100644 index 0000000..ad5d533 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 21cd95513cd44d52b6ae014403f9e3d1 +timeCreated: 1761571312 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Core.meta new file mode 100644 index 0000000..fb072d7 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4028bee0a1774348bb81e0c56e400ddc +timeCreated: 1761570805 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs new file mode 100644 index 0000000..92dfaf6 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs @@ -0,0 +1,17 @@ +namespace Code.Gameplay.Character.Core +{ + /* + * Перечисление режимов работы камеры + * Static - статичная камера без следования за целью + * FirstPerson - камера от первого лица (встроена в модель персонажа) + * ThirdPerson - камера от третьего лица (следует за персонажем на расстоянии) + * Cinematic - кинематографическая камера для скриптовых сцен + */ + public enum CameraMode + { + Static, + FirstPerson, + ThirdPerson, + Cinematic + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs.meta new file mode 100644 index 0000000..1ae16fb --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5a49105845a7435b9179e402fa7b8e30 +timeCreated: 1761570884 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs new file mode 100644 index 0000000..f0b82be --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs @@ -0,0 +1,117 @@ +using Code.Gameplay.Character.Camera; +using Code.Gameplay.Character.Controller; +using Code.Input; +using Reflex.Attributes; +using UnityEngine; + +namespace Code.Gameplay.Character.Core +{ +/* + * Главный скрипт персонажа, объединяющий камеру и контроллер персонажа + * Обрабатывает ввод пользователя и распределяет его между компонентами + * Синхронизирует состояние камеры и персонажа + * Управляет переключением режимов камеры через Input System + */ + public class Character : MonoBehaviour + { + [SerializeField] private CharacterCamera characterCamera; // Компонент камеры персонажа + [SerializeField] private CustomCharacterController customCharacterController; // Компонент управления персонажем + [SerializeField] private Transform cameraFollowPoint; // Точка следования камеры (уровень глаз для FirstPerson) + + [Inject] private IInputService _inputService; // Сервис ввода через DI + + // Флаг для отслеживания переключения режима камеры (чтобы избежать многократных срабатываний) + private bool _cameraModeToggleHandled; + + // Инициализация персонажа + private void Start() + { + // Настройка камеры для следования за точкой камеры + characterCamera.SetFollowTransform(cameraFollowPoint); + + // Включение Gameplay мапа ввода + _inputService.EnableMap(ActionMappings.Gameplay); + } + + // Обработка ввода для камеры (вращение и зум) + private void HandleCameraInput() + { + // Получаем данные ввода из Input System + var lookInput = _inputService.LookInput; + var scrollInput = _inputService.ZoomInput; + + // Передаем данные в камеру + characterCamera.UpdateWithInput(Time.deltaTime, scrollInput, + new Vector3(lookInput.x, lookInput.y, 0)); + } + + // Обработка ввода для движения персонажа + private void HandleCharacterInput() + { + // Создаем структуру ввода с полной информацией о камере + var inputs = new CharacterInputs(); + + // Получаем данные движения из Input System + var moveInput = _inputService.MoveInput; + inputs.MoveAxisForward = moveInput.y; // Vertical ось (W/S или Up/Down) + inputs.MoveAxisRight = moveInput.x; // Horizontal ось (A/D или Left/Right) + inputs.JumpPressed = _inputService.JumpPressed; // Прыжки (Space) + inputs.SprintPressed = _inputService.SprintPressed; // Спринт (Shift) + inputs.SlidePressed = _inputService.SlidePressed; // Слайд (Ctrl) + + // Передаем информацию о камере для адаптивного поведения + inputs.CameraRotation = characterCamera.transform.rotation; + inputs.CameraMode = characterCamera.CurrentMode; + inputs.CameraPlanarDirection = characterCamera.PlanarDirection; + + // Передаем ввод в контроллер персонажа + customCharacterController.SetInputs(ref inputs); + } + + // Обработка переключения режима камеры через Input System + private void HandleCameraModeInput() + { + // Получаем состояние переключения режима камеры из Input System + var togglePressed = _inputService.ToggleCameraModePressed; + + switch (togglePressed) + { + // Обрабатываем нажатие только один раз при первом обнаружении + case true when !_cameraModeToggleHandled: + ToggleCameraMode(); + _cameraModeToggleHandled = true; + break; + // Сбрасываем флаг когда кнопка отпущена + case false: + _cameraModeToggleHandled = false; + break; + } + } + + // Метод для переключения режимов камеры + private void ToggleCameraMode() + { + var currentMode = characterCamera.CurrentMode; + var newMode = currentMode == CameraMode.FirstPerson ? CameraMode.ThirdPerson : CameraMode.FirstPerson; + + characterCamera.SetCameraMode(newMode); + } + + // Обновление логики каждый кадр + private void Update() + { + // Обработка ввода для движения в Update (физика) + HandleCharacterInput(); + + // Обработка переключения режима камеры + HandleCameraModeInput(); + } + + // Обновление после всех вычислений (идеально для камеры) + private void LateUpdate() + { + // Обработка ввода для камеры в LateUpdate (после движения персонажа) + HandleCameraInput(); + } + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Character.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs.meta similarity index 100% rename from Game/CourierOld/Assets/Code/Gameplay/Character/Character.cs.meta rename to Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs.meta diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs new file mode 100644 index 0000000..3557d5c --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace Code.Gameplay.Character.Core +{ + /* + * Структура для хранения входных данных от персонажа + * Содержит информацию о движении и состоянии камеры + */ + public struct CharacterInputs + { + public float MoveAxisForward; // Ввод движения вперед/назад (W/S или Up/Down) + public float MoveAxisRight; // Ввод движения влево/вправо (A/D или Left/Right) + public Quaternion CameraRotation; // Текущее вращение камеры для определения направления движения + public CameraMode CameraMode; // Текущий режим камеры для адаптации поведения + public Vector3 CameraPlanarDirection; // Направление камеры в горизонтальной плоскости + public bool JumpPressed; // Флаг указывающий, нажата ли кнопка прыжка + public bool SprintPressed; // Флаг указывающий, нажата ли кнопка спринта + public bool SlidePressed; // Флаг указывающий, нажата ли кнопка слайда + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs.meta b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs.meta new file mode 100644 index 0000000..3154df5 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CharacterInputs.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c3edde7108664ecc9b84a8096a3dec93 +timeCreated: 1761570939 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/CustomCharacterController.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/CustomCharacterController.cs deleted file mode 100644 index 631052e..0000000 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/CustomCharacterController.cs +++ /dev/null @@ -1,443 +0,0 @@ -using KinematicCharacterController; -using UnityEngine; - -namespace Code.Gameplay.Character -{ - /* - * Структура для хранения входных данных от персонажа - * Содержит информацию о движении и состоянии камеры - */ - public struct CharacterInputs - { - public float MoveAxisForward; // Ввод движения вперед/назад (W/S или Up/Down) - public float MoveAxisRight; // Ввод движения влево/вправо (A/D или Left/Right) - public Quaternion CameraRotation; // Текущее вращение камеры для определения направления движения - public CameraMode CameraMode; // Текущий режим камеры для адаптации поведения - public Vector3 CameraPlanarDirection; // Направление камеры в горизонтальной плоскости - public bool JumpPressed; // Флаг указывающий, нажата ли кнопка прыжка - public bool SprintPressed; // Флаг указывающий, нажата ли кнопка спринта - public bool SlidePressed; // Флаг указывающий, нажата ли кнопка слайда - } - - /* - * Скрипт, отвечающий за передвижение персонажа - * Реализует физическое движение персонажа с использованием Kinematic Character Controller - * Обрабатывает ввод пользователя, применяет движение и вращение с учетом поверхности - * - * Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM - */ - public class CustomCharacterController : MonoBehaviour, ICharacterController - { - [SerializeField] - private KinematicCharacterMotor motor; // Движок физического перемещения персонажа - - [SerializeField] - private float - maxStableMoveSpeed = 8f, // Максимальная скорость движения по устойчивой поверхности - sprintMoveSpeed = 12f, // Максимальная скорость спринта - slideSpeed = 20f, // Скорость слайда - slideDuration = 0.8f, // Длительность слайда в секундах - slideCooldown = 1.5f, // Время перезарядки слайда - stableMovementSharpness = 15f, // Резкость изменения скорости движения (чем выше - тем резче) - orientationSharpness = 10f, // Резкость изменения вращения персонажа (чем выше - тем резче) - jumpSpeed = 10f, // Скорость прыжка - wallClimbSpeedFactor = 0.8f, // Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену - minWallClimbSpeed = 9f; // Минимальная горизонтальная скорость для начала залазания на стену - - [SerializeField] - private Vector3 gravity = new(0, -30f, 0); // Вектор, отвечающий за применение гравитации - - // Векторы движения и направления взгляда - private Vector3 _moveInputVector, _lookInputVector; - - // Текущий режим камеры для адаптации поведения - private CameraMode _currentCameraMode = CameraMode.ThirdPerson; - - // Вращение камеры в FirstPerson режиме для синхронизации (только горизонтальная компонента) - private Quaternion _firstPersonCameraRotation; - - private bool - _shouldApplyCameraRotation, // Флаг указывающий, что нужно применить вращение камеры к персонажу - _jumpRequested, // Флаг указывающий, что был запрошен прыжок - _isSprinting; // Флаг указывающий, что активен режим спринта - - // Переменные для управления слайдом - private bool _isSliding; // Флаг активного слайда - private float _slideTimer; // Таймер слайда - private float _slideCooldownTimer; // Таймер перезарядки слайда - private Vector3 _slideDirection; // Направление слайда - private bool _slideRequested; // Флаг запроса слайда - - // Флаг для предотвращения повторного залазания на стену в один и тот же кадр - private bool _wallClimbAppliedThisFrame; - - // Инициализация контроллера - private void Start() - { - motor.CharacterController = this; // Регистрация этого контроллера в движке - _firstPersonCameraRotation = motor.Transform.rotation; // Начальное вращение совпадает с персонажем - } - - // Обработка и преобразование входных данных от персонажа - public void SetInputs(ref CharacterInputs inputs) - { - // Обновляем таймер перезарядки слайда - if (_slideCooldownTimer > 0f) - { - _slideCooldownTimer -= Time.deltaTime; - } - - // Если активен слайд - игнорируем весь остальной ввод - if (_isSliding) - { - // Во время слайда не обрабатываем движение, прыжки и спринт - _moveInputVector = Vector3.zero; - _lookInputVector = Vector3.one; - _isSprinting = false; - return; - } - - // Сохраняем текущий режим камеры - _currentCameraMode = inputs.CameraMode; - - // Обработка спринта - _isSprinting = inputs is { SprintPressed: true, MoveAxisForward: > 0.1f }; // Спринт только при движении вперёд - - // Обработка слайда - if (inputs.SlidePressed && !_slideRequested && _slideCooldownTimer <= 0f) - { - _slideRequested = true; - } - - // Нормализация вектора движения и ограничение его величины до 1 - var moveInputVector = - Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0, inputs.MoveAxisForward), 1f); - - // Раздельная обработка в зависимости от режима камеры - if (_currentCameraMode == CameraMode.FirstPerson) - { - HandleFirstPersonInputs(ref inputs, moveInputVector); - } - else - { - HandleThirdPersonInputs(ref inputs, moveInputVector); - } - - // При запрашивании прыжка - if (inputs.JumpPressed) - _jumpRequested = true; - } - - // Начало слайда - private void StartSlide() - { - // Проверяем условия для старта слайда - if (!motor.GroundingStatus.IsStableOnGround || _slideCooldownTimer > 0f) - { - _slideRequested = false; - return; - } - - _isSliding = true; - _slideTimer = slideDuration; - - // НАПРАВЛЕНИЕ СЛАЙДА - ВСЕГДА ВПЕРЕД ПО КАМЕРЕ - // Используем направление камеры, но проецируем на горизонтальную плоскость - var slideDir = _currentCameraMode == CameraMode.FirstPerson ? - _firstPersonCameraRotation * Vector3.forward : - _lookInputVector; - - // Проецируем на горизонтальную плоскость и нормализуем - _slideDirection = Vector3.ProjectOnPlane(slideDir, motor.CharacterUp).normalized; - - // Если направление нулевое (персонаж стоит на месте), используем направление вперед - if (_slideDirection.sqrMagnitude < 0.01f) - { - _slideDirection = motor.CharacterForward; - } - - // Принудительно отключаем спринт и сбрасываем прыжок во время слайда - _isSprinting = false; - _jumpRequested = false; - _slideRequested = false; - - // Устанавливаем перезарядку - _slideCooldownTimer = slideCooldown; - } - - // Обработка слайда - private void HandleSlide(ref Vector3 currentVelocity, float deltaTime) - { - // Уменьшаем таймер слайда - _slideTimer -= deltaTime; - - // Если время слайда вышло, персонаж в воздухе - заканчиваем слайд - if (_slideTimer <= 0f || !motor.GroundingStatus.IsStableOnGround) - { - EndSlide(); - return; - } - - // Устанавливаем скорость слайда вперед - // Используем прямую установку скорости вместо сложных вычислений - currentVelocity = _slideDirection * slideSpeed; - - // Добавляем небольшую вертикальную компоненту для лучшего скольжения по неровностям - currentVelocity += motor.CharacterUp * 2f; - } - - // Завершение слайда - private void EndSlide() - { - _isSliding = false; - _slideTimer = 0f; - } - - // Обработка ввода для FirstPerson режима - private void HandleFirstPersonInputs(ref CharacterInputs inputs, Vector3 moveInputVector) - { - // Сохраняем вращение камеры для применения в UpdateRotation (только горизонтальная компонента) - _firstPersonCameraRotation = ExtractHorizontalRotation(inputs.CameraRotation); - _shouldApplyCameraRotation = true; - - // В FirstPerson режиме движение всегда относительно направления камеры - // Получаем направление камеры в горизонтальной плоскости - var cameraPlanarDirection = inputs.CameraPlanarDirection; - - // Создание вращения на основе направления камеры - var cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, motor.CharacterUp); - - // Преобразование локального ввода в мировые координаты относительно камеры - _moveInputVector = cameraPlanarRotation * moveInputVector; - - // В FirstPerson режиме направление взгляда всегда совпадает с направлением камеры (горизонтально) - _lookInputVector = cameraPlanarDirection; - } - - // Обработка ввода для ThirdPerson режима - private void HandleThirdPersonInputs(ref CharacterInputs inputs, Vector3 moveInputVector) - { - // Сбрасываем флаг применения вращения камеры - _shouldApplyCameraRotation = false; - - // Получение направления камеры в горизонтальной плоскости (игнорируя наклон) - var cameraPlanarDirection = inputs.CameraPlanarDirection; - - // Резервный расчет направления, если основное направление параллельно вертикали - if (cameraPlanarDirection.sqrMagnitude == 0f) - { - cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, motor.CharacterUp); - } - - // Создание вращения на основе направления камеры - var cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, motor.CharacterUp); - - // Преобразование локального ввода в мировые координаты относительно камеры - _moveInputVector = cameraPlanarRotation * moveInputVector; - - // Направление взгляда совпадает с направлением движения (нормализованное) - _lookInputVector = _moveInputVector.normalized; - } - - // Извлечение только горизонтальной компоненты вращения (игнорируя наклон камеры) - private Quaternion ExtractHorizontalRotation(Quaternion cameraRotation) - { - // Получаем вектор направления камеры - var cameraForward = cameraRotation * Vector3.forward; - - // Проецируем на горизонтальную плоскость - var horizontalForward = Vector3.ProjectOnPlane(cameraForward, motor.CharacterUp).normalized; - - // Если проекция вырождена, используем текущее направление персонажа - if (horizontalForward.sqrMagnitude < 0.01f) - { - horizontalForward = motor.CharacterForward; - } - - // Создаем вращение только по горизонтали - return Quaternion.LookRotation(horizontalForward, motor.CharacterUp); - } - - // Обновление вращения персонажа на основе входных данных - public void UpdateRotation(ref Quaternion currentRotation, float deltaTime) - { - // ВО ВРЕМЯ СЛАЙДА НЕ ОБНОВЛЯЕМ ВРАЩЕНИЕ - ПЕРСОНАЖ СМОТРИТ ПРЯМО - if (_isSliding) return; - - switch (_currentCameraMode) - { - // В FirstPerson режиме применяем вращение камеры напрямую к персонажу (только горизонталь) - case CameraMode.FirstPerson when _shouldApplyCameraRotation: - // Немедленно применяем горизонтальное вращение камеры к персонажу - currentRotation = _firstPersonCameraRotation; - _shouldApplyCameraRotation = false; - break; - // В ThirdPerson режиме плавно поворачиваем персонаж в направлении движения - case CameraMode.ThirdPerson: - { - // Вращение применяется только если есть направление движения и разрешена резкость вращения - if (_lookInputVector.sqrMagnitude > 0 && orientationSharpness > 0) - { - // Плавная интерполяция текущего направления к целевому с экспоненциальным сглаживанием - var smoothedLookInputDirection = Vector3.Slerp( - motor.CharacterForward, - _lookInputVector, - 1f - Mathf.Exp(-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) - { - // Сбрасываем флаг залазания на стену в начале каждого кадра - _wallClimbAppliedThisFrame = false; - - // Проверяем запрос слайда и запускаем его если можно - if (_slideRequested && !_isSliding && motor.GroundingStatus.IsStableOnGround && _slideCooldownTimer <= 0f) - { - StartSlide(); - } - - // Если активен слайд - обрабатываем отдельно - if (_isSliding) - { - HandleSlide(ref currentVelocity, deltaTime); - return; - } - - // Если персонаж находится на земле - if (motor.GroundingStatus.IsStableOnGround) - { - // Сохранение текущей величины скорости - var currentVelocityMagnitude = currentVelocity.magnitude; - - // Получение нормали поверхности для корректного движения по наклонным плоскостям - var effectiveGroundNormal = motor.GroundingStatus.GroundNormal; - - // Переориентация текущей скорости вдоль поверхности - currentVelocity = motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) * - currentVelocityMagnitude; - - // Переориентация входного вектора движения вдоль поверхности - var inputRight = Vector3.Cross(_moveInputVector, motor.CharacterUp); - var reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * - _moveInputVector.magnitude; - - // Расчет целевой скорости движения с учетом ограничения максимальной скорости и спринта - var targetSpeed = _isSprinting ? sprintMoveSpeed : maxStableMoveSpeed; - var targetMovementVelocity = reorientedInput * targetSpeed; - - // Плавная интерполяция текущей скорости к целевой с экспоненциальным сглаживанием - currentVelocity = Vector3.Lerp( - currentVelocity, - targetMovementVelocity, - 1f - Mathf.Exp(-stableMovementSharpness * deltaTime) - ); - - // Если запрошен прыжок - if (_jumpRequested) - { - currentVelocity += motor.CharacterUp * jumpSpeed - Vector3.Project(currentVelocity, motor.CharacterUp); - _jumpRequested = false; - motor.ForceUnground(); - } - } - // Если персонаж не находится на земле - else - { - // Применяем гравитацию - currentVelocity += gravity * deltaTime; - - // Отменяем прыжок (фикс проблемы с двойным прыжком после приземления) - _jumpRequested = false; - } - } - - // Метод вызывается перед обновлением персонажа (можно использовать для подготовки) - public void BeforeCharacterUpdate(float deltaTime) - { - } - - // Метод вызывается после обработки столкновения с землей - public void PostGroundingUpdate(float deltaTime) - { - // Если во время слайда персонаж оторвался от земли - заканчиваем слайд - if (_isSliding && !motor.GroundingStatus.IsStableOnGround) - { - EndSlide(); - } - } - - // Метод вызывается после обновления персонажа (можно использовать для пост-обработки) - public void AfterCharacterUpdate(float 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 (_wallClimbAppliedThisFrame || _isSliding) return; - - // Проверяем, что это вертикальная или почти вертикальная поверхность (стена) - var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp); - if (Mathf.Abs(dotWithUp) > 0.1f) return; // Не стена — это пол или потолок - - // Получаем текущую скорость персонажа - var currentVelocity = motor.Velocity; - - // Проекция скорости на направление к стене (отрицательная, если летим в стену) - var velocityIntoWall = Vector3.Dot(currentVelocity, -hitNormal); - - // Если движемся в стену с достаточной скоростью - if (velocityIntoWall > minWallClimbSpeed) - { - // Преобразуем часть горизонтальной скорости в вертикальную - var verticalBoost = velocityIntoWall * wallClimbSpeedFactor; - - // Добавляем вертикальный импульс через BaseVelocity - motor.BaseVelocity += motor.CharacterUp * verticalBoost; - - // Предотвращаем заземление и повторное применение в этом кадре - motor.ForceUnground(); - _wallClimbAppliedThisFrame = true; - } - } - - // Обработка отчета о стабильности столкновения - public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, - Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) - { - } - - // Обработка дискретных столкновений (для триггеров и специальных коллайдеров) - public void OnDiscreteCollisionDetected(Collider hitCollider) - { - } - } -} \ No newline at end of file From 7114f1136c5388db84de62f5430cb9049b8fa19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C5=A1a=20Ceviel?= <39222425+MrTorex@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:24:51 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20=D0=BF=D1=80=D1=8B=D0=B3=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20=D0=B2=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20=D1=81=D0=BB?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Прыжок во время слайда отменяет его, но не уменьшает импульс, созданный слайдом --- .../Controller/CustomCharacterController.cs | 8 +++++++- .../Controller/Movement/GroundMovementSystem.cs | 13 +++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs index 96b2763..b4dbb14 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs @@ -75,7 +75,7 @@ namespace Code.Gameplay.Character.Controller _inputHandler.MoveInputVector = Vector3.zero; _inputHandler.LookInputVector = Vector3.one; _inputHandler.IsSprinting = false; - _groundMovement.CancelJumpRequest(); + _groundMovement.RequestJump(inputs.JumpPressed); return; } @@ -135,6 +135,12 @@ namespace Code.Gameplay.Character.Controller // Обновление скорости движения персонажа public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) { + // Если во время слайда запрошен прыжок — прерываем слайд + if (_slideMechanic.IsSliding && _groundMovement.JumpRequested) + { + _slideMechanic.EndSlide(); + } + // Проверяем запрос слайда и запускаем его если можно if (!_slideMechanic.IsSliding) { diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs index 767c0b8..9d95784 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/Movement/GroundMovementSystem.cs @@ -17,8 +17,9 @@ namespace Code.Gameplay.Character.Controller.Movement private readonly float _stableMovementSharpness; // Скорость прыжка private readonly float _jumpSpeed; - // Флаг указывающий, что был запрошен прыжок - private bool _jumpRequested; + + // Свойство-флаг указывающий, что был запрошен прыжок + public bool JumpRequested { get; private set; } // Конструктор системы движения на земле public GroundMovementSystem(float maxStableMoveSpeed, float sprintMoveSpeed, float stableMovementSharpness, float jumpSpeed) @@ -33,13 +34,13 @@ namespace Code.Gameplay.Character.Controller.Movement public void RequestJump(bool jumpPressed) { if (jumpPressed) - _jumpRequested = true; + JumpRequested = true; } // Метод для отмены запроса на прыжок извне public void CancelJumpRequest() { - _jumpRequested = false; + JumpRequested = false; } // Обновление скорости движения персонажа на земле @@ -76,10 +77,10 @@ namespace Code.Gameplay.Character.Controller.Movement ); // Если запрошен прыжок - if (_jumpRequested) + if (JumpRequested) { currentVelocity += motor.CharacterUp * _jumpSpeed - Vector3.Project(currentVelocity, motor.CharacterUp); - _jumpRequested = false; // Сброс сразу после применения + JumpRequested = false; // Сброс сразу после применения motor.ForceUnground(); } } From a198d7fc00d63d0210a2921da4fef9991cfec549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C5=A1a=20Ceviel?= <39222425+MrTorex@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:26:57 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BA=D0=B0=D0=BC=D0=B5=D1=80=D1=8B=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=8E=20=D0=B2=20=D0=BA=D0=B0=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пока грязно, но сейчас сделаю покрасивее --- .../Code/Configs/CharacterCameraConfig.cs | 34 +++++ .../Configs/CharacterCameraConfig.cs.meta | 3 + .../Character/Camera/CharacterCamera.cs | 125 +++++++++++------- .../Gameplay/Character/Core/CameraMode.cs | 1 + .../Code/StaticData/IStaticDataService.cs | 1 + .../Code/StaticData/StaticDataService.cs | 2 + .../Configs/App/CharacterCameraConfig.asset | 25 ++++ .../App/CharacterCameraConfig.asset.meta | 8 ++ Game/CourierOld/Packages/manifest.json | 6 +- Game/CourierOld/Packages/packages-lock.json | 10 +- 10 files changed, 157 insertions(+), 58 deletions(-) create mode 100644 Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs create mode 100644 Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs.meta create mode 100644 Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset create mode 100644 Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset.meta diff --git a/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs b/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs new file mode 100644 index 0000000..d1eeba3 --- /dev/null +++ b/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs @@ -0,0 +1,34 @@ +using Code.Gameplay.Character.Core; +using UnityEngine; + +namespace Code.Configs +{ + [CreateAssetMenu(fileName = "CharacterCameraConfig", menuName = "Config/CharacterCameraConfig")] + public class CharacterCameraConfig : ScriptableObject + { + public float + // Расстояния от персонажа до камеры + defaultDistance = 6f, // Стандартное расстояние камеры от цели + minDistance = 3f, // Минимальное расстояние при зуме + maxDistance = 10f, // Максимальное расстояние при зуме + + // Скорость зума камеры + distanceMovementSpeed = 5f, // Множитель скорости изменения дистанции при зуме + + // Скорость поворота камеры + rotationSpeed = 0.3f, // Множитель скорости вращения камеры + + // Скорость интерполяции текущее -> целевое вращение + rotationSharpness = 100f, // Коэффициент плавности вращения камеры (чем выше - тем резче) + + // Резкость следования камеры за целью + followSharpness = 100f, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче) + + // Углы наклона камеры + minVerticalAngle = -70f, // Минимальный угол наклона камеры (вниз) + maxVerticalAngle = 70f, // Максимальный угол наклона камеры (вверх) + defaultVerticalAngle = 10f; // Стандартный угол наклона камеры + + public CameraMode defaultCameraMode = CameraMode.MainMenu; // Текущий режим камеры + } +} \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs.meta b/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs.meta new file mode 100644 index 0000000..773ba0a --- /dev/null +++ b/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 72fbf590e5fd4747acfdd32eb26bba3a +timeCreated: 1761637489 \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs index 9278075..4d59bb2 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs @@ -1,5 +1,7 @@ using System; using Code.Gameplay.Character.Core; +using Code.StaticData; +using Reflex.Attributes; using UnityEngine; namespace Code.Gameplay.Character.Camera @@ -13,38 +15,39 @@ namespace Code.Gameplay.Character.Camera */ public class CharacterCamera : MonoBehaviour { - [SerializeField] + // Значения теперь берутся из конфига private float // Расстояния от персонажа до камеры - defaultDistance = 6f, // Стандартное расстояние камеры от цели - minDistance = 3f, // Минимальное расстояние при зуме - maxDistance = 10f, // Максимальное расстояние при зуме + _defaultDistance, // Стандартное расстояние камеры от цели + _minDistance, // Минимальное расстояние при зуме + _maxDistance, // Максимальное расстояние при зуме // Скорость зума камеры - distanceMovementSpeed = 5f, // Множитель скорости изменения дистанции при зуме + _distanceMovementSpeed, // Множитель скорости изменения дистанции при зуме // Скорость поворота камеры - rotationSpeed = 0.3f, // Множитель скорости вращения камеры + _rotationSpeed, // Множитель скорости вращения камеры // Скорость интерполяции текущее -> целевое вращение - rotationSharpness = 100f, // Коэффициент плавности вращения камеры (чем выше - тем резче) + _rotationSharpness, // Коэффициент плавности вращения камеры (чем выше - тем резче) // Резкость следования камеры за целью - followSharpness = 100f, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче) + _followSharpness, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче) // Углы наклона камеры - minVerticalAngle = -70f, // Минимальный угол наклона камеры (вниз) - maxVerticalAngle = 70f, // Максимальный угол наклона камеры (вверх) - defaultVerticalAngle = 10f; // Стандартный угол наклона камеры + _minVerticalAngle, // Минимальный угол наклона камеры (вниз) + _maxVerticalAngle, // Максимальный угол наклона камеры (вверх) + _defaultVerticalAngle; // Стандартный угол наклона камеры - // Текущий режим работы камеры - // Трансформ объекта, за которым следует камера (для 1st-person: уровень глаз) private Transform _followTransform; // Позиция следования: текущая сглаженная позиция и горизонтальное направление камеры private Vector3 _currentFollowPosition; + // Позиция следования по умолчанию (для меню) + private readonly Vector3 _defaultFollowPosition = Vector3.zero; + // Целевой вертикальный угол камеры (наклон вверх/вниз) private float _targetVerticalAngle; @@ -53,24 +56,53 @@ namespace Code.Gameplay.Character.Camera // Исходные значения дистанции для восстановления после FirstPerson режима private float _originalDefaultDistance, _originalMinDistance, _originalMaxDistance; - + // Свойства для доступа к важным данным камеры извне public Vector3 PlanarDirection { get; private set; } // Горизонтальное направление камеры - public CameraMode CurrentMode { get; private set; } = CameraMode.ThirdPerson; // Текущий режим камеры + public CameraMode CurrentMode { get; private set; } + + [Inject] private IStaticDataService _staticDataService; + + // Загрузка конфига для камеры + private void LoadConfig() + { + var config = _staticDataService.CharacterCameraConfig; + + // Загрузка значений из конфигов + _defaultDistance = config.defaultDistance; + _minDistance = config.minDistance; + _maxDistance = config.maxDistance; + _distanceMovementSpeed = config.distanceMovementSpeed; + _rotationSpeed = config.rotationSpeed; + _rotationSharpness = config.rotationSharpness; + _followSharpness = config.followSharpness; + _minVerticalAngle = config.minVerticalAngle; + _maxVerticalAngle = config.maxVerticalAngle; + _defaultVerticalAngle = config.defaultVerticalAngle; + CurrentMode = config.defaultCameraMode; + + // Обеспечиваем корректные значения дистанции и угла + _defaultDistance = Mathf.Clamp(_defaultDistance, _minDistance, _maxDistance); + _defaultVerticalAngle = Mathf.Clamp(_defaultVerticalAngle, _minVerticalAngle, _maxVerticalAngle); + // Сохраняем оригинальные значения для восстановления + _originalDefaultDistance = _defaultDistance; + _originalMinDistance = _minDistance; + _originalMaxDistance = _maxDistance; + + _currentDistance = _defaultDistance; + _targetDistance = _currentDistance; + _targetVerticalAngle = _defaultVerticalAngle; + PlanarDirection = Vector3.forward; // Начальное направление - вперед по оси Z + + _currentFollowPosition = _defaultFollowPosition; + } + // Инициализация камеры при создании private void Awake() { - // Сохраняем исходные значения дистанции для восстановления - _originalDefaultDistance = defaultDistance; - _originalMinDistance = minDistance; - _originalMaxDistance = maxDistance; - - _currentDistance = defaultDistance; - _targetDistance = _currentDistance; - _targetVerticalAngle = 0f; - PlanarDirection = Vector3.forward; // Начальное направление - вперед по оси Z + LoadConfig(); } // Установка цели для следования камеры @@ -81,29 +113,21 @@ namespace Code.Gameplay.Character.Camera PlanarDirection = t.forward; // Начальное направление совпадает с направлением цели } - // Валидация параметров в редакторе Unity - private void OnValidate() - { - // Обеспечиваем корректные значения дистанции и угла - defaultDistance = Mathf.Clamp(defaultDistance, minDistance, maxDistance); - defaultVerticalAngle = Mathf.Clamp(defaultVerticalAngle, minVerticalAngle, maxVerticalAngle); - } - // Обработка вращения камеры на основе пользовательского ввода private void HandleRotationInput(float deltaTime, Vector3 rotationInput, out Quaternion targetRotation) { // Вращение вокруг вертикальной оси (горизонтальный поворот) - var rotationFromInput = Quaternion.Euler(_followTransform.up * (rotationInput.x * rotationSpeed)); + var rotationFromInput = Quaternion.Euler(_followTransform.up * (rotationInput.x * _rotationSpeed)); PlanarDirection = rotationFromInput * PlanarDirection; var planarRotation = Quaternion.LookRotation(PlanarDirection, _followTransform.up); // Вращение вокруг горизонтальной оси (вертикальный наклон) - _targetVerticalAngle -= rotationInput.y * rotationSpeed; - _targetVerticalAngle = Math.Clamp(_targetVerticalAngle, minVerticalAngle, maxVerticalAngle); + _targetVerticalAngle -= rotationInput.y * _rotationSpeed; + _targetVerticalAngle = Math.Clamp(_targetVerticalAngle, _minVerticalAngle, _maxVerticalAngle); var verticalRotation = Quaternion.Euler(_targetVerticalAngle, 0, 0); // Интерполяция к целевому вращению для плавности - targetRotation = Quaternion.Slerp(transform.rotation, planarRotation * verticalRotation, deltaTime * rotationSharpness); + targetRotation = Quaternion.Slerp(transform.rotation, planarRotation * verticalRotation, deltaTime * _rotationSharpness); // Применение финального вращения transform.rotation = targetRotation; @@ -116,12 +140,12 @@ namespace Code.Gameplay.Character.Camera if (CurrentMode != CameraMode.FirstPerson) { // Обновление целевой дистанции на основе ввода зума - _targetDistance += zoomInput * distanceMovementSpeed; - _targetDistance = Mathf.Clamp(_targetDistance, minDistance, maxDistance); + _targetDistance += zoomInput * _distanceMovementSpeed; + _targetDistance = Mathf.Clamp(_targetDistance, _minDistance, _maxDistance); } // Плавное следование за целью с использованием экспоненциального сглаживания - _currentFollowPosition = Vector3.Lerp(_currentFollowPosition, _followTransform.position, 1f - Mathf.Exp(-followSharpness * deltaTime)); + _currentFollowPosition = Vector3.Lerp(_currentFollowPosition, _followTransform.position, 1f - Mathf.Exp(-_followSharpness * deltaTime)); // Вычисление целевой позиции камеры с учетом текущего режима Vector3 targetPosition; @@ -139,7 +163,7 @@ namespace Code.Gameplay.Character.Camera // Плавное изменение текущей дистанции к целевой (только для ThirdPerson) if (CurrentMode != CameraMode.FirstPerson) { - _currentDistance = Mathf.Lerp(_currentDistance, _targetDistance, 1f - Mathf.Exp(-distanceMovementSpeed * deltaTime)); + _currentDistance = Mathf.Lerp(_currentDistance, _targetDistance, 1f - Mathf.Exp(-_distanceMovementSpeed * deltaTime)); } // Установка финальной позиции камеры @@ -149,11 +173,11 @@ namespace Code.Gameplay.Character.Camera // Основной метод обновления камеры, вызываемый извне каждый кадр public void UpdateWithInput(float deltaTime, float zoomInput, Vector3 rotationInput) { - // Проверка наличия цели для следования - if (!_followTransform) return; + // Проверка наличия цели для следования и проверка на режим главного меню + if (!_followTransform || CurrentMode == CameraMode.MainMenu) return; // Обработка ввода и позиционирования - HandleRotationInput(deltaTime, rotationInput, out Quaternion targetRotation); + HandleRotationInput(deltaTime, rotationInput, out var targetRotation); HandlePosition(deltaTime, zoomInput, targetRotation); } @@ -174,20 +198,21 @@ namespace Code.Gameplay.Character.Camera { case CameraMode.FirstPerson: // В FirstPerson режиме дистанция всегда равна 0 - minDistance = 0f; - maxDistance = 0f; - defaultDistance = 0f; + _minDistance = 0f; + _maxDistance = 0f; + _defaultDistance = 0f; _targetDistance = 0f; _currentDistance = 0f; break; case CameraMode.ThirdPerson: // Восстанавливаем исходные значения дистанции - minDistance = _originalMinDistance; - maxDistance = _originalMaxDistance; - defaultDistance = _originalDefaultDistance; - _targetDistance = defaultDistance; + _minDistance = _originalMinDistance; + _maxDistance = _originalMaxDistance; + _defaultDistance = _originalDefaultDistance; + _targetDistance = _defaultDistance; break; // Заготовка на будущее (если понадобится) + case CameraMode.MainMenu: case CameraMode.Static: case CameraMode.Cinematic: default: diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs index 92dfaf6..6c86d84 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs @@ -9,6 +9,7 @@ namespace Code.Gameplay.Character.Core */ public enum CameraMode { + MainMenu, Static, FirstPerson, ThirdPerson, diff --git a/Game/CourierOld/Assets/Code/StaticData/IStaticDataService.cs b/Game/CourierOld/Assets/Code/StaticData/IStaticDataService.cs index 6b4561c..213466d 100644 --- a/Game/CourierOld/Assets/Code/StaticData/IStaticDataService.cs +++ b/Game/CourierOld/Assets/Code/StaticData/IStaticDataService.cs @@ -8,5 +8,6 @@ namespace Code.StaticData void Load(); InputConfig InputConfig { get; } AppConfig AppConfig { get; } + CharacterCameraConfig CharacterCameraConfig { get; } } } \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/StaticData/StaticDataService.cs b/Game/CourierOld/Assets/Code/StaticData/StaticDataService.cs index 33a1b9a..2b7af3a 100644 --- a/Game/CourierOld/Assets/Code/StaticData/StaticDataService.cs +++ b/Game/CourierOld/Assets/Code/StaticData/StaticDataService.cs @@ -8,11 +8,13 @@ namespace Code.StaticData { public AppConfig AppConfig { get; private set; } public InputConfig InputConfig { get; private set; } + public CharacterCameraConfig CharacterCameraConfig { get; private set; } public void Load() { InputConfig = Resources.Load(string.Format(StringPaths.ConfigSingle, "App", "InputConfig")); AppConfig = Resources.Load(string.Format(StringPaths.ConfigSingle, "App", "AppConfig")); + CharacterCameraConfig = Resources.Load(string.Format(StringPaths.ConfigSingle, "App", "CharacterCameraConfig")); } } } \ No newline at end of file diff --git a/Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset b/Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset new file mode 100644 index 0000000..c85fd84 --- /dev/null +++ b/Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset @@ -0,0 +1,25 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 72fbf590e5fd4747acfdd32eb26bba3a, type: 3} + m_Name: CharacterCameraConfig + m_EditorClassIdentifier: Assembly-CSharp::Code.Configs.CharacterCameraConfig + defaultDistance: 6 + minDistance: 3 + maxDistance: 10 + distanceMovementSpeed: 5 + rotationSpeed: 0.3 + rotationSharpness: 100 + followSharpness: 100 + minVerticalAngle: -70 + maxVerticalAngle: 70 + defaultVerticalAngle: 10 + defaultCameraMode: 0 diff --git a/Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset.meta b/Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset.meta new file mode 100644 index 0000000..b58b958 --- /dev/null +++ b/Game/CourierOld/Assets/Resources/Configs/App/CharacterCameraConfig.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6938984fc7d25285abb4922dd46d9272 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/CourierOld/Packages/manifest.json b/Game/CourierOld/Packages/manifest.json index 876c157..8e3a006 100644 --- a/Game/CourierOld/Packages/manifest.json +++ b/Game/CourierOld/Packages/manifest.json @@ -5,15 +5,15 @@ "com.unity.ai.navigation": "2.0.9", "com.unity.collab-proxy": "2.10.0", "com.unity.ide.rider": "3.0.38", - "com.unity.ide.visualstudio": "2.0.23", + "com.unity.ide.visualstudio": "2.0.25", "com.unity.inputsystem": "1.14.2", "com.unity.multiplayer.center": "1.0.0", "com.unity.render-pipelines.universal": "17.2.0", "com.unity.test-framework": "1.6.0", "com.unity.timeline": "1.8.9", - "com.unity.toolchain.linux-x86_64": "2.0.10", + "com.unity.toolchain.linux-x86_64": "2.0.11", "com.unity.ugui": "2.0.0", - "com.unity.visualscripting": "1.9.7", + "com.unity.visualscripting": "1.9.9", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/Game/CourierOld/Packages/packages-lock.json b/Game/CourierOld/Packages/packages-lock.json index 2f4354a..bafd245 100644 --- a/Game/CourierOld/Packages/packages-lock.json +++ b/Game/CourierOld/Packages/packages-lock.json @@ -7,7 +7,7 @@ "dependencies": { "com.unity.localization": "1.0.0" }, - "hash": "bd1d662b53cff58b86c2cef02b284a52ce964cae" + "hash": "dfa901c9eeef09febe0dfb2f2a52319ea532747f" }, "com.gustavopsantos.reflex": { "version": "https://github.com/gustavopsantos/reflex.git?path=/Assets/Reflex/#13.0.2", @@ -87,11 +87,11 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.23", + "version": "2.0.25", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.9" + "com.unity.test-framework": "1.1.31" }, "url": "https://packages.unity.com" }, @@ -268,7 +268,7 @@ "url": "https://packages.unity.com" }, "com.unity.toolchain.linux-x86_64": { - "version": "2.0.10", + "version": "2.0.11", "depth": 0, "source": "registry", "dependencies": { @@ -287,7 +287,7 @@ } }, "com.unity.visualscripting": { - "version": "1.9.7", + "version": "1.9.9", "depth": 0, "source": "registry", "dependencies": { From 7538ce81af685c5d9a81330ab6922d3b0371d6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C5=A1a=20Ceviel?= <39222425+MrTorex@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:38:28 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=B8=D0=BB=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D0=B0,=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Чисто для удобства, чтобы не было много грязюки --- .../Code/Configs/CharacterCameraConfig.cs | 6 ++- .../Character/Camera/CharacterCamera.cs | 54 ++++++++----------- .../Controller/CustomCharacterController.cs | 1 + .../Gameplay/Character/Core/CameraMode.cs | 1 + .../Code/Gameplay/Character/Core/Character.cs | 1 + 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs b/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs index d1eeba3..6ad8eb5 100644 --- a/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs +++ b/Game/CourierOld/Assets/Code/Configs/CharacterCameraConfig.cs @@ -29,6 +29,10 @@ namespace Code.Configs maxVerticalAngle = 70f, // Максимальный угол наклона камеры (вверх) defaultVerticalAngle = 10f; // Стандартный угол наклона камеры - public CameraMode defaultCameraMode = CameraMode.MainMenu; // Текущий режим камеры + // Текущий режим камеры + public CameraMode defaultCameraMode = CameraMode.MainMenu; + + // Позиция следования по умолчанию (для меню) + public Vector3 defaultFollowPosition = Vector3.zero; } } \ No newline at end of file diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs index 4d59bb2..b68b33e 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Camera/CharacterCamera.cs @@ -15,29 +15,18 @@ namespace Code.Gameplay.Character.Camera */ public class CharacterCamera : MonoBehaviour { - // Значения теперь берутся из конфига + // Параметры загружаются из CharacterCameraConfig private float - // Расстояния от персонажа до камеры - _defaultDistance, // Стандартное расстояние камеры от цели - _minDistance, // Минимальное расстояние при зуме - _maxDistance, // Максимальное расстояние при зуме - - // Скорость зума камеры - _distanceMovementSpeed, // Множитель скорости изменения дистанции при зуме - - // Скорость поворота камеры - _rotationSpeed, // Множитель скорости вращения камеры - - // Скорость интерполяции текущее -> целевое вращение - _rotationSharpness, // Коэффициент плавности вращения камеры (чем выше - тем резче) - - // Резкость следования камеры за целью - _followSharpness, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче) - - // Углы наклона камеры - _minVerticalAngle, // Минимальный угол наклона камеры (вниз) - _maxVerticalAngle, // Максимальный угол наклона камеры (вверх) - _defaultVerticalAngle; // Стандартный угол наклона камеры + _defaultDistance, + _minDistance, + _maxDistance, + _distanceMovementSpeed, + _rotationSpeed, + _rotationSharpness, + _followSharpness, + _minVerticalAngle, + _maxVerticalAngle, + _defaultVerticalAngle; // Трансформ объекта, за которым следует камера (для 1st-person: уровень глаз) private Transform _followTransform; @@ -45,8 +34,8 @@ namespace Code.Gameplay.Character.Camera // Позиция следования: текущая сглаженная позиция и горизонтальное направление камеры private Vector3 _currentFollowPosition; - // Позиция следования по умолчанию (для меню) - private readonly Vector3 _defaultFollowPosition = Vector3.zero; + // Fallback-позиция для режимов без цели (например, MainMenu) + private Vector3 _defaultFollowPosition; // Целевой вертикальный угол камеры (наклон вверх/вниз) private float _targetVerticalAngle; @@ -57,19 +46,21 @@ namespace Code.Gameplay.Character.Camera // Исходные значения дистанции для восстановления после FirstPerson режима private float _originalDefaultDistance, _originalMinDistance, _originalMaxDistance; - // Свойства для доступа к важным данным камеры извне - public Vector3 PlanarDirection { get; private set; } // Горизонтальное направление камеры - + // Горизонтальное направление камеры (проекция forward на плоскость) + public Vector3 PlanarDirection { get; private set; } + + // Текущий режим работы камеры public CameraMode CurrentMode { get; private set; } + // DI для загрузки конфига [Inject] private IStaticDataService _staticDataService; // Загрузка конфига для камеры private void LoadConfig() { + // Загрузка значений из конфигов var config = _staticDataService.CharacterCameraConfig; - // Загрузка значений из конфигов _defaultDistance = config.defaultDistance; _minDistance = config.minDistance; _maxDistance = config.maxDistance; @@ -81,8 +72,9 @@ namespace Code.Gameplay.Character.Camera _maxVerticalAngle = config.maxVerticalAngle; _defaultVerticalAngle = config.defaultVerticalAngle; CurrentMode = config.defaultCameraMode; + _defaultFollowPosition = config.defaultFollowPosition; - // Обеспечиваем корректные значения дистанции и угла + // Валидация значений (на случай ручного редактирования конфига) _defaultDistance = Mathf.Clamp(_defaultDistance, _minDistance, _maxDistance); _defaultVerticalAngle = Mathf.Clamp(_defaultVerticalAngle, _minVerticalAngle, _maxVerticalAngle); @@ -90,12 +82,12 @@ namespace Code.Gameplay.Character.Camera _originalDefaultDistance = _defaultDistance; _originalMinDistance = _minDistance; _originalMaxDistance = _maxDistance; - + + // Инициализация состояния камеры _currentDistance = _defaultDistance; _targetDistance = _currentDistance; _targetVerticalAngle = _defaultVerticalAngle; PlanarDirection = Vector3.forward; // Начальное направление - вперед по оси Z - _currentFollowPosition = _defaultFollowPosition; } diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs index b4dbb14..50b9159 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Controller/CustomCharacterController.cs @@ -127,6 +127,7 @@ namespace Code.Gameplay.Character.Controller // Заготовка на будущее (если понадобится) case CameraMode.Static: case CameraMode.Cinematic: + case CameraMode.MainMenu: // Скорее всего юзлес, но пускай будет default: break; } diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs index 6c86d84..355aaad 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/CameraMode.cs @@ -2,6 +2,7 @@ namespace Code.Gameplay.Character.Core { /* * Перечисление режимов работы камеры + * MainMenu - статичная камера, смотрит на заданную позицию (то что надо в главном меню) * Static - статичная камера без следования за целью * FirstPerson - камера от первого лица (встроена в модель персонажа) * ThirdPerson - камера от третьего лица (следует за персонажем на расстоянии) diff --git a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs index f0b82be..1dc024f 100644 --- a/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs +++ b/Game/CourierOld/Assets/Code/Gameplay/Character/Core/Character.cs @@ -28,6 +28,7 @@ namespace Code.Gameplay.Character.Core { // Настройка камеры для следования за точкой камеры characterCamera.SetFollowTransform(cameraFollowPoint); + characterCamera.SetCameraMode(CameraMode.FirstPerson); // Включение Gameplay мапа ввода _inputService.EnableMap(ActionMappings.Gameplay);