Search Unity

Raycasting for beginners?

Discussion in 'Scripting' started by mikef, Sep 15, 2010.

  1. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    I'm trying to find some information on raycasting but everything i've read is way over my head.

    Essentially i have a first person perspective targeting reticle that i want to use to select an enemy in the distance. I know i need to set up some sort of ray casting system, but i'm totaly lost on where to even begin,

    I know that the enemy must have some sort of colider, and the reticle must emit a ray, but beyond that i'm lost

    any help / posting of a rudimentary script would be most appreciated...
     
  2. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    A couple of links to get you started:

    Camera.ScreenPointToRay()
    Physics.Raycast()

    In brief, the steps are:

    1. Create a ray corresponding to the current mouse position using ScreenPointToRay().

    2. Perform the raycast using Physics.Raycast().

    3. Do whatever you want to with the results.
     
  3. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    thanks so much, i'l have a look at those links as soon as i get home tonight, i'm sure i'l have plenty more questions heheheh
     
  4. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    ok, so i've been fidling with this for a better pat of the day and i'm still unsure how to implement the actual raycast.

    As far as i understand (correct me if i'm wrong)

    camera.screenPointToRay is setting the direction of the cast, and Physics.Raycast is actually casting the ray.

    this is what i've put down so far and got the ray projecting through debug.DrawRay the way i want, but i'm unsure how to pass this on to the actual raycast
    then receive info on what has been hit

    Code (csharp):
    1. function Update() {
    2. var ray = camera.ScreenPointToRay (Vector3(371,210,0));
    3. Debug.DrawRay (ray.origin, ray.direction * 400, Color.blue);
    4.  
    5. }  
     
  5. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Check out the documentation for Physics.Raycast() again. Towards the middle of the page, under the second overload of the function, is an example showing how to perform a raycast and then access the returned data.

    To see how to get the necessary info from the ray in order to pass it to the raycast function, check out the documentation for Ray. To see what information is returned by the raycast, check out the documentation for RaycastHit.

    The documentation is usually a good place to start for this sort of thing; I find that typing whatever I'm interested in in the script reference 'search' box usually leads me to the information that I need. (Other good resources are the forums and Unity Answers; searching for 'raycast' on either site will most likely turn up many examples.)
     
  6. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    Thanks Jesse, i got it working just the way i need it.

    Perhaps you could shed some light on the next step... how do i go about collecting information from the object assigned to the collider that the raycast has hit, then send it back to perform a new function based on the results of the gathered information?
     
  7. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    You can acquire a reference to the game object through several of RaycastHit's member fields, such as 'collider' or 'transform' (which you may already know).

    Once you have access to the game object, there are many ways you can 'communicate' with it. One is to acquire references to components using GetComponent() and interact with the components directly. Another would be to use the GameObject messaging functions to invoke functions that you've associated with the game object via scripts (see the GameObject documentation for info and examples).

    Beyond that, it really depends on what you want to do with the object exactly. If you have a particular example in mind and can describe it, someone could probably offer some more specific feedback.
     
  8. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    ok, i guess more specifically i just need a selection border (in the form of a gameobject i've already created) to apear around the intended object when the ray hits. Then a seperate input (spacebar) to destroy the objects in the selection
     
  9. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    The 'selection border' question has come up before in the forums; maybe try searching for 'highlight object' or 'outline object' and see if you can find any of those threads.

    For deleting the objects, you'll probably need to keep track of which objects are selected, either by storing them in a container or somehow flagging them as being selected. Then, when the space bar (or whatever control) is pressed, you would invoke GameObject.Destroy() on all selected objects.
     
  10. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    Thanks for all the assistance jesse, it's been very helpfull, i've got another question.

    Here's my script for selecting the objects in question
    Code (csharp):
    1.  
    2. function DrawGreenRay() {
    3. var ray = camera.ScreenPointToRay (Vector3(371,210,0));
    4. Debug.DrawRay (ray.origin, ray.direction * 400, Color.green);
    5.  
    6. var hit : RaycastHit;
    7.     if (Physics.Raycast (transform.position, transform.forward, hit, 400)){
    8.     if(hit.collider.gameObject.tag=="GreenEnemy" ){
    9.    
    10. var go = GameObject.Find("EnemySelectionReticle");
    11. go.GetComponent(SelectionReticleAnimator).selected = true;
    12. yield WaitForSeconds (.1);
    13. go.GetComponent(SelectionReticleAnimator).selected = false;
    14.         print ("Green Selected" + hit.collider);
    15. }
    16. else
    17.         print ("wrong color");
    18. }
    19. }
    Everything works the way i want, however i'm stumped as to how to make the GameObject.Find("EnemySelectionReticle") search only within the hierarchy of the object "GreenEnemy" instead of the entire scene.

    i've followed the documentation in http://ws.cis.sojo-u.ac.jp/~izumi/U...entation/ScriptReference/GameObject.Find.html
    which seems to say that i need to specify
    Code (csharp):
    1. GameObject.Find("/GreenEnemy/ReticleObject/EnemySelectionReticle");
    but that returns a NullReferenceException

    i've also tried sendmessage but that didnt seem to work either

    This ones's got me confused, any help would once again be appreciated
     
  11. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    hehehe, well of course i try the entire weekend before posting, then 15 minutes after posting figure it out.

    IF its of any help to anyone, here's the cod snipet that i adjusted

    Code (csharp):
    1. function DrawGreenRay() {
    2. var ray = camera.ScreenPointToRay (Vector3(371,210,0));
    3. Debug.DrawRay (ray.origin, ray.direction * 400, Color.green);
    4.  
    5. var hit : RaycastHit;
    6.     if (Physics.Raycast (transform.position, transform.forward, hit, 400)){
    7.     if(hit.collider.gameObject.tag=="GreenEnemy" ){
    8.    
    9. transform.Find("/Green Enemy/ReticleObject/EnemySelectionReticle").GetComponent (SelectionReticleAnimator) .selected = true;
    10. yield WaitForSeconds (.1);
    11. transform.Find("/Green Enemy/ReticleObject/EnemySelectionReticle").GetComponent (SelectionReticleAnimator) .selected = false;
    12.         print ("Green Selected" + hit.collider);
    13. }
    14. else
    15.         print ("wrong color");
    16. }
    17. }
    18.  
    getcomponent had to be changed to a
    Code (csharp):
    1. transform.Find("/parent/child/childofchild/") .GetComponent (Child's script name) .function to call
     
  12. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    ok, got a new problem

    a summary of what i'm trying to do:
    select objects based on their color (in this case i've taged them) then once selected press "execute" to destroy the active selection. Works fine on multiple enemies of different colors, but as soon as i have 2 of the same it targets only the first of that color in the scene hierarchy no matter what object is being aimed at

    Here's my scripts

    Code (csharp):
    1. function DrawYellowRay() {
    2. var ray = camera.ScreenPointToRay (Vector3(371,210,0));
    3. Debug.DrawRay (ray.origin, ray.direction * 400, Color.yellow);
    4.  
    5. var hit : RaycastHit;
    6.     if (Physics.Raycast (transform.position, transform.forward, hit, 400)){
    7.    
    8.   if(hit.collider.gameObject.tag=="YellowEnemy" ){
    9.         print ("Yellow Selected" + hit.collider);
    10.        
    11. transform.Find("/Yellow Enemy/ReticleObject/EnemySelectionReticle") .GetComponent (SelectionReticleAnimator) .selected = true;
    12. yield WaitForSeconds (.1);
    13. transform.Find("/Yellow Enemy/ReticleObject/EnemySelectionReticle") .GetComponent (SelectionReticleAnimator) .selected = false;
    14.         print ("Yellow Selected" + hit.collider);
    15. }
    16. else
    17.         print ("wrong color");
    18. }
    19. }
    Code (csharp):
    1.  
    2. var SelectTargetSound : AudioSource;
    3. var selected = false;
    4. var selectionActive = false;
    5. var AnimationSpeed = 1;
    6. var destroyer = false;
    7.  
    8. function Update () {
    9. if (selected == true)
    10. {   playSelectionReticleAnimation();    }
    11.  
    12. //execute is spacebar
    13. if (Input.GetButtonDown ("Execute"))
    14. {   DestroySelected();  }
    15. }
    16.  
    17.  
    18. function playSelectionReticleAnimation()
    19. {
    20. if (selectionActive == true) {
    21.  print ("Already Selected") ;
    22.  }
    23.  
    24. else if (selectionActive == false) {
    25. SelectTargetSound.Play();
    26. animation.Play ("ReticleSelect");
    27. animation ["ReticleSelect"].speed = AnimationSpeed;
    28. selectionActive = true;
    29. }
    30. }
    31.  
    32.  
    33. function DestroySelected() {
    34. if (selectionActive == true){
    35. Destroy ( transform.root.gameObject );
    36. print ("Destroyed!!!");
    37. }
    38.  
    39. }
     
  13. Vicenti

    Vicenti

    Joined:
    Feb 10, 2010
    Posts:
    664
    .Find only finds the first instance of an object that matches the name.

    Since you have the collider component already, follow that to the part you're looking for

    e.g.

    var thing = hit.collider.GetComponent(Enemy);

    thing.SetSelected();

    and in your Enemy.SetSelected() function, you'd activate the animated object.

    Also Find is "really really slow" and calling it outside of initialization time is generally not something you want to happen if you can avoid it.
     
  14. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    thanks for the help but i'm really confused on how to implement what you're talking about. i understand i need to get rid of the transform.find lines, but the rest is over my head, sorry... not my specialty here
     
  15. Vicenti

    Vicenti

    Joined:
    Feb 10, 2010
    Posts:
    664
    Code (csharp):
    1.  
    2. var ray = camera.ScreenPointToRay (Vector3(371,210,0));
    3. Debug.DrawRay (ray.origin, ray.direction * 400, Color.yellow);
    4.  
    5. var hit : RaycastHit;
    6.     if (Physics.Raycast (transform.position, transform.forward, hit, 400)){
    7.    
    8.   if(hit.collider.gameObject.tag=="YellowEnemy" ){
    9.         print ("Yellow Selected" + hit.collider);
    10.  
    Replace the following Find functions
    Code (csharp):
    1. transform.Find("/Yellow Enemy/ReticleObject/EnemySelectionReticle") .GetComponent (SelectionReticleAnimator) .selected = true;
    2. yield WaitForSeconds (.1);
    3. transform.Find("/Yellow Enemy/ReticleObject/EnemySelectionReticle") .GetComponent (SelectionReticleAnimator) .selected = false;
    4.  
    With a much more happy
    Code (csharp):
    1. hit.collider.GetComponent(YellowEnemy).SetSelected();
    Your YellowEnemy script (or Enemy script, or whatever script it is that you've attached to your YellowEnemy-tagged objects) would then have a SetSelected function that did what the find functions tried to do.
    Code (csharp):
    1. private var animatorComponent : SelectionReticleAnimator;
    2. function Start() {
    3.  animatorComponent = GetComponentInChildren(SelectionReticleAnimator);
    4. }
    5.  
    6. function SetSelected() {
    7.  animatorComponent.selected = true;
    8.  yield WaitForSeconds(0.1);
    9.  animatorComponent.selected = false;
    10. }
     
  16. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    ah, much more clear, thanks a lot for the help, i'l try putting it all together in the morning and see how it goes. Cheers!
     
  17. mikef

    mikef

    Joined:
    Apr 2, 2009
    Posts:
    57
    Just chiming in again to say major thanks to both of you, i think that was the last of my issues in this matter. Cant wait to show off what i've got working here in the coming weeks!