2_Физика движка

Урок 2 : Физика движка

О чём этот урок

Разбираем физическую систему Unity: коллайдеры и триггеры как инструменты обнаружения, Rigidbody как физическое тело, и активные запросы к физическому движку (Raycast, OverlapSphere). Именно эти инструменты позволят персонажу не проваливаться сквозь пол и реагировать на мир вокруг.


1. Коллайдеры и триггеры

Что такое коллайдер

Коллайдер (Collider) - компонент, который задаёт физическую форму объекта. Без коллайдера объект невидим для физического движка: он будет проваливаться сквозь пол и пропускать сквозь себя другие объекты.

Важно: коллайдер не двигает объект. Это только форма для обнаружения столкновений. Движением управляет Rigidbody (раздел 2).

Коллайдер Когда использовать
BoxCollider Ящики, стены, платформы, двери
SphereCollider Снаряды, сферические объекты, дешёвые приближения
CapsuleCollider Персонажи (стандарт - капсула охватывает тело)
MeshCollider Точное повторение меша - дорого, использовать осторожно

Триггеры - зоны без физической реакции

Если у коллайдера включён флаг Is Trigger, он перестаёт быть твёрдым телом и становится «зоной обнаружения». Объекты проходят сквозь него, но движок фиксирует вход и выход.

Типичные применения: зона сбора предметов, зона телепортации, зона смерти, детектор врагов.

// Этот скрипт - на объекте с коллайдером в режиме Is Trigger
public class CollectibleZone : MonoBehaviour
{
    // OnTriggerEnter - вызывается в первый кадр, когда объект входит в зону
    void OnTriggerEnter(Collider other)
    {
        // other - коллайдер объекта, который вошёл
        if (other.CompareTag("Player"))
        {
            Debug.Log("Игрок вошёл в зону сбора");
            Destroy(gameObject);
        }
    }

    // OnTriggerStay - вызывается каждый физический кадр, пока объект внутри
    void OnTriggerStay(Collider other)
    {
        if (other.CompareTag("Player"))
            Debug.Log("Игрок в зоне урона - наносим урон");
    }

    // OnTriggerExit - вызывается когда объект покидает зону
    void OnTriggerExit(Collider other)
    {
        Debug.Log($"{other.name} покинул зону");
    }
}
Требования для работы триггера

Для срабатывания триггерных событий нужно выполнить три условия: оба объекта имеют коллайдер, один из коллайдеров - Is Trigger, хотя бы один из объектов имеет компонент Rigidbody. Без Rigidbody триггер не сработает.

Слои и управление коллизиями

Каждый объект принадлежит одному слою (Layer). В Project Settings → Physics находится Layer Collision Matrix: таблица, где можно включить или отключить коллизии между любыми парами слоёв.

Например: слой Bullet не должен сталкиваться со слоем Player (пуля летит от игрока). Просто снимаем галочку Bullet × Player в матрице.

Из кода можно отключить коллизию между конкретными коллайдерами:

void Start()
{
    var playerCollider = GetComponent<Collider>();
    var weaponCollider = GetComponentInChildren<Collider>();

    // Оружие не будет сталкиваться с владельцем
    Physics.IgnoreCollision(playerCollider, weaponCollider);
}

2. Rigidbody и физика

Что добавляет Rigidbody

Rigidbody - компонент, который подключает объект к физическому движку Unity. Без него объект статичен (или движется только через код). С Rigidbody объект получает:

Kinematic режим

isKinematic = true - объект управляется кодом, а не физикой. Гравитация не действует, силы игнорируются. При этом объект всё ещё участвует в триггерах и отталкивает не-кинематические Rigidbody при столкновении.

Используй kinematic для: платформ, дверей, управляемых персонажей (CharacterController + Rigidbody kinematic).

Ограничения и режимы обнаружения

void Start()
{
    var rb = GetComponent<Rigidbody>();

    // Заморозить вращение по осям X и Z - персонаж не заваливается
    rb.constraints = RigidbodyConstraints.FreezeRotationX
                   | RigidbodyConstraints.FreezeRotationZ;

    // Для быстрых объектов (пули, быстрые машины)
    // Discrete (стандарт) может «проскочить» сквозь тонкую стену за один кадр
    // Continuous - проверяет непрерывный путь, не пропускает столкновения
    rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
}

Коллизии - полноценные столкновения

В отличие от триггеров, OnCollisionEnter вызывается при физическом столкновении (оба коллайдера - твёрдые тела):

public class Ball : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        Debug.Log($"Столкновение с {collision.gameObject.name}");

        // Точка первого контакта в мировых координатах
        Vector3 contactPoint = collision.contacts[0].point;

        // Относительная скорость столкновения
        float impactSpeed = collision.relativeVelocity.magnitude;
        Debug.Log($"Скорость удара: {impactSpeed:F1}");
    }

    void OnCollisionStay(Collision collision) { }
    void OnCollisionExit(Collision collision) { }
}
Физический цикл

Порядок событий в физическом тике: FixedUpdate() → движок рассчитывает физику → обнаружение столкновений → OnCollisionEnter/Stay/Exit и OnTriggerEnter/Stay/Exit. Поэтому весь код, влияющий на физику, должен быть в FixedUpdate.


3. Методы перемещения через Rigidbody

Управлять объектом с Rigidbody нужно именно через него, а не через Transform - иначе физика не успевает реагировать на изменения позиции.

public class PhysicsMovement : MonoBehaviour
{
    [SerializeField] private float _speed = 5f;
    [SerializeField] private float _jumpForce = 7f;
    private Rigidbody _rb;

    void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        _rb.constraints = RigidbodyConstraints.FreezeRotationX
                        | RigidbodyConstraints.FreezeRotationZ;
    }

    void FixedUpdate()
    {
        // MovePosition - плавное кинематическое перемещение
        // Корректно взаимодействует с физикой: не проходит сквозь стены
        Vector3 move = new Vector3(1f, 0f, 0f); // направление задаст Input в следующем уроке
        _rb.MovePosition(_rb.position + move * _speed * Time.fixedDeltaTime);
    }

    public void Jump()
    {
        // AddForce с Impulse - мгновенный единовременный импульс
        _rb.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse);
    }
}
Метод Где вызывать Физика Коллизии Когда использовать
rb.MovePosition FixedUpdate Да Корректно Основное движение персонажа
rb.AddForce FixedUpdate Да Корректно Прыжок, взрыв, применение сил
rb.velocity = v FixedUpdate Частично Корректно Быстрый контроль горизонтальной скорости
transform.position = p Update Нет Игнорируются Спавн, телепорт, объекты без физики
transform.Translate Update Нет Игнорируются Визуальные объекты без физики
Transform.Translate игнорирует физику

Перемещение через transform.Translate или прямое присвоение transform.position не учитывает коллайдеры. При высокой скорости объект пройдёт сквозь стену. Для объектов с Rigidbody используй методы самого Rigidbody.


4. Физические запросы

Коллайдеры и триггеры срабатывают реактивно - движок сам вызывает методы при контакте. Иногда нужен противоположный подход: активно опросить пространство из кода в конкретный момент. Для этого служат физические запросы.

Physics.Raycast - луч

Physics.Raycast пускает невидимый луч из точки в направлении и возвращает информацию о первом объекте с коллайдером, который луч пересёк.

void Update()
{
    // Луч из позиции объекта вперёд, максимальная длина 10 единиц
    if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 10f))
    {
        Debug.Log($"Попал в: {hit.collider.name}");
        Debug.Log($"Точка попадания: {hit.point}");
        Debug.Log($"Нормаль поверхности: {hit.normal}");
        Debug.Log($"Расстояние: {hit.distance:F2}");
    }
}

Луч можно ограничить слоями через LayerMask, чтобы он реагировал только на нужные объекты:

[SerializeField] private LayerMask _interactableLayers;

void Update()
{
    // Луч из центра экрана (через камеру) в мир
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    if (Physics.Raycast(ray, out RaycastHit hit, 100f, _interactableLayers))
    {
        if (Input.GetMouseButtonDown(0))
            hit.collider.GetComponent<IInteractable>()?.Interact();
    }
}

Physics.CheckSphere и OverlapSphere

Physics.CheckSphere проверяет, есть ли коллайдер в сфере заданного радиуса. Используется для проверки земли под ногами персонажа:

// Возвращает true, если в точке pos есть коллайдер на слое groundLayer
bool isGrounded = Physics.CheckSphere(groundCheck.position, 0.2f, groundLayer);

Physics.OverlapSphere возвращает массив всех коллайдеров в сфере - удобно для зоны взрыва:

// Все коллайдеры в радиусе 5 единиц от точки взрыва
Collider[] inBlastZone = Physics.OverlapSphere(explosionPoint, blastRadius);
foreach (var col in inBlastZone)
{
    col.GetComponent<IDamageable>()?.TakeDamage(50f);
}
Метод Возвращает Когда использовать
Physics.Raycast bool + RaycastHit Стрельба, интерактивный объект под курсором
Physics.RaycastAll RaycastHit[] Луч сквозь несколько объектов (пробивная пуля)
Physics.CheckSphere bool Быстрая проверка: есть ли что-то рядом (земля под ногами)
Physics.OverlapSphere Collider[] Все объекты в радиусе (взрыв, агро-зона)
Визуализируй лучи через Debug.DrawRay

Physics.Raycast невидим во вьюпорте. Добавь рядом Debug.DrawRay(origin, direction * maxDistance, Color.red) - линия отобразится в Scene view во время Play Mode и покажет точное направление и длину луча.


📋 Перейти к практике урока 2