Detecting which Collider involved in Trigger 'Collision'

My issue is mainly down to this: OnCollisionEnter() returns a Collision. OnTriggerEnter() returns a Collider.

With the former, I have been able to detect a Collider (bullet) hitting my rigidbody collection of colliders (player). Using Collision.contacts is the only way I've been able to discern which collider in my Player object has taken the hit, which is important for damage allocation.

However, in the case of what I shall call AlternativeBullet, I am using a trigger rather than a collider (because I don't want the collision to have a physics knockback). All I've got when a trigger touches part of the Player is the collider (ie. Trigger) that touched, not any actual location data such as Collision gives.

This is deceptively simple-looking. I originally expected the script calling OnCollisionEnter() or whatever to only be called on the object who's collider is being hit, but this is not the case. It is called on the object with the rigidbody, ie. the master Player object to which these GameObjects with colliders are parented. Hence the need to use Collision.contacts to find which collider actually got hit.

Can anyone suggest any alternative ways of finding which collider was 'hit' by the trigger version of the bullet. Or, better yet, a way of nullifying a collision the physics engine has already registered so the bullet won't have any momentum effect.

Thanks!

You could get slightly better bullet code by doing a RayCast or a SphereCast every update, from the previous location to the current location of the bullet. Think of the sphere cast like a thick ray cast. Casting a ray or a sphere from one point to another reduce odd physics behaviour like tunneling and is probably easier to fix than your current situation.

You can then obtain an exact or approximate position where the ray or sphere hit the object.

You then don't need any collider at all on your bullet prefab.

You can send a custom message on bullet hit such as this psuedo code (just after the ray test):

if (hitOther)
{
  other.SendMessage("OnBulletHit", bulletHit,
                     SendMessageOptions.DontRequireReciever);
}

And your player/enemy could have a function that handle this gracefully such as:

function OnBulletHit(bulletHit : BulletHit)
{
    health -= bulletHit.damage;
    BloodEffects(bulletHit.position);
    PlayHitSound(bulletHit.position);

    if (bulletHit.isRadioactive)
    {
         AddRadiationSickness();
    }
    if (health <= 0 && bulletHit.damage > 25)
    {
         ExplodeIntoGibParts();
    } 
}

You'd have to design your BulletHit structure or class yourself but that shouldn't pose any problems. You need to consider what data is important describing a bullet hit event and put that in your type.

  • Note that there is a bug (unless they fixed that) where the SphereCast doesn't register hits with a Trigger. So if you need to be able to hit triggers with your bullets you should probably use RayCast instead.

OnTriggerEnter seems to return the right collider (actual descendant hit) in a test project I did: https://www.yousendit.com/download/cEd0K2VucVhsUi92Wmc9PQ

May I be overlooking something important in your specification?

You can prevent the rigidbody problem by destroying the rigidbody using a character controller in the physics section instead and use the OnControllerColliderHit function.

void OnControllerColliderHit(ControllerColliderHit cch)
{
    Debug.Log("cch hit");   
}

Doing this detects the start of a collision using the regular physics but the energy (kinetic) will not be transfered from object A to B. However the collision occurs is registered and a property of cch.collider DOES exist and can be retrieved giving you the collider you need without getting punched in the chest by some force

P.S.

I just tested this in a new project I whipped up for the question. It worked without a problem. Let me know if you get any results that differ. I can't promise I didn't forget something stupid (Humans tend to do that :/)

To see which sub-collider hit the trigger object you might use the following method:

Create a simple script to use on each sub collider called ColliderProxy

using UnityEngine;
using System.Collections;

public class ColliderProxy : MonoBehaviour
{
	public enum ColliderId { None, Body, Range };
	public ColliderId colliderId = ColliderId.None;

	public delegate void ColliderDelegate(ColliderId collider);
	public event ColliderDelegate OnTriggerEnterByCollider;

	void OnTriggerEnter(Collider other)
	{
		if (OnTriggerEnterByCollider != null)
		{
			OnTriggerEnterByCollider(colliderId);
		}
	}
}

On the main object you’ll have something like this:

using UnityEngine;
using System.Collections;

public class MainObjectScript : MonoBehaviour
{
    public GameObject objectWithSubcollider1;
    public GameObject objectWithSubcollider2;
    
    void Awake()
    {
        objectWithSubcollider1.GetComponent<ColliderProxy>().OnTriggerEnterByCollider += OnTriggerEnterByCollider;
        objectWithSubcollider2.GetComponent<ColliderProxy>().OnTriggerEnterByCollider += OnTriggerEnterByCollider;
    }

    void OnTriggerEnterByCollider(ColliderProxy.ColliderId colliderId)
    {
        Debug.Log(“OnTriggerEnter by collider: " + colliderId);
    }
}

Just set the ids for the subcolliders after attaching the script.
On the scene object holding the trigger collider you’ll need to have a rigidbody.
On the subobjects holding the subcolliders you’ll have just the colliders, if the main object doesn’t have a rigidbody.
If the main object has a rigidbody, the subobjects might need one as well. A no gravity, kinematic one.