Why I have inconsistencies in getting a component

I’m working on an FPS game and I am using a bullet projectile with a rigidbody. I am using SphereCast to detect collisions. The SphereCast works fine, but I’m having problems with doing damage to the enemy.

This is my code attached to the bullet:

RaycastHit rayhit = new RaycastHit();
        if (Physics.SphereCast(transform.position, radius, transform.forward, out rayhit, collisionDistance, layerMask, QueryTriggerInteraction.Collide))
        {
           

            if (rayhit.transform.tag != "Player")
            {
                Debug.Log("NotPlayer");

                if (rayhit.transform.GetComponent<Health>() == null)
                {
                    Debug.Log("No Health Script");
                }
                if (rayhit.transform.GetComponent<Health>() != null && rayhit.transform.tag == "MetallicEnemy")
                {
                    rayhit.transform.GetComponent<Health>().TakeDamage(Variables.pistolBaseDamage);
                    Debug.Log("DoDamage");
                }
                //Detach trail from bullet so that it shows after the bullet is destroyed
                if (transform.childCount > 0)
                {
                    Transform trail = transform.GetChild(0).parent = null;
                    Destroy(trail, 0.5f);
                }
                Destroy(gameObject);
            }
        }

As you can see, I get the health script of the enemy by getting the component from the rayhit game object. This works most of the time, but sometimes, the script is not found(it’s null) even though it is on the enemy. By the way, I instantiate the enemies during runtime so I can’t make the health script a public variable. I have no idea what causes this inconsistency. Does anyone know why this is happening and how I can stop this from happening?

Well it’s pretty unlikely this happens. The only cases where this might happen is when you have attached a child object to your enemy which also has a rigidbody attached. RaycastHit.transform will return either the transform of the rigidbody component the hit collider belongs to or if there’s no rigidbody up the hierarchy it will just return the transform of the object the collider is on. Note that a collider can only belong to one rigidbody. So when you nest rigidbodies a collider will only belong to the rigidbody that is found first when going up the hierarchy starting at the collider that was hit.

So for example if the enemy has a rigodbody on it’s root object and you hit any child collider of that enemy, you get the transform of the root object. However if you added another rigidbody as child to your enemy (for example a weapon that can be dropped), then the colliders of that child and sub child objects will belong to that rigidbody.

If that is the case you have several choices.

  • First prevent hitting that child rigidbody object in the first place by using layers for the objects and a layermask in the spherecast.
  • Instead of using rayhit.transform.GetComponent you can use rayhit.collider.GetComponentInParent.

GetComponentInParent will start searching for a component of that type on the object the collider is attached to. If no such component is found on that object it will go up the hierarchy and try again. This repeats until it reaches the topmost / root object. This would be similar to rayhit.transform.root.GetComponentInChildren however the other way round. So GetComponentInParent has the advantage that it will find the component that is as closest to the collider up the hierarchy while when starting at the root and going down the hierarchy it will find the topmost component.

Finally some other things I’ve noticed about your code:

  • You should use rayhit.transform.CompareTag("sometag") instead of using rayhit.transform.tag == "sometag". The CompareTag is faster and doesn’t allocate memory
  • The way you treat your trail renderer can’t work. First of all relying on a certain child index is prone to cause all sorts of errors when the hierarchical structure changes. It would be much better to just have a public GameObject variable to reference that trail renderer sub object. You can initialize this reference in the inspector. When you instantiate objects, the reference will reference the instantiated child correctly.
  • Next thing is this line Transform trail = transform.GetChild(0).parent = null; will actually store null in the “trail” variable. Assignments are handled from right to left.
  • Finally assuming trail now actually contains a valid reference to the trail child object, you can not destroy a transform component. You have to destroy the gameobject instead.

If you want to keep the code with the GetChild approach, you have to do:

if (transform.childCount > 0)
{
    Transform trail = transform.GetChild(0);
    trail.parent = null;
    Destroy(trail.gameObject, 0.5f);
}