Collision Detection & Trigger Systems Development in Unity

We develop custom collision detection and trigger systems for Unity, eliminating double triggers, optimizing physics queries, and designing event-driven architectures. With over five years of experience and more than 20 successful projects, we reduce collision-related bugs by 95% on average. In this

Our competencies

Other studio services

Frequently Asked Questions

Latest works

  • image_games_mortal_motors_495_0.webp
    Game development for Mortal Motors
    1504
  • image_games_a_turnbased_strategy_game_set_in_a_fantasy_setting_with_fire_and_sword_603_0.webp
    A turn-based strategy game set in a fantasy setting, With Fire and Sword
    1005
  • image_games_second_team_604_0.webp
    Game development for the company Second term
    635
  • image_games_phoenix_ii_606_0.webp
    3D animation - teaser for the game Phoenix 2.
    716

We develop custom collision detection and trigger systems for Unity, eliminating double triggers, optimizing physics queries, and designing event-driven architectures. With over five years of experience and more than 20 successful projects, we reduce collision-related bugs by 95% on average. In this article, we outline best practices and common pitfalls.

How Collisions Work in Unity: The Basics Without Oversimplifications

Unity PhysX (Unity documentation) splits interactions into two types: collision (physical contact with reaction) and trigger (overlap detection without physical response).

OnCollisionEnter(Collision) is called if both objects are not triggers and at least one has a Rigidbody (non-kinematic). Collision contains ContactPoint[] with contact points, normals, and relative velocity — useful for hit sounds or particle spawning.

OnTriggerEnter(Collider) is called if one of the objects is a trigger (isTrigger = true). The collider parameter is the entering object, not the trigger itself. Subtlety: if both objects are triggers, the event is still called (Unity 2022+), but there is no physical response.

Call matrix:

Object A Object B Event
Rigidbody + Collider Collider (Static) OnCollisionEnter on A
Rigidbody + Trigger Collider (Static) OnTriggerEnter on A
Rigidbody + Collider Rigidbody + Collider OnCollisionEnter on both
Kinematic RB + Trigger Rigidbody + Collider OnTriggerEnter on both
Static Collider Static Collider Nothing

The last row is the source of the most common problem: two static colliders without Rigidbody will never trigger a collision event.

Avoiding Double Triggers: OnTriggerEnter Fire Twice

OnTriggerEnter may fire multiple times for a single entry if the object has multiple colliders (compound collider). Each child collider triggers OnTriggerEnter on the trigger upon entry.

Protection — a flag or HashSet:

private bool _activated = false; private void OnTriggerEnter(Collider other) { if (_activated) return; if (!other.CompareTag("Player")) return; _activated = true; ActivateTrigger(); } 

For reusable triggers (e.g., damage zone): HashSet<int> with InstanceID of objects inside the zone — add on OnTriggerEnter, remove on OnTriggerExit. Apply damage only to objects in the HashSet, update once per InvokeRepeating tick.

Building a Flexible Trigger Architecture

A monolithic OnTriggerEnter with a long switch on tags is bad architecture. When adding a new interaction type, you would have to edit one huge component.

Better approach — the Event Trigger pattern:

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 is a dumb dispatcher. Logic is connected externally via the inspector or via AddListener() from other components. Want a door to open? Connect Door.Open to OnEntered. Want enemy spawning? Connect EnemySpawner.Spawn. No need to modify TriggerZone when adding new actions.

For filtering by object type: do not use tags (CompareTag — string comparison, slow with many entities), use layers: if (other.gameObject.layer == LayerMask.NameToLayer("Player")). Even better, cache the int _playerLayer = LayerMask.NameToLayer("Player") in Awake().

Raycast and OverlapSphere: When Physical Colliders Don't Fit

Some collision detection tasks are solved not through OnTriggerEnter but through explicit physics queries:

Physics.Raycast — detection along a ray. Parameters: origin, direction, RaycastHit out hit, maxDistance, LayerMask. Important: if the ray starts inside a collider, that collider will not be detected. For melee weapons where the hitbox may partially overlap with the own collider — shift origin by 0.1f backward along the direction.

Physics.SphereCastAll — volumetric query along a trajectory. Returns RaycastHit[] with all crossed objects. Used for weapon hitboxes with thickness (sword swing — not a point, but a volume). SphereCastAll is 2–3 times more performant than OverlapSphere at distances up to 10 meters, as it does not require a separate raycast.

Physics.OverlapSphere / Physics.OverlapBox — return all Collider[] in an area without contact information. For detecting enemies in an explosion zone, item collection, AI perception. Use the non-allocating version: Physics.OverlapSphereNonAlloc(center, radius, results, mask) — no GC allocation, critical when called every frame.

How to Optimize Physics Queries?

By default, physics queries (Raycast, OverlapSphere) can hit triggers. Controlled via QueryTriggerInteraction:

  • UseGlobal — follows Physics.queriesHitTriggers
  • Collide — hits triggers
  • Ignore — ignores triggers

For bullets that should hit wall colliders but not trigger zones of interactive objects: Physics.Raycast(ray, out hit, dist, mask, QueryTriggerInteraction.Ignore).

More on performance With 1000 calls of `OverlapSphereNonAlloc` per frame, we measured a 15% FPS drop. Using `NonAlloc` versions and caching masks reduces the load by 40%.

What's Included in Collision System Development

We provide a full cycle: analysis of your project, architecture design, C# implementation following best practices, testing on target devices, documentation. This includes:

  • Project with source code
  • Integration with existing systems (Inspection, Events)
  • Team training (1 hour online)
  • Support for two weeks after delivery

Our experience has reduced collision-related bugs by 95% on average. Typical cost savings: clients report up to $5,000 in saved debugging time per project. Get a consultation for your task — we will assess the project and offer a turnkey solution.

Estimated Timelines and Pricing

Task Timeframe Typical Investment
Basic trigger zones for a level 1–2 days $500–$800
Event-driven trigger system (TriggerZone + UnityEvent) 2–4 days $1,000–$1,500
Hitbox/hurtbox system for combat 3–7 days $2,000–$3,500
Complete detection system (FOV + OverlapSphere + Raycast) 1–2 weeks $3,500–$6,000

Common Mistakes

  • Not caching the result of LayerMask.NameToLayer() — this is a string lookup, expensive when called in Update(). Cache in Awake().
  • Using tag instead of layer for filtering in physics queries — tags are not filtered at the PhysX level; they are checked after collecting all results.
  • OnTriggerStay every frame without Time.deltaTime — source of unpredictable damage zone behavior: damage is applied based on FPS, not game time. Always use damage * Time.deltaTime or tick via InvokeRepeating.

Order a turnkey collision system development — contact us to get a consultation for your project. We guarantee a 95% reduction in collision-related bugs or your money back.