Проектирование игровых механик передвижения
Персонаж отрывается от земли на полкадра раньше, чем нажата кнопка прыжка — и игрок ощущает это как «пластилиновое» управление. Именно здесь начинается работа по проектированию механик передвижения: не в настройке Rigidbody.mass наугад, а в формализации ощущения отклика через конкретные числа и архитектурные решения.
Почему «просто CharacterController» не работает
Unity предлагает два пути: физический Rigidbody + коллайдер и кинематический CharacterController. Обе реализации несут специфические грабли.
CharacterController.Move() не взаимодействует с физическим движком напрямую — персонаж проходит сквозь движущиеся платформы, если не реализовать кастомный механизм riding. Стандартный isGrounded флаг возвращает false на одном кадре при спуске по склону — и персонаж начинает бесконечно «подпрыгивать» из-за накопленной гравитации в буфере вертикальной скорости.
Rigidbody-персонаж устойчивее к физическим взаимодействиям, но требует ручного контроля трения: без PhysicMaterial с нулевым dynamicFriction на капсуле персонаж застревает на углах геометрии. При этом Rigidbody.AddForce() в режиме ForceMode.VelocityChange ведёт себя предсказуемо только при fixedDeltaTime 0.02 — стоит изменить шаг физики, и все настроенные ощущения «плывут».
Отдельный класс проблем — детектирование земли. Physics.SphereCast вниз с радиусом чуть меньше капсулы надёжнее, чем Physics.Raycast, но требует аккуратной настройки LayerMask, иначе каст будет попадать в сам коллайдер персонажа.
Как строится архитектура системы передвижения
Хорошо спроектированная система передвижения разделяет три зоны ответственности: ввод, состояние и физика/перемещение.
Ввод читается в Update() и пишется в структуру MovementInput. Состояния (Idle, Running, Jumping, Falling, Crouching, WallRunning) управляются конечным автоматом — обычно это кастомный класс поверх MonoBehaviour, а не Animator State Machine, потому что логика переходов часто нелинейна и завязана на игровые условия, а не на анимационные веса. Сам сдвиг позиции происходит в FixedUpdate() через Rigidbody.MovePosition() или напрямую через velocity.
Для платформеров с воздушным контролем важна кривая airControlCurve: горизонтальное ускорение в воздухе должно быть меньше наземного, но не нулевым. Реализуется через AnimationCurve в ScriptableObject настроек персонажа — дизайнер меняет кривую в инспекторе, не трогая код.
Coyote time и jump buffering — обязательные компоненты для отзывчивого управления. Первый позволяет прыгнуть через coyoteTimeDuration (обычно 0.1–0.15 секунды) после схода с платформы. Второй сохраняет нажатие прыжка в буфер на jumpBufferDuration (0.1–0.2 секунды) и выполняет его при первой возможности. Без этих двух механик игрок постоянно «промахивается» мимо прыжка на краю платформы.
Переменная высота прыжка — ещё одна точка, где детали решают всё. Если отпустить кнопку прыжка в середине подъёма, вертикальная скорость срезается до minJumpVelocity. Это реализуется простой проверкой в Update(): если rb.velocity.y > minJumpVelocity && !jumpButtonHeld, то rb.velocity = new Vector3(rb.velocity.x, minJumpVelocity, rb.velocity.z).
Таблица ориентировочных сроков по масштабу
| Масштаб задачи | Описание | Срок |
|---|---|---|
| Базовый | 2D/3D персонаж, земля, прыжок, coyote time | 2–5 дней |
| Средний | + double jump, dash, wall jump, crouch | 1–2 недели |
| Расширенный | + swimming, climbing, ragdoll transition, moving platforms | 2–4 недели |
| Полная система | + procedural footstep IK, lean, network replication | 4–8 недель |
Особенности для специфических жанров
В шутерах от первого лица Camera и CharacterController живут в разных иерархиях и обновляются независимо — это предотвращает дёрганье камеры при физических коллизиях корпуса. Head bobbing реализуется на уровне камеры через синус от пройденного расстояния, а не через анимацию родительского объекта.
В топ-даун RPG с NavMeshAgent передвижение по клику требует разграничения: NavMeshAgent управляет путём, но не анимацией. Animator-параметры Speed и Direction вычисляются из agent.velocity каждый кадр, а не из нажатых клавиш. Частая ошибка — включить updateRotation = true на агенте и одновременно крутить трансформ вручную, что приводит к дрожанию поворота.
Процесс работы над проектом
Начинаем с анализа GDD и референсных игр — выписываем конкретные числа из механик (скорость бега в units/sec, высота прыжка, время дэша). Затем проектируем MovementSettings ScriptableObject со всеми параметрами и PlayerMovement MonoBehaviour с задокументированными зонами ответственности.
Прототип собирается на примитивах без анимаций — только коллайдеры и логика. Это позволяет нащупать feel управления за день-два, не тратя время на интеграцию с Animator. После утверждения feel подключаем Animator Controller с Blend Tree для локомоции и финальные коллайдеры по меш-геометрии.
Тестирование включает граничные кейсы: прыжок в 1-юнитовый проём, движущиеся платформы с вращением, переход между NavMesh Surface разных сцен при аддитивной загрузке. Эти ситуации чаще всего вскрывают проблемы с детектированием земли и накоплением velocity.





