Search Unity

Raycasting a cone instead of single ray?

Discussion in 'Scripting' started by Danneman, Jan 21, 2010.

  1. Danneman

    Danneman

    Joined:
    Sep 29, 2009
    Posts:
    190
    Im using this code on an enemy to check when he spots the player in the game.


    Code (csharp):
    1.  
    2. var int_HitRadiusDistance = 20;
    3.  
    4. function Update ()
    5. {
    6.     var ray1 = new Ray( transform.position, transform.forward );
    7.                    
    8.     var hit : RaycastHit;
    9.                    
    10.     if( Physics.Raycast( ray1, hit, int_HitRadiusDistance ))
    11.     {
    12.         if( (hit.collider.name == "FPS") || (hit.collider.name == "Player") )
    13.         {  
    14.             // TODO: Attack-code   
    15.         }
    16.     }
    17. }
    18.  
    The problem is that it's just shooting a single ray, which means the enemy has no peripheral vision what so ever. So a cone of rays would be preferable.

    Now, I could probably create a few additional rays with the same origin but with different directions, and check for each of them in the if-statement.

    Code (csharp):
    1.  
    2. var int_HitRadiusDistance = 20;
    3.  
    4. function Update ()
    5. {
    6.     var ray1 = new Ray( transform.position, transform.forward );
    7.     var ray2 = new Ray( transform.position, transform.left );            // ERROR      
    8.     var ray3 = new Ray( transform.position, transform.right );
    9.                    
    10.     var hit : RaycastHit;
    11.                    
    12.     if(  ( Physics.Raycast( ray1, hit, int_HitRadiusDistance )) || ( Physics.Raycast( ray2, hit, int_HitRadiusDistance )) || ( Physics.Raycast( ray3, hit, int_HitRadiusDistance ))  )
    13.     {
    14.         if( (hit.collider.name == "FPS") || (hit.collider.name == "Player") )
    15.         {  
    16.             // TODO: Attack-code   
    17.         }
    18.     }
    19. }
    20.  
    But perhaps there is a smoother inbuilt function that I havent been able to find in the docs? Besides, Im not quite sure about the syntax for the second parameter of the Ray-object (transform.[direction]) and how to create a cone. I mean, how do I express the direction in numbers? In degrees?
     
  2. nickavv

    nickavv

    Joined:
    Aug 2, 2006
    Posts:
    1,801
    You could have an invisible cone shaped object and do collision testing on that. Then if any collisions come up positive do a ray from the enemy to the player to make sure its an actual line-of-sight.
     
  3. Danneman

    Danneman

    Joined:
    Sep 29, 2009
    Posts:
    190
    That sounds like a better idea, actually. And simply unchecking the MeshRenderer of the object makes it invisible (did not know that until now :D ). I think Ill try that instead - Thanks :)

    But just for educational value, is there a method such as the one I described with raycast?
     
  4. Danneman

    Danneman

    Joined:
    Sep 29, 2009
    Posts:
    190
    Actually, using an invisible cone-object wont work in my case because of the layout of the map. It would mean that parts of the cone would penetrate walls and other objects that raycast wont, and the effect would be that of an x-ray. So back to the raycasting, any suggestions there? :)
     
    BlckBrd likes this.
  5. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    That's why Nick said to use a linecast between the enemy and the player when the trigger picks up the player in range. So you still use raycasting, but only when necessary. That's going to be a lot more efficient than continuously emitting a spread of raycasts every frame.

    --Eric
     
  6. Danneman

    Danneman

    Joined:
    Sep 29, 2009
    Posts:
    190
    Sorry, I must have read the first part of Nicks post and just assumed the rest I think, or my brain freezed up or something. That definitely sounds like the way to go, so thanks again :)

    And I dont have to continue to figure out how to set a diagonal direction with ray, which has been a pain the last hour. ?

    But out of curiosity, how do you set the direction? Lets say I wanted a ray to shoot horisontally but 45 degrees to the right, what would I put instead of [direction]?

    Code (csharp):
    1.  
    2. var ray = new Ray (transform.position, [direction]));
    3.  
     
  7. bpatters

    bpatters

    Joined:
    Oct 5, 2009
    Posts:
    164
    I've tried that before and it doesn't work. Problem is if you don't mark it as a trigger things actually collide with it and if you mark it as a trigger it breaks the collision detection in one scenario.

    From what I've seen if you make a trigger and attach it to your player and the player move the OnTriggerEnter will never fire for what it collides with. Triggers only seem to fire when something else moves into a trigger, not the other way around.


    The way I solved a similar problem, not the same but close, was to put NPC's on their own layer and then use:
    Code (csharp):
    1.  
    2. Collider[] targets;
    3.             targets = Physics.OverlapSphere(damageAreaTransform.position,range,1 <<
    4. NPCLayerNumber);
    5.  
    If you then get a collision with that you can do a raycast for line of sight.
     
    howong likes this.
  8. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    There's an easy way to get a cone using the dot product. Get a vector pointing from the player to a potential target using their positions, and normalise that vector:-
    Code (csharp):
    1. var heading: Vector3 = (target.transform.position - transform.position).normalized;
    Then, get the dot product of this heading vector with the vector representing the player's direction of sight (this should also be normalised):-
    Code (csharp):
    1. var dot: float = Vector3.Dot(sightVec, heading);
    The dot product of two normalised vectors is equal to the cosine of the angle between them (ie, it will be one when the directions are the same, zero when they are at 90º and 0.5 when they are at 60º). You can convert this to an angle if you want particular accuracy, but for most purposes, you can just play with a value between 0 and 1 until you get good results. Basically, the check is whether the dot is greater than your threshold value. Since the angle could be in any direction, up/down, left/right, you effectively get a cone of sight this way.
     
  9. Danneman

    Danneman

    Joined:
    Sep 29, 2009
    Posts:
    190
    Ive been knocking my head against the wall with trying to get the cone-object method to work, but without any luck.

    Let me see if I understand whats going on here:

    // 1) Get the direction between player and enemy (normalized value, between 0 and 1)
    var heading : Vector3 = (target_player.transform.position - transform.position).normalized;

    // 2) Get the direction the enemy is watching (normalized)
    var sightView : Vector3 = ( transform.forward );

    // 3) Compare heading with sightView to get the difference (normalized, which corresponds to an angle: 0(=0)/0,5(=60)/1(=90)
    var dot : float = Vector3.Dot( sightView, heading );

    Q: But how can I use this value (dot) with Raycast? I mean, dot is a single value, not a direction any more, while Raycast uses directions (Vector3) as parameters.
     
  10. Danneman

    Danneman

    Joined:
    Sep 29, 2009
    Posts:
    190
    Alright, I got tired of this and constructed a couple of child objects for the enemy with different angles (forming sort of a cone), and attached this code to each one.

    Code (csharp):
    1.  
    2. var int_HitRadiusDistance = 20;
    3. function Update ()  {  
    4.     var ray1 = new Ray( transform.position, transform.forward );    
    5.     var hit : RaycastHit;              
    6.     if( Physics.Raycast( ray1, hit, int_HitRadiusDistance ) )   {
    7.         if( (hit.collider.name == "FPS") || (hit.collider.name == "Player") )   {  
    8.             // TODO: Attack-code   
    9.         }
    10.     }
    11. }
    12.  
    We'll see later on if this puts a too heavy load on the game, but since I only have one enemy I dont think so in my case.

    Im wondering if creating the objects (ray1 and hit) outside the Update-function will go less heavy on the engine than doing it each time the Update-function is called, or if its all the same?
     
  11. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    Sorry - reading that again, I see I didn't make it very clear what I meant. You can use the dot product thing as a quick test to see if the other object is roughly within the cone. Once you've established that it is, you can use a raycast for a line of sight check.

    Incidentally, you can also use the result of the dot product as a probability of spotting the player (ie, 1 when straight ahead, 0 when at the side).
     
  12. darkmoose

    darkmoose

    Joined:
    Jan 19, 2010
    Posts:
    2
    Since this is my first post ever let me say hello:

    Hello,

    I was trying to do something very similar with a cone of view, or a circular perception area. I have been unable to find a way to graphically represent it. I just want to draw two lines represting the cone and maybe a transparent layer in between them, or just a simple circle around the player but so far I could not find such a functionality except for a drawline function that is only available to pro version. Am I missing something or can it be done with creating a cone shaped object with transparent material and texture (which is what I am currently trying to do)?

    @Danneman: It would be great if you could tell me how you tackled this problem.

    Also there is the problem of the aforementioned perception circle/cone object being visible to the player (assuming all enemies have one) when it is not really convenient for the enemy's location being given away while there is an actual obstruction between the player and the enemy. Or in another case I think it would be nice for the player to know that he can hide behind an object which casts a perception circle/cone shadow.

    So I specifically want the rest of the circle/cone to be culled according to the objects that intersect it.

    I am thinking about raycasting to find the objects that the circle intersects, get their dimensions, and maybe modify the circle/cone/transparent area so that they seem to cast a shadow but I have no idea how to do it apart from the raycasting part. Any ideas?

    Much thanks in advance for any help to be followed by more thanks.


    edit: the following link is a good example of the perception cone that I am trying to get.
    http://www.youtube.com/watch?v=2lQQt4qc79Q&feature=player_embedded
     
    X0FloH and Westland like this.
  13. ivkoni

    ivkoni

    Joined:
    Jan 26, 2009
    Posts:
    978
    are you sure you want a cone or a simple triangle will do, as it is in the video?

    If a triangle is enough, then you can build a mesh dynamically using the mesh class. Cutting it where it intersects other objects though is not going to be easy.

    you can check this thread as an example of how to create dynamic 2d shadows with meshes:
    http://forum.unity3d.com/viewtopic.php?t=26679
    which is not quite what you want, but you might get some ideas from it.

    I have also done something closer to what you ask (modelling a 2d spot light with a mesh) but it's not too general and I can't give you the code for it (since it was done per somebody's request). I can give you an overview of the algorithm though, if you are interested.
     
    X0FloH likes this.
  14. erix

    erix

    Joined:
    May 25, 2009
    Posts:
    36
    I'm very interested in an overview of the algorithm.
    Please, if you have the time it would be really appreciated.
     
  15. darkmoose

    darkmoose

    Joined:
    Jan 19, 2010
    Posts:
    2
    Thanks for the reply, your link was very useful. (I am still trying to get used to scripting with unity :) )

    I am also interested in the algorithm, if it is not much trouble.
     
  16. theBrandonWu

    theBrandonWu

    Joined:
    Sep 3, 2009
    Posts:
    244
    Not sure if you solved the problem. I found this link from Unity Answers and this worked for me.

    http://answers.unity3d.com/questions/8453/field-of-view-using-raycasting

    my code (slightly modified from the above link)

    Code (csharp):
    1. //from [url]http://answers.unity3d.com/questions/8453/field-of-view-using-raycasting[/url]
    2. function CanSeePlayer() : boolean{
    3.  
    4.     var hit : RaycastHit;
    5.     var rayDirection = ObjectToSee.transform.position - transform.position;
    6.  
    7.     // If the ObjectToSee is close to this object and is in front of it, then return true
    8.     if((Vector3.Angle(rayDirection, transform.forward)) < 90  (Vector3.Distance(transform.position, ObjectToSee.transform.position)<=CloseDistanceRange)){
    9.         //Debug.Log("close");
    10.         return true;
    11.     }
    12.    
    13.  
    14.     //Debug.Log("rayDirection - transform.forward =" + Vector3.Angle(rayDirection, transform.forward));
    15.  
    16.  
    17.  
    18.     if((Vector3.Angle(rayDirection, transform.forward)) < fieldOfViewRangeInHalf){ // Detect if player is within the field of view
    19.        //Debug.Log("within field of view");
    20.  
    21.        
    22.         if (Physics.Raycast (transform.position, rayDirection, hit, HowFarCanISee)) {
    23.  
    24.             if (hit.collider.gameObject == ObjectToSee) {
    25.                 //Debug.Log("Can see player");
    26.                 return true;
    27.             }else{
    28.                 //Debug.Log("Can not see player");
    29.                 return false;
    30.             }
    31.         }
    32.     }
    33. }
    You'll have to define all the variables first.

    Hope this helps!
     
    arkon likes this.
  17. theBrandonWu

    theBrandonWu

    Joined:
    Sep 3, 2009
    Posts:
    244
    On a related note, I've finally create a visual for the line of sight in the game. Nothing fancy, just attaching a Line Renderer to the game object.

    "In the end, I decided to use a simple solution to create a Line Renderer object and tweak the starting and ending sizes to make it look somewhat like a cone. I created an image of a dotted line for the Line Renderer material. And I attached the Line Renderer object to the NPC object and have it drawn in the Z-axis(which is the direction the NPC is facing) by 2 units. See the image below for how I set up the Line Renderer object in the inspector. (Please ignore the transform as it's related to how my NPCs are constructed.)"



    See the post here http://www.pepwuper.com/unity3d-for...-the-line-of-sight-vision-cone-field-of-view/
     
    Last edited: Mar 13, 2012
    Xepherys likes this.
  18. selzero

    selzero

    Joined:
    Dec 3, 2009
    Posts:
    32
    Incase anyone is interested here is how I do it.

    I have an array of non-player characters accessible to a main game script. On every update I calculate the distance between NPC and the character, only NPCs that are close enough to see the character are processed.

    Then I fire a raycast from the NPC to the player, this is to determine if anything is in the way.

    If the distance is low and the line of sight is open, I can then (depending on the NPC) evaluate the rotation of the NPC and deal with "he is behind you" scenarios seperately.

    If all of the tests return "True" I set a "spotted" boolean on the NPC script and his behaviour changes accordingly.
     
  19. Z3r0Sign4l

    Z3r0Sign4l

    Joined:
    Oct 23, 2014
    Posts:
    11
    Slightly modified version in C#, uses player height to take into account uneven terrain.

    Code (CSharp):
    1.     //from http://forum.unity3d.com/threads/raycasting-a-cone-instead-of-single-ray.39426/
    2.     bool CanSeeMonster(GameObject target)
    3.     {
    4.         float heightOfPlayer = 1.5f;
    5.  
    6.         Vector3 startVec = transform.position;
    7.         startVec.y += heightOfPlayer;
    8.         Vector3 startVecFwd = transform.forward;
    9.         startVecFwd.y += heightOfPlayer;
    10.  
    11.         RaycastHit hit;
    12.         Vector3 rayDirection = target.transform.position - startVec;
    13.  
    14.         // If the ObjectToSee is close to this object and is in front of it, then return true
    15.         if ((Vector3.Angle(rayDirection, startVecFwd)) < 110 &&
    16.             (Vector3.Distance(startVec, target.transform.position) <= 20f))
    17.         {
    18.             //Debug.Log("close");
    19.             return true;
    20.         }
    21.         if ((Vector3.Angle(rayDirection, startVecFwd)) < 90 &&
    22.             Physics.Raycast(startVec, rayDirection, out hit, 100f))
    23.         { // Detect if player is within the field of view
    24.  
    25.             if (hit.collider.gameObject == target)
    26.             {
    27.                 //Debug.Log("Can see player");
    28.                 return true;
    29.             }
    30.             else
    31.             {
    32.                 //Debug.Log("Can not see player");
    33.                 return false;
    34.             }
    35.         }
    36.         return false;
    37.     }
    Hope this helps someone. :D
     
  20. Tolufoyeh

    Tolufoyeh

    Joined:
    Nov 13, 2014
    Posts:
    16
    Great! Between that and Unity's own code tutor's code here https://unity3d.com/learn/tutorials/projects/stealth-tutorial-4x-only/enemy-sight, people should have all they need
     
  21. Torhque

    Torhque

    Joined:
    Feb 4, 2017
    Posts:
    1
    Thank you so much for translating and posting the code like this for the rest of us.

    I've got a few questions though. I'm playing with values in your code because I want to understand what's going on and why. However I can't figure out what your numbered values affect. I'm specifically referring to the following lines:
    Code (CSharp):
    1. if ((Vector3.Angle(rayDirection, startVecFwd)) < 110 &&
    2.     Vector3.Distance(startVec, target.transform.position) <= 20f))
    3.  
    4. if ((Vector3.Angle(rayDirection, startVecFwd)) < 90 &&
    5.     Physics.Raycast(startVec, rayDirection, out hit, 100f))
    At first sight I thought they were related to Field of View or Distance values, but my Debug.DrawRays are not confirming this. Thanks in advance to anyone that helps me decipher this haha
     
  22. Deleted User

    Deleted User

    Guest

    This question addresses whether or not something is in a cone.

    If you would like a random direction within a cone (for example to ray test in a cone), try this:

    Code (CSharp):
    1. // rotate the forward vector randomly in some range
    2. Quaternion randomRot = Quaternion.Euler(
    3. Random.Range(-10f, 10f),
    4. Random.Range(-10f, 10f),
    5. Random.Range(-10f, 10f));
    6.  
    7. Vector3 coneVector = randomRot * transform.forward;
    8.  
    9. Debug.DrawRay(transform.position, transform.forward * 3f, Color.yellow, 0.05f);
    10. Debug.DrawRay(transform.position, coneVector * 3f, Color.red, 3f);
    A bit off topic... but tangentially related and maybe useful for someone googling. Maybe.

    Unity has some function for rotating vectors with quaternions in the multiplication operator, it's very useful.
     
    Last edited by a moderator: Jul 12, 2018
    okletshavesomefun likes this.
  23. okletshavesomefun

    okletshavesomefun

    Joined:
    Jul 30, 2015
    Posts:
    5
    Kjurek likes this.
  24. Blockehh

    Blockehh

    Joined:
    Dec 17, 2018
    Posts:
    1
  25. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870
  26. morten-gram

    morten-gram

    Joined:
    Mar 12, 2021
    Posts:
    1
    So i had the same problem...

    what I did was:
    - first I added a cirkel gameobject as a trigger to my enemy that would check if the player was inside it or not
    - then I added so if the player was inside the cirkle it would send a ray towards the player
    - I added a specific layer to my walls and player, if the enemy hit something of that layer it that did not have the tag "player" i would return a bool that said false, on the other hand if it hit something with the tag "player" i would attack

    hope it helps :)

    Code (CSharp):
    1. bool CanSeePlayer(float distance)
    2.     {
    3.         bool val = false;
    4.         float castDist = distance;
    5.  
    6.         //Vector2 endPos = castPoint.position + Vector3.right * distance;
    7.  
    8.         Vector2 endPos = player.position;
    9.  
    10.         RaycastHit2D hit = Physics2D.Linecast(castPoint.position, endPos, 1 << LayerMask.NameToLayer("Action"));
    11.  
    12.         if(hit.collider != null)
    13.         {
    14.  
    15.            
    16.             if (hit.collider.gameObject.CompareTag("Player"))
    17.             {
    18.                 val = true;
    19.            
    20.             }
    21.             else
    22.             {
    23.                 val = false;
    24.             }
    25.         }
    26.         return val;
    27.     }
     
  27. arkon

    arkon

    Joined:
    Jun 27, 2011
    Posts:
    1,122
    it helped me!
     
  28. davidnibi

    davidnibi

    Joined:
    Dec 19, 2012
    Posts:
    426
    I personally used a single ray that 'shot out' at a different direction (in a cone shape) every fraction of a second.
    It pretty much always spotted what was in the defined cone shape but there was an element of chance in it and probably cheaper with ten of these at a time - who knows.
     
  29. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,442
    Don't necro-post a thread started in 2010 and ended over two years ago, just to share some chatbot pablum.