Search Unity

Raycasting from the camera

Discussion in 'Scripting' started by Epictickle, Apr 8, 2018.

  1. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    Hey guys,

    So I'm trying to make a 3rd person mouse aim camera, but I'm having a heck of a time trying to get my camera to stop going through the terrain whenever it goes too far down. I'm trying to constrain it's position on the Y axis based on a raycast like so:

    Code (CSharp):
    1. void LateUpdate()
    2.     {
    3.        
    4.         float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
    5.         vertical += (Input.GetAxis("Mouse Y") * rotateSpeed) * -1;
    6.         vertical = Mathf.Clamp(vertical, yMin, 30);
    7.         target.transform.Rotate(0, horizontal, 0);
    8.  
    9.         float desiredAngle = target.transform.eulerAngles.y;
    10.         Quaternion rotation = Quaternion.Euler(vertical, desiredAngle, 0);
    11.         transform.position = target.transform.position - (rotation * offset);
    12.  
    13.         Ray down = new Ray(this.transform.position, new Vector3(0, -1f, 0));
    14.         Debug.DrawRay(down.origin, down.direction, Color.green); // For visualization
    15.  
    16.         RaycastHit downInfo;
    17.         if (Physics.Raycast(down, out downInfo))
    18.         {
    19.             if (downInfo.distance < 0.6f)
    20.                 yMin = vertical;
    21.             else
    22.                 yMin = -100f;
    23.         }
    24.         transform.LookAt(target.transform);
    25.     }
    This works very well if I move the camera slowly toward the ground. It will constrain yMin and I won't be able to move the camera any further down. But if I move the camera fast it breaks the raycast and the Y axis is no longer constrained.

    If I put all of my camera movement into FixedUpdate(), it will catch the raycast no matter how fast I move the camera, but the movement of the camera becomes extremely choppy... Does anyone know how to overcome this issue?
     
  2. CubicCBridger

    CubicCBridger

    Joined:
    Apr 19, 2017
    Posts:
    44
    How come you are using vertical += rather than just vertical =, any movement in the vertical plane is going to exponentially grow isn't it? I actually have no idea if it's correct or not, just curious as you don't do it for horizontal. Does the horizontal plane work fine?
     
  3. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    Input.GetAxis("Mouse Y") returns a positive or negative number depending on which direction the mouse is going. It is okay keep horizontal as it is since I'm doing target.transform.Rotate(). For my vertical, I'm not rotating the target but the camera itself, which requires me to set the rotation each LateUpdate instead of relying on Transform.Rotate. This functionality is working perfectly.. I'm just wondering why my Raycast breaks and how I can fix it.
     
  4. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    What about casting the ray from the camera's last position in fixed update, and then setting the camera in late update?
     
  5. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    Hmm, this sounds like a good idea. Where would I populate the last position of the camera? Here's the modified script I've tried, but still not working for fast camera movement:

    Code (CSharp):
    1. private void FixedUpdate()
    2.     {
    3.         Ray down = new Ray(lastPos, new Vector3(0, -1f, 0));
    4.  
    5.         RaycastHit downInfo;
    6.         if (Physics.Raycast(down, out downInfo))
    7.         {
    8.             Debug.DrawLine(down.origin, downInfo.point, Color.green);
    9.            
    10.             if (downInfo.distance < 1f)
    11.                 yMin = vertical;
    12.             else
    13.                 yMin = -100f;
    14.         }
    15.         lastPos = this.transform.position;
    16.     }
    17.  
    18.     void LateUpdate()
    19.     {
    20.        
    21.         float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
    22.         vertical += (Input.GetAxis("Mouse Y") * rotateSpeed) * -1;
    23.         vertical = Mathf.Clamp(vertical, yMin, 30);
    24.         target.transform.Rotate(0, horizontal, 0);
    25.  
    26.         float desiredAngle = target.transform.eulerAngles.y;
    27.         Quaternion rotation = Quaternion.Euler(vertical, desiredAngle, 0);
    28.         Vector3 targetPos = target.transform.position - (rotation * offset);
    29.         transform.position = target.transform.position - (rotation * offset);
    30.  
    31.         transform.LookAt(target.transform);
    32.        
    33.     }

    I think I'm doing it how you suggested, but just want to make sure.
     
  6. CubicCBridger

    CubicCBridger

    Joined:
    Apr 19, 2017
    Posts:
    44
    Also, are you sure you should be getting the y field of transform.eulerAngles? in Unity y axis would give yaw axis, where as x field would give pitch, and z gives roll (I'm pretty sure, correct if I'm wrong).

    As z is into the screen (foward), any angle around z would roll, y is skyward (up), so any rotation around y would give yaw (e.g looking left right), and x is to the right, so and angle around that axis would give pitch (e.g looking up and down).


    EDIT: Strike that just saw youre using it as a parameter to Euler(), so its more of a misnamed variable than anything.

    EDIT #2: I would suggest using a slerp on the camera rotation, currently if you set the rotation too far "downwards" the ray cast will detect the ground but then set the minimum to whatever the current incorrect "downwards" angle is, so next time it can still go as "downwards" as it did previously. If you slerp the camera rotation between current and target each update, the camera wont be able to jump over the boundary condition (although making it slerp at a linear speed may be difficult).

    EDIT #3: Also doesn't look like you are even using lastPosition?
     
    Last edited: Apr 9, 2018
  7. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    I would get the last position in late update, the last thing. Also, have you tried this without Debug.Drawline? It's pretty slow. You can also just set the min distance in the ray and then just check for a hit, just set the ray to that length.
     
  8. CubicCBridger

    CubicCBridger

    Joined:
    Apr 19, 2017
    Posts:
    44
    Also I would raycast using target.transform.position, and if it's valid update the camera to use target, otherwise set it to whatever is the closest valid.

    Another option is to not set the minimum angle to -100f ever, if you get a valid angle don't reset the minimum valid angle, why wold you do that? if you look down and get an invalid angle, you set the minimum valid angle to that, if it looks up and get a valid angle, you lose all the information you just obtained previously by overwriting it with an invalid minimum angle -100f.
     
  9. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    Hmm, still not working... I tested it without the else statement that resets the yMin to -100 and it works.. I'm wondering how I could calculate what "vertical" would be at the raycast hit position?

    If I can figure that out then I can continuously set yMin and I believe the problem would go away.
     
  10. CubicCBridger

    CubicCBridger

    Joined:
    Apr 19, 2017
    Posts:
    44
    Simple trig, you the know the Cameras vertical height/distance from the ground right (the y field in the transform position)? You know the minimum hypotenuse, which is 1f? just solve for angle. Which is

    minimum distance= 1f

    height = camera height from ground

    minimum angle = inverse cos (height/ minimum distance)

    or

    minimum angle = 90 - inverse cos (height/ minimum distance)

    depending on if you want angle from perpendicular to ground or angle from parallel to ground

    EDIT: Here is link explaining : http://www.mathwords.com/s/sohcahtoa.htm

    EDIT #2: If ground isn't always at y = SOME_CONSTANT (e.g 0), you'll have to do a raycast to determine the height straight down from camera to determine height and use that distance instead of camera y position.
     
    Last edited: Apr 9, 2018
    Epictickle likes this.
  11. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    I'm a little lost, but I think this is the right track... Here's what I've done:

    Code (CSharp):
    1. float min = Mathf.Acos( downInfo.distance);
    2. yMin = min;
    3.  
    This is actually working, but every time it hits the minimum position it jumps back up.
     
  12. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    I GOT IT!!!!! This is working perfectly!!

    Code (CSharp):
    1. Ray down = new Ray(this.transform.position, new Vector3(0, -1f, 0));
    2.  
    3.         RaycastHit downInfo;
    4.         if (Physics.Raycast(down, out downInfo))
    5.         {
    6.             Debug.DrawLine(down.origin, downInfo.point, Color.green);
    7.            
    8.             Vector3 target = downInfo.point - transform.position;
    9.             float angle = Vector3.Angle(transform.forward, target) + 10;
    10.            
    11.             float min = (Mathf.Rad2Deg * Mathf.Acos(downInfo.distance * 0.1f)) - angle;
    12.             yMin = min;
    13.         }