Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved How to ignore raycast without using layers

Discussion in 'Scripting' started by MinhocaNice, Jan 13, 2021.

  1. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    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):
    Code (CSharp):
    1.     void Attack()
    2.     {
    3.         CurrentAttackDelay = 1f / AttackSpeed;
    4.  
    5.         Vector3 AttackPath = FPCamera.transform.forward;
    6.         Vector3 AttackSource = FPCamera.transform.position;
    7.  
    8.         if (CurrentSpread > 0f)
    9.         {
    10.             AttackPath.x += Random.Range(-CurrentSpread, CurrentSpread);
    11.             AttackPath.y += Random.Range(-CurrentSpread, CurrentSpread);
    12.             AttackPath.z += Random.Range(-CurrentSpread, CurrentSpread);
    13.         }
    14.  
    15.         if (Physics.Raycast(AttackSource, AttackPath, out AimPoint, AttackReach, ~gameObject.layer))
    16.         {
    17.             Debug.Log(AimPoint.transform.name + " was hit by a melee attack;");
    18.  
    19.             if (Player.NoiseLevel < Loudness)
    20.             {
    21.                 Player.NoiseLevel = Loudness;
    22.             }
    23.  
    24.             if (AimPoint.transform.CompareTag("Shootable"))
    25.             {
    26.                 EnemyStats Enemy = AimPoint.transform.GetComponent<EnemyStats>();
    27.                 PlayerStats OtherPlayer = AimPoint.transform.GetComponentInParent<PlayerStats>();
    28.  
    29.                 if (Enemy != null)
    30.                 {
    31.                     Enemy.Hurt(Damage);
    32.  
    33.                     GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
    34.                     Destroy(Impact, 2f);
    35.                 }
    36.  
    37.                 if (OtherPlayer != null && OtherPlayer != Player)
    38.                 {
    39.                     OtherPlayer.Hurt(Damage);
    40.  
    41.                     GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
    42.                     Destroy(Impact, 2f);
    43.                 }
    44.             }
    45.             else
    46.             {
    47.                 GameObject Impact = Instantiate(ImpactEffect [1], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
    48.                 Destroy(Impact, 2f);
    49.             }
    50.         }
    51.     }
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,890
    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.
     
    MinhocaNice likes this.
  3. alexeu

    alexeu

    Joined:
    Jan 24, 2016
    Posts:
    257
    And what about the Ignore Raycast layer ?
     
  4. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    So instead of this:

    Code (CSharp):
    1.         if (Physics.Raycast(AttackSource, AttackPath, out AimPoint, AttackReach, ~gameObject.layer))
    2.         {
    3.             Debug.Log(AimPoint.transform.name + " was hit by a melee attack;");
    4.             if (Player.NoiseLevel < Loudness)
    5.             {
    6.                 Player.NoiseLevel = Loudness;
    7.             }
    8.             if (AimPoint.transform.CompareTag("Shootable"))
    9.             {
    10.                 EnemyStats Enemy = AimPoint.transform.GetComponent<EnemyStats>();
    11.                 PlayerStats OtherPlayer = AimPoint.transform.GetComponentInParent<PlayerStats>();
    12.                 if (Enemy != null)
    13.                 {
    14.                     Enemy.Hurt(Damage);
    15.                     GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
    16.                     Destroy(Impact, 2f);
    17.                 }
    18.                 if (OtherPlayer != null && OtherPlayer != Player)
    19.                 {
    20.                     OtherPlayer.Hurt(Damage);
    21.                     GameObject Impact = Instantiate(ImpactEffect [0], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
    22.                     Destroy(Impact, 2f);
    23.                 }
    24.             }
    25.             else
    26.             {
    27.                 GameObject Impact = Instantiate(ImpactEffect [1], AimPoint.point, Quaternion.LookRotation(AimPoint.normal));
    28.                 Destroy(Impact, 2f);
    29.             }
    30.         }
    I would do this?

    Code (CSharp):
    1.         if (Physics.Raycast(AttackSource, AttackPath, out HitPoint, AttackReach))
    2.         {
    3.             RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath);
    4.  
    5.             for (int i = 0; i < HitObjects.Length; i++)
    6.             {
    7.                 if (HitObjects[i].collider == Player.GetComponentInChildren<Collider>())
    8.                 {
    9.                     Debug.Log(HitPoint.transform.name + " was hit by a melee attack;");
    10.  
    11.                     if (Player.NoiseLevel < Loudness)
    12.                     {
    13.                         Player.NoiseLevel = Loudness;
    14.                     }
    15.  
    16.                     if (HitPoint.transform.CompareTag("Shootable"))
    17.                     {
    18.                         EnemyStats Enemy = HitPoint.transform.GetComponent<EnemyStats>();
    19.                         PlayerStats OtherPlayer = HitPoint.transform.GetComponentInParent<PlayerStats>();
    20.  
    21.                         if (Enemy != null)
    22.                         {
    23.                             Enemy.Hurt(Damage);
    24.  
    25.                             GameObject Impact = Instantiate(ImpactEffect [0], HitPoint.point, Quaternion.LookRotation(HitPoint.normal));
    26.                             Destroy(Impact, 2f);
    27.                         }
    28.  
    29.                         if (OtherPlayer != null && OtherPlayer != Player)
    30.                         {
    31.                             OtherPlayer.Hurt(Damage);
    32.  
    33.                             GameObject Impact = Instantiate(ImpactEffect [0], HitPoint.point, Quaternion.LookRotation(HitPoint.normal));
    34.                             Destroy(Impact, 2f);
    35.                         }
    36.                     }
    37.                     else
    38.                     {
    39.                         GameObject Impact = Instantiate(ImpactEffect [1], HitPoint.point, Quaternion.LookRotation(HitPoint.normal));
    40.                         Destroy(Impact, 2f);
    41.                     }
    42.                 }
    43.             }
    44.  
    45.         }
     
  5. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    687
    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.
     
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    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:

    Code (csharp):
    1. // old:
    2. if (Physics.Raycast(AttackSource, AttackPath, out HitPoint, AttackReach))
    3. {
    4.     RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath);
    5.  
    6.     for (int i = 0; i < HitObjects.Length; i++)
    7.     {
    8.  
    9. // new:
    10. RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath, AttackReach);
    11. for (int i = 0; i < HitObjects.Length; i++)
    12. {
    13.     var HitPoint = HitObjects[i];
    14.  
     
    MinhocaNice likes this.
  7. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    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.
     
  8. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    Thank you for helping me out you all.

    This code doesn't significantly demand more performance than normal raycasting, does it?
     
  9. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    The solution did not work. The player still hits themselves even though I used
    if (HitObjects [i].transform != Player.GetComponentInChildren<Transform>())
    .

    Full code (Attack() function):


    Code (CSharp):
    1.     void Attack()
    2.     {
    3.         CurrentAttackDelay = 1f / AttackSpeed;
    4.  
    5.         Vector3 AttackPath = FPCamera.transform.forward;
    6.         Vector3 AttackSource = FPCamera.transform.position;
    7.  
    8.         if (CurrentSpread > 0f)
    9.         {
    10.             AttackPath.x += Random.Range(-CurrentSpread, CurrentSpread);
    11.             AttackPath.y += Random.Range(-CurrentSpread, CurrentSpread);
    12.             AttackPath.z += Random.Range(-CurrentSpread, CurrentSpread);
    13.         }
    14.  
    15.         RaycastHit[] HitObjects = Physics.RaycastAll(AttackSource, AttackPath, AttackReach);
    16.  
    17.         for (int i = 0; i < HitObjects.Length; i++)
    18.         {
    19.             if (HitObjects [i].transform != Player.GetComponentInChildren<Transform>())
    20.             {
    21.                 Debug.Log(HitObjects [i].transform.name + " was hit by a melee attack;");
    22.  
    23.                 if (Player.NoiseLevel < Loudness)
    24.                 {
    25.                     Player.NoiseLevel = Loudness;
    26.                 }
    27.  
    28.                 if (HitObjects [i].transform.CompareTag("Shootable"))
    29.                 {
    30.                     EnemyStats Enemy = HitObjects [i].transform.GetComponent<EnemyStats>();
    31.                     PlayerStats OtherPlayer = HitObjects [i].transform.GetComponentInParent<PlayerStats>();
    32.  
    33.                     if (Enemy != null)
    34.                     {
    35.                         Enemy.Hurt(Damage);
    36.  
    37.                         GameObject Impact = Instantiate(ImpactEffect [0], HitObjects [i].point, Quaternion.LookRotation(HitObjects [i].normal));
    38.                         Destroy(Impact, 2f);
    39.                     }
    40.  
    41.                     if (OtherPlayer != null)
    42.                     {
    43.                         OtherPlayer.Hurt(Damage);
    44.  
    45.                         GameObject Impact = Instantiate(ImpactEffect [0], HitObjects [i].point, Quaternion.LookRotation(HitObjects [i].normal));
    46.                         Destroy(Impact, 2f);
    47.                     }
    48.                 }
    49.                 else
    50.                 {
    51.                     GameObject Impact = Instantiate(ImpactEffect [1], HitObjects [i].point, Quaternion.LookRotation(HitObjects [i].normal));
    52.                     Destroy(Impact, 2f);
    53.                 }
    54.  
    55.                 i = HitObjects.Length;
    56.             }
    57.         }
    58.     }
    EDIT: Forgot to mention, I already tried earlier using
    if (HitObjects [i].collider!= Player.GetComponentInChildren<Collider>())
    but it also didn't work.
     
    Last edited: Jan 14, 2021
  10. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    If your player is controlled by a Rigidbody, then you would need to compare the RaycastHit's GameObject with the GameObject that the Rigidbody is attached to - that's where collision events occur.

    Something like this:
    Code (CSharp):
    1. for(int i = 0; i < HitObjects.Length; i++) {
    2.    if(HitObjects[i].gameObject == this.rigidBody.gameObject) {
    3.       //Self was hit; do nothing.
    4.    }
    5. }
     
    MinhocaNice likes this.
  11. MinhocaNice

    MinhocaNice

    Joined:
    May 3, 2020
    Posts:
    249
    My player is controlled by a character controller. And the colliders sit in child objects of the player.
     
  12. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    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.
     
    MinhocaNice likes this.
  13. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    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.
     
    MinhocaNice likes this.
  14. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10