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

Detecting an object from a point on the screen

Discussion in 'Scripting' started by andeeeee, Oct 7, 2009.

  1. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    How can you detect which object in a 3D scene is at a given screen pixel position? Say, when the user clicks the mouse and you want to know which scene object was clicked on.

    This is more complicated in 3D than 2D because the screen image is a "flattened" view of the 3D space, and so each screen point corresponds to a line in the game world. These lines diverge outward into the scene and they all actually originate from the camera's location.

    Mercifully, the Unity Camera class contains a function, ScreenPointToRay, that calculates the direction of a line corresponding to a screen position for the given camera. This line, in the form of a Ray object, can then be passed to Physics.Raycast. This function searches along the line from its origin until it makes contact with an object. The object returned by Physics.Raycast (in the RaycastHit parameter) is the object at the original screen position.

    It probably sounds more difficult than it really is. I've attached an example script in C# and JS versions to illustrate how the raycast is done. The RaycastHit structure actually contains several useful pieces of information about the object that was hit, including the exact point in space where the contact occurred. Note that in C#, you need to call Physics.Raycast with the "out" keyword in front of the RaycastHit parameter. This is because the function returns data back to you in this parameter.
     

    Attached Files:

  2. cedrico

    cedrico

    Joined:
    May 4, 2009
    Posts:
    40
    Hello there and thx for the relevant example, may i ask you how you would deal with the distance, let's say you have a few pixels of an object somewhere on the screen but the user is totally out of range to manipulate it or drag it, which classe or function would deal with that ?

    THX a bunch for your great example !
     
  3. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    The Physics.Raycast function has an optional parameter to let you set the distance the ray travels. If, say, your object can be a maximum of 10 units away from the camera, then set the distance to 10.
     
  4. heohni

    heohni

    Joined:
    Feb 26, 2010
    Posts:
    14
  5. Dieneces

    Dieneces

    Joined:
    Aug 1, 2009
    Posts:
    26
    Nice script, I had some problems with raycast but slowly i'm getting to understand them!

    I'm trying to adapt this idea but to make an instance of a prefab where the mouse hits... Already viewed the foruns and unity answers but still not getting luck #)
     
  6. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    If you are placing prefabs on the surface of another object, then you can use the point field of the RaycastHit to get the right position:-
    Code (csharp):
    1. var hit: RaycastHit;
    2. if (Physics.Raycast(ray, hit)) {
    3.     Instantiate(prefab, hit.point, Quaternion.identity);
    4. }
    If you are placing objects in empty space, you might want to add an invisible plane collider or else use the Plane.Raycast function.
     
    Powzone likes this.
  7. screenhogdotcom

    screenhogdotcom

    Joined:
    Aug 23, 2009
    Posts:
    126
    Useful script!

    However, I'm trying to make a situation where RaycastHit can see a collider on a specific layer (the ground), even when it's behind other colliders.

    I know about LayerMask, and that's sort of working. When I cast my ray and it hits the ground, the hit is successful. When I cast my ray and it hits another object that's not on the ground layer, however, the ray does not go through that non-ground object to try and find more ground. It just assumes that the ray has stopped on a non-ground object.

    In short, the LayerMask doesn't work how I expect it to. Instead of fully ignoring objects that it's supposed to ignore, it hits them, with the hit being false. Any way to send my ray through colliders, only hitting some on a certain layer?

    EDIT: Never mind... I just realized that I was putting my script inside OnMouseOver... so the script itself won't work when I'm over an object that's not the ground. D'oh!
     
  8. drewradley

    drewradley

    Joined:
    Sep 22, 2010
    Posts:
    3,063
    Thanks for this script! How do I make it repeat? Right now, I can only click on each target only once before it stops returning anything. I'm sure it has to with resetting a variable but I can't seem to figure it out.

    edit: never mind... figured out what was happening.
     
    Last edited: Oct 25, 2010
  9. vincentm

    vincentm

    Joined:
    Oct 25, 2010
    Posts:
    3
    Hi all,

    drewadley, I'm having the same problem, can you please let me know how you solved it?
    thanks in advance.

    By the way, thank you Andeeee for the code sample, it was very helpful.

    Vincent.
     
  10. Nexum1

    Nexum1

    Joined:
    Sep 16, 2010
    Posts:
    116
    Thanks a lot, this was a big help. :)

    I was wondering, in Javascript, what does "Physics.Raycast(ray, hit);" do.. Does it give the values over to the 2nd part of the function?
     
  11. Goldwyvern11

    Goldwyvern11

    Joined:
    Apr 13, 2011
    Posts:
    3
    Hi everybody
    Im working on an rts and want a script which will select an object with a certain component attached to it if the raycast hits it.
    any suggestions?

    ty in advance
     
  12. Krodil

    Krodil

    Joined:
    Jun 30, 2010
    Posts:
    141
    thank you for the help. If I want to exchange MouseInput with touch on a tablet, do I simply put in "Input.GetTouch"?
    I have seacrhed everywhere for a simple working solution to be able to work from there.
    In my case (with your raycast script) I want to touch two objects and then do something different to them. It works fine in the editor, but on my device (Xoom) nothing happens.
     
  13. m267612

    m267612

    Joined:
    Oct 15, 2011
    Posts:
    3
    that's great !!
     
  14. dinkmcdinklemans

    dinkmcdinklemans

    Joined:
    Dec 18, 2011
    Posts:
    4
    Is it correct that RaycastHit returns data on the object hit? I'm looking at the code reference trying to see how I can get an object name back so I can change it's material color - it's 3D hex tiles for a strategy game. Any suggestion would be helpful, thank you for the code!

    Edit: I think I've found what I need. I have this:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. //Attaches to the camera
    5.  
    6. public class camScript : MonoBehaviour {
    7.    
    8.     // Update is called once per frame
    9.     void Update () {
    10.         Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
    11.         RaycastHit hit;
    12.        
    13.         if(Physics.Raycast(ray, out hit)){
    14.                
    15.             Debug.Log(hit.collider.gameObject.name);
    16.         }
    17.     }
    18.    
    19. }
    It's attached to Main Camera. However, I don't see the Debug.Log output anywhere when testing my game, so I can't tell if it works. I don't get any errors of course, or my game wouldn't run. Where is the debug log output supposed to show up? If it's just that the code isn't working, can anyone tell why it might not work? I have one object, 'hexTile' in the scene, I'm expecting that when I mouse over it I should see output somewhere in Unity that gives me the name of the object.

    Thanks in advance.

    Edit: I did not have a collider attached to my mesh. It works now, but it is VERY VERY slow. Any suggestions on how to speed things up?

    Edit: Still want to know how to speed things up, but also want to modify my code slightly to change the target's material color when the ray hits it based on a boolean member variable 'occupied', i.e. if the tile's occupied value is true the tile turns red, if it's false it turns green when moused over. Any help is appreciated.
     
    Last edited: Dec 23, 2011
  15. HROP

    HROP

    Joined:
    Dec 21, 2010
    Posts:
    3
    Thanks ! Excellent example, saved me a lot of time !