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

preventing objects from clipping camera view port

Discussion in 'Scripting' started by scr33ner, Oct 13, 2015.

  1. scr33ner

    scr33ner

    Joined:
    May 15, 2012
    Posts:
    188
    Hi all,

    I've a 3rd person camera similar to Mario64. Currently, I'm working on preventing the camera from getting stuck inside a mesh. The goal is to move the camera overhead & this much trickier than I'd expected.



    Here's the code:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. /// <summary>
    5. /// This is a C# and VCS friendly conversion of ThirdPersonCamera.js found in
    6. /// Unity's Character Controllers package.  For any questions, contact
    7. /// support@bitbybitstudios.com.
    8. /// Appendeded camera clipping function
    9. /// Camera culling set up:
    10. /// 1. create 2 layers name them clip & clipNone
    11. /// 2. use bitswitch to switch between layer masks
    12. /// 3. ensure mesh in game has colliders
    13. /// 4. create a tag & label environment - checked by RayCastHit
    14. /// 5. if camera is occluded, move it overhead
    15. /// 6. if an object will clip move it overhead
    16. /// </summary>
    17. public class VCThirdPersonCamera : MonoBehaviour
    18. {
    19.     public  Transform cameraTransform; //use this as raycast emitterPoint
    20.     private Transform _target;
    21.  
    22.     // The distance in the x-z plane to the target
    23.     public float distance= 7.0f;
    24.  
    25.     // the height we want the camera to be above the target
    26.     public float height= 3.0f;
    27.  
    28.     public float angularSmoothLag = 0.3f;
    29.     public float angularMaxSpeed  = 15.0f;
    30.  
    31.     public float heightSmoothLag = 0.3f;
    32.  
    33.     public float snapSmoothLag = 0.2f;
    34.     public float snapMaxSpeed  = 720.0f;
    35.  
    36.     public float clampHeadPositionScreenSpace = 0.75f;
    37.  
    38.     public float lockCameraTimeout = 0.2f;
    39.  
    40.     private Vector3 headOffset   = Vector3.zero;
    41.     private Vector3 centerOffset = Vector3.zero;
    42.  
    43.     private float heightVelocity = 0.0f;
    44.     private float angleVelocity  = 0.0f;
    45.     private bool  snap           = false;
    46.     private VCThirdPersonController controller;
    47.     private float targetHeight= 100000.0f;
    48.  
    49.     /// clipping variables
    50.     /// use the camera's slider to determine defaultFOV
    51.     public Vector3    cameraOffset = new Vector3(0,0.125f,-0.125f); //moves camera so player doesn't get clipped
    52.  
    53.     public float      fovDefault   = 60f;                           //the desired default field of view set by user
    54.     public float      camTurnTime  = 0f;                            //use to compensate for the camera turning
    55.     public float      headCamTimer = 1.5f;                          //how long should the camera stay overHead
    56.     public float      fieldOfView  = 95.0f;                         //set when camera is overHead
    57.     public bool       camOccluded  = false;                         //is the camera occluded
    58.     public bool       clipped      = true;                          //used for clipping
    59.     public Camera     _camera;
    60.     public GameObject theCamera;
    61.  
    62.     private LayerMask   _mask;
    63.     private RaycastHit  _hit;
    64.     /// end occlusionHandlers
    65.  
    66.     private void Awake ()
    67.     {
    68.         if (cameraTransform == null && Camera.main != null)
    69.             cameraTransform = Camera.main.transform;
    70.         if (cameraTransform == null)
    71.         {
    72.             Debug.Log("Please assign a camera to the ThirdPersonCamera script.");
    73.             enabled = false;
    74.         }
    75.      
    76.         _target = transform;
    77.         if (_target != null)
    78.         {
    79.             controller = _target.GetComponent<VCThirdPersonController>();
    80.         }
    81.      
    82.         if (controller != null)
    83.         {
    84.             CharacterController characterController = _target.GetComponent<CharacterController>();
    85.             centerOffset = characterController.bounds.center - _target.position;
    86.             headOffset = centerOffset;
    87.             headOffset.y = characterController.bounds.max.y - _target.position.y;
    88.         }
    89.         else
    90.             Debug.Log("Please assign a target to the camera that has a ThirdPersonController script attached.");
    91.      
    92.         Cut(_target, centerOffset);
    93.  
    94.         /// instantiate occlusion culling handler through bitSwitch
    95.         _mask = 1 << LayerMask.NameToLayer("clip") | 0 << LayerMask.NameToLayer("clipNone");
    96.     }
    97.  
    98.     public float AngleDistance ( float a ,   float b  )
    99.     {
    100.         a = Mathf.Repeat(a, 360);
    101.         b = Mathf.Repeat(b, 360);
    102.      
    103.         return Mathf.Abs(b - a);
    104.     }
    105.  
    106.     public void Apply (Transform dummyTarget, Vector3 dummyCenter)
    107.     {
    108.         // Early out if we don't have a target
    109.         if (controller == null)
    110.             return;
    111.      
    112.         Vector3 targetCenter = _target.position + centerOffset;
    113.         Vector3 targetHead = _target.position + headOffset;
    114.              
    115.         // Calculate the current & target rotation angles
    116.         float originalTargetAngle= _target.eulerAngles.y;
    117.         float currentAngle= cameraTransform.eulerAngles.y;
    118.      
    119.         // Adjust real target angle when camera is locked
    120.         float targetAngle= originalTargetAngle;
    121.      
    122.         // When pressing Fire2 (alt) the camera will snap to the target direction real quick.
    123.         // It will stop snapping when it reaches the target
    124.         if (Input.GetButton("Fire2")) // VCS Note: For touch controls, you may want to change this to use a VCAnalogJoystick's TapCount or a VCButtonBase's Pressed properties.
    125.             snap = true;
    126.      
    127.         if (snap)
    128.         {
    129.             // We are close to the target, so we can stop snapping now!
    130.             if (AngleDistance (currentAngle, originalTargetAngle) < 3.0f)
    131.                 snap = false;
    132.          
    133.             currentAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, ref angleVelocity, snapSmoothLag, snapMaxSpeed);
    134.         }
    135.         // Normal camera motion
    136.         else
    137.         {
    138.             if (controller.GetLockCameraTimer() < lockCameraTimeout)
    139.             {
    140.                 targetAngle = currentAngle;
    141.             }        
    142.             // Lock the camera when moving backwards!
    143.             // * It is really confusing to do 180 degree spins when turning around.
    144.             if (AngleDistance (currentAngle, targetAngle) > 160 && controller.IsMovingBackwards ())
    145.                 targetAngle += 180;
    146.          
    147.             currentAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, ref angleVelocity, angularSmoothLag, angularMaxSpeed);
    148.         }    
    149.      
    150.         // When jumping don't move camera upwards but only down!
    151.         if (controller.IsJumping ())
    152.         {
    153.             // We'd be moving the camera upwards, do that only if it's really high
    154.             float newTargetHeight= targetCenter.y + height;
    155.             if (newTargetHeight < targetHeight || newTargetHeight - targetHeight > 5)
    156.                 targetHeight = targetCenter.y + height;
    157.         }
    158.         // When walking always update the target height
    159.         else
    160.         {
    161.             targetHeight = targetCenter.y + height;
    162.         }
    163.      
    164.         // Damp the height
    165.         float currentHeight= cameraTransform.position.y;
    166.         currentHeight = Mathf.SmoothDamp (currentHeight, targetHeight, ref heightVelocity, heightSmoothLag);
    167.      
    168.         // Convert the angle into a rotation, by which we then reposition the camera
    169.         Quaternion currentRotation= Quaternion.Euler (0, currentAngle, 0);
    170.      
    171.         // Set the position of the camera on the x-z plane to:
    172.         // distance meters behind the target
    173.         cameraTransform.position = targetCenter;
    174.         cameraTransform.position += currentRotation * Vector3.back * distance;
    175.      
    176.         Vector3 tempCameraTransformPos=cameraTransform.position;
    177.      
    178.         tempCameraTransformPos.y = currentHeight;
    179.         cameraTransform.position = tempCameraTransformPos;
    180.      
    181.         // Always look at the target
    182.         SetUpRotation(targetCenter, targetHead);
    183.     }
    184.     private void LateUpdate ()
    185.     {
    186.         Vector3 targetCenter = _target.position + centerOffset;
    187.         Vector3 targetHead = _target.position + headOffset;
    188.  
    189.         Vector3 theTarget = transform.TransformDirection(Vector3.forward)* 3.5f;// <--original line
    190.         //Vector3 theTarget = transform.LookAt(targetHead);
    191.         Ray centerRay = _camera.ViewportPointToRay(new Vector3(.5f, 0.5f, 1f));
    192.         //cameraTransform.transform.LookAt(targetHead);//alwasy lookAt the player's head
    193.         // Raycast(Vector3 origin, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
    194.  
    195.         if(Physics.Raycast(_camera.transform.position, theTarget, out _hit, 2.77f) && _hit.collider.CompareTag("Player"))
    196.         {
    197.             camOccluded = false;
    198.         }
    199.         if(Physics.Raycast(_camera.transform.position, theTarget, out _hit, 2.77f) && _hit.collider.CompareTag("environment"))
    200.         {
    201.             camOccluded = true;
    202.         }
    203.  
    204.         /// creates ray from cameraViewPort to nearClipPlane
    205.         float camClipPlane = _camera.nearClipPlane;
    206.         /// sets up clipPoints from camera to viewPort
    207.         Vector3 pos1 = _camera.ViewportToWorldPoint(new Vector3(0,0,camClipPlane));   //btmL
    208.         Vector3 pos2 = _camera.ViewportToWorldPoint(new Vector3(.5f,0,camClipPlane)); //btmCtr
    209.         Vector3 pos3 = _camera.ViewportToWorldPoint(new Vector3(1,0,camClipPlane));   //btmR
    210.         //--------------------------
    211.         Vector3 pos4 = _camera.ViewportToWorldPoint(new Vector3(0,.5f,camClipPlane)); //ctrL
    212.         Vector3 pos5 = _camera.ViewportToWorldPoint(new Vector3(1,.5f,camClipPlane)); //ctrR
    213.         //--------------------------
    214.         Vector3 pos6 = _camera.ViewportToWorldPoint(new Vector3(0,1,camClipPlane));   //topL
    215.         Vector3 pos7 = _camera.ViewportToWorldPoint(new Vector3(.5f,1,camClipPlane)); //topCtr
    216.         Vector3 pos8 = _camera.ViewportToWorldPoint(new Vector3(1,1,camClipPlane));   //topR
    217.         /// visualizes clipPoints- can be deleted
    218.         //Debug.DrawLine(cameraTransform.position, pos1, Color.yellow);
    219.         //Debug.DrawLine(cameraTransform.position, pos2, Color.yellow);
    220.         //Debug.DrawLine(cameraTransform.position, pos3, Color.yellow);
    221.         //Debug.DrawLine(cameraTransform.position, pos4, Color.yellow);
    222.         //Debug.DrawLine(cameraTransform.position, pos5, Color.yellow);
    223.         //Debug.DrawLine(cameraTransform.position, pos6, Color.yellow);
    224.         //Debug.DrawLine(cameraTransform.position, pos7, Color.yellow);
    225.         //Debug.DrawLine(cameraTransform.position, pos8, Color.yellow);
    226.  
    227.         bool clipLine01 = Physics.Linecast(cameraTransform.position, pos1, out _hit, _mask.value);
    228.         bool clipLine02 = Physics.Linecast(cameraTransform.position, pos2, out _hit, _mask.value);
    229.         bool clipLine03 = Physics.Linecast(cameraTransform.position, pos3, out _hit, _mask.value);
    230.         bool clipLine04 = Physics.Linecast(cameraTransform.position, pos4, out _hit, _mask.value);
    231.         bool clipLine05 = Physics.Linecast(cameraTransform.position, pos5, out _hit, _mask.value);
    232.         bool clipLine06 = Physics.Linecast(cameraTransform.position, pos6, out _hit, _mask.value);
    233.         bool clipLine07 = Physics.Linecast(cameraTransform.position, pos7, out _hit, _mask.value);
    234.         bool clipLine08 = Physics.Linecast(cameraTransform.position, pos8, out _hit, _mask.value);
    235.  
    236.         ///clip conditions are met & camera is occluded
    237.         if((clipLine01)||(clipLine02)||(clipLine03)||(clipLine04)||(clipLine05)||(clipLine06)||(clipLine07)||(clipLine08))
    238.         {
    239.             camOccluded = true;
    240.             //StartCoroutine(ResetCamera());
    241.         }
    242.         else
    243.         {
    244.             camOccluded = false;
    245.         }
    246.  
    247.         if(!camOccluded)
    248.         {
    249.             Apply (transform, Vector3.zero);//default VCScamera method
    250.                      
    251.         }
    252.         else
    253.         {
    254.             StartCoroutine(ResetCamera());
    255.         }
    256.         print(camOccluded);
    257.         print(headCamTimer);
    258.  
    259.     }
    260.     public void OverheadCamera()
    261.     {
    262.         Vector3 targetCenter = _target.position + centerOffset;
    263.         Vector3 targetHead = _target.position + headOffset;
    264.      
    265.         _camera.fieldOfView = fieldOfView;
    266.      
    267.         cameraTransform.position = (targetHead + cameraOffset) ;
    268.         cameraTransform.LookAt(targetHead);
    269.     }
    270.  
    271.     IEnumerator ResetCamera()
    272.     {
    273.         //camOccluded = true;
    274.         OverheadCamera();
    275.         yield return new WaitForSeconds(headCamTimer);
    276.         _camera.fieldOfView = fovDefault;
    277.         Apply (transform, Vector3.zero);//default VCScamera position
    278.         camOccluded = false;
    279.     }
    280.  
    281.     public void Cut ( Transform dummyTarget ,   Vector3 dummyCenter  ){
    282.         float oldHeightSmooth= heightSmoothLag;
    283.         float oldSnapMaxSpeed= snapMaxSpeed;
    284.         float oldSnapSmooth= snapSmoothLag;
    285.      
    286.         snapMaxSpeed = 10000;
    287.         snapSmoothLag = 0.001f;
    288.         heightSmoothLag = 0.001f;
    289.      
    290.         snap = true;
    291.         Apply (transform, Vector3.zero);
    292.      
    293.         heightSmoothLag = oldHeightSmooth;
    294.         snapMaxSpeed = oldSnapMaxSpeed;
    295.         snapSmoothLag = oldSnapSmooth;
    296.     }
    297.  
    298.     public void SetUpRotation (Vector3 centerPos,  Vector3 headPos)
    299.     {
    300.         // Now it's getting hairy. The devil is in the details here, the big issue is jumping of course.
    301.         // * When jumping up and down we don't want to center the guy in screen space.
    302.         //  This is important to give a feel for how high you jump and avoiding large camera movements.
    303.         //
    304.         // * At the same time we dont want him to ever go out of screen and we want all rotations to be totally smooth.
    305.         //
    306.         // So here is what we will do:
    307.         //
    308.         // 1. We first find the rotation around the y axis. Thus he is always centered on the y-axis
    309.         // 2. When grounded we make him be centered
    310.         // 3. When jumping we keep the camera rotation but rotate the camera to get him back into view if his head is above some threshold
    311.         // 4. When landing we smoothly interpolate towards centering him on screen
    312.  
    313.         //--------------------------------------------------------------------------//
    314.  
    315.         _camera.fieldOfView = fovDefault; // defaults camera to desired FOV
    316.  
    317.         Vector3 cameraPos = cameraTransform.position;
    318.         Vector3 offsetToCenter= centerPos - cameraPos;
    319.      
    320.         // Generate base rotation only around y-axis
    321.         Quaternion yRotation= Quaternion.LookRotation(new Vector3(offsetToCenter.x, 0, offsetToCenter.z));
    322.      
    323.         Vector3 relativeOffset= Vector3.forward * distance + Vector3.down * height;
    324.         cameraTransform.rotation = yRotation * Quaternion.LookRotation(relativeOffset);
    325.      
    326.         // Calculate the projected center position and top position in world space
    327.         Ray centerRay = _camera.ViewportPointToRay(new Vector3(.5f, 0.5f, 1f));
    328.         Ray topRay = _camera.ViewportPointToRay(new Vector3(.5f, clampHeadPositionScreenSpace, 1f));
    329.      
    330.         Vector3 centerRayPos= centerRay.GetPoint(distance);
    331.         Vector3 topRayPos= topRay.GetPoint(distance);
    332.      
    333.         float centerToTopAngle= Vector3.Angle(centerRay.direction, topRay.direction);
    334.      
    335.         float heightToAngle= centerToTopAngle / (centerRayPos.y - topRayPos.y);
    336.      
    337.         float extraLookAngle= heightToAngle * (centerRayPos.y - centerPos.y);
    338.         if (extraLookAngle < centerToTopAngle)
    339.         {
    340.             extraLookAngle = 0;
    341.         }
    342.         else
    343.         {
    344.             extraLookAngle = extraLookAngle - centerToTopAngle;
    345.             cameraTransform.rotation *= Quaternion.Euler(-extraLookAngle, 0, 0);
    346.         }
    347.     }
    348.  
    349.     public Vector3 GetCenterOffset ()
    350.     {
    351.         return centerOffset;
    352.     }
    353. }
    354.  
    Using a while loops crash the game...

    Any advise is greatly appreciated.
     
    Last edited: Oct 13, 2015
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    One approach I have used is to put a big invisible sphere collider on the camera, give it a rigidbody, attach it to your character by a joint, and then let the physics "push" the camera upwards when the camera's sphere bumps into geometry.

    It's not perfect but it might get you a long way there. The next step after the above is to come up with good damping solutions to prevent jitter and bounce and jerking of the camera, which means then you put your camera on yet another gameobject, and have some kind of "tween towards" script to dampen out the sharp motions of impact.
     
  3. scr33ner

    scr33ner

    Joined:
    May 15, 2012
    Posts:
    188
    Thanks for that @Kurt Dekker, believe me I've tried that approach already. That method isn't very compatible with Virtual Control Suite's 3rd person camera.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Then looking specifically at the problem you are seeing in your video, you might need to implement some kind of dwell timer that doesn't let the camera choose a new location sooner than (say) a full second after the last time I chose.

    Another way to do it is to select possible altitudes and/or offsets for your camera, and then score them based on a rough estimate of how much geometry is between the camera and the player (say a raycast to head, middle and foot).

    Then using that score value, switch the camera only when there is a clearly superior score at another location than the one you are at.
     
  5. scr33ner

    scr33ner

    Joined:
    May 15, 2012
    Posts:
    188
    You just gave me an idea, that simplifies everything...set up volume colliders along the borders that will set colClip true, thus moving the camera overhead....
     
  6. scr33ner

    scr33ner

    Joined:
    May 15, 2012
    Posts:
    188
    Yup, using volume triggers- is the simplest solution & it works...