Search Unity

Moving Colliders and Raycasts

Discussion in 'Scripting' started by Divinux, Sep 22, 2013.

  1. Divinux

    Divinux

    Joined:
    Jul 5, 2011
    Posts:
    205
    Guys, I'm getting desperate here. This question was asked several times before by other people, but either my case is set up incorrectly or I don't understand what I'm actually reading, in any case I do have to resort to wasting your valuable time.

    I have a car. This car has a Rigidbody which is set to interpolate and continuous dynamic collision detection. The car has wheelchlildren, which as far as I can see ultimately move the Rigidbody in

    Code (csharp):
    1.  
    2. public class Wheel : MonoBehaviour
    3. {
    4. [...]
    5. void FixedUpdate ()
    6. {
    7. [...]
    8. body.AddForceAtPosition (suspensionForce + roadForce, pos);
    9. }
    10. }
    11.  
    12.  
    Additionally, the car has a PlayerObject as child. The player also has a rigidbody in case that matters, set to interpolate and kinematic when in car. Also a camera, which has a script that casts forwards from the camera to the center of the screen on buttonUp:

    Code (csharp):
    1.  
    2. var vDist : float = 200;
    3. var vHitObj : Transform;
    4.  
    5. function Update ()
    6. {
    7.     if(Input.GetButtonUp("Fire2"))
    8.     {
    9.         Cast();
    10.        
    11.        
    12.     }
    13. }
    14.  
    15. function Cast()
    16. {
    17.               yield new WaitForFixedUpdate ();
    18.     var hit : RaycastHit;
    19.     if (Physics.Raycast (this.transform.position, this.transform.forward, hit, vDist))
    20.     {
    21.        
    22.         Debug.DrawLine (this.transform.position, hit.point);
    23.         vHitObj = hit.collider.transform;
    24.         print(vHitObj);
    25.                             vHitObj.SendMessage ("OnHit");
    26.               }
    27. }
    28.  
    Furthermore, the car has multiple cubes that represent switches and such, to be clicked by the player. They have only a collider set to trigger, and a script that receives messages and triggers an animation:

    Code (csharp):
    1.  
    2. function OnHit()
    3. {
    4.     if(this.transform.parent.GetComponent(DoorCar).vStatus == 1)
    5.     {
    6.        
    7.         this.transform.parent.GetComponent(DoorCar).vStatus = 0;
    8.         this.transform.parent.GetComponent(Animation).Play("CarDoorLeftOpen");
    9.        
    10.         print("Door Open");
    11.     }
    12.    
    13.     else
    14.    
    15.     {
    16.        
    17.         this.transform.parent.GetComponent(DoorCar).vStatus = 1;
    18.         this.transform.parent.GetComponent(Animation).Play("CarDoorLeftClose");
    19.         print("Door Closed");
    20.     }
    21.    
    22. }
    23.  
    Now, that works just fine when the car stands still or moves slowly. When I get over 50mph my raysasts start missing the cube and return the terrain behind them 9 out of 10 times. Now, as I said a google search returns multiple cases of this happening, I took care to have colliders where I should, no rigidbodies where I shouldn't, increased and lowered the timestep, as far as I can see I'm doing everything in FixedUpdate, but I have a feeling I missunderstand that somehow, I don't feel like I have control over when the position updates and when the casts trigger. Using layers is often suggested but it doesn't sound like my problem, It's not that I'm hitting colliders in front of what I want to hit, it's that I'm hitting stuff behind it. There was a nice solution that used a polygon library to raycast entirely without colliders, but alas, that doesn't work on moving meshes.

    So yeah, please give me any insight you have on this, I just wanna click on multiple small-ish objects that move and trigger their scripts. I'd prefer I did something really stupid since that usually means less work, but really any new information is greatly appreciated.

    Thanks for reading.
     
    Last edited: Sep 23, 2013
  2. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    So the camera is inside the car?

    1. why dont you setup your raycasts from the camera?
    2. whats the purpose of the yield in the cast function?
     
  3. Divinux

    Divinux

    Joined:
    Jul 5, 2011
    Posts:
    205
    Hi and thanks for th response,

    1. Yeah, the camera is part of a first person controller, and the raycast script is attached to that. When the player is in the car, the hierarchy is Car>Player>Camera, and the player is indeed inside of the car. You can check out the behavior in the build I made for my WIP thread, copypasted from there:

    Here's a webplayer, Isn't horrible if you enable fullscreen mode to evade the pop up menu on right mouse.

    And here's a zipped build for windows.

    Controls are as following: WASD to run/drive, Mouse1 to attack (only if you have a weapon), Mouse2 to interact with doorknobs and buttons and pick up items, R to reload (you will need an additional clip in your inventory). If you can't figure it out: open a car door, and click the seat to enter and exit cars. Car's in the garage. Try going fast and opening the door.

    2. It is my understanding that colliders are moved in fixedUpdate, which is called after Update, where I get my mouse input. I was thinking if after triggering the function, I wait until the next fixedUpdate before raycasting, I'll have it timed properly. Doesn't seem to have any effect whether it is in there or commented out though.
     
  4. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    I would get rid of the yield.

    I think that will be telling it to wait until the next frame, buy which time, the collider may have moved.

    I would also consider doing the raycast from the camera, rather than the transform.
     
  5. Divinux

    Divinux

    Joined:
    Jul 5, 2011
    Posts:
    205
    All right, commented the yield out, and it somehow feels worse. Might be a placebo though, I'll leave it commented out. Raycasting from the camera, had to look up how to do that, I'm assuming you meant something like this:

    Code (csharp):
    1.  
    2. var vDist : float = 200;
    3. var vHitObj : Transform;
    4.  
    5. function Update ()
    6. {
    7.     if(Input.GetButtonUp("Fire2"))
    8.     {
    9.         Cast();
    10.        
    11.     }
    12. }
    13.  
    14. function Cast()
    15. {
    16.     //yield new WaitForFixedUpdate ();
    17.     var ray : Ray = camera.ViewportPointToRay (Vector3(0.5,0.5,0));
    18.     var hit : RaycastHit;
    19.     if (Physics.Raycast (ray, hit, vDist))
    20.     {
    21.        
    22.         Debug.DrawLine (this.transform.position, hit.point);
    23.         vHitObj = hit.collider.transform;
    24.         print(vHitObj);
    25.         vHitObj.SendMessage ("OnHit");
    26.        
    27.     }
    28. }
    29.  
    Produces the same results. By now I can hit the colliders relatively reliably by leading my aim, the colliders are always farther in travel direction than they should be. So... I'm casting too late, after they already moved to their next position? Or too early, before the actual meshes had time to "catch up" with the physics? Just trying to figure out the chronological order of events.
     
  6. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Are you always wanting to shoot your ray down the center of the screen?

    It sounds like you want to be shooting the ray depending on where the mouse is positioned? Im assuming you are trying to hit vairious buttons inside the car cockpit?

    you probably want your raycast to be along these lines if thats the case...

    Code (csharp):
    1.  
    2.  
    3. function Update() {
    4.     var ray = Camera.main.ScreenPointToRay (Input.mousePosition);
    5.     if (Physics.Raycast (ray, 100)) {
    6.         print ("Hit something");
    7.     }
    8. }
    9.  
    10.  
    You can also move your input test function into the FixedUpdate if you prefer. It works just the same as Update. Maybe even LateUpdate might be the best choice.
     
  7. Divinux

    Divinux

    Joined:
    Jul 5, 2011
    Posts:
    205
    I'm not sure why I would want to cast anywhere but the center of the screen in a first person context. The mouse is locked and hidden in the center of the screen anyways, and casting to screen center alleviates jittering and issues in webplayer and editor where the mouse isn't properly locked. My first iteration of the code actually did that.

    I can not put the input test into fixedUpdate because it is not quite the same, unfortunately, since it can be called multiple times per update. This results in the getButtonUp or getButtonDown (not just getButton) being called multiple times, which leads to 1-4 raycasts per one user input, none of which hit what they're supposed to.

    LateUpdate was my thinking too, since I thought it would be called after FixedUpdate, but that's apparently not the case and has no effect on the issue either.

    By now I started thinking about workarounds, best I can come up with is finding all colliders within a sphere on mouseclick, then specifically casting to all of them, then comparing the raycast directions to camera.transform.forward, and then sending my "OnHit" message to the closest one. In my head that sounds like it could work, but only for speeds where this magical positionoffset is smaller than half the distance between the buttons on the dashboard, so it's still not foolproof, and judging by how far forwards I have to lead my mouse to hit, not nearly enough fidelity to be feasible.

    And besides, thats crazy talk, raycasts get used by big games all the time, all the first person shooter packages that use raycasts for their weapons have nothing special in them either, so it must mean I'm missing something here.
     
  8. Divinux

    Divinux

    Joined:
    Jul 5, 2011
    Posts:
    205
    All right, It works perfectly now.

    Post on UAnswers made me remember I was moving the player not only by the fact it's a child, but also via script to my carseat, in LateUpdate. After playing around with all that, moving the car, the playerposition in FixedUpdate, getting the mouse input in Update, then waiting for fixedUpdate and then raycasting produces flawless results at any speed. Here's the relevant portions of the code:

    Code (csharp):
    1.  
    2. //wheel.cs
    3. public class Wheel : MonoBehaviour
    4.  
    5.  
    6. {
    7.  
    8.  
    9. [...]
    10.  
    11.  
    12. void FixedUpdate ()
    13.  
    14.  
    15. {
    16.  
    17.  
    18. [...]
    19.  
    20.  
    21. body.AddForceAtPosition (suspensionForce + roadForce, pos);
    22.  
    23.  
    24. }
    25.  
    26.  
    27. }
    Code (csharp):
    1.  
    2. //raycaster.js
    3. var vDist : float = 200;
    4. var vHitObj : Transform;
    5.  
    6. function Update ()
    7. {
    8.     if(Input.GetButtonUp("Fire2"))
    9.     {
    10.         Cast();
    11.         print(this);
    12.        
    13.     }
    14. }
    15.  
    16. function Cast()
    17. {
    18.     yield new WaitForFixedUpdate ();
    19.     var ray : Ray = camera.ViewportPointToRay (Vector3(0.5,0.5,0));
    20.     var hit : RaycastHit;
    21.     if (Physics.Raycast (ray, hit, vDist))
    22.     {
    23.        
    24.         Debug.DrawLine (this.transform.position, hit.point);
    25.         vHitObj = hit.collider.transform;
    26.         print(vHitObj);
    27.         vHitObj.SendMessage ("OnHit");
    28.        
    29.     }
    30. }
    Code (csharp):
    1.  
    2. //seatposition.js
    3. function FixedUpdate ()
    4. {
    5.  
    6.     if(vInside == false)
    7.     {
    8.         //quick out if the carseat is empty
    9.         return;
    10.        
    11.     }
    12.     else
    13.     {
    14.         //rotate x and z in any case
    15.         vPlayer.transform.localEulerAngles.x = Mathf.LerpAngle(vPlayer.transform.localEulerAngles.x, vSeatpos.transform.localEulerAngles.x, Time.deltaTime * vRotationdamping * 2);
    16.         vPlayer.transform.localEulerAngles.z = Mathf.LerpAngle(vPlayer.transform.localEulerAngles.z, vSeatpos.transform.localEulerAngles.z, Time.deltaTime * vRotationdamping * 2);
    17.        
    18.         vPlayer.transform.position = vSeatpos.transform.position;
    19.        
    20.         //keep the player rotation centered when turning
    21.         if(vLerpComplete == true)
    22.         {
    23.             if(transform.root.rigidbody.angularVelocity.y > 0.1 || transform.root.rigidbody.angularVelocity.y < -0.1)
    24.             {
    25.                 vPlayer.transform.localEulerAngles.y = vSeatpos.transform.localEulerAngles.y;
    26.             }
    27.             }
    28.        
    29.         //check if the player used the mouse in the last few seconds
    30.         input();
    31.         //if he didnt, rotate y
    32.         if(vPlayerMovedMouse == false)
    33.         {
    34.             //rotateY();
    35.             rotateback();
    36.         }
    37.        
    38.        
    39.         vCam.GetComponent(MouseLook).carplug = 0;
    40.         if(vPlayerMovedMouse == false)
    41.         {
    42.             rotateY();
    43.             //rotateback();
    44.         }
    45.     }
    46. }
    Thanks again Owen Reynolds, syclamoth and JamesLeeNZ for bearing with me for so long!
     
  9. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I'm not sure if it'll cause the issue you're having, but Rigidbody components are never meant to be parented under one another.

    This is pretty clear in the physics page of the manual (link), but for whatever reason is not mentioned in the Rigidbody page.

    Also, you mentioned not being sure about when different things get updated in regard to one another. I'd strongly suggest setting up a known test case and printing the values at various points. As long as you know what the values should be you should be able to figure out in what order they're getting updated to see if that's your issue.
     
    Last edited: Sep 23, 2013
  10. Divinux

    Divinux

    Joined:
    Jul 5, 2011
    Posts:
    205
    Thanks for the comment penguin,

    it doesn't seem like having two rigidbodies causes anything but involuntary movement on the child rigidbody, but I read that page as well and will consider how I can incorporate a rigidcar with a rigidplayer differently, maybe I can turn off the entire child object or destroy and spawn a new rigidbody as necessary.

    And yeah, a test case seems like a good idea. I need some print() statements in my log to see what's happening when. The same for yield statements, I could have some more control over those as well.