Collision Detection and Trigger System Development

Our video game development company runs independent projects, jointly creates games with the client and provides additional operational services. Expertise of our team allows us to cover all gaming platforms and develop an amazing product that matches the customer’s vision and players preferences.
Showing 1 of 1 servicesAll 242 services
Collision Detection and Trigger System Development
Medium
from 2 business days to 2 weeks
FAQ
Our competencies
What are the stages of Game Development?
Latest works
  • image_games_mortal_motors_495_0.webp
    Game development for Mortal Motors
    670
  • 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
    860
  • image_games_second_team_604_0.webp
    Game development for the company Second term
    490
  • image_games_phoenix_ii_606_0.webp
    3D animation - teaser for the game Phoenix 2.
    533

Collision Detection and Trigger System Development

OnTriggerEnter fired twice in a row—player got item twice. OnCollisionEnter doesn't fire at all—forgot to put Rigidbody on one object. Trigger zone reacts to projectiles, debris, and NPCs when should only react to player. Not engine bugs—these are natural consequences of working with collisions without understanding architecture.

How Collisions Work in Unity: Basics Without Simplification

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

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

OnTriggerEnter(Collider) is called if one object is a trigger (isTrigger = true). The collider passed as parameter is the entering object, not the trigger itself. Nuance: if both objects are triggers, event still fires (in Unity 2022+), but 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 most common problem: two static colliders without Rigidbody never fire collision events.

Trigger Double-Fire Problem

OnTriggerEnter can fire multiple times for one entry if object has multiple colliders (compound collider). Each child collider fires OnTriggerEnter on the trigger on entry.

Protection—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 zone—add on OnTriggerEnter, remove on OnTriggerExit. Apply damage only to objects in HashSet, update via InvokeRepeating tick.

Trigger System Architecture for Levels

Monolithic OnTriggerEnter with long switch by tags—bad architecture. Adding new interaction type requires editing one huge component.

Better approach—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 dumb dispatcher. Logic connects outside via inspector or AddListener() from other components. Want door to open—connect Door.Open to OnEntered. Want enemy spawn—connect EnemySpawner.Spawn. No need to touch TriggerZone when adding new actions.

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

Raycast and OverlapSphere: When Trigger Colliders Don't Fit

Some collision detection tasks solve not via OnTriggerEnter but explicit physics queries:

Physics.Raycast detects in a ray. Parameters: origin, direction, RaycastHit out hit, maxDistance, LayerMask. Important: if ray starts inside collider, that collider won't be detected. For melee weapons where hitbox can partially overlap own collider—offset origin 0.1f backward along direction.

Physics.SphereCastAll is volume query along trajectory. Returns RaycastHit[] with all intersected objects. Used for weapon hitbox with thickness (sword strike—not point, but volume). More performant than OverlapSphere at path end + raycast at start.

Physics.OverlapSphere / Physics.OverlapBox return all Collider[] in zone without contact info. For enemies in explosion zone detection, item pickup, AI perception. Result written to preallocated buffer via Physics.OverlapSphereNonAlloc(center, radius, results, mask)—variant without GC allocation, critical for per-frame calls.

Optimization: QueryTriggerInteraction

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

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

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

Timeline Guidelines

Task Timeframe
Basic trigger zones for level 1–2 days
Event trigger system (TriggerZone + UnityEvent) 2–4 days
Hitbox/hurtbox system for combat 3–7 days
Full detection system (FOV + OverlapSphere + Raycast) 1–2 weeks

Common Mistakes

Don't cache LayerMask.NameToLayer() result—string lookup, expensive if called in Update(). Cache in Awake().

Use tag instead of layer for query filtering—tags aren't filtered at PhysX level, checked after collecting all results.

OnTriggerStay every frame without Time.deltaTime—source of unpredictable damage zone behavior: damage applied depending on fps, not game time. Always damage * Time.deltaTime or tick via InvokeRepeating.