Столкновения и триггеры: OnCollision и OnTrigger

Столкновения бывают двух видов: «твёрдые» (герой упирается в стену) и «проходные» (герой собирает монету сквозь неё). Разница — в галочке IsTrigger.

Суть: обычный коллайдер даёт физическое столкновение — объекты не проходят друг сквозь друга, событие ловит OnCollisionEnter2D. Если включить IsTrigger, коллайдер становится триггером — объекты проходят насквозь, но событие OnTriggerEnter2D всё равно срабатывает. Триггеры — для зон, монет, чек-поинтов.

Подумай о двух ситуациях. Герой бежит и упирается в стену — это столкновение: его должно остановить. Герой пробегает сквозь монету и подбирает её — это триггер: монета не должна его тормозить, но факт касания важен. Обе ситуации — про коллайдеры, разница в галочке Is Trigger.

Как работает под капотом

            Is Trigger выключен      Is Trigger включён
            (СТОЛКНОВЕНИЕ)            (ТРИГГЕР)
  объекты:  отталкиваются            проходят насквозь
  событие:  OnCollisionEnter2D       OnTriggerEnter2D
  пример:   герой и стена            герой и монета / зона урона

Unity вызывает три фазы события: ...Enter2D (момент касания), ...Stay2D (пока касаются), ...Exit2D (момент расставания). Чаще всего нужен Enter.

using UnityEngine;

public class PlayerPickup : MonoBehaviour
{
    private int coins = 0;

    // Триггер: монета помечена Is Trigger
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Coin"))
        {
            coins++;
            Destroy(other.gameObject);   // убрать монету
            Debug.Log("Монет: " + coins);
        }
    }

    // Столкновение: с врагом
    void OnCollisionEnter2D(Collision2D col)
    {
        if (col.gameObject.CompareTag("Enemy"))
            Debug.Log("Получили урон!");
    }
}

Смоделируем обработку событий касания на Python — диспетчер по типу события и тегу:

# События приходят как (тип, тег). Реагируем по-разному
state = {"hp": 100, "coins": 0}

def handle(event_type, tag):
    if event_type == "trigger" and tag == "Coin":
        state["coins"] += 1
        return "Монета подобрана (прошли насквозь)"
    if event_type == "collision" and tag == "Enemy":
        state["hp"] -= 25
        return "Удар о врага (оттолкнулись)"
    if event_type == "trigger" and tag == "DamageZone":
        state["hp"] -= 10
        return "Вошли в зону урона"
    return "Ничего"

events = [("trigger", "Coin"), ("collision", "Enemy"), ("trigger", "DamageZone")]
for et, tag in events:
    print(handle(et, tag), "| hp:", state["hp"], "монеты:", state["coins"])

Та же логика на Python ▶ — движок просто доставляет тебе событие «коснулись, вот кто», а решение (урон, подбор, ничего) принимает твой код по типу события и тегу. Это сердце игрового взаимодействия.

Частые ошибки

  • Ждать OnTrigger без галочки Is Trigger. Если коллайдер не помечен триггером, вызовется OnCollision, а не OnTrigger. И наоборот.
  • Ни одного Rigidbody. Чтобы событие сработало, хотя бы у одного объекта нужен Rigidbody2D (часто Dynamic или Kinematic).
  • Тяжёлый код в OnCollisionStay2D. Stay вызывается каждый кадр контакта — не клади туда дорогую логику.
  • Destroy не того объекта. В триггере монеты уничтожай other.gameObject (монету), а не себя.

Best practices

  • Зоны, бонусы, чек-поинты, концы уровня — делай триггерами; твёрдую геометрию — обычными коллайдерами.
  • Внутри обработчика сразу проверяй тег через CompareTag — не реагируй на всё подряд.
  • Для частых событий (зона урона) используй таймер, а не урон каждый кадр в Stay.

Итоги: обычный коллайдер — физическое столкновение (OnCollisionEnter2D), коллайдер с Is Trigger — проходное событие (OnTriggerEnter2D). Для срабатывания нужен хотя бы один Rigidbody2D. Движок сообщает о касании, а реакцию определяешь ты по типу события и тегу.

Порядок событий и уничтожение объектов

Один тонкий момент, на котором спотыкаются даже опытные: что происходит, когда в обработчике столкновения ты уничтожаешь объект. Вызов Destroy не убирает объект мгновенно — он лишь помечает его на удаление в конце текущего кадра. Поэтому код после Destroy в том же кадре ещё может обращаться к объекту, но полагаться на это не стоит. Опасная ситуация — когда два объекта одновременно пытаются уничтожить друг друга при столкновении: легко получить двойной подсчёт очков или двойной урон. Защита проста: ставь флаг «уже обработано» и проверяй его в начале обработчика. Ещё одно правило гигиены — внутри обработчика всегда проверяй тег второго участника прежде, чем что-то делать: иначе твоя монета среагирует на стену, врага и стрелу одинаково, и логика поплывёт.

Проверьте себя
1. Чем триггер (Is Trigger) отличается от обычного столкновения?
AТриггер ярче
BЧерез триггер объекты проходят насквозь, но событие OnTriggerEnter2D срабатывает
CТриггер быстрее рендерится
DРазницы нет
2. Что нужно, чтобы событие столкновения вообще сработало?
AОба объекта должны быть Static
BХотя бы у одного объекта нужен Rigidbody2D
CНужен скрипт на камере
DНичего, оно срабатывает всегда