Добавлено нормальное переключение между FirstPerson и ThirdPerson камерами, правильная логика работы FirstPerson камеры (игрок поворачивается вместе с ней), добавлены заготовки для других камер, стабилизированы настройки камеры. Перехожу к движению

This commit is contained in:
MrTorex committed 2025-10-17 12:07:48 +03:00
1 parent 8981457f2d
commit bd0cee77a6
11 files changed
+760 -87

No files matched your search

@@ -3,22 +3,26 @@ using UnityEngine;
namespace Code.Gameplay.Player
{
/*
* Структура для хранения входных данных от игрока
* Содержит информацию о движении и состоянии камеры
*/
public struct PlayerInputs
{
public float MoveAxisForward; // Ввод движения вперед/назад (W/S или Up/Down)
public float MoveAxisRight; // Ввод движения влево/вправо (A/D или Left/Right)
public Quaternion CameraRotation; // Текущее вращение камеры для определения направления движения
public CameraMode CameraMode; // Текущий режим камеры для адаптации поведения
public Vector3 CameraPlanarDirection; // Направление камеры в горизонтальной плоскости
}
/*
* Скрипт, отвечающий за передвижения игрока
* Реализует физическое движение персонажа с использованием Kinematic Character Controller
* Обрабатывает ввод пользователя, применяет движение и вращение с учетом поверхности
*
* Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM
*/
// Структура для хранения входных данных от игрока
public struct PlayerInputs
{
public float MoveAxisForward; // Ввод движения вперед/назад (W/S или Up/Down)
public float MoveAxisRight; // Ввод движения влево/вправо (A/D или Left/Right)
public Quaternion CameraRotation; // Текущее вращение камеры для определения направления движения
}
*/
public class CharacterController : MonoBehaviour, ICharacterController
{
[SerializeField]
@@ -33,54 +37,143 @@ namespace Code.Gameplay.Player
// Векторы движения и направления взгляда
private Vector3 _moveInputVector, _lookInputVector;
// Текущий режим камеры для адаптации поведения
private CameraMode _currentCameraMode = CameraMode.ThirdPerson;
// Вращение камеры в FirstPerson режиме для синхронизации (только горизонтальная компонента)
private Quaternion _firstPersonCameraRotation;
// Флаг указывающий, что нужно применить вращение камеры к персонажу
private bool _shouldApplyCameraRotation;
// Инициализация контроллера
private void Start()
{
motor.CharacterController = this; // Регистрация этого контроллера в движке
_firstPersonCameraRotation = motor.Transform.rotation; // Начальное вращение совпадает с персонажем
}
// Обработка и преобразование входных данных от игрока
public void SetInputs(ref PlayerInputs inputs)
{
// Сохраняем текущий режим камеры
_currentCameraMode = inputs.CameraMode;
// Нормализация вектора движения и ограничение его величины до 1
Vector3 moveInputVector =
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);
}
}
// Обработка ввода для FirstPerson режима
private void HandleFirstPersonInputs(ref PlayerInputs 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 PlayerInputs inputs, Vector3 moveInputVector)
{
// Сбрасываем флаг применения вращения камеры
_shouldApplyCameraRotation = false;
// Получение направления камеры в горизонтальной плоскости (игнорируя наклон)
Vector3 cameraPlanarDirection =
Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, motor.CharacterUp).normalized;
var cameraPlanarDirection = inputs.CameraPlanarDirection;
// Резервный расчет направления, если основное направление параллельно вертикали
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, motor.CharacterUp);
}
// Создание вращения на основе направления камеры
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, 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 (_lookInputVector.sqrMagnitude > 0 && orientationSharpness > 0)
switch (_currentCameraMode)
{
// Плавная интерполяция текущего направления к целевому с экспоненциальным сглаживанием
Vector3 smoothedLookInputDirection = Vector3.Slerp(
motor.CharacterForward,
_lookInputVector,
1f - Mathf.Exp(-orientationSharpness * deltaTime)
).normalized;
// Установка нового вращения смотря в сглаженном направлении
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, motor.CharacterUp);
// В 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;
}
}
@@ -88,20 +181,20 @@ namespace Code.Gameplay.Player
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
// Сохранение текущей величины скорости
float currentVelocityMagnitude = currentVelocity.magnitude;
var currentVelocityMagnitude = currentVelocity.magnitude;
// Получение нормали поверхности для корректного движения по наклонным плоскостям
Vector3 effectiveGroundNormal = motor.GroundingStatus.GroundNormal;
var effectiveGroundNormal = motor.GroundingStatus.GroundNormal;
// Переориентация текущей скорости вдоль поверхности
currentVelocity = motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) * currentVelocityMagnitude;
// Переориентация входного вектора движения вдоль поверхности
Vector3 inputRight = Vector3.Cross(_moveInputVector, motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * _moveInputVector.magnitude;
var inputRight = Vector3.Cross(_moveInputVector, motor.CharacterUp);
var reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * _moveInputVector.magnitude;
// Расчет целевой скорости движения с учетом ограничения максимальной скорости
Vector3 targetMovementVelocity = reorientedInput * maxStableMoveSpeed;
var targetMovementVelocity = reorientedInput * maxStableMoveSpeed;
// Плавная интерполяция текущей скорости к целевой с экспоненциальным сглаживанием
currentVelocity = Vector3.Lerp(
@@ -4,52 +4,113 @@ using Reflex.Attributes;
using UnityEngine;
using CharacterController = Code.Gameplay.Player.CharacterController;
/*
* Главный скрипт игрока, объединяющий камеру и контроллер персонажа
* Обрабатывает ввод пользователя и распределяет его между компонентами
* Синхронизирует состояние камеры и персонажа
* Управляет переключением режимов камеры через Input System
*/
public class Player : MonoBehaviour
{
[SerializeField]
private PlayerCamera playerCamera;
private PlayerCamera playerCamera; // Компонент камеры игрока
[SerializeField]
private CharacterController characterController;
private CharacterController characterController; // Компонент управления персонажем
[SerializeField]
private Transform cameraFollowPoint;
[Inject] private IInputService _inputService;
private Transform cameraFollowPoint; // Точка следования камеры (уровень глаз для FirstPerson)
private Vector3 _lookInputVector;
[Inject]
private IInputService _inputService; // Сервис ввода через DI
// Флаг для отслеживания переключения режима камеры (чтобы избежать многократных срабатываний)
private bool _cameraModeToggleHandled;
// Инициализация игрока
private void Start()
{
// Настройка камеры для следования за точкой камеры
playerCamera.SetFollowTransform(cameraFollowPoint);
// Включение Gameplay мапа ввода
_inputService.EnableMap(ActionMappings.Gameplay);
}
// Обработка ввода для камеры (вращение и зум)
private void HandleCameraInput()
{
// Получаем данные ввода из Input System
var lookInput = _inputService.LookInput;
var scrollInput = _inputService.ZoomInput;
// Передаем данные в камеру
playerCamera.UpdateWithInput(Time.deltaTime, scrollInput,
new Vector3(lookInput.x, lookInput.y, 0));
}
// Обработка ввода для движения персонажа
private void HandleCharacterInput()
{
PlayerInputs inputs = new PlayerInputs();
// Создаем структуру ввода с полной информацией о камере
var inputs = new PlayerInputs();
Vector2 moveInput = _inputService.MoveInput;
inputs.MoveAxisForward = moveInput.y;
inputs.MoveAxisRight = moveInput.x;
// Получаем данные движения из 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.CameraRotation = playerCamera.transform.rotation;
inputs.CameraMode = playerCamera.CurrentMode;
inputs.CameraPlanarDirection = playerCamera.PlanarDirection;
// Передаем ввод в контроллер персонажа
characterController.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 = playerCamera.CurrentMode;
var newMode = currentMode == CameraMode.FirstPerson ?
CameraMode.ThirdPerson : CameraMode.FirstPerson;
playerCamera.SetCameraMode(newMode);
}
// Обновление логики каждый кадр
private void Update()
{
// Обработка ввода для движения в Update (физика)
HandleCharacterInput();
// Обработка переключения режима камеры
HandleCameraModeInput();
}
// Обновление после всех вычислений (идеально для камеры)
private void LateUpdate()
{
// Обработка ввода для камеры в LateUpdate (после движения персонажа)
HandleCameraInput();
}
}
@@ -3,6 +3,21 @@ using UnityEngine;
namespace Code.Gameplay.Player
{
/*
* Перечисление режимов работы камеры
* Static - статичная камера без следования за целью
* FirstPerson - камера от первого лица (встроена в модель персонажа)
* ThirdPerson - камера от третьего лица (следует за персонажем на расстоянии)
* Cinematic - кинематографическая камера для скриптовых сцен
*/
public enum CameraMode
{
Static,
FirstPerson,
ThirdPerson,
Cinematic
}
/*
* Скрипт, отвечающий за поведение камеры при передвижении
* Поддерживает как 3rd-person, так и 1st-person режимы камеры
@@ -23,27 +38,29 @@ namespace Code.Gameplay.Player
distanceMovementSpeed = 5f, // Множитель скорости изменения дистанции при зуме
// Скорость интерполяции текущая -> целевая позиция зума
distanceMovementSharpness = 10000f, // Коэффициент плавности изменения дистанции (чем выше - тем резче)
distanceMovementSharpness = 100f, // Коэффициент плавности изменения дистанции (чем выше - тем резче)
// Скорость поворота камеры
rotationSpeed = 1f, // Множитель скорости вращения камеры
rotationSpeed = 0.3f, // Множитель скорости вращения камеры
// Скорость интерполяции текущее -> целевое вращение
rotationSharpness = 10000f, // Коэффициент плавности вращения камеры (чем выше - тем резче)
rotationSharpness = 100f, // Коэффициент плавности вращения камеры (чем выше - тем резче)
// Резкость следования камеры за целью
followSharpness = 10000f, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче)
followSharpness = 100f, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче)
// Углы наклона камеры
minVerticalAngle = -45f, // Минимальный угол наклона камеры (вниз)
maxVerticalAngle = 45f, // Максимальный угол наклона камеры (вверх)
minVerticalAngle = -70f, // Минимальный угол наклона камеры (вниз)
maxVerticalAngle = 70f, // Максимальный угол наклона камеры (вверх)
defaultVerticalAngle = 10f; // Стандартный угол наклона камеры
// Текущий режим работы камеры
// Трансформ объекта, за которым следует камера (для 1st-person: уровень глаз)
private Transform _followTransform;
// Позиция следования: текущая сглаженная позиция и горизонтальное направление камеры
private Vector3 _currentFollowPosition, _planarDirection;
private Vector3 _currentFollowPosition;
// Целевой вертикальный угол камеры (наклон вверх/вниз)
private float _targetVerticalAngle;
@@ -51,13 +68,26 @@ namespace Code.Gameplay.Player
// Текущая и целевая дистанция камеры от цели
private float _currentDistance, _targetDistance;
// Исходные значения дистанции для восстановления после FirstPerson режима
private float _originalDefaultDistance, _originalMinDistance, _originalMaxDistance;
// Свойства для доступа к важным данным камеры извне
public Vector3 PlanarDirection { get; private set; } // Горизонтальное направление камеры
public CameraMode CurrentMode { get; private set; } = CameraMode.ThirdPerson; // Текущий режим камеры
// Инициализация камеры при создании
private void Awake()
{
// Сохраняем исходные значения дистанции для восстановления
_originalDefaultDistance = defaultDistance;
_originalMinDistance = minDistance;
_originalMaxDistance = maxDistance;
_currentDistance = defaultDistance;
_targetDistance = _currentDistance;
_targetVerticalAngle = 0f;
_planarDirection = Vector3.forward; // Начальное направление - вперед по оси Z
PlanarDirection = Vector3.forward; // Начальное направление - вперед по оси Z
}
// Установка цели для следования камеры
@@ -65,7 +95,7 @@ namespace Code.Gameplay.Player
{
_followTransform = t;
_currentFollowPosition = t.position;
_planarDirection = t.forward; // Начальное направление совпадает с направлением цели
PlanarDirection = t.forward; // Начальное направление совпадает с направлением цели
}
// Валидация параметров в редакторе Unity
@@ -81,8 +111,8 @@ namespace Code.Gameplay.Player
{
// Вращение вокруг вертикальной оси (горизонтальный поворот)
var rotationFromInput = Quaternion.Euler(_followTransform.up * (rotationInput.x * rotationSpeed));
_planarDirection = rotationFromInput * _planarDirection;
var planarRotation = Quaternion.LookRotation(_planarDirection, _followTransform.up);
PlanarDirection = rotationFromInput * PlanarDirection;
var planarRotation = Quaternion.LookRotation(PlanarDirection, _followTransform.up);
// Вращение вокруг горизонтальной оси (вертикальный наклон)
_targetVerticalAngle -= rotationInput.y * rotationSpeed;
@@ -96,21 +126,38 @@ namespace Code.Gameplay.Player
transform.rotation = targetRotation;
}
// Обработка позиционирования камеры и зума
// Обработка позиционирования камеры и зума с учетом текущего режима
private void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
// Обновление целевой дистанции на основе ввода зума
_targetDistance += zoomInput * distanceMovementSpeed;
_targetDistance = Mathf.Clamp(_targetDistance, minDistance, maxDistance);
// В FirstPerson режиме зум отключается
if (CurrentMode != CameraMode.FirstPerson)
{
// Обновление целевой дистанции на основе ввода зума
_targetDistance += zoomInput * distanceMovementSpeed;
_targetDistance = Mathf.Clamp(_targetDistance, minDistance, maxDistance);
}
// Плавное следование за целью с использованием экспоненциального сглаживания
_currentFollowPosition = Vector3.Lerp(_currentFollowPosition, _followTransform.position, 1f - Mathf.Exp(-followSharpness * deltaTime));
// Вычисление целевой позиции камеры за вычетом дистанции по направлению взгляда
var targetPosition = _currentFollowPosition - targetRotation * Vector3.forward * _currentDistance;
// Вычисление целевой позиции камеры с учетом текущего режима
Vector3 targetPosition;
if (CurrentMode == CameraMode.FirstPerson)
{
// В FirstPerson режиме камера находится прямо на позиции цели
targetPosition = _currentFollowPosition;
}
else
{
// В ThirdPerson режиме камера отодвинута на расстояние от цели
targetPosition = _currentFollowPosition - targetRotation * Vector3.forward * _currentDistance;
}
// Плавное изменение текущей дистанции к целевой
_currentDistance = Mathf.Lerp(_currentDistance, _targetDistance, 1f - Mathf.Exp(-distanceMovementSpeed * deltaTime));
// Плавное изменение текущей дистанции к целевой (только для ThirdPerson)
if (CurrentMode != CameraMode.FirstPerson)
{
_currentDistance = Mathf.Lerp(_currentDistance, _targetDistance, 1f - Mathf.Exp(-distanceMovementSpeed * deltaTime));
}
// Установка финальной позиции камеры
transform.position = targetPosition;
@@ -121,8 +168,48 @@ namespace Code.Gameplay.Player
{
// Проверка наличия цели для следования
if (!_followTransform) return;
// Обработка ввода и позиционирования
HandleRotationInput(deltaTime, rotationInput, out Quaternion targetRotation);
HandlePosition(deltaTime, zoomInput, targetRotation);
}
// Смена режима камеры
public void SetCameraMode(CameraMode newMode)
{
if (CurrentMode == newMode) return;
CurrentMode = newMode;
// Применяем настройки специфичные для режима
ApplyModeSpecificSettings();
}
// Применение настроек специфичных для текущего режима
private void ApplyModeSpecificSettings()
{
switch (CurrentMode)
{
case CameraMode.FirstPerson:
// В FirstPerson режиме дистанция всегда равна 0
minDistance = 0f;
maxDistance = 0f;
defaultDistance = 0f;
_targetDistance = 0f;
_currentDistance = 0f;
break;
case CameraMode.ThirdPerson:
// Восстанавливаем исходные значения дистанции
minDistance = _originalMinDistance;
maxDistance = _originalMaxDistance;
defaultDistance = _originalDefaultDistance;
_targetDistance = defaultDistance;
break;
// Заготовка на будущее (если понадобится)
case CameraMode.Static:
case CameraMode.Cinematic:
default:
break;
}
}
}
}