How to ignore raycast without using layers

I am making a simple zombie FPS game and when I added melee weapons I had an issue were the player can hit themselves with them. I was able to make so the player can’t hurt themselves through code, but they are still able to hit and the raycast for the weapon can’t go through them, meaning that hitting objects below a certain angle is impossible since the player’s body is an obstacle.

A simple fix would be to simply filter it using layers, and I already have a layer for players so this wouldn’t be too hard. However, the game I making is multiplayer and I plan on having friendly fire toggles, meaning players should be able to hit other players, not themselves.

So I need a way to make the raycast ignore only the player it belongs too. The player’s body is not a child of object that has the weapon script.

Code for melee attacks(not necessary but in any case here it is):

    void Attack()
    {
        CurrentAttackDelay = 1f / AttackSpeed;

        Vector3 AttackPath = FPCamera.transform.forward;
        Vector3 AttackSource = FPCamera.transform.position;

        if (CurrentSpread > 0f)
        {
            AttackPath.x += Random.Range(-CurrentSpread, CurrentSpread);
            AttackPath.y += Random.Range(-CurrentSpread, CurrentSpread);
            AttackPath.z += Random.Range(-CurrentSpread, CurrentSpread);
        }

        if (Physics.Raycast(AttackSource, AttackPath, out AimPoint, AttackReach, ~gameObject.layer))
        {
            Debug.Log(AimPoint.transform.name + " was hit by a melee attack;");

            if (Player.NoiseLevel < Loudness)
            {
                Player.NoiseLevel = Loudness;
            }

            if (AimPoint.transform.CompareTag("Shootable"))
            {
                EnemyStats Enemy = AimPoint.transform.GetComponent<EnemyStats>();
                PlayerStats OtherPlayer = AimPoint.transform.GetComponentInParent<PlayerStats>();

                if (Enemy != null)
                {
                    Enemy.Hurt(Damage);

                    GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
                    Destroy(Impact, 2f);
                }

                if (OtherPlayer != null && OtherPlayer != Player)
                {
                    OtherPlayer.Hurt(Damage);

                    GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
                    Destroy(Impact, 2f);
                }
            }
            else
            {
                GameObject Impact = Instantiate(ImpactEffect [1], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
                Destroy(Impact, 2f);
            }
        }
    }

You could use the Physics.RaycastAll*** variants to get all colliders that the ray intersects. Then your code just needs to loop through them ignore your character’s own body.

1 Like

And what about the Ignore Raycast layer ?

So instead of this:

        if (Physics.Raycast(AttackSource, AttackPath, out AimPoint, AttackReach, ~gameObject.layer))
        {
            Debug.Log(AimPoint.transform.name + " was hit by a melee attack;");
            if (Player.NoiseLevel < Loudness)
            {
                Player.NoiseLevel = Loudness;
            }
            if (AimPoint.transform.CompareTag("Shootable"))
            {
                EnemyStats Enemy = AimPoint.transform.GetComponent<EnemyStats>();
                PlayerStats OtherPlayer = AimPoint.transform.GetComponentInParent<PlayerStats>();
                if (Enemy != null)
                {
                    Enemy.Hurt(Damage);
                    GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
                    Destroy(Impact, 2f);
                }
                if (OtherPlayer != null && OtherPlayer != Player)
                {
                    OtherPlayer.Hurt(Damage);
                    GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
                    Destroy(Impact, 2f);
                }
            }
            else
            {
                GameObject Impact = Instantiate(ImpactEffect [1], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
                Destroy(Impact, 2f);
            }
        }

I would do this?

        if (Physics.Raycast(AttackSource, AttackPath, out HitPoint, AttackReach))
        {
            RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath);

            for (int i = 0; i < HitObjects.Length; i++)
            {
                if (HitObjects[i].collider == Player.GetComponentInChildren<Collider>())
                {
                    Debug.Log(HitPoint.transform.name + " was hit by a melee attack;");

                    if (Player.NoiseLevel < Loudness)
                    {
                        Player.NoiseLevel = Loudness;
                    }

                    if (HitPoint.transform.CompareTag("Shootable"))
                    {
                        EnemyStats Enemy = HitPoint.transform.GetComponent<EnemyStats>();
                        PlayerStats OtherPlayer = HitPoint.transform.GetComponentInParent<PlayerStats>();

                        if (Enemy != null)
                        {
                            Enemy.Hurt(Damage);

                            GameObject Impact = Instantiate(ImpactEffect [0], HitPoint.point, Quaternion.LookRotation(HitPoint.normal));
                            Destroy(Impact, 2f);
                        }

                        if (OtherPlayer != null && OtherPlayer != Player)
                        {
                            OtherPlayer.Hurt(Damage);

                            GameObject Impact = Instantiate(ImpactEffect [0], HitPoint.point, Quaternion.LookRotation(HitPoint.normal));
                            Destroy(Impact, 2f);
                        }
                    }
                    else
                    {
                        GameObject Impact = Instantiate(ImpactEffect [1], HitPoint.point, Quaternion.LookRotation(HitPoint.normal));
                        Destroy(Impact, 2f);
                    }
                }
            }

        }

You can also make an empty child object and set its Transform just outside the player’s collider (in the forward-facing direction), and use that Transform as the raycast starting point. Or just add a bit to the AttackSource vector as needed.

You still need to pass the AttackReach to the RaycastAll. Right now you’re going "if I hit a single thing in range, hit everything in an infinite range.

It’s also a bit too complex. You don’t need to first raycast to check if there’s anything there, and then do a raycast all for the same path! Just drop the outermost raycast:

// old:
if (Physics.Raycast(AttackSource, AttackPath, out HitPoint, AttackReach))
{
    RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath);

    for (int i = 0; i < HitObjects.Length; i++)
    {

// new:
RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath, AttackReach);
for (int i = 0; i < HitObjects.Length; i++) 
{
    var HitPoint = HitObjects[i];
1 Like

That wouldn’t work because the player has two colliders, one for the head and other for the body, and the camera can move the head. So even if I placed the starting point outwards it could clip through the body when the player looked down.

Thank you for helping me out you all.

This code doesn’t significantly demand more performance than normal raycasting, does it?

The solution did not work. The player still hits themselves even though I used if (HitObjects *.transform != Player.GetComponentInChildren<Transform>()).*
Full code (Attack() function):
```csharp

  • void Attack()
    {
    CurrentAttackDelay = 1f / AttackSpeed;

    Vector3 AttackPath = FPCamera.transform.forward;
    Vector3 AttackSource = FPCamera.transform.position;

    if (CurrentSpread > 0f)
    {
    AttackPath.x += Random.Range(-CurrentSpread, CurrentSpread);
    AttackPath.y += Random.Range(-CurrentSpread, CurrentSpread);
    AttackPath.z += Random.Range(-CurrentSpread, CurrentSpread);
    }

    RaycastHit HitObjects = Physics.RaycastAll(AttackSource, AttackPath, AttackReach);

    for (int i = 0; i < HitObjects.Length; i++)
    {
    if (HitObjects [i].transform != Player.GetComponentInChildren())
    {
    Debug.Log(HitObjects [i].transform.name + " was hit by a melee attack;");

           if (Player.NoiseLevel < Loudness)
           {
               Player.NoiseLevel = Loudness;
           }
    
           if (HitObjects [i].transform.CompareTag("Shootable"))
           {
               EnemyStats Enemy = HitObjects [i].transform.GetComponent<EnemyStats>();
               PlayerStats OtherPlayer = HitObjects [i].transform.GetComponentInParent<PlayerStats>();
    
               if (Enemy != null)
               {
                   Enemy.Hurt(Damage);
    
                   GameObject Impact = Instantiate(ImpactEffect [0], HitObjects [i].point, Quaternion.LookRotation(HitObjects [i].normal));
                   Destroy(Impact, 2f);
               }
    
               if (OtherPlayer != null)
               {
                   OtherPlayer.Hurt(Damage);
    
                   GameObject Impact = Instantiate(ImpactEffect [0], HitObjects [i].point, Quaternion.LookRotation(HitObjects [i].normal));
                   Destroy(Impact, 2f);
               }
           }
           else
           {
               GameObject Impact = Instantiate(ImpactEffect [1], HitObjects [i].point, Quaternion.LookRotation(HitObjects [i].normal));
               Destroy(Impact, 2f);
           }
    
           i = HitObjects.Length;
       }
    

    }
    }*
    ** _EDIT: Forgot to mention, I already tried earlier using if (HitObjects .collider!= Player.GetComponentInChildren())``` but it also didn’t work._

1 Like

My player is controlled by a character controller. And the colliders sit in child objects of the player.

Using RaycastAll does work when used properly. Though this approach requires a lot more analysis on your side. So you have to put a lot more work into figuring out what object each hit logically belongs to. So you may need to search the hierarchy upwards until you find the root (or use transform.root if your objects aren’t parented to anything else). I would not recommend using transform.root as it limits the way how you can parent things. For example you could no longer parent your player to a car or lift temporarily since it would break the raycasting. It’s better to search for some sort of component on the “root” object (GetComponentInParent may be useful here).

If you only want to hit the closest target you also have to pay attention to the hit distances.

I’m not sure how many teams / factions your game will support. Though using layers is usually the easiest solution. You have 32 layers available. Though only 24 of them should be used for your own special behaviour as the first 8 are kinda predefined but technically you could use all 32 layers however you want.

1 Like

It would be great if Unity could add a new overload of Physics.Raycast() that lets you pass a callback function such as a Func<GameObject, bool> that determines whether any given GameObject should be ignored by the raycast. This way, it could still only give back a single RaycastHit object as the result of the raycast, and it wouldn’t have to allocate an array in memory.

1 Like

FYI I posted some related code in another thread:

1 Like