Реализация продвинутого мувсета в Unity: архитектура вызовов и обработка ввода
Основываясь на интересе к предыдущему материалу, в этой части мы углубимся в архитектуру системы перемещения. Мы разберем схему вызова методов, интеграцию современной системы ввода (Input System) и событийную модель, которая позволяет гибко настраивать визуальные и звуковые эффекты: от встряски камеры при приземлении до озвучки прыжков.
Для тех, кто пропустил первую часть, рекомендую ознакомиться с ней для понимания базовой логики. В данной статье мы сосредоточимся на том, как превратить набор разрозненных методов в стройную систему.
Архитектура методов перемещения
Ниже представлены ключевые функции, отвечающие за физику и логику движений. Мы разделим их на блоки ответственности.
Управление обзором и направлением
Метод CalculateView() отвечает за вращение камеры и корпуса персонажа, учитывая настройки чувствительности и инверсии.
private void CalculateView() { _newCameraRotation.x += _inputView.y * _playerSettings.verticalSensetivity * Time.deltaTime * (_playerSettings.verticalInverted ? 1f : -1f);_newCameraRotation.x = Mathf.Clamp(_newCameraRotation.x, _playerConfigs.cameraVerticalRotateMin, _playerConfigs.cameraVerticalRotateMax); _newCaracterRotation.y += _inputView.x * _playerSettings.horisontalSensetivity * Time.deltaTime * (_playerSettings.horisontalInverted ? -1f : 1f); _cameraHolder.localRotation = Quaternion.Euler(_newCameraRotation); transform.localRotation = Quaternion.Euler(_newCaracterRotation);}
Для определения вектора движения используется вспомогательный метод
GetMoveDirrection(), который ориентируется на положение камеры, но игнорирует вертикальную составляющую, чтобы игрок не «взлетал» при взгляде вверх.private Vector3 GetMoveDirrection() { Vector3 moveDirection = _cameraHolder.forward * _inputMovement.y + _cameraHolder.right * _inputMovement.x; moveDirection.y = 0; moveDirection.Normalize(); return moveDirection; }Логика движения и ускорения
Основной расчет скорости
CalculateMoveVelocity()разделяет поведение персонажа на земле и в воздухе, обеспечивая инерцию и контроль в полете.private void CalculateMoveVelocity() { if (_isDashNow) return;Vector3 moveDirection = GetMoveDirrection(); float currentAcceleration = _isGrounded ? _playerConfigs.acceleration : _playerConfigs.airAcceleration; if (!_isGrounded) { Vector3 currentVelocity = new Vector3(_rb.linearVelocity.x, 0, _rb.linearVelocity.z); float maxAirSpeed = _playerConfigs.walkSpeed; if (currentVelocity.magnitude > maxAirSpeed) currentVelocity = currentVelocity.normalized * maxAirSpeed; Vector3 airControl = moveDirection * (currentAcceleration * _playerConfigs.walkSpeedAirModif * Time.fixedDeltaTime); Vector3 newVelocity = currentVelocity + airControl; if (newVelocity.magnitude > maxAirSpeed) newVelocity = newVelocity.normalized * maxAirSpeed; newVelocity.y = _rb.linearVelocity.y; _rb.linearVelocity = newVelocity; } else { Vector3 targetVelocity = moveDirection * _playerConfigs.walkSpeed; Vector3 newVelocity = Vector3.MoveTowards( new Vector3(_rb.linearVelocity.x, 0, _rb.linearVelocity.z), targetVelocity, currentAcceleration * Time.fixedDeltaTime ); newVelocity.y = _rb.linearVelocity.y; _rb.linearVelocity = newVelocity; }}
Вертикальный геймплей: прыжки и рывки
Система поддерживает «койот-тайм» (возможность прыгнуть в течение доли секунды после схода с платформы) и прыжки от стен.
private void PerformJump() { bool canJump = (Time.time - _lastGroundedTime <= _coyoteTime) && (Time.time - _lastJumpTime >= _jumpCoyoteTime);if (canJump) Jump(); else if (CheckWallsAround(out Vector3 wallNormal) && _wallJumpWithoutGrounded < _playerConfigs.maxWallJumpsWithoutGrounded) JumpWall(wallNormal);}
private void Dash()
{
Vector3 dashVector = GetMoveDirrection();
if (dashVector.magnitude < 0.1f)
{
dashVector = _cameraHolder.transform.forward;
dashVector.y = 0;
}_dashStartTime = Time.time; _rb.linearVelocity = Vector3.zero; _rb.AddForce(dashVector * _playerConfigs.dashForce, ForceMode.Impulse);}
Система жизненного цикла и вызовов
Для корректной работы физики и плавности управления мы распределяем вызовы между
UpdateиFixedUpdate.private void FixedUpdate() { CheckGround(); HandleGravity(); }private void Update() { CalculateView(); CalculateDashStatus(); CalculateMoveVelocity(); }
Метод
CheckGround()не просто проверяет наличие коллизии под ногами, но и генерирует событие приземления, что важно для эффектов соударения.private void CheckGround() { bool wasGrounded = _isGrounded; _isGrounded = Physics.Raycast(transform.position, Vector3.down, _rayLength, _groundLayer);if (_isGrounded) { _lastGroundedTime = Time.time; _wallJumpWithoutGrounded = 0; } if (!wasGrounded && _isGrounded) landingEvent?.Invoke();}
Интеграция New Input System
Использование современной системы ввода позволяет абстрагироваться от конкретных клавиш и легко реализовать кроссплатформенность. Вместо проверки нажатия условной клавиши «Space», мы подписываемся на абстрактное действие
Jump.Настройка Action Maps
- Actions: Создаем действия для перемещения (Vector2), обзора (Vector2) и триггерные действия (Button) для прыжка и рывка.
- Bindings: Привязываем WASD к движению и Delta мыши к обзору.
Чтобы избежать дублирования кода, создадим единую точку доступа к вводу:
public class PlayerInputSystem : MonoBehaviour { private InputSystem_Actions _actions; public InputSystem_Actions Actions => _actions;private void Awake() { _actions = new InputSystem_Actions(); _actions.Enable(); }}
Связывание ввода с логикой
В методе
Startмы подписываем методы контроллера на события системы ввода:private void Start() { _rb = GetComponent<Rigidbody>();_playerInputSystem.Actions.Character.Movement.performed += ctx => _inputMovement = ctx.ReadValue<Vector2>(); _playerInputSystem.Actions.Character.Movement.canceled += ctx => _inputMovement = Vector2.zero; _playerInputSystem.Actions.Character.View.performed += ctx => _inputView = ctx.ReadValue<Vector2>(); _playerInputSystem.Actions.Character.View.canceled += ctx => _inputView = Vector2.zero; _playerInputSystem.Actions.Character.Jump.performed += ctx => PerformJump(); _playerInputSystem.Actions.Character.Dash.performed += ctx => PerformDash();}
Событийная модель (Events)
Для того чтобы система перемещения оставалась «чистой» и не содержала в себе ссылок на аудио-сорс или компоненты тряски камеры, мы используем
UnityEvent. Это позволяет дизайнерам настраивать эффекты прямо в инспекторе Unity.public UnityEvent jumpEvent; public UnityEvent dashEvent; public UnityEvent landingEvent; public UnityEvent wallJumpEvent;Такой подход делает систему модульной: вы можете добавить звук шагов или партиклы пыли, не меняя ни единой строчки в основном скрипте передвижения.
Заключение
Представленная архитектура обеспечивает баланс между производительностью и гибкостью. Разделение на логические блоки, использование New Input System и событийная модель позволяют создать надежную основу для динамичного шутера. Код легко расширяется новыми механиками, такими как скольжение по поверхностям или бег по стенам, сохраняя при этом читаемость и стабильность.



