Unity: физический контроллер персонажа — детальное руководство

Контроллер персонажа с физикой — это распространённый способ реализовать реалистичное движение в играх. Такой подход даёт естественные реакции на столкновения и силы, но требует понимания физики Unity (Rigidbody, силы, крутящий момент, коллайдеры и слои). Ниже шаг за шагом описано создание базового контроллера на C#, затем — улучшения, отладка и варианты под разные задачи.
Что мы создаём
Коротко: сцена с плоским ландшафтом (Plane), кубом как моделью игрока (Cube) и камерой третьего лица. Куб становится физическим объектом через Rigidbody. Скрипт на C# добавляет движение вперёд/назад, повороты и прыжок.
Ключевые понятия (в 1–2 строках):
- Rigidbody — компонент Unity, отвечающий за физическое поведение объекта.
- AddForce / AddTorque — методы для применения силы и крутящего момента.
- FixedUpdate — функция Unity для физики, вызывается с фиксированным шагом.
Шаг 1: Создание сцены с игроком и плоскостью
- Откройте Unity и создайте новый проект.
- В Hierarchy правой кнопкой — 3D Object → Plane. Это будет ваш тестовый ландшафт. Можно масштабировать Plane для большой зоны тестирования.
- В Hierarchy — 3D Object → Cube. Это временная модель игрока.
- Выберите Cube → Inspector → Add Component → Rigidbody. Включите Use Gravity.
- Разместите Main Camera над кубом; перетащите камеру на Cube в Hierarchy, чтобы сделать её дочерним объектом — камера будет следовать за кубом.
Простой тест: нажмите Play — куб должен упасть на плоскость и камера следовать за ним.
Важно: этот базовый набор компонентов позволяет быстро начать, но для финального проекта замените Cube на модель персонажа с корректными коллайдерами.
Шаг 2: Создание C# файла
Создайте папку Scripts в Project. Внутри — Create → C# Script и назовите файл, например, Character_Control. Откройте файл и убедитесь, что структура класса корректна:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character_Control : MonoBehaviour {
void Start() {
}
void Update() {
}
}Примечание: для работы с физикой используйте FixedUpdate вместо Update, когда применяете силы к Rigidbody — об этом ниже.
Шаг 3: Движение вперёд и назад через AddForce
Добавьте публичные переменные в класс, чтобы задавать Rigidbody и скорость из инспектора:
public Rigidbody rigidbody;
public float speed;В Start присвойте компонент rigidbody:
void Start() {
rigidbody = GetComponent();
} Исходный пример использует Input.GetKey и AddForce:
if (Input.GetKey("w")) {
rigidbody.AddForce(transform.forward * speed);
}
if (Input.GetKey("s")) {
rigidbody.AddForce((transform.forward * -1) * speed);
}Однако есть важные улучшения и замечания:
- Для физики используйте FixedUpdate вместо Update, чтобы силы применялись согласованно с шагом симуляции.
- Используйте Time.fixedDeltaTime или ForceMode для предсказуемой силы при разных настройках скорости кадров.
- Лучше применять относительную силу с ForceMode.VelocityChange, если вы хотите мгновенно менять скорость, или ForceMode.Force для постепенного ускорения.
- Для плавного управления используйте Input.GetAxis(“Vertical”) вместо отдельных GetKey — это даёт аналоговые значения (от -1 до 1).
Пример улучшенного фрагмента:
void FixedUpdate() {
float moveInput = Input.GetAxis("Vertical"); // -1..1
Vector3 force = transform.forward * moveInput * speed;
rigidbody.AddForce(force, ForceMode.Force);
}Если куб начинает катиться или крутиться, зайдите в Rigidbody и зафиксируйте вращение по осям X и Z (Constraints → Freeze Rotation X,Z). Этот шаг предотвращает переворачивания при столкновениях.
Шаг 4: Повороты через AddTorque
Добавьте переменную torque:
public float torque;Исходный код использует Input.GetAxis(“Horizontal”) и AddTorque:
if (Input.GetKey("d")) {
float turn = Input.GetAxis("Horizontal");
rigidbody.AddTorque(transform.up * torque * turn);
}
if (Input.GetKey("a")) {
float turn = Input.GetAxis("Horizontal");
rigidbody.AddTorque(transform.up * torque * turn);
}Упрощённый и улучшенный вариант в FixedUpdate:
void FixedUpdate() {
float moveInput = Input.GetAxis("Vertical");
float turnInput = Input.GetAxis("Horizontal");
rigidbody.AddForce(transform.forward * moveInput * speed, ForceMode.Force);
rigidbody.AddTorque(transform.up * turnInput * torque, ForceMode.Force);
}Советы по настройке:
- Меняйте массу (Mass) и затухание (Drag) у Rigidbody для регулировки инерции и торможения.
- Torque зависит от массы; если персонаж слишком медленно поворачивается, увеличьте torque или уменьшите массу.
- Для более игрового (не физического) управления используйте transform.Rotate вместе с подконтролем коллизий, но в этом случае вы теряете реакцию физики.
Пример рекомендованных начальных значений (пример): Mass = 1, Drag = 1, torque = 2.
Шаг 5: Прыжок с контролем высоты
Прыжок требует контроля: игрок должен подпрыгивать ограниченно и не «многократно» в воздухе. Для этого используйте флаг isJumping и проверку на землю.
Исходный примитивный пример из текста:
private bool isJumping = false;
if (!isJumping) {
if (Input.GetKeyDown("space")) {
isJumping = true;
rigidbody.AddForce(transform.up * speed * 120);
rigidbody.angularVelocity = Vector3.zero;
Invoke("Move_Setter", 0.8f);
}
}
void Move_Setter() {
isJumping = false;
}Этот код работает, но имеет недостатки: Invoke с фиксированным временем не учитывает высоту прыжка, платформы или возможность восстановления в воздухе. Лучше определить, находится ли объект на земле, через Raycast или коллайдер, и использовать физически корректное приложение силы с ForceMode. Вот улучшенный подход.
Пример улучшенного кода для прыжка с проверкой земли:
public float jumpForce = 5f;
public Transform groundCheck; // пустой объект внизу модели
public float groundCheckDistance = 0.1f;
public LayerMask groundLayers; // слои, считающиеся землёй
private bool isGrounded = false;
void FixedUpdate() {
// движение и поворот как выше
}
void Update() {
// проверка ввода прыжка в Update
if (Input.GetKeyDown(KeyCode.Space) && isGrounded) {
rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
void LateUpdate() {
// простая проверка на землю
isGrounded = Physics.Raycast(groundCheck.position, Vector3.down, groundCheckDistance, groundLayers);
}Пояснения:
- groundCheck — пустой объект (empty GameObject) на уровне стоп персонажа. Raycast проверяет, есть ли земля под персонажем.
- ForceMode.Impulse применяется для мгновенного импульса (подходит для прыжка).
- Это надёжнее, чем «таймер», так как прыжок снова возможен только после реального контакта с землёй.
Общие улучшения и лучшие практики
- FixedUpdate для всех действий, изменяющих Rigidbody (AddForce, AddTorque, MovePosition, MoveRotation).
- Input собирайте в Update (например, флаги или оси), а применяйте в FixedUpdate.
- Нормализуйте векторы перед умножением на скорость, если комбинируете направления (чтобы диагональная скорость не была больше).
- Используйте constraints в Rigidbody, чтобы отключить ненужные степени свободы (например, зафиксировать ось Y вращения для 2D-плоскости).
- При необходимости используйте Physics Materials для управления скольжением и сцеплением поверхностей.
- Для персонажа, требующего точного контроля (платформер, шутер), рассмотрите использование CharacterController или кинематического управления, вместо физики.
Отладка и распространённые проблемы
Important: если объект крутится или ведёт себя нестабильно:
- Проверьте коллайдеры: пересекающиеся или слишком большие коллайдеры могут создавать непредсказуемые силы.
- Заморозьте ненужные вращения (Freeze Rotation) или используйте constraints.
- Убедитесь, что не применяете силы одновременно в Update и FixedUpdate.
- Проверяйте консоль Unity на ошибки компиляции и предупреждения о несоответствиях типов.
Если прыжки срабатывают несколько раз подряд или персонаж «застревает» в воздухе — используйте Raycast/OnCollisionEnter для надёжного определения контакта с землёй.
Альтернативные подходы
- CharacterController: компонент Unity, предоставляющий методы Move и SimpleMove. Он не использует Rigidbody и даёт точный контроль над движением (удобно для платформ и шутеров).
- Kinematic Rigidbody: используйте Rigidbody в режиме kinematic и вручную управляйте позицией через MovePosition и MoveRotation.
- Контроль через NavMeshAgent: для NPC и навигации на уровне сцены.
- Смешанный подход: физика для столкновений, но управление через контролируемое изменение позиции.
Когда физика не подходит:
- Нужен стабильный, предсказуемый контроль (например, в соревновательных играх с быстрым откликом).
- Взаимодействия с физикой приводят к слишком затруднённым багам (задевания, «флиппинг»).
Стратегия тестирования и Критерии приёмки
Критерии приёмки:
- Персонаж движется вперёд/назад при нажатии W/S с предсказуемой скоростью.
- Персонаж поворачивается влево/вправо при нажатии A/D без заметного дрожания.
- Прыжок срабатывает только при контакте с землёй; повторный прыжок в воздухе невозможен.
- При фиксации вращений по X/Z куб не переворачивается при столкновениях.
Тест-кейсы:
- Нажать W: персонаж ускоряется вперед.
- Нажать S: персонаж движется назад.
- Нажать A/D: поворот вокруг оси Y с ожидаемой скоростью.
- Прыжок: нажать пробел на земле — персонаж подпрыгивает; нажать пробел в воздухе — прыжок не срабатывает.
- Столкновение со стеной: персонаж не проходит сквозь коллайдер.
Чек-листы по ролям
Для разработчика:
- Добавить Rigidbody и Collider.
- Создать скрипт управления и присвоить его объекту.
- Тестировать FixedUpdate и Update разделение.
- Настроить groundCheck и слои земли.
Для дизайнера уровня:
- Проверить геометрию коллайдеров на сцене.
- Убедиться, что навигационные области не пересекаются с физическими объектами.
Для QA:
- Выполнить все тест-кейсы из Критерии приёмки.
- Проверить экстремальные случаи (высокая скорость, наклонные поверхности).
Примеры улучшенных скриптов — «чистая» версия
Ниже пример компактного, практичного скрипта, который объединяет всё вышеописанное (комментарии внутри):
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class SimplePhysicsCharacter : MonoBehaviour {
public float speed = 5f;
public float torque = 2f;
public float jumpForce = 5f;
public Transform groundCheck;
public float groundCheckDistance = 0.1f;
public LayerMask groundLayers;
private Rigidbody rb;
private float moveInput;
private float turnInput;
private bool jumpRequest;
private bool isGrounded;
void Start() {
rb = GetComponent();
}
void Update() {
// Сбор ввода в Update
moveInput = Input.GetAxis("Vertical");
turnInput = Input.GetAxis("Horizontal");
if (Input.GetKeyDown(KeyCode.Space) && isGrounded) {
jumpRequest = true;
}
// Проверяем землю
isGrounded = Physics.Raycast(groundCheck.position, Vector3.down, groundCheckDistance, groundLayers);
}
void FixedUpdate() {
// Движение и поворот в физической петле
rb.AddForce(transform.forward * moveInput * speed, ForceMode.Force);
rb.AddTorque(transform.up * turnInput * torque, ForceMode.Force);
if (jumpRequest && isGrounded) {
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
jumpRequest = false;
}
}
} Этот шаблон легко настраивается через Inspector и безопасен с точки зрения разделения ввода и применения сил.
Когда подход с Rigidbody не подойдёт (контрпримеры)
- Платформер с прыжками «по пиксельной» точности: физика может внести непредсказуемость.
- Сетевые игры с авторитетным сервером: передача физического состояния между клиентами — сложная задача. Часто используют предсказание и репликацию, либо кинематические методы.
- 2D-игры, где требуется строгое поведение по оси — лучше использовать специализированные 2D-компоненты.
Ментальные модели и эвристики
- Сила (AddForce) — это непрерывное влияние, оно постепенно меняет скорость.
- Импульс (ForceMode.Impulse) — мгновенное изменение скорости (удар, прыжок).
- Крутящий момент (AddTorque) — вращение вокруг оси; комбинируйте с ограничениями, чтобы избегать переворотов.
- Разделяй ввод и физику: считывай ввод в Update, применяй в FixedUpdate.
Советы по производительности
- Минимизируйте частые вызовы Raycast на каждом кадре для множества объектов; группируйте проверки или используйте триггерные коллайдеры.
- Для крупных сцен используйте слои и LayerMasks, чтобы Raycast проверял только релевантные поверхности.
- Отключайте физику для объектов за пределами зоны интереса (Sleeping Mode).
Совместимость и миграция
- Скрипты, использующие устаревшие API, могут потребовать приведения типов после обновления Unity. Всегда тестируйте на целевой версии движка.
- Если проект переходит от физического подхода к CharacterController, потребуется переработать логику столкновений и анимаций.
Краткая сводка
- Используйте Rigidbody и AddForce/AddTorque для естественного физического движения.
- Применяйте силы в FixedUpdate, собирайте ввод в Update.
- Для прыжков используйте Raycast/OnCollision для определения контакта с землёй.
- Рассмотрите альтернативы (CharacterController, kinematic), если нужна более строгая предсказуемость.
Если вы хотите, я могу подготовить готовый пак скриптов с настройками Inspector, или адаптировать пример под 2D/серверную синхронизацию.
Похожие материалы
Как сделать правый клик на Chromebook
Не удалось инициализировать Direct3D — решения
Исправить Data Retrieval в Diablo 4 на Steam
Open Graph в WordPress — настройка мета‑тегов
getconf: адаптивные скрипты для разных Linux