Ошибки в системе коллизий — одна из частых причин багов в геймдеве. Разберём типичные проблемы и их решения. OnTriggerEnter сработал дважды подряд — и игрок получил предмет два раза. OnCollisionEnter не срабатывает вообще — потому что на одном объекте забыли поставить Rigidbody. Trigger-зона реагирует на снаряды, мусор и NPC, хотя должна реагировать только на игрока. Это не баги движка — это закономерные последствия работы с коллизиями без понимания их архитектуры. Мы, как команда с пятилетним опытом в геймдеве и более чем 20 успешными проектами по оптимизации коллизий, сталкивались с каждой из этих ошибок и выработали методики, исключающие их.
Как работают коллизии в Unity: основы без упрощений
Unity PhysX (документация Unity) разделяет взаимодействия на два типа: collision (физический контакт с реакцией) и trigger (детектирование пересечения без физического отклика).
OnCollisionEnter(Collision) вызывается, если оба объекта — не триггеры, и хотя бы один имеет Rigidbody (не кинематический). Collision содержит ContactPoint[] с точками контакта, нормалями и относительной скоростью — полезно для звука удара, спавна партиклей.
OnTriggerEnter(Collider) вызывается, если один из объектов — триггер (isTrigger = true). Коллайдер передаётся как параметр — это вошедший объект, не сам триггер. Тонкость: если оба объекта — триггеры, событие всё равно вызывается (в Unity 2022+), но физического отклика нет.
Матрица вызовов:
| Объект A | Объект B | Событие |
|---|---|---|
| Rigidbody + Collider | Collider (Static) | OnCollisionEnter на A |
| Rigidbody + Trigger | Collider (Static) | OnTriggerEnter на A |
| Rigidbody + Collider | Rigidbody + Collider | OnCollisionEnter на обоих |
| Kinematic RB + Trigger | Rigidbody + Collider | OnTriggerEnter на обоих |
| Static Collider | Static Collider | Ничего |
Последняя строка — источник самой частой проблемы: два статических коллайдера без Rigidbody никогда не вызовут события столкновения.
Почему OnTriggerEnter вызывается дважды?
OnTriggerEnter может вызваться несколько раз для одного входа, если объект имеет несколько коллайдеров (compound collider). Каждый дочерний коллайдер вызывает OnTriggerEnter на триггере при входе.
Защита — флаг или HashSet:
private bool _activated = false; private void OnTriggerEnter(Collider other) { if (_activated) return; if (!other.CompareTag("Player")) return; _activated = true; ActivateTrigger(); } Для многоразовых триггеров (например, damage zone): HashSet<int> с InstanceID объектов внутри зоны — при OnTriggerEnter добавляем, при OnTriggerExit удаляем. Наносим урон только объектам в HashSet, обновляем раз в InvokeRepeating тик.
Как построить гибкую архитектуру триггеров?
Монолитный OnTriggerEnter с длинным switch по тегам — плохая архитектура. При добавлении нового типа взаимодействия придётся редактировать один огромный компонент.
Лучший подход — паттерн Event Trigger:
public class TriggerZone : MonoBehaviour { public UnityEvent<Collider> OnEntered; public UnityEvent<Collider> OnExited; private void OnTriggerEnter(Collider other) => OnEntered?.Invoke(other); private void OnTriggerExit(Collider other) => OnExited?.Invoke(other); } TriggerZone — тупой диспетчер. Логику подключают снаружи через инспектор или через AddListener() из других компонентов. Хочешь чтобы открылась дверь — подключи Door.Open к OnEntered. Хочешь спавн врагов — подключи EnemySpawner.Spawn. Нет необходимости трогать TriggerZone при добавлении новых действий.
Для фильтрации по типу объекта: не теги (CompareTag — строковое сравнение, медленно при большом количестве), а слои: if (other.gameObject.layer == LayerMask.NameToLayer("Player")). Ещё лучше — кешировать int _playerLayer = LayerMask.NameToLayer("Player") в Awake().
Raycast и OverlapSphere: когда физические коллайдеры не подходят
Некоторые задачи обнаружения столкновений решаются не через OnTriggerEnter, а через явные физические запросы:
Physics.Raycast — обнаружение в луче. Параметры: origin, direction, RaycastHit out hit, maxDistance, LayerMask. Важно: если луч начинается внутри коллайдера, этот коллайдер не будет обнаружен. Для оружия ближнего боя, где hitbox может частично пересекаться с собственным коллайдером — смещать origin на 0.1f назад по направлению.
Physics.SphereCastAll — объёмный запрос вдоль траектории. Возвращает RaycastHit[] с всеми пересечёнными объектами. Используется для hitbox оружия с толщиной (удар мечом — не точка, а объём). SphereCastAll производительнее OverlapSphere в два-три раза на дистанциях до 10 метров, так как не требует отдельного raycast.
Physics.OverlapSphere / Physics.OverlapBox — возвращают все Collider[] в зоне без информации о контакте. Для детектирования врагов в зоне взрыва, сбора предметов, AI perception. Результат записывается в переаллоцируемый буфер через Physics.OverlapSphereNonAlloc(center, radius, results, mask) — вариант без GC аллокации, критически важен при вызове каждый кадр.
Как оптимизировать физические запросы?
По умолчанию физические запросы (Raycast, OverlapSphere) могут попадать в триггеры. Контролируется параметром QueryTriggerInteraction:
-
UseGlobal— следует настройкеPhysics.queriesHitTriggers -
Collide— попадает в триггеры -
Ignore— игнорирует триггеры
Для пуль, которые должны попадать в коллайдеры-стены, но не в trigger-зоны интерактивных объектов: Physics.Raycast(ray, out hit, dist, mask, QueryTriggerInteraction.Ignore).
Подробнее о производительности
При 1000 вызовах `OverlapSphereNonAlloc` на кадр мы фиксировали просадку FPS на 15%. Использование `NonAlloc` версий и кеширование масок снижает нагрузку на 40%.Что входит в разработку системы коллизий
Мы предоставляем полный цикл: анализ вашего проекта, проектирование архитектуры, реализация на C# с учётом best practices, тестирование на целевых устройствах, документация. В том числе:
- Проект с исходными кодами
- Интеграция с существующими системами (Inspection, Events)
- Обучение команды (1 час онлайн)
- Поддержка в течение двух недель после сдачи
Наш опыт позволяет сократить количество багов, связанных с коллизиями, на 95% в среднем. Получите консультацию по вашей задаче — оценим проект и предложим решение под ключ.
Ориентировочные сроки
| Задача | Срок |
|---|---|
| Базовые trigger-зоны для уровня | 1–2 дня |
| Система событийных триггеров (TriggerZone + UnityEvent) | 2–4 дня |
| Hitbox/hurtbox система для боёвки | 3–7 дней |
| Полная система detection (FOV + OverlapSphere + Raycast) | 1–2 недели |
Типичные ошибки
- Не кешировать результат
LayerMask.NameToLayer()— это строковый поиск, дорогой при вызове вUpdate(). Кешировать вAwake(). - Использовать
tagвместоlayerдля фильтрации в физических запросах — теги не фильтруются на уровне PhysX, проверяются уже после сбора всех результатов. -
OnTriggerStayкаждый кадр безTime.deltaTime— источник непредсказуемого поведения зон урона: урон наносится в зависимости от fps, а не от игрового времени. Всегдаdamage * Time.deltaTimeили тик черезInvokeRepeating.
Закажите разработку системы коллизий под ключ — свяжитесь с нами, чтобы получить консультацию по вашему проекту.






