Двухмерный персонаж проходит сквозь светящуюся зону-триггер, а рядом сплошная стена останавливает мяч

Физика в Unity: коллайдеры, триггеры, Rigidbody и Raycast

·  11 мин чтения Для начинающих Unity 2D C# Физика Туториал

Коротко. Коллайдер задаёт форму объекта для физики, а Rigidbody2D заставляет объект двигаться по её законам. Обычные коллайдеры сталкиваются и вызывают OnCollisionEnter2D. Коллайдер с галочкой Is Trigger пропускает объекты сквозь себя и вызывает OnTriggerEnter2D. Чтобы событие сработало, хотя бы у одного из двух объектов должен быть Rigidbody2D, а проверить что-то на расстоянии поможет Physics2D.Raycast(origin, direction, distance, layers).

Ниже: готовый код под Unity 6.6, таблица всех сочетаний коллайдеров, измеренная в редакторе, и чеклист на случай, когда событие молчит или объект проваливается сквозь пол.

Почти все вопросы новичков про физику сводятся к трём: почему объекты не сталкиваются, почему триггер молчит и почему персонаж упал сквозь пол. На форумах на них отвечают обрывками, и половина ответов написана для старых версий движка. Поэтому мы не стали пересказывать документацию, а собрали демо-сцену в Unity 6.6 (6000.6) и прогнали в Play Mode все сочетания типов коллайдеров. Цифры ниже взяты из этого теста, весь код из статьи компилируется в этой версии без предупреждений.

Тест пригодился сразу. Пока мы собирали сцену, герой провалился сквозь пол, хотя компонент Box Collider 2D на полу был. Оказалось, что коллайдер добавили до назначения спрайта, и он получил нулевой размер. Такие ошибки не видны в консоли, поэтому в конце статьи есть отдельный чеклист.

Что такое коллайдер, триггер и Rigidbody

Коллайдер — это невидимая форма объекта, по которой физика определяет касания: Box Collider 2D, Circle Collider 2D, Capsule Collider 2D. Триггер — тот же коллайдер с включённой галочкой Is Trigger: он не мешает движению, а только сообщает, что в него кто-то вошёл. Rigidbody2D — компонент, который подключает объект к физическому движку.

Окно Scene в Unity: герой с Rigidbody2D, монета с Circle Collider 2D и Is Trigger, стена и пол только с коллайдерами
Наша демо-сцена: всё, о чём пойдёт речь дальше.

Поведение объекта задаёт поле Body Type в Rigidbody2D:

Шаг 1. Столкновения: OnCollisionEnter2D

Событие OnCollisionEnter2D вызывается в момент удара двух обычных коллайдеров, если хотя бы один из них принадлежит объекту с Rigidbody2D в режиме Dynamic. Настройте сцену так: герою добавьте Rigidbody2D и Capsule Collider 2D, полу и стене только Box Collider 2D. Подробно про настройку персонажа мы писали в статье про движение и прыжок в Unity 2D.

Инспектор Unity: у героя Rigidbody2D с Body Type Dynamic и Collision Detection Continuous, в Capsule Collider 2D выключен Is Trigger
Три настройки героя, которые решают большинство проблем.

Создайте скрипт HardLanding и добавьте его герою. Он узнаёт силу удара и отличает пол от стены: relativeVelocity хранит скорость столкновения, а нормаль показывает, с какой стороны пришёлся удар.

C#
using UnityEngine;

public class HardLanding : MonoBehaviour
{
    [SerializeField] private float dangerSpeed = 8f;

    // Вызывается в момент удара двух обычных (не триггерных) коллайдеров
    private void OnCollisionEnter2D(Collision2D collision)
    {
        float hitSpeed = collision.relativeVelocity.magnitude;
        Vector2 normal = collision.GetContact(0).normal;

        Debug.Log($"Удар о {collision.gameObject.name}: скорость {hitSpeed:F1}, нормаль {normal}");

        // normal.y > 0.5 — удар пришёлся снизу, то есть это пол, а не стена
        if (hitSpeed >= dangerSpeed && normal.y > 0.5f)
            Debug.Log("Жёсткое приземление!");
    }
}

В нашей сцене герой упал с высоты 5 метров, и консоль показала Удар о Floor: скорость 17,1, нормаль (0.00, 1.00). Пара к этому методу: OnCollisionStay2D работает, пока объекты соприкасаются, а OnCollisionExit2D вызывается, когда они разошлись.

Шаг 2. Триггер: OnTriggerEnter2D

Триггер нужен там, где важен сам факт входа в зону, а препятствие не нужно: монеты, урон от шипов, чекпоинты, финиш уровня. Добавьте монете Circle Collider 2D, включите Is Trigger и повесьте на неё скрипт:

C#
using UnityEngine;

public class CoinPickup : MonoBehaviour
{
    [SerializeField] private int score = 1;

    // Вызывается, когда что-то вошло в зону с галочкой Is Trigger
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (!other.CompareTag("Player")) return;

        Debug.Log($"Монета подобрана: +{score}");
        Destroy(gameObject);
    }
}
Инспектор Unity: у монеты в Circle Collider 2D включён Is Trigger, ниже компонент Coin Pickup
Обратите внимание: у монеты нет Rigidbody2D, он есть у героя. Этого достаточно.

Не забудьте выбрать герою тег Player в верхней части инспектора. Сравнивать тег лучше через CompareTag, а не через other.tag == "Player": так нет лишних выделений памяти, а ошибка в имени тега сразу видна в консоли.

Какие сочетания коллайдеров срабатывают

Событие сработает не при любом сочетании объектов. В тесте мы столкнули все 21 пару типов и посчитали вызовы. «Static» здесь значит объект с одним коллайдером, без Rigidbody2D.

Static Dynamic Kinematic Static триггер Dynamic триггер Kinematic триггер
StaticнетCollisionнетнетTriggerTrigger
DynamicCollisionCollisionCollisionTriggerTriggerTrigger
KinematicнетCollisionнетTriggerTriggerTrigger
Static триггернетTriggerTriggerнетTriggerTrigger
Dynamic триггерTriggerTriggerTriggerTriggerTriggerTrigger
Kinematic триггерTriggerTriggerTriggerTriggerTriggerTrigger

Из таблицы следуют три правила, которые стоит запомнить:

Шаг 3. Raycast: проверка лучом

Physics2D.Raycast запускает невидимый луч и сообщает, во что он попал. Луч нужен, когда касания ещё нет: враг видит игрока, выстрел без пули, проверка пропасти впереди. Метод возвращает RaycastHit2D: при попадании в нём есть collider, point и distance, при промахе collider равен null.

C#
using UnityEngine;

public class RaySensor : MonoBehaviour
{
    [SerializeField] private float distance = 6f;
    [SerializeField] private LayerMask hitLayers;

    private void Update()
    {
        Vector2 origin = transform.position;
        Vector2 direction = transform.right;

        RaycastHit2D hit = Physics2D.Raycast(origin, direction, distance, hitLayers);

        if (hit.collider != null)
        {
            Debug.DrawLine(origin, hit.point, Color.red);
            Debug.Log($"Луч попал в {hit.collider.name} на расстоянии {hit.distance:F2}");
        }
        else
        {
            Debug.DrawRay(origin, direction * distance, Color.green);
        }
    }
}

В инспекторе в поле Hit Layers отметьте слой, в который должен попадать луч, например Ground. Debug.DrawLine рисует линию только в редакторе, в окне Scene, поэтому в игре её не будет. Debug.Log здесь для наглядности: в настоящей игре вызов каждый кадр лучше убрать.

Unity в режиме Play: красный луч от героя до стены и консоль с сообщениями OnTriggerEnter2D, Raycast и OnCollisionEnter2D
Результат всех трёх шагов. Кнопка Collapse в консоли схлопывает одинаковые сообщения.

Две ловушки Raycast. Настройки по умолчанию проверены в тесте. Queries Start In Colliders включён: луч из центра объекта без LayerMask попадает в собственный коллайдер на расстоянии 0. Queries Hit Triggers включён: луч остановится на триггере перед стеной. Лечится LayerMask без своего слоя и без слоя триггеров, либо галочками в Project Settings → Physics 2D.

Почему OnTriggerEnter2D или OnCollisionEnter2D не срабатывает

Чаще всего событие молчит из-за одной из семи причин. Проверяйте их по порядку и поставьте первой строкой метода Debug.Log, чтобы понять, вызывается ли он вообще.

  1. Нет Rigidbody2D. Нужен хотя бы у одного объекта, смотрите таблицу выше.
  2. Не тот Is Trigger. Если галочка стоит хотя бы у одного объекта, сработает OnTriggerEnter2D, а OnCollisionEnter2D нет.
  3. Метод из 3D. OnTriggerEnter без «2D» на 2D-коллайдерах не вызывается: в тесте 0 вызовов и никакой ошибки в консоли.
  4. Опечатка в имени. onTriggerEnter2D с маленькой буквы тоже даёт 0 вызовов и молчание компилятора.
  5. Неверный тип параметра. OnTriggerEnter2D(Collision2D c) не вызовется, зато Unity напишет в консоль: Script error: OnTriggerEnter2D. This message parameter has to be of type: Collider2D.
  6. Слои не взаимодействуют. Откройте Edit → Project Settings → Physics 2D → Layer Collision Matrix. Если галочка на пересечении двух слоёв снята, не будет ни столкновения, ни триггера.
  7. Не совпал тег. Метод сработал, но CompareTag вернул false, потому что герою не назначен тег Player.
Окно Project Settings в Unity: раздел Physics 2D, вкладка Layer Collision Matrix с галочками для слоёв Ground, Player и Enemy
В Unity 6.6 матрица вынесена на отдельную вкладку раздела Physics 2D.

Почему объект проваливается сквозь пол

Объект проваливается, когда физика не видит контакта. Проверьте по порядку: есть ли у пола Collider2D, не включён ли Is Trigger у пола или героя и не равен ли нулю размер коллайдера. Размер смотрите в поле Size: если Box Collider 2D добавлен до назначения спрайта, там будет ноль, и контактов не будет.

Если объект пролетает препятствие только на большой скорости, это туннелирование. Физика считается шагами по 0,02 секунды, и быстрый объект просто перепрыгивает тонкую стену между шагами. Мы запустили по 20 «пуль» размером 0,2 в стену толщиной 0,2:

СкоростьDiscrete (по умолчанию)Continuous
20пролетели 8 из 200 из 20
100пролетели 15 из 200 из 20
300пролетели 20 из 200 из 20

Вывод простой: быстрым объектам ставьте Collision Detection: Continuous в Rigidbody2D. Стоит чуть дороже по производительности, поэтому включайте его только там, где скорость реально большая: герою, пулям, снарядам.

Чем физика в 3D отличается от 2D

Логика в 3D та же, но это отдельный движок с другими классами. Смешивать их нельзя: 2D-коллайдер не столкнётся с 3D-коллайдером.

Что2D3D
ТелоRigidbody2DRigidbody
СтолкновениеOnCollisionEnter2D(Collision2D)OnCollisionEnter(Collision)
ТриггерOnTriggerEnter2D(Collider2D)OnTriggerEnter(Collider)
ЛучPhysics2D.Raycast вернёт RaycastHit2DPhysics.Raycast вернёт bool, попадание в out RaycastHit

Вывод

Обычный коллайдер останавливает, триггер сообщает, луч проверяет на расстоянии, а Rigidbody2D у одного из объектов запускает всю эту механику. Если событие молчит, идите по чеклисту, а быстрым объектам сразу ставьте Continuous.

Если движок ещё не установлен, начните с установки Unity и первого проекта, а если пока непонятно, что такое GetComponent и SerializeField, загляните в основы C# для Unity. Собрать из этих механик целую игру с проверкой заданий можно на бесплатном курсе Unity Старт и дальше по полной программе.

Частые вопросы

Чем коллайдер отличается от триггера в Unity?

Обычный коллайдер физически останавливает другие объекты и вызывает OnCollisionEnter2D. Коллайдер с галочкой Is Trigger пропускает объекты сквозь себя и вызывает OnTriggerEnter2D. Триггеры используют для монет, зон урона, финиша и других областей.

Почему не срабатывает OnTriggerEnter2D?

Чаще всего ни у одного из двух объектов нет Rigidbody2D, не стоит галочка Is Trigger, слои отключены в Layer Collision Matrix или метод назван неверно. В 2D нужен именно OnTriggerEnter2D(Collider2D other): версия без 2D и написание с маленькой буквы не вызываются.

Почему объект проваливается сквозь пол в Unity?

Проверьте, что у пола есть Collider2D с ненулевым размером и что у объекта не включён Is Trigger. Если объект пролетает пол только на большой скорости, поставьте в Rigidbody2D режим Collision Detection: Continuous.

Что такое Raycast в Unity?

Raycast — это невидимый луч, который проверяет, во что он попал на заданном расстоянии. В 2D его запускают через Physics2D.Raycast(origin, direction, distance, layerMask): метод возвращает RaycastHit2D, а при промахе его collider равен null.

Нужен ли Rigidbody на обоих объектах?

Нет. Для столкновения нужен хотя бы один объект с Rigidbody2D в режиме Dynamic. Для триггера достаточно, чтобы Rigidbody2D в режиме Dynamic или Kinematic был хотя бы у одного из двух объектов.

Хочешь собрать из этих механик настоящую игру?

На бесплатном курсе Unity Старт мы ставим движок, делаем первый проект и первый скрипт, а дальше по программе собираем полноценные игры с проверкой заданий.

Начать бесплатно